Model konteyneri: modelni rasm ichiga solamizmi?
Ko'chish kuni
Yangi uyga ko'chyapsiz. Oldingizda quti va narsalar.
Bitta savol bor va u ko'ringanidan muhimroq: televizorni qutiga solasizmi, yoki alohida olib borasizmi?
Qutiga solsangiz — hammasi bir joyda, bir marta ko'tarasiz, hech narsa yo'qolmaydi. Lekin quti og'ir bo'ladi va televizor almashtirmoqchi bo'lsangiz, butun qutini qayta yig'asiz.
Alohida olib borsangiz — quti yengil, televizorni istalgan payt almashtirasiz. Lekin endi ikkita narsani kuzatish kerak, va biri yetib bormasa, uy to'liq bo'lmaydi.
27-bobda servis yozdik. Endi uni ko'chirish kerak — ishlab chiqarish serveriga. Va savol aynan o'sha: model faylini rasm ichiga solamizmi?
Ikki yo'l, ikki xil og'riq
Javob "albatta soling" emas. Ikkalasi ham amalda ishlatiladi.
import pandas as pd show(pd.DataFrame([ {"": "Rasm hajmi", "Model ICHIDA": "Katta (model + kod)", "Model TASHQARIDA": "Kichik"}, {"": "Ishga tushish", "Model ICHIDA": "Tez — hammasi joyida", "Model TASHQARIDA": "Sekin — yuklab olish kerak"}, {"": "Model almashtirish", "Model ICHIDA": "Yangi rasm qurish kerak", "Model TASHQARIDA": "Faqat qayta ishga tushirish"}, {"": "Takrorlanish", "Model ICHIDA": "To'liq — rasm = hamma narsa", "Model TASHQARIDA": "Qisman — registrga bog'liq"}, {"": "Qaytarish (rollback)", "Model ICHIDA": "Eski rasmga qaytasiz", "Model TASHQARIDA": "Alias ko'chirasiz (23-bob)"}, {"": "Internetsiz ishlaydimi", "Model ICHIDA": "Ha", "Model TASHQARIDA": "Yo'q"}, ]))
| Model ICHIDA | Model TASHQARIDA | |
|---|---|---|
| Rasm hajmi | Katta (model + kod) | Kichik |
| Ishga tushish | Tez — hammasi joyida | Sekin — yuklab olish kerak |
| Model almashtirish | Yangi rasm qurish kerak | Faqat qayta ishga tushirish |
| Takrorlanish | To'liq — rasm = hamma narsa | Qisman — registrga bog'liq |
| Qaytarish (rollback) | Eski rasmga qaytasiz | Alias ko'chirasiz (23-bob) |
| Internetsiz ishlaydimi | Ha | Yo'q |
Jadvalga qarang va eng muhim qatorni toping: takrorlanish.
Model rasm ichida bo'lsa, rasm butun haqiqatni o'z ichiga oladi. Olti oydan keyin
mlops-servis:v3 ni ishga tushirsangiz, siz aynan o'sha kodni va aynan o'sha modelni
olasiz. 2-bobdagi takrorlanish shu yerda yakuniga yetadi.
Model tashqarida bo'lsa, rasm faqat yarmi. Ikkinchi yarmi registrda, va u o'zgargan bo'lishi mumkin.
Qaysi birini tanlash
Amaliy qoida:
- Model kichik (< 500 MB) va kamdan-kam o'zgaradi → ichiga soling. Sodda, ishonchli, takrorlanadigan.
- Model katta (bir necha GB) yoki tez-tez yangilanadi → tashqarida qoldiring.
Bizning model 14 MB. Demak ichiga solamiz — va shu bobda buni qilamiz. Lekin ikkinchi yo'lni ham ko'rsatamiz, chunki katta modellarda boshqa tanlov yo'q.
Tayyorgarlik
27-bobdagi servis va 26-bobdagi ONNX modeli kerak. Ular yo'q bo'lsa qayta yaratamiz.
python3 -c " import sys, pathlib; sys.path.insert(0, 'src') if not pathlib.Path('model.onnx').exists(): import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from quvur.xususiyat import XUSUSIYATLAR, tayyorla from quvur.format import onnx_ga d = pd.read_csv('../datasets/ijara.csv') x, y = tayyorla(d), d['broker'] Xtr, _, ytr, _ = train_test_split(x, y, test_size=0.25, stratify=y, random_state=42) m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xtr, ytr) onnx_ga(m, len(XUSUSIYATLAR), 'model.onnx') print('model.onnx yaratildi') else: print('model.onnx bor') print('hajmi:', round(pathlib.Path('model.onnx').stat().st_size/1024/1024, 1), 'MB') "
model.onnx yaratildi hajmi: 6.8 MB
Servis uchun Dockerfile
7-bobdagi Dockerfile o'qitish uchun edi — unda pandas, scikit-learn, hamma narsa bor.
Servis uchun bu ortiqcha. Servis model o'qitmaydi, u faqat bashorat qiladi.
shu faylni yaratingtalablar-servis.txt
fastapi==0.115.6 uvicorn==0.34.0 onnxruntime==1.23.2 numpy==2.2.1 pandas==2.2.3
shu faylni yaratingDockerfile.servis
# 1-BOSQICH: bog'liqliklarni yig'amiz
FROM python:3.11-slim AS quruvchi
WORKDIR /qurish
COPY talablar-servis.txt .
RUN pip install --no-cache-dir --target=/paketlar -r talablar-servis.txt
# 2-BOSQICH: faqat kerakligini ko'chiramiz
FROM python:3.11-slim
# xavfsizlik: root emas, oddiy foydalanuvchi
RUN useradd --create-home --uid 10001 servis
WORKDIR /app
COPY --from=quruvchi /paketlar /paketlar
ENV PYTHONPATH=/paketlar:/app/src
# kod va model (eng ko'p o'zgaradigani oxirida - 7-bob)
COPY src/ /app/src/
COPY model.onnx /app/model.onnx
USER servis
ENV MODEL_YOLI=/app/model.onnx
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
CMD python3 -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz')"
CMD ["python3", "-m", "uvicorn", "xizmat.servis:app", "--host", "0.0.0.0", "--port", "8000"]
Bu Dockerfile da to'rtta qaror bor va har biri sabab bilan:
Ikki bosqich (multi-stage). pip install o'z ortidan kesh va quruvchi asboblar
qoldiradi. Ikkinchi bosqich faqat natijani ko'chiradi, axlatni emas.
USER servis. Konteyner ichida root bo'lish shart emas. Agar servisda zaiflik topilsa,
buzg'unchi root emas, oddiy foydalanuvchi huquqlarini oladi. Bu ko'p vaqt e'tibordan
chetda qoladi — standart holda konteyner root bo'lib ishlaydi.
HEALTHCHECK. Docker endi konteyner tirikmi emas, sog'mi ekanini biladi.
27-bobdagi /healthz shu yerda ishlaydi.
Tartib. COPY src/ va COPY model.onnx eng oxirida — 7-bobdagi qatlam keshi qoidasi.
ONNX "ko'chma", lekin versiya chegarasida
Bu yerda men tuzoqqa tushdim va konteyner ishga tushmadi.
Sabab: model opset 22 bilan saqlangan (mening mashinamdagi onnxruntime 1.23), lekin
talablar-servis.txt da onnxruntime==1.20.1 yozilgan edi. 1.20 esa faqat opset 21 gacha
tushunadi.
Xato xabari aniq emas:
Load model from /app/model.onnx failed: ... Fail
ONNX "har joyda ishlaydi" deyiladi va bu asosan to'g'ri. Lekin fayl ichida opset raqami bor, va uni o'qiydigan kutubxona o'sha raqamni qo'llab-quvvatlashi kerak.
Qoida: modelni yaratgan va o'qiydigan onnxruntime versiyalarini bir xil qiling.
Tekshirish:
python3 -c "import onnx; m=onnx.load('model.onnx'); print(m.opset_import)"
`EXPOSE` port ochmaydi
Ko'pchilik EXPOSE 8000 portni ochadi deb o'ylaydi. Ochmaydi.
U shunchaki hujjat: "bu rasm 8000-portda ishlaydi". Portni haqiqatan ochish uchun
docker run -p kerak.
Xuddi shunday HEALTHCHECK ham konteynerni qayta ishga tushirmaydi — u faqat holatni
belgilaydi. Qayta ishga tushirish Docker Compose yoki Kubernetes ning ishi.
Rasmni quramiz
t0=$SECONDS docker build -q -f Dockerfile.servis -t mlops-servis:v1 . > /dev/null 2>&1 echo "qurish vaqti: $((SECONDS - t0)) s" docker images mlops-servis:v1 --format '{{.Repository}}:{{.Tag}} {{.Size}}' echo "" echo "taqqoslash uchun (7-bobdagi o'qitish rasmi):" docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep -E "mlops-kitob" | head -2
qurish vaqti: 42 s mlops-servis:v1 618MB taqqoslash uchun (7-bobdagi o'qitish rasmi): mlops-kitob:yaxshi 665MB
Ikki raqamni solishtiring. Servis rasmi o'qitish rasmidan kichikroq, lekin farq kutilganchalik katta emas.
Nega? Chunki scikit-learn ni olib tashladik, lekin pandas va numpy qoldi — ular
xususiyat tayyorlash uchun kerak (3-bob: bitta tayyorlash kodi). Ular esa o'zi og'ir.
Haqiqiy kichraytirish uchun pandas ni ham olib tashlash kerak bo'lardi — ya'ni
xususiyatlarni toza numpy bilan hisoblash. Bu mumkin, lekin 3-bobdagi "bitta tayyorlash
kodi" qoidasini buzadi: o'qitish pandas ishlatadi, xizmat esa boshqa kod ishlatadi.
Mana haqiqiy savdo: kichik rasm yoki bitta tayyorlash kodi. Ikkinchisi muhimroq, shuning uchun biz hajmni qabul qilamiz.
Ishga tushiramiz
docker rm -f kitob-servis > /dev/null 2>&1 docker run -d --name kitob-servis -p 18100:8000 mlops-servis:v1 > /dev/null 2>&1 for i in $(seq 1 40); do curl -s http://127.0.0.1:18100/healthz > /dev/null 2>&1 && { echo "servis ${i} soniyada tayyor"; break; } sleep 0.5 done echo -n "healthz : "; curl -s http://127.0.0.1:18100/healthz echo "" echo -n "ready : "; curl -s http://127.0.0.1:18100/ready echo "" echo -n "kim bo'lib ishlayapti: "; docker exec kitob-servis whoami
servis 3 soniyada tayyor
healthz : {"holat":"tirik"}
ready : {"holat":"tayyor","sorovlar":0,"chegara":0.5}
kim bo'lib ishlayapti: servis
Oxirgi qatorga e'tibor bering: konteyner servis foydalanuvchisi bo'lib ishlayapti,
root emas. Bitta USER qatori shuni berdi.
Bashorat so'raymiz
echo "=== EGA ===" curl -s -X POST http://127.0.0.1:18100/bashorat -H "Content-Type: application/json" -d '{ "xonalar": 2, "maydon": 55, "qavat": 3, "jami_qavat": 9, "narx_mln": 4.5, "tavsif_uzunligi": 600, "rasmlar_soni": 15, "telefon_takrorlanishi": 1, "kunlik_elonlar": 1, "egasi_tasdiqlangan": 1}' echo "" echo "=== BROKER ===" curl -s -X POST http://127.0.0.1:18100/bashorat -H "Content-Type: application/json" -d '{ "xonalar": 2, "maydon": 55, "qavat": 3, "jami_qavat": 9, "narx_mln": 4.2, "tavsif_uzunligi": 60, "rasmlar_soni": 2, "telefon_takrorlanishi": 18, "kunlik_elonlar": 9, "egasi_tasdiqlangan": 0}' echo "" echo "=== YOMON KIRISH ===" curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST http://127.0.0.1:18100/bashorat \ -H "Content-Type: application/json" -d '{"xonalar": 2, "maydon": -5}'
=== EGA ===
{"broker_ehtimoli":0.0563,"broker":false,"chegara":0.5,"ms":3.15}
=== BROKER ===
{"broker_ehtimoli":0.9179,"broker":true,"chegara":0.5,"ms":1.75}
=== YOMON KIRISH ===
HTTP 422
27-bobdagi hamma xulq saqlandi: to'g'ri bashorat, 422 yomon kirishda. Faqat endi u
ko'chiriladigan rasm ichida.
Docker sog'liqni ko'radimi
sleep 12 docker inspect kitob-servis --format 'holat: {{.State.Status}} sogliq: {{.State.Health.Status}}' 2>/dev/null \ || docker inspect kitob-servis --format 'holat: {{.State.Status}}' echo "" echo "oxirgi tekshiruv:" docker inspect kitob-servis --format '{{range .State.Health.Log}}{{.ExitCode}} {{end}}' 2>/dev/null | head -1
holat: running sogliq: healthy oxirgi tekshiruv: 0
healthy — demak HEALTHCHECK ishlayapti va Docker servisning ichki holatini biladi.
Bu 30-bobda muhim bo'ladi: yuk muvozanatlagich shu belgiga qarab so'rov yuboradi yoki yubormaydi.
Ikkinchi yo'l: model tashqarida
Katta modellar uchun boshqa tanlov yo'q. Rasm modelsiz quriladi, model esa ishga tushganda yuklanadi.
docker rm -f kitob-servis-t > /dev/null 2>&1 docker run -d --name kitob-servis-t -p 18101:8000 \ -v "$(pwd)/model.onnx:/modellar/model.onnx:ro" \ -e MODEL_YOLI=/modellar/model.onnx \ mlops-servis:v1 > /dev/null 2>&1 for i in $(seq 1 40); do curl -s http://127.0.0.1:18101/ready > /dev/null 2>&1 && break sleep 0.5 done echo -n "tashqi model bilan ready: "; curl -s http://127.0.0.1:18101/ready echo "" echo "diqqat: rasm O'SHA rasm, faqat MODEL_YOLI boshqa"
tashqi model bilan ready: {"holat":"tayyor","sorovlar":0,"chegara":0.5}
diqqat: rasm O'SHA rasm, faqat MODEL_YOLI boshqa
Bir xil rasm, boshqa model. Ya'ni bitta rasm ko'p model bilan ishlay oladi — 23-bobdagi registr bilan birga bu kuchli naqsh bo'ladi.
Model tashqarida bo'lsa, rasm yolg'on gapiradi
mlops-servis:v1 nomi endi to'liq haqiqat emas. U kodni belgilaydi, lekin modelni yo'q.
Bir xil v1 rasmi ikki serverda boshqa-boshqa model bilan ishlashi mumkin va buni
tashqaridan bilib bo'lmaydi.
Shuning uchun model tashqarida bo'lganda, servis o'zi haqida gapirishi kerak:
/ready javobiga model versiyasini qo'shing (23-bobdagi alias va versiya raqami).
Aks holda "qaysi model ishlayapti?" degan savol yana javobsiz qoladi — 23-bobdagi
model_final_v2_YANGI.pkl papkasiga qaytamiz.
Tozalash
docker rm -f kitob-servis kitob-servis-t > /dev/null 2>&1 && echo "konteynerlar o'chirildi" docker ps --format '{{.Names}}' | grep -v k3d
konteynerlar o'chirildi novavivo-postgres novavivo-redis stratix-med-frontend-1
Rasm (mlops-servis:v1) saqlanadi — u 29 va 30-boblarda kerak.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Asosiy savol | Model rasm ichidami yoki tashqarida |
| Model ichida | To'liq takrorlanish; rasm = butun haqiqat |
| Model tashqarida | Kichik rasm, tez almashtirish; lekin yarim haqiqat |
| Tanlov mezoni | < 500 MB va kam o'zgarsa — ichiga |
| Ko'p bosqichli qurish | Quruvchi asboblar yakuniy rasmga tushmaydi |
USER |
Root bo'lib ishlamang — zaiflik narxini kamaytiradi |
EXPOSE |
Port ochmaydi, faqat hujjat |
HEALTHCHECK |
Docker ichki holatni biladi (30-bobda kerak) |
| ONNX foydasi | Tezlik (26-bob) va kichik rasm |
| Tashqi model xavfi | Rasm nomi modelni belgilamaydi — versiyani /ready ga yozing |
Talaba ishi
python:3.11-slimo'rnigapython:3.11ishlating va hajmni solishtiring. Farq qancha?USER servisqatorini olib tashlang vawhoamini qayta ishga tushiring. Nima o'zgardi va nega bu xavfli?HEALTHCHECKni ataylab buzing (noto'g'ri port). Konteyner holati qanday bo'ladi?docker psda nima ko'rinadi?/readyjavobiga model versiyasini qo'shing. Tashqi model bilan ishga tushirib tekshiring.- Ikki bosqichni bitta qilib yozing (
FROMbitta) va hajmni solishtiring.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.