Docker: muhitni ham qulflash
"Mening kompyuterimda ishlaydi"
Kodingizni hamkasbingizga berdingiz. Yarim soatdan keyin xabar keladi:
— Ishlamayapti. Xato beryapti.
Siz o'zingizda ochasiz — ishlaydi. Bir xil kod, bir xil fayl, bir xil buyruq.
Sabab kodda emas. Sizda Python 3.11, unda 3.10. Sizda scikit-learn 1.5, unda 1.3. Sizda
tizimda bir kutubxona o'rnatilgan, unda yo'q.
Bu dasturlashdagi eng eski hazil, va u har kuni takrorlanadi.
Retsept emas, oshxonani yuboring
Muammoning ildizi shunda: siz retsept yubordingiz, lekin oshxona boshqa.
Retseptda "180 darajada 40 daqiqa" deb yozilgan. Lekin uning duxovkasi boshqacha qizidi. Retsept to'g'ri, natija boshqa.
Yagona ishonchli yechim — retsept bilan birga butun oshxonani yuborish: duxovka, tarozi, idishlar, hatto un va tuzning aynan o'sha markasi.
Kompyuterda buni qilish mumkin, va u konteyner deyiladi.
Docker ning ishi aynan shu: u kodingizni Python, kutubxonalar va tizim fayllari bilan birga bitta qutiga joylaydi. Hamkasbingiz o'sha qutini ishga tushiradi — va uning mashinasida nima o'rnatilgani ahamiyatsiz bo'lib qoladi.
Takrorlanishning ikkinchi darajasi
2-bobda takrorlanishning uch darajasi bor edi. Birinchisini bajardik: seed va ma'lumot hash i bilan bir xil mashinada natijani qaytara olamiz.
Ikkinchi daraja qiyinroq: boshqa mashinada. U yerda boshqa Python, boshqa kutubxona versiyasi, boshqa tizim kutubxonalari bo'lishi mumkin.
diagramma( { "Sizning mashinangiz\nPython 3.10, sklearn 1.7": (0, 0, 3.0, 1.1, "manba"), "Hamkasb mashinasi\nPython 3.12, sklearn 1.3": (0, 2.2, 3.0, 1.1, "manba"), "Server\nPython 3.9, sklearn 1.1": (0, 4.4, 3.0, 1.1, "manba"), "DOCKER IMAGE\n(aynan bitta muhit)": (4.2, 2.2, 3.0, 1.1, "qayta"), "Bir xil natija\nhamma joyda": (8.0, 2.2, 2.8, 1.1, "saqlash"), }, [ ("Sizning mashinangiz\nPython 3.10, sklearn 1.7", "DOCKER IMAGE\n(aynan bitta muhit)"), ("Hamkasb mashinasi\nPython 3.12, sklearn 1.3", "DOCKER IMAGE\n(aynan bitta muhit)"), ("Server\nPython 3.9, sklearn 1.1", "DOCKER IMAGE\n(aynan bitta muhit)"), ("DOCKER IMAGE\n(aynan bitta muhit)", "Bir xil natija\nhamma joyda"), ], nom="docker_nima_uchun.png", kenglik=13, izoh="Docker muhitni ham artefaktga aylantiradi: Python, kutubxonalar va tizim fayllari birga qadoqlanadi.", )

