Maqolalar 10 daqiqa 144 ko‘rish
push → test → deploy: shu saytning o'zi qanday chiqib turibdi
Hozir o'qiyotgan maqolangiz serverga qo'lda yuklanmagan: men push qildim, GitHub Actions testlarni o'tkazdi, o'zi deploy qildi, hatto Telegram kanalga e'lonni ham o'zi yubordi. Shu pipeline'ni boshidan oxirigacha ochib beraman — real fayllar bilan.
Mundarija
Bu maqolaning o'zi — mavzuning jonli isboti. Men uni yozib git push
qildim, keyin hech narsa qilmadim: GitHub Actions testlarni o'tkazdi,
serverga yukladi, xizmatni yangiladi va Telegram kanalimga e'lonni ham
o'zi yuborib qo'ydi. Qo'lda deploy degan tushuncha bu loyihada birinchi
haftadan keyin yo'qolgan. Bugun shu zanjirni boshidan oxirigacha, haqiqiy
fayllar bilan ko'rsataman — bularning hammasi
saytning ochiq repositoriysida
turibdi, solishtirib borishingiz mumkin.
Umumiy manzara#
git push (main)
│
▼
[checks] ruff → format → migratsiya tekshiruvi → pytest
│ hammasi yashil bo'lsa
▼
[deploy] rsync bilan serverga → uv sync → collectstatic → migrate
│ → systemctl restart → yangi maqolalarni kanalga e'lon qilish
▼
sayt jonli, kanalda post
Eng muhim tamoyil shu chizmaning o'zida: deploy testlardan keyin turadi. Buzuq kod serverga yetib bora olmaydi, chunki unga yo'l testlar orqali o'tadi.
checks: qo'riqchi job#
Workflow fayli .github/workflows/ci.ymlda yashaydi. Birinchi job to'rt
qatlamli tekshiruv:
# .github/workflows/ci.yml (qisqartirilgan, mohiyati saqlangan)
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
checks:
runs-on: ubuntu-latest
services:
postgres: # testlar haqiqiy Postgres bilan yuradi
image: postgres:16
env:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
ports: ["5432:5432"]
# baza tayyor bo'lmaguncha qadamlar boshlanmasin
options: >-
--health-cmd "pg_isready -U app"
--health-interval 5s
--health-retries 5
env: # testlar shu qiymatlar bilan yuradi
SECRET_KEY: ci-only-secret-key
DATABASE_URL: postgres://app:app@localhost:5432/app
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.0.1
- run: uv sync
- run: uv run ruff check . # lint xatolari
- run: uv run ruff format --check . # formatlash buzilmaganmi
- run: uv run python manage.py makemigrations --check --dry-run
- run: uv run pytest -W error # testlar, warning ham xato
Ikkita qadam alohida izohga arziydi. makemigrations --check — modelni
o'zgartirib, migratsiya yaratishni unutgan holatni ushlaydi; bu xato lokalda
sezilmaydi-yu, production'da birinchi so'rovda portlaydi. pytest -W error
esa har warning'ni xatoga aylantiradi — kutubxonalar "bu funksiya keyingi
versiyada o'chadi" deb ogohlantirsa, siz buni deploy'dan oldin eshitasiz,
yarim yildan keyin sinib emas.
deploy: faqat main, faqat yashildan keyin#
deploy:
needs: checks # checks yiqilsa, bu job boshlanmaydi
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: SSH kalitni tayyorlash
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan SERVER_IP >> ~/.ssh/known_hosts
- name: Deploy
run: DEPLOY_KEY=~/.ssh/deploy_key ./scripts/deploy.sh deploy@SERVER_IP
needs: checks va if: sharti birga ishlaydi: pull request'lar faqat
tekshiruvdan o'tadi, serverga esa faqat main'dagi yashil push boradi.
ssh-keyscan qatori serverning SSH host kalitini known_hosts ga yozadi.
Busiz ssh "yes" deb javob beradigan odamni topolmaydi va deploy
Host key verification failed bilan yiqiladi. Bir narsani
bilib qo'ying: bu qator o'sha paytda javob bergan kalitga tekshirmasdan
ishonadi. Qat'iyroq yo'l — ssh-keyscan -H SERVER_IP natijasini bir marta
o'z kompyuteringizda olib, alohida secret qilib saqlash va workflow'da
known_hosts ga shu secret'ni yozish.
Deploy kaliti: alohida va faqat shu ish uchun#
Serverga kirish uchun Actions'ga SSH kalit kerak. Bu yerda bitta qat'iy qoida bor: shaxsiy kalitingizni bermang — deploy uchun alohida kalit yasaladi:
# o'z kompyuteringizda:
ssh-keygen -t ed25519 -f deploy_key -C "github-actions deploy" -N ""
# ochiq qismini serverdagi deploy foydalanuvchisiga qo'shamiz:
ssh-copy-id -i deploy_key.pub deploy@SERVER_IP
# maxfiy qismini GitHub'ga: repo → Settings → Secrets and variables → Actions
# → New repository secret, nomi DEPLOY_SSH_KEY
cat deploy_key # shu matnni to'liq (BEGIN/END qatorlari bilan) secret qilib saqlaysiz
rm deploy_key deploy_key.pub # secret'ga saqlagach, fayl kompyuterda qolmasin
Secret — repo'ga emas, Secrets'ga
Kalit, token, parol — bular hech qachon repositoriy ichiga tushmasligi kerak, hatto private repo bo'lsa ham. GitHub Secrets aynan shu uchun: workflow o'qiy oladi, loglarda avtomatik yashiriladi, tarixda qolmaydi. Kalit tasodifan commit bo'lib ketsa, uni "o'chirish" yetmaydi — tarixda qoladi, almashtirish shart.
scripts/deploy.sh: serverda nima bo'ladi#
Deploy skripti — pipeline'ning serverga tegadigan qo'li:
#!/usr/bin/env bash
# scripts/deploy.sh (soddalashtirilgan)
set -euo pipefail
HOST=$1
# kodni serverga ko'chiramiz. --delete serverdagi, repo'da yo'q fayllarni
# o'chiradi, exclude qilinganlarga esa tegmaydi — serverning o'zida
# yaratiladigan hamma narsa shu ro'yxatda turishi shart
rsync -az --delete \
--exclude .git --exclude .venv --exclude .env --exclude var \
--exclude media --exclude staticfiles --exclude db.sqlite3 \
-e "ssh -i $DEPLOY_KEY" ./ "$HOST":/opt/mysite/
ssh -i "$DEPLOY_KEY" "$HOST" bash -s <<'REMOTE'
set -euo pipefail # serverda ham: bir qadam yiqilsa, deploy qizil bo'lsin
cd /opt/mysite
export PATH="$HOME/.local/bin:$PATH" # uv shu yerda; SSH orqali kelgan buyruq .profile'ni o'qimaydi
# .env ni systemd'ning EnvironmentFile= qatori kabi yuklaymiz — har qator
# so'zma-so'z. `source .env` bu yerda yaramaydi: SECRET_KEY'dagi $ ( & belgilari
# bash'ni sindiradi
while IFS= read -r qator; do
case "$qator" in [A-Za-z_]*=*) export "$qator" ;; esac
done < .env
uv sync --frozen --no-dev # bog'liqliklar lock fayl bo'yicha, dev kutubxonalarsiz
.venv/bin/python manage.py collectstatic --noinput
.venv/bin/python manage.py migrate --noinput
sudo systemctl restart mysite-web # yoki reload — 2-qismda farqini ko'rgandik
.venv/bin/python manage.py announce_posts || true # yangi maqola bo'lsa kanalga
REMOTE
Oxirgi qatordagi || truega e'tibor bering: kanalga e'lon yuborish —
qo'shimcha qulaylik, deploy'ning sharti emas. Telegram API yotgan bo'lsa
ham sayt yangilanishi to'xtamasligi kerak.
Uchta qatorni tashlab ketish oson, lekin har biri alohida tuzoqdan saqlaydi:
-
REMOTEichidagiset -euo pipefail. ssh serverdagi oxirgi buyruqning natijasini qaytaradi, oxirgisi esa|| true. Bu qatorsizmigrateyiqilsa ham skript davom etadi va GitHub deploy'ni yashil ko'rsatadi. -
export PATH=.... uv o'rnatuvchisi~/.local/binni.profileva.bashrcga yozadi. Siz serverga kirganingizdauvishlaydi, lekin ssh orqali yuborilgan buyruqqa bu sozlama yetib bormaydi vauv: command not foundchiqadi. -
.envni yuklash. Sirlar maqolasida kelishganimizdek, serverda.envni kod o'qimaydi — uni faqat xizmatningEnvironmentFile=qatori yuklaydi. ssh orqali ishga tushganmanage.pyesa xizmat emas: bu tsiklsiz uSECRET_KEYkabi sozlamalarni topolmay yiqiladi. Qiymatlarni.envda qo'shtirnoqsiz yozing — shunda tsikl ham, systemd ham ularni bir xil o'qiydi.
Exclude ro'yxati ham shunday. Bu yerda media bo'lmasa, birinchi deploy
3-qismdagi /opt/mysite/media ni —
foydalanuvchilar yuklagan fayllarni — butunlay o'chirib yuboradi. Serverda
qo'lda yaratilgan boshqa fayllar ham xavf ostida:
5-qismdagi scripts/backup.sh repo'da
bo'lmasa, deploy uni o'chiradi va tungi zaxira yiqila boshlaydi. Uni
loyihaning scripts/ papkasiga qo'shib commit qiling — chmod +x bilan
berilgan bajarish huquqini git saqlaydi.
Serverda bir martalik tayyorgarlik#
Skript ishlashi uchun serverda ikki narsa oldindan turishi kerak. Ikkalasini
deploy foydalanuvchisi sifatida bajaramiz:
# 1. uv — ~/.local/bin ga o'rnatiladi
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. deploy'ga FAQAT bitta buyruqni parolsiz sudo bilan bajarishga ruxsat
echo "deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart mysite-web" \
| sudo tee /etc/sudoers.d/mysite-deploy
sudo chmod 440 /etc/sudoers.d/mysite-deploy
sudo visudo -cf /etc/sudoers.d/mysite-deploy # "parsed OK" chiqishi shart
Ikkinchi qadam nega kerak: GitHub Actions'da parol yozadigan terminal yo'q,
shuning uchun oddiy sudo systemctl restart deploy'ni
sudo: a terminal is required to read the password bilan yiqitadi. Qoida
esa aynan shu bitta buyruqqa ruxsat beradi — deploy kaliti o'g'irlansa ham,
u orqali boshqa hech narsani root sifatida bajarib bo'lmaydi. restart
o'rniga reload ishlatsangiz, qoidani ham shunga moslang. visudo -c
tekshiruvini o'tkazib yubormang: sudoers faylidagi sintaksis xatosi
sudo ning o'zini butunlay ishdan chiqaradi.
Env drift: .env'ni rsync ko'chirmaydi
Skript .envni ataylab chetlab o'tadi — serverdagi sirlar repo'dan
kelmaydi. Buning teskari tomoni ham bor: kodga yangi muhit o'zgaruvchisi
qo'shsangiz, serverdagi .envga uni qo'lda yozib qo'yish kerak.
Men buni bir marta unutib, "lokalda ishlaydi, jonlida ishlamaydi" degan
klassik holatga tushganman — o'shandan beri yangi o'zgaruvchi qo'shish
checklist'imda alohida band turadi.
Ishlayotganini qanday bilaman#
Push'dan keyin ikki daqiqa ichida hammasi tugaydi. Kuzatish uchun terminaldan chiqish shart emas:
gh run watch # joriy run'ni jonli ko'rsatadi
gh run list --limit 5 # oxirgi natijalar
Yiqilgan deploy qo'rqinchli emas — u serverga yetmagan deploy. needs: checks
tufayli xato har doim serverdan oldin, GitHub'ning o'zida to'xtaydi. Mening
tajribamda eng ko'p yiqiladigan qadam — ruff format --check: lokalda
formatlashni unutib push qilasiz, CI eslatib qo'yadi.
Yakun#
CI/CD sirli narsa emas: bitta YAML fayl, bitta bash skript, bitta maxsus SSH kalit. Lekin bergan narsasi katta — har push serverga yetguncha to'rt tekshiruvdan o'tadi, deploy esa odamning kayfiyatiga emas, testlarning rangiga bog'liq bo'lib qoladi. Shu maqolani o'qib turganingizning o'zi pipeline ishlayotganining isboti.
Keyingi qismda muammo qidirish tomoniga o'tamiz: journalctl bilan loglarni o'qish — xizmat yiqilganda qayerdan boshlash va nimalarga qarash.