Asosiy kontentga o‘tish

Maqolalar

Darslik · 2026-08-16 · ~6 daqiqa o‘qiladi · o'rta

systemd'ni chuqurroq: web-ilovani xizmatga aylantirish

#linux #devops #django #deploy

Mundarija

Aladdin seriyasida botni systemd xizmatiga aylantirgan edik — u yerda asosiy g'oyalarni ko'rgansiz: unit fayl, restart, enable. Endi web-ilova navbati, va bu yerda ish biroz nozikroq. Bot yiqilsa foydalanuvchi bir daqiqa kutib qayta yozadi, sayt yiqilsa esa har soniya ko'rinib turadi. Shuning uchun web-xizmatda bog'liqliklar, yumshoq qayta yuklash va himoya chegaralari degan mavzular paydo bo'ladi.

Misol sifatida uzoqqa bormayman — hozir o'qiyotgan saytingizning o'zi serverda aynan shu sxemada ishlaydi: Django ilovasini gunicorn ko'tarib turadi, uni esa systemd boshqaradi.

To'liq unit: satrma-satr#

# /etc/systemd/system/mysite-web.service
[Unit]
Description=MySite Django web service
# tarmoq va baza tayyor bo'lmaguncha boshlamaymiz
After=network-online.target postgresql.service
Wants=network-online.target
# baza yo'q bo'lsa boshlanmasin ham: Requires — qattiq bog'liqlik
Requires=postgresql.service

[Service]
Type=notify
User=deploy
Group=deploy
WorkingDirectory=/opt/mysite
EnvironmentFile=/opt/mysite/.env
ExecStart=/opt/mysite/.venv/bin/gunicorn config.wsgi:application \
    --workers 2 \
    --bind 127.0.0.1:8001

# yumshoq qayta yuklash: HUP signalida gunicorn worker'larni birma-bir
# almashtiradi — sayt bir soniya ham yotmaydi
ExecReload=/bin/kill -HUP $MAINPID

Restart=always
RestartSec=3

# kichik VPS'da bitta xizmat butun xotirani yeb qo'ymasin
MemoryMax=512M

# minimal sandbox: xizmat buzilsa ham zarar chegaralangan bo'lsin
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target

Botdagi unit'dan farq qiladigan joylarini birma-bir ochamiz.

Bog'liqliklar: After va Requires farqi#

Django bazasiz yashay olmaydi, shuning uchun unit'da postgresql ikki marta tilga olingan. Bu takror emas, ikki xil ma'no:

  • After=postgresql.servicetartib: baza avval, biz keyin. Lekin baza yiqilsa, bizga baribir;
  • Requires=postgresql.serviceshart: baza to'xtatilsa, biz ham to'xtatilamiz; baza start bo'lolmasa, biz ham boshlanmaymiz.

Ikkalasini birga yozish odatiy amaliyot: tartib ham, shart ham kerak. Aftersiz yolg'iz Requires yozsangiz, ikkalasi baravar boshlanadi va ilova "connection refused" bilan bir marta yiqilib olib, restart'dan keyin ishlaydi — loglarda doimiy ko'rinadigan, lekin hech kim tushunmaydigan o'sha birinchi xato shundan chiqadi.

Type=notify: "tayyor bo'ldim" xabari#

Botda Type=simple ishlatgan edik: systemd jarayon ishga tushdi — demak xizmat tayyor, deb hisoblaydi. Web-ilovada bu yetarli emas, chunki gunicorn ishga tushishi bilan emas, worker'lari ko'tarilib bo'lgachgina so'rov qabul qila oladi. Gunicorn systemd bilan gaplasha oladi: Type=notify qo'yilsa, u "endi tayyorman" deb o'zi xabar beradi. Buning amaliy foydasi shuki, systemctl start buyrug'i xizmat haqiqatan tayyor bo'lgandagina qaytadi — deploy skriptida undan keyingi tekshiruvlar ishonchli ishlaydi.