Docker muhitni ham artefaktga aylantiradi: Python, kutubxonalar va tizim fayllari birga qadoqlanadi.
Docker muhitni fayl qilib qadoqlaydi. Shu fayl (image) qayerda ishga tushsa, ichi bir xil.
docker --version docker compose version
Docker version 29.2.1, build a5c7197 Docker Compose version v5.0.2
Birinchi Dockerfile — sodda, lekin yomon
Avval eng oddiy variantni yozamiz va uni o'lchaymiz:
shu faylni yaratingDockerfile.sodda
# ENG SODDA variant — ataylab optimallashtirilmagan.
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install scikit-learn pandas fastapi uvicorn
CMD ["python", "-c", "print('salom')"]
Uni qurishdan oldin muhim narsa: Docker COPY . . bilan butun papkani nusxalaydi — jumladan
.venv, .ombor, ma'lumot fayllari va __pycache__. Buning oldini .dockerignore oladi:
shu faylni yarating.dockerignore
# Image ga TUSHMASLIGI kerak bo'lgan narsalar .venv/ .ombor/ __pycache__/ *.pyc .pytest_cache/ .mypy_cache/ .git/ *.csv *.parquet *.json modellar/ namuna/
docker build -q -f Dockerfile.sodda -t mlops-kitob:sodda . 2>&1 | tail -2 docker images mlops-kitob:sodda --format "sodda variant: {{.Size}}"
sha256:acfecdbfcd38ecb2b71dad7eb1805b5f570ed6973145f662a2dd48136e0caf0a sodda variant: 2.27GB
Yaxshilangan variant
Uchta o'zgarish qilamiz va har birining sababi bor:
shu faylni yaratingDockerfile
# 1-QADAM: quruvchi (builder) — bu yerda kutubxonalar o'rnatiladi
FROM python:3.11-slim AS quruvchi
WORKDIR /app
# Avval FAQAT bog'liqliklar ro'yxati nusxalanadi.
# Sabab: kod o'zgarganda bu qatlam KESHDAN olinadi, pip qayta ishlamaydi.
COPY talablar.txt .
RUN pip install --no-cache-dir --user -r talablar.txt
# 2-QADAM: yakuniy image — faqat kerakli narsalar
FROM python:3.11-slim
# xavfsizlik: root emas, oddiy foydalanuvchi
RUN useradd --create-home --uid 10001 ishchi
USER ishchi
WORKDIR /app
# quruvchidan faqat o'rnatilgan paketlar ko'chiriladi
COPY --from=quruvchi --chown=ishchi:ishchi /root/.local /home/ishchi/.local
ENV PATH=/home/ishchi/.local/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
COPY --chown=ishchi:ishchi src/ ./src/
CMD ["python", "-c", "import sklearn, pandas; print('tayyor:', sklearn.__version__)"]
shu faylni yaratingtalablar.txt
scikit-learn==1.7.2 pandas==2.3.3 numpy==2.2.6
docker build -q -t mlops-kitob:yaxshi . 2>&1 | tail -2 docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | grep mlops-kitob
sha256:2e929b10725324206e398bba021c67e51760952f8e4a05bac65cb3fa12a379bf mlops-kitob:yaxshi 665MB mlops-kitob:sodda 2.27GB
Farq katta: 2.27 GB dan 665 MB ga, ya'ni uch barobardan ko'p kichrayadi.
Farqni jadvalga yig'amiz:
docker images --format "{{.Tag}}|{{.Size}}" | grep -E "^(sodda|yaxshi)" | while IFS='|' read tag size; do printf " %-8s %s\n" "$tag" "$size" done
yaxshi 665MB sodda 2.27GB
Nima uchun kichrayadi
import pandas as pd show(pd.DataFrame([ {"o'zgarish": "python:3.11 -> python:3.11-slim", "sabab": "Asosiy image dan kompilyator va keraksiz tizim paketlari olib tashlangan"}, {"o'zgarish": "Ko'p bosqichli qurish (multi-stage)", "sabab": "pip ning keshi va qurish artefaktlari yakuniy image ga tushmaydi"}, {"o'zgarish": "--no-cache-dir", "sabab": "pip yuklab olgan .whl fayllarini saqlamaydi"}, {"o'zgarish": ".dockerignore", "sabab": "Ma'lumot, .venv va __pycache__ umuman nusxalanmaydi"}, {"o'zgarish": "COPY src/ (hammasi emas)", "sabab": "Faqat kerakli kod; test va hujjatlar tushmaydi"}, ]))
| o'zgarish | sabab |
|---|---|
| python:3.11 -> python:3.11-slim | Asosiy image dan kompilyator va keraksiz tizim paketlari olib tashlangan |
| Ko'p bosqichli qurish (multi-stage) | pip ning keshi va qurish artefaktlari yakuniy image ga tushmaydi |
| --no-cache-dir | pip yuklab olgan .whl fayllarini saqlamaydi |
| .dockerignore | Ma'lumot, .venv va __pycache__ umuman nusxalanmaydi |
| COPY src/ (hammasi emas) | Faqat kerakli kod; test va hujjatlar tushmaydi |
Qatlam keshi: nima uchun tartib muhim
Bu bo'lim Dockerfile yozishdagi eng ko'p vaqt tejaydigan bilim, va u ko'p qo'llanmada aytilmaydi.
Dockerfile dagi har buyruq qatlam (layer) yasaydi. Qatlamlar bir-birining ustiga qo'yiladi, xuddi tort qavatlari kabi.
Docker juda foydali bir ish qiladi: agar qatlam o'zgarmagan bo'lsa, uni qaytadan tayyorlamaydi — keshdan oladi.
Lekin bitta shafqatsiz qoida bor: bir qatlam o'zgarsa, undan yuqoridagi hamma qatlam qaytadan tayyorlanadi.
Tortni tasavvur qiling. Pastki qavatni almashtirmoqchisiz — demak ustidagi hamma qavatni olib tashlab, qaytadan terishingiz kerak. Eng yuqoridagi qavatni almashtirsangiz esa, qolganiga tegmaysiz.
Shuning uchun tartib muhim. COPY talablar.txt oldin, COPY src/ keyin yoziladi:
kodni o'zgartirganda pip install qatlami pastda qoladi va qayta ishlamaydi.
Buni raqam bilan ko'rsatamiz.
O'lchaymiz — faqat kodni o'zgartirib, qayta quramiz:
echo "# kichik o'zgarish $(date +%s)" >> src/quvur/takrorlanish.py t0=$SECONDS docker build -q -t mlops-kitob:yaxshi . > /dev/null 2>&1 echo "kod o'zgardi, qayta qurish: $((SECONDS - t0)) s (pip KESHDAN olindi)"
kod o'zgardi, qayta qurish: 5 s (pip KESHDAN olindi)
# talablar.txt ga o'zgarish kiritamiz -> COPY talablar.txt qatlami eskiradi echo "# qayta qurish belgisi $(date +%s%N)" >> talablar.txt t0=$SECONDS docker build -q -t mlops-kitob:yaxshi . > /dev/null 2>&1 echo "talablar.txt o'zgardi: $((SECONDS - t0)) s (pip QAYTA ishladi)"
talablar.txt o'zgardi: 37 s (pip QAYTA ishladi)
Ikki raqamni solishtiring — farq o'nlab barobar.
Birinchi holatda faqat kod o'zgardi. Kod qatlami eng tepada, shuning uchun pip install
keshdan olindi.
Ikkinchi holatda talablar.txt o'zgardi. U pastroqda turibdi — demak undan yuqoridagi
hamma narsa, shu jumladan pip install, qaytadan bajarildi.
Endi hisoblang. Kod kuniga o'n marta o'zgaradi, bog'liqliklar esa oyiga bir marta.
Tartib to'g'ri bo'lsa: kuniga 10 × 3 s = 30 soniya.
Tartib teskari bo'lsa: kuniga 10 × 56 s = 9 daqiqa, har kuni, hech qanday sababsiz.
Yiliga bu 40 soatdan ko'p — bir ish haftasi, bitta qator tartibi tufayli.
Buni shunday eslang
"Eng kam o'zgaradigani pastga, eng ko'p o'zgaradigani tepaga."
Xuddi kiyinishdek: ichki kiyimni oxirida almashtirmaysiz.
Eng ko'p o'zgaradigan narsa eng oxirida
Dockerfile ni shunday tartibda yozing:
- Asosiy image (
FROM) — deyarli o'zgarmaydi - Tizim paketlari (
apt-get) — kamdan-kam - Bog'liqliklar (
COPY talablar.txt+pip install) — oyiga bir marta - Kod (
COPY src/) — kuniga o'n marta
Teskari tartib har qurishda hamma narsani qayta bajaradi.
Konteynerni ishga tushiramiz
docker run --rm mlops-kitob:yaxshi
tayyor: 1.7.2
Ichkaridagi muhitni tekshiramiz:
docker run --rm mlops-kitob:yaxshi python -c " import sys, sklearn, pandas, numpy, getpass, os print('Python :', sys.version.split()[0]) print('sklearn :', sklearn.__version__) print('pandas :', pandas.__version__) print('numpy :', numpy.__version__) print('foydalanuvchi:', os.getuid(), '(0 = root, xavfli)') "
Python : 3.11.16 sklearn : 1.7.2 pandas : 2.3.3 numpy : 2.2.6 foydalanuvchi: 10001 (0 = root, xavfli)
uid 0 emas — konteyner oddiy foydalanuvchi sifatida ishlayapti. Bu muhim xavfsizlik qoidasi:
konteyner buzilsa ham, hujumchi root huquqini olmaydi.
Model bilan ishlaydigan konteyner
Endi haqiqiy ish: modelni o'qitib, konteyner ichida ishlatamiz.
shu faylni yaratingsrc/oqitish/oddiy.py
"""Modelni o'qitib, faylga saqlaydi.""" import sys from pathlib import Path import joblib import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import f1_score XUS = ["xonalar", "maydon", "qavat", "jami_qavat", "narx_mln", "tavsif_uzunligi", "rasmlar_soni", "telefon_takrorlanishi", "kunlik_elonlar", "egasi_tasdiqlangan"] def oqit(malumot: str, chiqish: str) -> float: d = pd.read_csv(malumot) Xtr, Xte, ytr, yte = train_test_split(d[XUS], d["broker"], test_size=0.25, stratify=d["broker"], random_state=42) m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xtr, ytr) f1 = float(f1_score(yte, m.predict(Xte))) Path(chiqish).parent.mkdir(parents=True, exist_ok=True) joblib.dump({"model": m, "xususiyatlar": XUS, "f1": round(f1, 4)}, chiqish) return f1 if __name__ == "__main__": malumot = sys.argv[1] if len(sys.argv) > 1 else "datasets/ijara.csv" chiqish = sys.argv[2] if len(sys.argv) > 2 else "modellar/model.joblib" print(f"F1 = {oqit(malumot, chiqish):.4f} -> {chiqish}")
Modelni konteyner ichida o'qitamiz. Ma'lumot va natija papkasi tashqaridan ulanadi
(--mount), shunda model konteyner o'chganda ham qoladi:
sed -i 's/^joblib.*$//' talablar.txt 2>/dev/null grep -q joblib talablar.txt || echo "joblib==1.5.3" >> talablar.txt docker build -q -t mlops-kitob:yaxshi . > /dev/null 2>&1 mkdir -p modellar docker run --rm \ --mount type=bind,src="$(pwd)/../datasets",dst=/app/datasets,ro \ --mount type=bind,src="$(pwd)/modellar",dst=/app/modellar \ -w /app mlops-kitob:yaxshi \ python src/oqitish/oddiy.py datasets/ijara.csv modellar/model.joblib ls -la modellar/
F1 = 0.8603 -> modellar/model.joblib total 14580 drwxrwxrwx 1 abulqosim abulqosim 512 Sep 29 18:36 . drwxrwxrwx 1 abulqosim abulqosim 512 Sep 29 18:36 .. -rwxrwxrwx 1 abulqosim abulqosim 14928925 Sep 29 18:36 model.joblib
ro bayrog'iga e'tibor bering — ma'lumot faqat o'qish uchun ulandi. Konteyner uni o'zgartira
olmaydi. Bu tasodifiy zararning oldini oladi.
Model faylini tekshiramiz — u konteynerdan tashqarida ham o'qiladi:
python3 -c " import joblib m = joblib.load('modellar/model.joblib') print('model turi :', type(m['model']).__name__) print('daraxtlar :', m['model'].n_estimators) print('xususiyatlar :', len(m['xususiyatlar'])) print('F1 (o\'qitishda):', m['f1']) "
model turi : RandomForestClassifier daraxtlar : 150 xususiyatlar : 10 F1 (o'qitishda): 0.8603
Tozalash
Kitob yaratgan image larni o'chiramiz — sizning boshqa konteyneringizga tegmaydi:
docker rmi -f mlops-kitob:sodda > /dev/null 2>&1 docker images --format "{{.Repository}}:{{.Tag}}" | grep -c "^mlops-kitob" || true echo "qolgan mlops-kitob image lari yuqorida"
1 qolgan mlops-kitob image lari yuqorida
`docker system prune` ni o'ylamasdan ishlatmang
docker system prune -a hamma ishlatilmayotgan image, konteyner va tarmoqni o'chiradi —
jumladan boshqa loyihalaringiznikini ham.
Faqat o'zingiznikini o'chiring: docker rmi <nom>:<tag>, yoki loyihaga yorliq (label) qo'yib,
docker image prune --filter "label=loyiha=mlops-kitob" bilan filtrlang.
Docker nimani hal qilmaydi
import pandas as pd show(pd.DataFrame([ {"muammo": "Kutubxona versiyalari", "Docker hal qiladimi": "HA", "izoh": "Image ichida qulflangan"}, {"muammo": "Tizim kutubxonalari (libgomp)", "Docker hal qiladimi": "HA", "izoh": "Image ga kiradi"}, {"muammo": "Python versiyasi", "Docker hal qiladimi": "HA", "izoh": "FROM da belgilanadi"}, {"muammo": "Ma'lumot versiyasi", "Docker hal qiladimi": "YO'Q", "izoh": "5-bob: manifest va ombor"}, {"muammo": "Tasodifiy seed", "Docker hal qiladimi": "YO'Q", "izoh": "2-bob: kodda qulflanadi"}, {"muammo": "CPU arxitekturasi (arm64/amd64)", "Docker hal qiladimi": "QISMAN", "izoh": "Har platformaga alohida image"}, {"muammo": "GPU drayverlari", "Docker hal qiladimi": "QISMAN", "izoh": "nvidia-container-toolkit kerak"}, ]))
| muammo | Docker hal qiladimi | izoh |
|---|---|---|
| Kutubxona versiyalari | HA | Image ichida qulflangan |
| Tizim kutubxonalari (libgomp) | HA | Image ga kiradi |
| Python versiyasi | HA | FROM da belgilanadi |
| Ma'lumot versiyasi | YO'Q | 5-bob: manifest va ombor |
| Tasodifiy seed | YO'Q | 2-bob: kodda qulflanadi |
| CPU arxitekturasi (arm64/amd64) | QISMAN | Har platformaga alohida image |
| GPU drayverlari | QISMAN | nvidia-container-toolkit kerak |
Docker — takrorlanishning bir qismi, hammasi emas. Uni 2-bobdagi seed va 5-bobdagi ma'lumot versiyalash bilan birga ishlatish kerak.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Nima uchun | Muhitni ham artefaktga aylantiradi — 2-darajali takrorlanish |
-slim |
Asosiy image ni sezilarli kichraytiradi |
| Ko'p bosqichli | Qurish artefaktlari yakuniy image ga tushmaydi |
.dockerignore |
Ma'lumot va .venv umuman nusxalanmaydi |
| Qatlam tartibi | Bog'liqliklar oldin, kod oxirida — kesh shunda ishlaydi |
USER |
root emas; konteyner buzilsa zarar cheklanadi |
--mount ...,ro |
Ma'lumotni faqat o'qish uchun ulang |
prune xavfi |
-a boshqa loyihalaringizni ham o'chiradi |
| Chegara | Ma'lumot va seed ni Docker qulflamaydi |
Talaba ishi
DockerfiledagiCOPY talablar.txtvaCOPY src/qatorlarining o'rnini almashtiring. Kodni o'zgartirib qayta quring — qurish vaqti qanday o'zgardi?.dockerignoredan*.csvqatorini olib tashlang va image hajmini qayta o'lchang. Qancha oshdi?USER ishchiqatorini olib tashlang vaos.getuid()ni qayta tekshiring. Bu nega xavfli?python:3.11-slimo'rnigapython:3.11-alpineni sinab ko'ring. Kichikroqmi?numpyo'rnatilishi bilan nima bo'ladi? (Maslahat:muslvaglibc.)
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.