Asosiy kontentga o‘tish

Maqolalar 8 daqiqa 196 ko‘rish

systemd'ni chuqurroq: web-ilovani xizmatga aylantirish

Botni xizmat qilishni o'rgangan edik, endi web-ilova navbati. Gunicorn'li Django saytini systemd ostiga olamiz: bazaga bog'liqlik, graceful reload, xotira chegarasi va sandbox himoyalari — shu saytning o'zida ishlab turgan sozlamalar asosida.

linux devops django

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.

Boshlashdan oldin#

Unit fayl tayyor loyihani ishga tushiradi, lekin loyihaning o'zini serverga qo'ymaydi. Quyidagi misol shu holatni nazarda tutadi:

  • loyiha kodi /opt/mysite da va papka deploy foydalanuvchisiga tegishli (1-qismda yaratgan foydalanuvchimiz);

  • /opt/mysite/.venv virtual muhitida loyiha kutubxonalari va gunicorn o'rnatilgan;

  • /opt/mysite/.env da sozlamalar va sirlar turibdi;

  • PostgreSQL shu serverning o'zida o'rnatilgan.

Bir buyruq bilan tekshirib olamiz:

Terminal
ls -l /opt/mysite/.venv/bin/gunicorn /opt/mysite/.env
systemctl status postgresql --no-pager | head -3

Ikkala fayl ko'rinsa va postgresql active bo'lsa, davom etamiz. Bazangiz boshqa serverda bo'lsa yoki SQLite ishlatsangiz, pastdagi unit'dan postgresql.service tilga olingan qatorlarni olib tashlang — aks holda xizmat Unit postgresql.service not found deb ishga tushmaydi.

Unit faylni yaratamiz:

Terminal
sudo nano /etc/systemd/system/mysite-web.service

To'liq unit: satrma-satr#

INI
# /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.service — tartib: baza avval, biz keyin. Lekin baza yiqilsa, bizga baribir;

  • Requires=postgresql.service — shart: 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.

Ubuntu/Debian'da postgresql.service — o'rovchi

Bu unit bazani o'zi ishga tushirmaydi: ichida ExecStart=/bin/true bor xolos. Haqiqiy baza — klaster xizmati, masalan postgresql@16-main.service, va u Before=postgresql.service bilan o'rovchidan oldin ko'tariladi. Shuning uchun After=postgresql.service to'g'ri ishlaydi: biz klaster tayyor bo'lgach boshlanamiz. Lekin klaster yiqilsa ham o'rovchi active bo'lib qolaveradi va Requires buni sezmaydi. Qat'iy shart kerak bo'lsa, klasterni aniq nom bilan yozing — versiyani ls /etc/postgresql/ ko'rsatadi: Requires=postgresql@16-main.service va After=postgresql@16-main.service.

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: jarayon boshlangani bilan gunicorn hali Django'ni yuklamagan va portni ochmagan bo'lishi mumkin. Gunicorn systemd bilan gaplasha oladi: Type=notify qo'yilsa, u portni ochib bo'lgach "endi tayyorman" (READY=1) deb o'zi xabar beradi. Worker'lar shundan keyin ko'tariladi, lekin bu orada kelgan so'rovlar rad etilmaydi — port ochiq, ular worker bo'shashini navbatda kutadi. Buning amaliy foydasi shuki, systemctl start buyrug'i gunicorn ulanish qabul qila boshlagandagina qaytadi — deploy skriptida undan keyingi tekshiruvlar ishonchli ishlaydi. Faqat bitta nozik joy: READY port ochilganini bildiradi, Django kodi to'g'ri yuklanganini emas. .env fayli umuman topilmasa yoki port band bo'lsa, systemctl start darhol xato beradi. Lekin Django sozlamasidagi xato (masalan, .env da majburiy o'zgaruvchi yo'q) worker ko'tarilayotganda chiqadi — shuning uchun pastdagi curl tekshiruvi baribir kerak.

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:

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

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

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

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

-H "Host: mysite.uz" ni tashlab ketmang. Busiz curl Host: 127.0.0.1:8001 yuboradi, Django esa ALLOWED_HOSTS da yo'q manzilni rad etadi va 400 Bad Request qaytaradi — xizmat ishlayotgan bo'lsa ham. mysite.uz o'rniga o'z ALLOWED_HOSTS ingizdagi domenni yozing.

Status failed bo'lsa yoki curl Connection refused desa, sabab loglarda: sudo journalctl -u mysite-web -n 50 --no-pager. Eng ko'p uchraydiganlari — .venv yo'li xato, .env topilmadi yoki deploy foydalanuvchisi papkani o'qiy olmayapti.

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.

8 daqiqa 196 marta o‘qildi