Resurslar — xotira va CPU chegarasi, restart siyosati

~10 daq · Docker · Production

Muammodan boshlaylik

Serverda 4 GB xotira, unda API, baza, Redis va navbat ishlovchisi. Bir kuni API da xotira oqishi (memory leak) paydo bo'ldi. Bir necha soatdan keyin:

  • API butun xotirani egalladi
  • Linux OOM killer ishga tushdi va... MySQL ni o'chirdi (eng ko'p xotira ishlatgani uchun)
  • Sayt to'liq ishlamay qoldi, hech kim kechasi qayta ishga tushirmadi

Standart holatda konteyner serverning barcha resurslaridan foydalana oladi. Bitta noto'g'ri konteyner — hammasini qulatadi.


Xotira chegarasi

docker run -d --memory=512m --memory-reservation=256m dokon-api
services:
  api:
    image: dokon-api:1.4.2
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
        reservations:
          memory: 256M
  • limits.memory — qat'iy chegara. Oshib ketsa — konteyner o'ldiriladi (exit code 137, OOMKilled: true)
  • reservations.memory — kafolatlangan minimum (resurs tanqisligida hisobga olinadi)

Endi xotira oqishida faqat API o'ladi (va restart siyosati bo'yicha qayta tug'iladi), baza tegmay qoladi.

docker inspect api --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
# true 137

Ilova chegarani bilishi kerak

Runtime Sozlash
Node.js NODE_OPTIONS=--max-old-space-size=384 (chegaraning ~75%)
Java (17+) Avtomatik; yoki -XX:MaxRAMPercentage=75
PHP-FPM pm.max_children × bitta jarayon xotirasi < chegara
MySQL innodb_buffer_pool_size ≈ chegaraning 50–70%
Python (gunicorn) --workers soni × bitta worker xotirasi

Aks holda ilova "menda 16 GB bor" deb o'ylaydi va chegaraga urilib o'ladi.


CPU chegarasi

deploy:
  resources:
    limits:
      cpus: "1.5"          # 1.5 ta yadrodan ko'p emas
docker run --cpus=1.5 dokon-api

CPU chegarasidan oshganda konteyner o'ldirilmaydi — shunchaki sekinlashtiriladi (throttling). Xotiradan farqi shu.

Boshqa chegaralar

services:
  api:
    pids_limit: 200            # fork bomb ga qarshi
    ulimits:
      nofile:
        soft: 65536            # ochiq fayllar/ulanishlar soni
        hard: 65536

Kuzatish: docker stats

docker stats
NAME     CPU %   MEM USAGE / LIMIT     MEM %   NET I/O        PIDS
api      12.4%   287MiB / 512MiB       56.0%   1.2GB / 890MB  23
baza     3.1%    410MiB / 1GiB         40.0%   890MB / 1.1GB  38
kesh     0.4%    12MiB / 128MiB        9.4%    120MB / 95MB   5

Chegaralarni "taxminan" emas, haqiqiy ishlatishga qarab qo'ying: bir necha kun docker stats ni kuzating, maksimal ishlatish + 30–50% zaxira.


Restart siyosati

services:
  api:
    restart: unless-stopped
Siyosat Konteyner to'xtaganda Server qayta yuklanganda
no (standart) Qayta ishga tushmaydi Yo'q
on-failure[:5] Faqat xato bilan (exit ≠ 0), maks. 5 marta Ha (xato bilan to'xtagan bo'lsa)
always Har doim Ha — hatto qo'lda to'xtatilgan bo'lsa ham
unless-stopped Har doim Ha — qo'lda to'xtatilmagan bo'lsa
  • Veb-xizmatlar, baza — unless-stopped (eng ko'p ishlatiladi)
  • Bir martalik vazifalar (migratsiya) — no yoki on-failure
  • Docker o'zi server yuklanganda ishga tushishi kerak: sudo systemctl enable docker

Qayta ishga tushirishlar orasida Docker kutish vaqtini oshirib boradi (100ms, 200ms, 400ms...) — doimiy qulayotgan konteyner serverni band qilmasligi uchun.

docker inspect api --format '{{.RestartCount}}'     # necha marta qayta tug'ildi

RestartCount o'sib borayotgan bo'lsa — muammo bor, restart uni faqat yashiradi. Loglarni ko'ring (43-dars).

unhealthy ≠ restart

Muhim nozik jihat: oddiy Docker/Compose da healthcheck unhealthy bo'lsa — konteyner avtomatik qayta ishga tushirilmaydi. Restart siyosati faqat jarayon tugaganda ishlaydi. unhealthy dan qayta ishga tushirish — orkestratorlar (Swarm, Kubernetes) yoki autoheal kabi yordamchi konteyner vazifasi.


To'liq misol

services:
  api:
    image: ghcr.io/firdavsdev07/dokon-api:1.4.2
    restart: unless-stopped
    environment:
      NODE_OPTIONS: --max-old-space-size=384
    deploy:
      resources:
        limits: { memory: 512M, cpus: "1.0" }
        reservations: { memory: 256M }
    pids_limit: 200
    read_only: true
    tmpfs: [/tmp]
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 15s

  baza:
    image: mysql:8.4
    restart: unless-stopped
    command: --innodb-buffer-pool-size=512M
    deploy:
      resources:
        limits: { memory: 1G }
    volumes:
      - mysql-data:/var/lib/mysql

  kesh:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --maxmemory 100mb --maxmemory-policy allkeys-lru
    deploy:
      resources:
        limits: { memory: 128M }

volumes:
  mysql-data:

Redis da --maxmemory — chegaraga yetganda o'zi eski kalitlarni chiqaradi (allkeys-lru), o'ldirilish o'rniga.


Nima uchun shunday?

Nima uchun chegara qo'yish yaxshi — konteynerga ko'proq resurs bersak yaxshi emasmi?

Chunki chegara — nosozlikni izolyatsiya qilish. Chegarasiz bitta konteyner muammosi (xotira oqishi, cheksiz tsikl, hujum) butun serverga tarqaladi va eng muhim xizmatlarni (baza) tasodifiy ravishda o'ldiradi. Chegara bilan muammo o'z konteyneri ichida qoladi: u o'ladi, qayta tug'iladi, qolganlari ishlashda davom etadi. Bu kemalardagi suv o'tkazmaydigan bo'linmalar kabi — bitta bo'linmaga suv kirsa, kema cho'kmaydi.


Xulosa

Eslab qoling:

  • Standart holatda konteyner chegarasiz — bitta xizmat butun serverni qulatishi mumkin
  • deploy.resources.limits.memory — oshsa exit 137 (OOMKilled)
  • cpus — oshsa sekinlashadi, o'lmaydi
  • Ilovaga chegarani ayting: --max-old-space-size, innodb_buffer_pool_size, Redis maxmemory
  • docker stats — haqiqiy ishlatishni kuzating
  • restart: unless-stopped — xizmatlar uchun; systemctl enable docker
  • unhealthy — Compose da avtomatik restart qilmaydi
  • RestartCount o'sishi — muammo belgisi

Keyingi dars

41-dars: CI/CD — GitHub Actions bilan avtomatik build va push