Asosiy kontentga o‘tish

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.

devops

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#

Sxema
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:

YAML
# .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#

YAML
  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:

Terminal
# 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:

Terminal
#!/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:

  • REMOTE ichidagi set -euo pipefail. ssh serverdagi oxirgi buyruqning natijasini qaytaradi, oxirgisi esa || true. Bu qatorsiz migrate yiqilsa ham skript davom etadi va GitHub deploy'ni yashil ko'rsatadi.

  • export PATH=.... uv o'rnatuvchisi ~/.local/bin ni .profile va .bashrc ga yozadi. Siz serverga kirganingizda uv ishlaydi, lekin ssh orqali yuborilgan buyruqqa bu sozlama yetib bormaydi va uv: command not found chiqadi.

  • .env ni yuklash. Sirlar maqolasida kelishganimizdek, serverda .env ni kod o'qimaydi — uni faqat xizmatning EnvironmentFile= qatori yuklaydi. ssh orqali ishga tushgan manage.py esa xizmat emas: bu tsiklsiz u SECRET_KEY kabi sozlamalarni topolmay yiqiladi. Qiymatlarni .env da 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:

Terminal
# 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:

Terminal
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.

10 daqiqa 144 marta o‘qildi