17-bo‘lim
CI/CD va DevOps
Doimiy integratsiya va yetkazish, deploy strategiyalari, monitoring va infratuzilma kod sifatida.
Ushbu bo‘lim mundarijasi
Kod yozildi va testdan o'tdi. Endi uni foydalanuvchiga yetkazish kerak - va buni tez, xavfsiz va takroran qilish lozim.
DevOps nima? #
| Qisqartma | To'liq nomi | Ma'nosi |
|---|---|---|
| CI | Continuous Integration | Har bir o'zgarish avtomatik qurilib testlanadi |
| CD | Continuous Delivery | Reliz istalgan paytda chiqarishga tayyor |
| CD | Continuous Deployment | Har bir o'tgan o'zgarish avtomatik chiqariladi |
Farq: Delivery da tugmani odam bosadi, Deployment da avtomatik.
CI quvuri #
# .github/workflows/quvur.yml
name: CI/CD quvuri
on:
push:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
sifat:
name: Kod sifati
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- name: Composer keshi
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
- run: composer install --prefer-dist --no-progress
- name: Kod uslubi
run: vendor/bin/php-cs-fixer fix --dry-run --diff
- name: Statik tahlil
run: vendor/bin/phpstan analyse --error-format=github
- name: Xavfsizlik auditi
run: composer audit
testlar:
name: Testlar (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3']
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: sinov
MYSQL_DATABASE: sinov_baza
ports: ['3306:3306']
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-retries=5
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
- run: composer install --prefer-dist --no-progress
- name: Migratsiyalar
run: php tools/migrate.php
env:
DB_HOST: 127.0.0.1
DB_DATABASE: sinov_baza
DB_PASSWORD: sinov
- name: Testlar
run: vendor/bin/phpunit --coverage-clover=coverage.xml
- name: Qoplama hisoboti
uses: codecov/codecov-action@v4
with:
files: coverage.xml
deploy:
name: Ishlab chiqarishga chiqarish
needs: [sifat, testlar]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Serverga chiqarish
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KALIT }}
script: |
cd /var/www/dokon
./deploy.sh ${{ github.sha }}
| Usul | Tejaydi |
|---|---|
| Bog'liqliklarni keshlash | 1-3 daqiqa |
| Vazifalarni parallel bajarish | 50%+ |
| Testlarni bo'lish (sharding) | 60%+ |
fail-fast - birinchi xatoda to'xtash | Ko'p |
| Faqat o'zgargan qismni testlash | Katta loyihada |
| Docker qatlamlarini keshlash | 2-5 daqiqa |
Deploy strategiyalari #
#!/bin/bash
# deploy.sh - atomik deploy
set -euo pipefail
RELIZ=$1
ILDIZ=/var/www/dokon
RELIZLAR=$ILDIZ/relizlar
YANGI=$RELIZLAR/$RELIZ
echo "==> Reliz tayyorlanmoqda: $RELIZ"
mkdir -p "$YANGI"
git archive "$RELIZ" | tar -x -C "$YANGI"
echo "==> Bog'liqliklar"
cd "$YANGI"
composer install --no-dev --optimize-autoloader --no-interaction
echo "==> Umumiy kataloglar"
ln -sfn "$ILDIZ/umumiy/.env" "$YANGI/.env"
ln -sfn "$ILDIZ/umumiy/yuklamalar" "$YANGI/public/yuklamalar"
rm -rf "$YANGI/storage" && ln -sfn "$ILDIZ/umumiy/storage" "$YANGI/storage"
echo "==> Migratsiyalar"
php "$YANGI/tools/migrate.php"
echo "==> Salomatlik tekshiruvi"
php "$YANGI/tools/salomatlik.php" || { echo "Tekshiruv o'tmadi"; exit 1; }
echo "==> Almashtirish"
ln -sfn "$YANGI" "$ILDIZ/joriy"
sudo systemctl reload php8.3-fpm
echo "==> Eski relizlarni tozalash (oxirgi 5 tasi qoladi)"
cd "$RELIZLAR" && ls -1t | tail -n +6 | xargs -r rm -rf
echo "==> Tayyor: $RELIZ"
Simvolik havolani almashtirish - bir zumda bajariladi.
Foydalanuvchi hech qachon yarim ko'chirilgan fayllarni ko'rmaydi.
Orqaga qaytish ham bir zumda:
ln -sfn /var/www/dokon/relizlar/OLDINGI /var/www/dokon/joriy
sudo systemctl reload php8.3-fpm
Funksiya kalitlari #
final class FunksiyaKalitlari
{
public function __construct(
private KeshInterfeysi $kesh,
private SozlamaRepozitoriyi $repozitoriy,
) {}
public function yoqilganmi(string $nom, ?int $foydalanuvchiId = null): bool
{
$kalit = $this->kesh->eslab("kalit:{$nom}", 60,
fn () => $this->repozitoriy->oling($nom));
if ($kalit === null || !$kalit->faol) {
return false;
}
if ($kalit->foiz === 100) {
return true;
}
if ($foydalanuvchiId === null) {
return false;
}
// Bir foydalanuvchi har doim bir xil guruhga tushsin
$xesh = crc32("{$nom}:{$foydalanuvchiId}") % 100;
return $xesh < $kalit->foiz;
}
}
if ($kalitlar->yoqilganmi('yangi_savat', $foydalanuvchi->id)) {
return $this->yangiSavat->korsating();
}
return $this->eskiSavat->korsating();
Deploy - kodni serverga chiqarish (texnik amal). Reliz - imkoniyatni foydalanuvchiga ochish (biznes qarori).
Funksiya kalitlari bilan siz kodni har kuni deploy qilib, imkoniyatni kerakli paytda ochasiz.
Bu tugallanmagan ishni ham main ga merge qilish imkonini beradi.
Har bir kalit - kodda qo'shimcha shart.
100 ta eski kalit - o'qib bo'lmaydigan kod.
Qoida: kalit 100% ga yetgach, 2 hafta ichida uni va eski kodni o'chiring.
Muhitlar #
| Muhit | Maqsad | Ma'lumot |
|---|---|---|
| Lokal | Ishlab chiqish | Sun'iy |
| CI | Avtomatik testlar | Sun'iy, har safar toza |
| Staging | Qo'lda sinov, demo | Anonimlashtirilgan nusxa |
| Production | Foydalanuvchilar | Haqiqiy |
Bu keng tarqalgan, lekin xavfli amaliyot:
- Shaxsiy ma'lumotlar himoyasiz muhitda
- Tasodifan haqiqiy mijozlarga email yuborilishi mumkin
- Qonun buzilishi
To'g'ri yondashuv - anonimlashtirish:
UPDATE mijozlar SET
ism = CONCAT('Sinov', id),
email = CONCAT('sinov', id, '@example.test'),
telefon = CONCAT('+99890', LPAD(id, 7, '0')),
manzil = 'Sinov manzili';
Va staging da tashqi xizmatlarni o'chirib qo'ying: email, SMS, to'lov.
# .env.staging
APP_MUHIT=staging
EMAIL_YUBORILSINMI=false
SMS_YUBORILSINMI=false
TOLOV_REJIMI=sinov
Infratuzilma kod sifatida #
# infratuzilma/asosiy.tf
resource "digitalocean_droplet" "veb" {
count = 2
name = "dokon-veb-${count.index + 1}"
region = "fra1"
size = "s-2vcpu-4gb"
image = "ubuntu-24-04-x64"
ssh_keys = [var.ssh_kalit_id]
tags = ["dokon", "veb", "production"]
}
resource "digitalocean_database_cluster" "mysql" {
name = "dokon-mysql"
engine = "mysql"
version = "8"
size = "db-s-2vcpu-4gb"
region = "fra1"
node_count = 2
}
resource "digitalocean_loadbalancer" "asosiy" {
name = "dokon-balanser"
region = "fra1"
forwarding_rule {
entry_port = 443
entry_protocol = "https"
target_port = 80
target_protocol = "http"
certificate_name = "dokon-uz"
}
healthcheck {
port = 80
protocol = "http"
path = "/salomatlik"
}
droplet_ids = digitalocean_droplet.veb[*].id
}
terraform plan # nima o'zgarishini ko'rsatadi
terraform apply # qo'llaydi
- Takrorlanuvchanlik - bir xil muhitni qayta yaratish
- Versiyalash - infratuzilma o'zgarishi Git da
- Tekshiruv - PR orqali muhokama
- Hujjat - kod o'zi hujjat
- Falokatdan tiklanish - bir buyruq bilan qayta qurish
"Qo'lda sozlangan server" - bilim faqat bir odam boshida degani.
Konteynerlar #
# Ko'p bosqichli qurish
FROM composer:2 AS bogliqliklar
WORKDIR /ilova
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --optimize-autoloader
FROM php:8.3-fpm-alpine
RUN apk add --no-cache libzip-dev \
&& docker-php-ext-install pdo_mysql opcache zip
COPY --from=bogliqliklar /ilova/vendor /var/www/vendor
COPY . /var/www
WORKDIR /var/www
RUN addgroup -g 1000 ilova && adduser -u 1000 -G ilova -D ilova \
&& chown -R ilova:ilova /var/www/storage
USER ilova
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
CMD php -r "exit(@file_get_contents('http://localhost:9000/salomatlik') ? 0 : 1);"
EXPOSE 9000
CMD ["php-fpm"]
# docker-compose.yml
services:
ilova:
build: .
depends_on:
mysql:
condition: service_healthy
environment:
DB_HOST: mysql
volumes:
- ./storage:/var/www/storage
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PAROL}
MYSQL_DATABASE: dokon
volumes:
- mysql_malumot:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
nginx:
image: nginx:alpine
ports: ["80:80"]
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on: [ilova]
volumes:
mysql_malumot:
- Kichik obraz -
alpineyokislim - Ko'p bosqichli qurish - qurish vositalari yakuniy obrazda qolmasin
rootsifatida ishlamang- Sirlarni obrazga qo'ymang - muhit o'zgaruvchisi orqali
HEALTHCHECKqo'shing.dockerignoreyozing- Versiyani qotiring:
php:8.3-fpm-alpine,latestemas
Monitoring #
// Tuzilmali jurnal
$this->jurnal->info('buyurtma_yaratildi', [
'buyurtma_id' => $buyurtma->id,
'mijoz_id' => $buyurtma->mijozId,
'summa' => $buyurtma->jami()->tiyin,
'elementlar' => count($buyurtma->elementlar()),
'davomiylik_ms' => $davomiylik,
'sorov_id' => $sorovId,
]);
{
"vaqt": "2026-08-15T14:22:31.482+05:00",
"daraja": "info",
"hodisa": "buyurtma_yaratildi",
"buyurtma_id": 1042,
"mijoz_id": 7,
"summa": 75000000,
"elementlar": 3,
"davomiylik_ms": 187,
"sorov_id": "req_8f3a1d0e"
}
YOMON: "Buyurtma 1042 mijoz 7 uchun yaratildi, 187ms"
YAXSHI: {"hodisa":"buyurtma_yaratildi","buyurtma_id":1042,...}
Ikkinchisini filtrlash mumkin:
hodisa="buyurtma_yaratildi" AND davomiylik_ms > 1000
- Parollar va tokenlar
- Karta raqamlari
- Shaxsiy hujjat ma'lumotlari
- To'liq so'rov tanasi (sirlar bo'lishi mumkin)
$maxfiy = ['parol', 'token', 'karta_raqami', 'cvv', 'api_kalit'];
$tozalangan = array_map(
fn ($kalit, $qiymat) => in_array($kalit, $maxfiy, true) ? '***' : $qiymat,
array_keys($malumot), $malumot,
);
Ogohlantirishlar #
# prometheus/qoidalar.yml
groups:
- name: dokon
rules:
- alert: YuqoriXatoDarajasi
expr: |
sum(rate(http_sorovlar_total{kod=~"5.."}[5m]))
/ sum(rate(http_sorovlar_total[5m])) > 0.05
for: 5m
labels:
jiddiylik: kritik
annotations:
xulosa: "Xato darajasi 5% dan yuqori"
tavsif: "Oxirgi 5 daqiqada {{ $value | humanizePercentage }}"
- alert: SekinJavob
expr: |
histogram_quantile(0.95,
rate(http_davomiylik_seconds_bucket[5m])) > 2
for: 10m
labels:
jiddiylik: ogohlantirish
annotations:
xulosa: "95-protsentil javob vaqti 2 soniyadan yuqori"
- alert: BazaUlanishlariTolgan
expr: mysql_ulanishlar_faol / mysql_ulanishlar_maks > 0.8
for: 5m
labels:
jiddiylik: ogohlantirish
Kuniga 50 ta ogohlantirish kelsa, jamoa ularni e'tiborsiz qoldiradi. Keyin haqiqiy muammo ham o'tkazib yuboriladi.
Qoidalar:
- Ogohlantirish harakat talab qilsin - aks holda u metrika
- Kritik va ogohlantirish ni ajrating
- Kritik - tunda uyg'otishga arziydigan darajada
- Muntazam ishlaydigan ogohlantirishni tuzating yoki o'chiring
- Oyiga bir marta qoidalarni qayta ko'rib chiqing
Salomatlik tekshiruvi #
// public/salomatlik.php
$tekshiruvlar = [
'baza' => function (): bool {
try {
return (bool) app(PDO::class)->query('SELECT 1')->fetchColumn();
} catch (Throwable) {
return false;
}
},
'kesh' => function (): bool {
try {
app(KeshInterfeysi::class)->saqlang('salomatlik', '1', 10);
return app(KeshInterfeysi::class)->oling('salomatlik') === '1';
} catch (Throwable) {
return false;
}
},
'disk' => fn (): bool => disk_free_space('/') > 1024 * 1024 * 500,
];
$natijalar = [];
$sogmi = true;
foreach ($tekshiruvlar as $nom => $tekshiruv) {
$natija = $tekshiruv();
$natijalar[$nom] = $natija ? 'ok' : 'xato';
$sogmi = $sogmi && $natija;
}
http_response_code($sogmi ? 200 : 503);
header('Content-Type: application/json');
echo json_encode([
'holat' => $sogmi ? 'sog' : 'nosoz',
'versiya' => getenv('APP_VERSIYA'),
'tekshiruvlar' => $natijalar,
], JSON_UNESCAPED_UNICODE);
- Liveness - "jarayon tirikmi?" Yo'q bo'lsa - qayta ishga tushiring
- Readiness - "so'rov qabul qila oladimi?" Yo'q bo'lsa - trafik yubormang
Ishga tushish paytida ilova tirik, lekin tayyor emas.
- Loyihangiz uchun CI quvurini yozing (lint, test, xavfsizlik).
- Bog'liqliklarni keshlang va vaqt farqini o'lchang.
- Vazifalarni parallel bajarishga o'tkazing.
- Atomik
deploy.shskriptini yozing. - Orqaga qaytarish skriptini yozing.
- Funksiya kalitlari tizimini amalga oshiring.
Dockerfilevadocker-compose.ymlyozing.- Tuzilmali jurnal yozuvini qo'shing.
/salomatlikendpointini yozing.- Uchta ogohlantirish qoidasini yozing.
Xulosa #
- DevOps - madaniyat va umumiy javobgarlik, vosita emas.
- CI - har bir o'zgarish avtomatik qurilib testlanadi.
- CD (Delivery) - reliz tayyor; CD (Deployment) - avtomatik chiqadi.
- Quvur 10 daqiqadan kam bo'lsin; arzon bosqichlarni oldin bajaring.
- Atomik deploy - simvolik havolani almashtirish.
- Funksiya kalitlari deploy va relizni ajratadi.
- Kalitlarni tozalab turing - eskilari kodni chalkashtiradi.
- Staging da ma'lumotni anonimlashtiring, tashqi xizmatlarni o'chiring.
- Infratuzilma kod sifatida - takrorlanuvchanlik va versiyalash.
- Konteynerda
rootsifatida ishlamang, versiyani qotiring. - Kuzatuvchanlik: metrikalar, jurnallar, izlar.
- Tuzilmali jurnal qidirish va filtrlash imkonini beradi.
- Ogohlantirish charchashi - haqiqiy muammoni yashiradi.
Keyingi bo'limda xavfsizlikni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.