Graceful reload: saytni o'chirmasdan yangilash#

Yangi kod deploy bo'ldi — endi gunicorn'ni qayta yuklash kerak. restart qilsak, eski jarayon o'ladi, yangisi ko'tarilguncha bir necha soniya sayt javobsiz qoladi. reload esa boshqacha ishlaydi:

sudo systemctl reload mysite-web

Bu bizning ExecReload qatorimizni ishga soladi — gunicorn HUP signalini olib, worker'larni birma-bir almashtiradi: eski worker joriy so'rovini tugatib chiqib ketadi, yangisi yangi kod bilan kirib keladi. Foydalanuvchi hech narsa sezmaydi. Shu bir qator deploy'dagi "bir soniyalik uzilish"larni butunlay yo'qotadi.

reload qachon yetmaydi

HUP faqat Python kodini qayta yuklaydi. .env o'zgargan, gunicorn'ning o'z parametrlari (worker soni, port) o'zgargan bo'lsa — to'liq restart kerak. Deploy skriptida buni ajratib qo'ygan qulay: kod o'zgarsa reload, konfiguratsiya o'zgarsa restart.

Chegaralar va sandbox: yiqilganda ham chiroyli yiqilish#

Qolgan to'rt qator — "hech qachon kerak bo'lmasin, lekin tursin" toifasidan:

  • MemoryMax=512M — xotira oqishi bo'lsa, butun server emas, faqat shu xizmat qayta ishga tushadi. 1-qismdagi swap OOM-killer'dan saqlagani kabi, bu qator qo'shnilarni himoya qiladi;
  • NoNewPrivileges=true — jarayon va uning bolalari hech qanday yo'l bilan huquq oshira olmaydi;
  • PrivateTmp=true — xizmatning /tmp'i alohida: boshqa jarayonlar bilan vaqtinchalik fayl almashinuvi yopiladi;
  • ProtectSystem=full — /usr va /etc xizmat uchun faqat o'qish rejimida.

Bular kodda biror narsani o'zgartirmaydi, lekin ilovada zaiflik topilgan kuni hujumchining harakat maydonini keskin toraytiradi. Security bilan shug'ullanadigan odam sifatida aytamanki, bunday arzon himoyalarni qo'ymaslikka sabab yo'q.

Drop-in: paketdan kelgan unit'ni buzmasdan sozlash#

O'zingiz yozgan unit'ni bemalol tahrirlaysiz. Lekin paket bilan kelgan xizmatni (masalan postgresql) sozlash kerak bo'lsa, uning faylini to'g'ridan- to'g'ri o'zgartirmang — keyingi yangilanishda paket uni qayta yozib yuboradi. Buning to'g'ri yo'li drop-in:

sudo systemctl edit postgresql

Ochilgan bo'sh faylga faqat o'zgartirmoqchi bo'lgan qatorlarni yozasiz — systemd ularni asl unit ustiga "qoplaydi" va /etc/systemd/system/postgresql.service.d/override.conf ko'rinishida alohida saqlaydi. Asl fayl toza qoladi, sizning o'zgarishingiz yangilanishlardan omon chiqadi.

Tekshirib yakunlaymiz#

sudo systemctl daemon-reload
sudo systemctl enable --now mysite-web
systemctl status mysite-web        # active (running), Memory: ... ko'rinadi
curl -I http://127.0.0.1:8001/     # 200 OK

# reload jonli ishlashini ham sinab qo'yamiz:
sudo systemctl reload mysite-web
curl -I http://127.0.0.1:8001/     # uzilishsiz yana 200

Xizmat 127.0.0.1'ga bog'langaniga e'tibor bering — u tashqi dunyoga to'g'ridan- to'g'ri ochilmagan. Tashqariga uni nginx chiqaradi, va aynan shu bog'lanish keyingi qismning mavzusi: nginx'ni reverse proxy va statika uchun sozlash.