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.
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/mysiteda va papkadeployfoydalanuvchisiga tegishli (1-qismda yaratgan foydalanuvchimiz); -
/opt/mysite/.venvvirtual muhitida loyiha kutubxonalari vagunicorno'rnatilgan; -
/opt/mysite/.envda sozlamalar va sirlar turibdi; - PostgreSQL shu serverning o'zida o'rnatilgan.
Bir buyruq bilan tekshirib olamiz:
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:
sudo nano /etc/systemd/system/mysite-web.service
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.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:
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 -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.