← Notlara dön

Docker İmajım 4.7 GB'ydi. Kubernetes Umursamamı Sağladı.

Container local'de çalışıyordu. Küçük bir GKE cluster'ı, her bağımlılığı taşımanın maliyetini görmezden gelinemez hale getirdi.

Bir süre boyunca Dockerfile’ımın tek bir işi vardı: uygulamayı çalıştırmak.

Çalıştırdı da.

Sorun, bunun yanında yaklaşık 4.7 GB’lık bir imaj da üretmesiydi.

Bunun büyük olduğunu biliyordum. Ama imaj küçük bir Kubernetes cluster’ında yaşamak zorunda kalana kadar gerçekten umursamadım.

Geliştirme ortamımı production’a taşımışım

İlk Dockerfile aşağı yukarı şöyleydi:

FROM python:3.11-slim

WORKDIR /app
COPY . .
RUN pip install -e ".[all]"

CMD ["cairnops-api"]

Önemli hata .[all] idi.

Bu, repo’nun bildiği her şeyi kuruyordu: API bağımlılıkları, database kodu, Redis, RAG ingestion, evaluation araçları, test paketleri, lint araçları ve diğer geliştirme bağımlılıkları.

Laptop’ımda zararsızdı. Disk yeterliydi, imaj cache’leniyordu ve onu sürekli yeni node’lara çekmiyordum.

GKE bu israfı görünür kıldı.

Büyük pull’lar daha uzun sürdü, ephemeral storage gerçek bir kısıt haline geldi ve her gereksiz paket artık bir production artifact’inin parçasıydı.

Multi-stage çözümün sadece yarısıydı

Build zamanındaki işleri bir builder stage’ine taşıdım ve runtime stage’ine sadece virtual environment ile uygulama kodunu kopyaladım.

Runtime imajının artık compiler’lara veya build araçlarına ihtiyacı yoktu.

Daha önemlisi, “her şey” bağımlılık grubunu kurmayı bıraktım.

Son stage buna daha yakın görünüyordu:

FROM python:3.11-slim

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
 && rm -rf /var/lib/apt/lists/*

RUN useradd --create-home --shell /bin/bash appuser

COPY --from=builder --chown=appuser:appuser \
    /app/.venv /app/.venv

COPY --chown=appuser:appuser \
    src/ ./src/

ENV PATH="/app/.venv/bin:$PATH"

USER appuser

Ayrıca dosyaları root olarak kopyalayıp sahipliği sonradan düzeltmek yerine COPY --chown kullandım.

Sonuç, registry’de sıkıştırılmış halde yaklaşık 121 MB oldu.

Sayı güzel. Ama davranış değişikliği daha önemliydi.

Node storage baskısı sorun olmaktan çıktı. Pull’lar çok daha hızlandı. Yeni node’lar daha erken işe yarar hale geldi. CI build’leri cache’ten daha çok fayda gördü.

Sonradan silmek bir layer’ı geri almıyor

Bu, Docker layer’larının benim için nihayet soyut bir kavram olmaktan çıktığı proje de oldu.

Bu, insanların genelde beklediği gibi çalışmıyor:

RUN apt-get install build-essential
RUN apt-get remove build-essential

İkinci layer, birincideki byte’ları silmiyor. Önceki layer hâlâ imajın geçmişinin bir parçası.

Build araçlarını runtime imajının dışında tutmanın güvenilir yolu, onları baştan o imaja hiç kopyalamamak.

Multi-stage build’lerin önemli olmasının sebebi bu.

“Multi-stage” bir Docker best-practice listesindeki bir checkbox olduğu için değil; yazılımı build etmek için gerekenle onu çalıştırmak için gereken arasında net bir sınır sağladıkları için önemliler.

Artık her build’den sonra CI’da imaj boyutunu logluyorum.

Küçük bir kontrol, ama Kubernetes bana aynı dersi tekrar öğretmek zorunda kalmadan önce şaşırtıcı derecede maliyetli bir hata sınıfını yakalıyor.