İmaj — değişmez şablon
İmaj, konteynerin read-only kalıbıdır. Bir kez kurulur, etiketlenir ve her yerde birebir aynı şekilde çalışır. Aynı imajdan istediğiniz kadar konteyner üretebilirsiniz.
"Bende çalışıyordu ama sunucuda çalışmadı" cümlesi, konteynerlerin çözmek için doğduğu sorunun tam karşılığıdır. Uygulamayı tüm bağımlılıklarıyla tek bir taşınabilir pakete koyarsanız, çalıştığı yer ne olursa olsun aynı davranır. Onlarca konteyneri birden ayağa kaldırmak, sağlıklı tutmak ve trafikle birlikte ölçeklemek ise orkestrasyonun işidir — bugün bunun ortak dili Kubernetes. Bu sayfada konteyner imajından cluster güvenliğine kadar temel kavramları, birlikte ve adım adım yan yana koyduk.
Konteyner, bir uygulamayı çalıştırmak için gereken her şeyi — kodu, çalışma zamanını, kütüphaneleri ve ayarları — tek bir yalıtılmış pakette toplar. Sanal makinenin aksine her konteyner kendi işletim sistemini taşımaz; hepsi ana makinenin çekirdeğini paylaşır ve yalnızca süreç düzeyinde birbirinden ayrılır. Sonuç: saniyeler içinde başlayan, az yer kaplayan ve her ortamda aynı davranan birimler.
┌───────┐ ┌───────┐ ┌───────┐
│ App A │ │ App B │ │ App C │
├───────┤ ├───────┤ ├───────┤
│ OS │ │ OS │ │ OS │
└───────┘ └───────┘ └───────┘
Hipervizör
Host OS · Donanım
# her VM kendi OS'unu taşır → GB'lar
┌───────┐ ┌───────┐ ┌───────┐
│ App A │ │ App B │ │ App C │
├───────┤ ├───────┤ ├───────┤
│ deps │ │ deps │ │ deps │
└───────┘ └───────┘ └───────┘
Container runtime
Host OS · paylaşılan çekirdek
# ortak çekirdek → MB'lar, saniyeler
İmaj, konteynerin read-only kalıbıdır. Bir kez kurulur, etiketlenir ve her yerde birebir aynı şekilde çalışır. Aynı imajdan istediğiniz kadar konteyner üretebilirsiniz.
İmajlar layer'lardan oluşur; her komut yeni bir layer ekler. Layer'lar imajlar arasında paylaşılıp önbelleğe alınır, böylece indirme ve derleme hızlanır.
Konteyner, çalışan bir imaj örneğidir. Üstüne yazılabilir ince bir layer eklenir; durup kaldırıldığında bu layer silinir. Kalıcı veri ayrı bir hacimde tutulur.
Bir imaj, Dockerfile adındaki tarifle üretilir. Her satır bir adımdır ve kendi layer'ını oluşturur. İmaj derlenip etiketlenir, ardından bir registry'ye (Docker Hub, GitHub Container Registry, özel kayıt) gönderilir; oradan istenen her ortama indirilir. Sıralamayı doğru kurmak, hem derlemeyi hızlandırır hem de imajı küçük ve güvenli tutar.
# 1) Derleme aşaması — araçlar burada kalır
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 2) Çalışma aşaması — sadece çıktı taşınır
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
# Derle ve anlamlı bir sürümle etiketle
docker build -t app:1.4.2 .
# Registry adresiyle yeniden etiketle
docker tag app:1.4.2 \
ghcr.io/firma/app:1.4.2
# Kayda gönder
docker push ghcr.io/firma/app:1.4.2
# Üretimde imajı digest ile sabitle:
# app@sha256:9f86d0... → değişmezdir
alpine ya da distroless gibi küçük tabanlar; daha az paket, daha az saldırı yüzeyi ve daha hızlı indirme demektir.
Az değişen adımları (bağımlılık kurulumu) üste, sık değişenleri (kaynak kodu) alta koyun. Önbellek bozulmaz, derleme hızlanır.
Anlamlı sürüm (semver) ya da commit kısa karması kullanın. Hangi kodun çalıştığını izlemek mümkün olur, geri alma kolaylaşır.
latest etiketi bir sürüm değildir.
Üretimde latest'e dayanmak, hangi kodun çalıştığını belirsiz kılar; iki sunucu farklı imajları çekebilir. Sürümü sabit bir etiketle ya da içerik karmasını taşıyan digest ile sabitleyin.
Kubernetes, "şu uygulamadan üç replica, şu kaynaklarla çalışsın" dediğiniz istenen durumu alır ve gerçek durumu sürekli buna yaklaştırır. Beyin control plane'dedir; iş yükü ise node'larda koşar. Siz YAML ile niyeti söylersiniz, cluster onu hayata geçirip korur.
kube-apiserver # tüm isteklerin tek kapısı
etcd # cluster'ın durumu (key-value)
scheduler # Pod'u hangi node çalıştırsın?
controller-mgr # istenen durumu sürekli onarır
kubelet # node'daki Pod'ları ayağa kaldırır
kube-proxy # Service ağ kurallarını yazar
container runtime # containerd / CRI-O
kubectl, kimlik doğrulama, yetkilendirme ve doğrulama hep API sunucusunun üzerindedir.Kubernetes'te her şey bir nesnedir ve nesneleri YAML ile bildirirsiniz. En sık karşılaşacağınız birkaç tanesini, en küçük birimden dış dünyaya açılan kapıya doğru sıraladık.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
spec:
containers:
- name: web
image: ghcr.io/firma/app:1.4.2
ports:
- { containerPort: 8080 }
Bu tek dosya, cluster'a "web uygulamasından üç replica çalışsın" der. Bir replica çökerse Deployment yenisini açar; üç sayısı sürekli korunur.
Bir veya birkaç konteyneri aynı ağ ve depolama alanında çalıştıran kapsül. Genelde tek başına değil, bir denetleyici aracılığıyla yönetilir.
Belirtilen sayıda Pod replica'sını korur, yeni sürümleri kademeli yayar ve gerektiğinde önceki sürüme geri alır.
Pod'lar gelip gider, IP'leri değişir. Service, değişmeyen tek bir ad ve adres verip trafiği sağlıklı Pod'lara dağıtır.
Ayarları koddan ayırır. ConfigMap sıradan yapılandırma, Secret ise parola gibi hassas değerler içindir.
Konteynerler geçicidir; ağ adresi ve veri kalıcılığı ise ayrı ayrı ele alınır. Trafiği cluster içinde mi yoksa dış dünyaya mı açacağınıza Service tipi karar verir; HTTP yönlendirmesini Ingress üstlenir. Veriyi ise konteyner ömründen bağımsız tutmak için kalıcı hacimler (PersistentVolume) kullanılır.
| Service tipi | Erişim | Tipik kullanım |
|---|---|---|
ClusterIP |
Yalnızca cluster içi | Servisler arası iç iletişim — varsayılan tip. |
NodePort |
Her node'da sabit port | Basit dış erişim, test ortamları; üretimde tek başına nadiren. |
LoadBalancer |
Bulut load balancer | Dış dünyaya açık tek servis; bulut sağlayıcı bir IP atar. |
Ingress |
HTTP(S) yönlendirme | Tek giriş noktasından alan adı/yol bazlı dağıtım, TLS sonlandırma. |
PersistentVolume gerçek depolamayı temsil eder; PersistentVolumeClaim ise bir Pod'un "şu kadar, şu özellikte alan istiyorum" talebidir. İkisi eşleşir.
Talep geldiğinde hangi tip diskin (SSD, ağ diski) otomatik açılacağını tanımlar. Elle disk hazırlamaya gerek kalmaz.
Her Service bir DNS adı alır. web.varsayilan.svc gibi adlarla servisler birbirini IP bilmeden bulur.
Konteyner güvenliği tek bir ayarla değil, üst üste binen savunmalarla sağlanır: güvenilir ve taranmış bir imaj, en az yetkiyle çalışan bir süreç, kim neye erişebilir sorusunu yanıtlayan RBAC ve Pod'lar arası trafiği sınırlayan ağ politikaları. Her savunma, bir öncekinin atladığını yakalar.
İmajları yayımlamadan önce zafiyet tarayıcısından (Trivy, Grype) geçirin. Küçük temel imaj ve düzenli güncelleme, yamalanmamış paket sayısını düşürür.
Konteyner içindeki süreç root ise, bir kaçış zafiyeti node'a yayılabilir. Süreci yetkisiz bir kullanıcıyla, read-only dosya sistemiyle çalıştırın.
Her kullanıcı ve servis hesabı yalnızca işini yapacak kadar yetkiye sahip olsun. Geniş cluster-admin dağıtmaktan kaçının.
Varsayılanda her Pod her Pod'a konuşabilir. Ağ politikalarıyla yalnızca gerekli yolları açın; bir Pod ele geçse de yanal hareket zorlaşır.
Parola ve anahtarları Dockerfile'a ya da imaja yazmayın. Secret nesneleri ve etcd şifrelemesi kullanın; mümkünse harici bir secrets manager'a bağlanın.
İmajları imzalayın (cosign) ve cluster'a yalnızca imzası doğrulanan imajların girmesine izin verin. Böylece sahte ya da değiştirilmiş imaj çalışmaya başlayamaz.
securityContext:
runAsNonRoot: true
runAsUser: 10001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
Cluster'ın uygulamanızı sağlıklı tutabilmesi için ona iki şeyi söylemeniz gerekir: bir Pod ne zaman hazır, ne zaman yeniden başlatılmalı ve ne kadar kaynağa ihtiyacı var. Bu sinyalleri doğru verdiğinizde güncelleme, ölçekleme ve kendini onarma kendiliğinden işler.
Liveness probe başarısız olursa cluster'ı Pod'u yeniden başlatması için tetikler. Takılıp kalan süreçleri otomatik kurtarır.
Readiness probe geçene kadar Pod'a trafik gönderilmez. Henüz ısınmamış bir replica'nın istek almasını engeller.
requests planlamada ayrılan asgari kaynaktır; limits ise aşılamayan tavandır.
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
readinessProbe:
httpGet: { path: /ready, port: 8080 }
Deployment yeni sürümü Pod Pod değiştirir; sorun çıkarsa kubectl rollout undo ile saniyeler içinde önceki sürüme döner. Kesinti olmadan yayın mümkün olur.
Horizontal Pod Autoscaler, CPU ya da özel metriklere bakıp replica sayısını yük arttıkça çoğaltır, azaldıkça düşürür. Kaynak isteklerini doğru tanımlamak bunun ön koşuludur.
Docker imaj kurup tek tek konteyner çalıştıran araçtır. Kubernetes ise çok sayıda konteyneri birçok makinede çalıştırıp ölçekleyen, sağlıklı tutan orkestrasyon sistemidir. Biri konteyneri üretir, diğeri filoyu yönetir; rakip değil, ardışık halkalardır.
Sanal makine kendi işletim sistemini taşır; ağır ama tam yalıtımlıdır. Konteyner ana çekirdeği paylaşır; hafif ve hızlıdır, yalıtımı süreç düzeyindedir. Çoğu uygulama için konteyner yeterli, en yüksek yalıtım gereken durumlarda VM tercih edilir.
Deployment birbirinin aynı, durumsuz Pod'lar içindir — hangisinin istek aldığı önemsizdir. StatefulSet ise her Pod'a sabit kimlik ve kendi kalıcı diski verir; veri tabanı gibi durum tutan uygulamalar için uygundur.
İkisi de ayarı koddan ayırır. ConfigMap sıradan yapılandırma (özellik bayrakları, URL'ler) içindir. Secret parola, anahtar gibi hassas değerler içindir ve erişimi RBAC ile kısıtlanmalı, etcd'de şifrelenmelidir — varsayılan kodlama şifreleme değildir.
requests Pod'un planlanması için ayrılan asgari kaynaktır; scheduler buna göre yer bulur. limits ise Pod'un aşamayacağı tavandır — bellekte aşım Pod'un sonlandırılmasına, CPU'da kısılmaya yol açar. İkisini birlikte tanımlamak istikrar getirir.
Kubernetes, Docker, CNCF ve OCI gibi konteyner ekosistemini tanımlayan kuruluşların resmi belgeleri.
İmaj hattınızdan cluster güvenliğine ve ölçeklenebilir bir Kubernetes kurulumuna kadar; doğru kurulmuş bir temel, sonradan toparlamaktan her zaman kolaydır. Nereden başlayacağınıza birlikte bakalım.