Eski Kubernetes Manifest'lerini Okumak: dockershim, PodSecurityPolicy ve Kaldırılan API'ler (1.16–1.25)
2018–2021 dönemi manifest'lerini çevirirken yalnız apiVersion'ı değiştirmek yetmez: apps/v1 selector ister, kubectl convert eski varsayılanları (sonsuz progressDeadlineSeconds, maxUnavailable: 1) manifest'e yazar, Ingress'in backend yapısı değişti. dockershim'in kaldırılmasının Docker image'larını etkilemediğini, PodSecurityPolicy'nin yerine Pod Security Admission'ı ve pluto ile taramayı gerçek çıktılarla görüyoruz.
Sürüm notu: Bu yazıdaki örnekler kind 0.33 ile Kubernetes 1.37, kubectl 1.37, kubectl-convert 1.37.2 ve pluto 5.24.5 ile denenmiştir.
Önce Tahmin Et
2019'dan kalma bir blog yazısındaki Deployment manifest'ini buluyorsun; apiVersion: extensions/v1beta1. Bugünkü cluster bunu reddediyor, sen de resmi araç kubectl convert ile apps/v1'e çeviriyorsun. Çıktı temiz, cluster kabul ediyor, pod'lar ayağa kalkıyor. Soru: çevrilmiş Deployment, aynı alanları elle yazılmış modern bir Deployment'la aynı şekilde mi davranır? İpucu: kubectl convert yalnız apiVersion'ı değiştirmiyor, eski API'nin (Application Programming Interface) varsayılan değerlerini de manifest'e yazıyor. Hangi değerler olduğunu aşağıda gerçek çıktıyla göreceğiz.
Neden Eski Manifest'ler Hâlâ Karşına Çıkıyor
Kubernetes'in en çok tutorial üretilen yılları 2018–2021 arasıydı. Stack Overflow cevapları, kitapların ilk baskıları, şirket içi wiki'ler ve yıllardır dokunulmamış Helm chart'ları o dönemin API'lerini kullanıyor. Kubernetes'in deprecation politikası beta API'leri en az 9 ay ya da 3 sürüm boyunca, deprecation duyurusundan sonra da çalışır tutar; sonra kaldırır. Kaldırılan bir API sürümü için cluster artık hiçbir şey bilmez:
$ kubectl apply -f old/api-deployment.yaml
error: resource mapping not found for name: "api" namespace: "bookstore" from "old/api-deployment.yaml": no matches for kind "Deployment" in version "extensions/v1beta1"
ensure CRDs are installed first
Hata mesajının ikinci satırı yanıltıcı: CRD (CustomResourceDefinition) kurmana gerek yok, mesele Deployment'ın artık apps/v1'de yaşaması. Bu yazıda eski bir manifest'i okurken hangi satırlara takılacağını, hangi değişikliklerin yalnız apiVersion değiştirmekle bitmediğini ve bunları otomatik nasıl tarayacağını görüyoruz. Hangi API'nin cluster'da ne zaman kaldırıldığını aklında tutmak zorunda değilsin; aşağıdaki tablo ve resmi deprecation guide yeterli.
| Eski | Yeni | Kaldırıldığı sürüm | Sadece apiVersion mı? |
|---|---|---|---|
extensions/v1beta1, apps/v1beta1, apps/v1beta2 Deployment, DaemonSet, ReplicaSet | apps/v1 | 1.16 | Hayır: selector zorunlu ve immutable, varsayılanlar farklı |
extensions/v1beta1, networking.k8s.io/v1beta1 Ingress | networking.k8s.io/v1 | 1.22 | Hayır: backend yapısı ve pathType değişti |
rbac.authorization.k8s.io/v1beta1, apiextensions.k8s.io/v1beta1 (CRD) | v1 | 1.22 | RBAC (Role-Based Access Control) için evet; CRD için hayır, şema zorunlu |
batch/v1beta1 CronJob | batch/v1 | 1.25 | Evet; timeZone gibi yeni alanlar sonradan geldi |
policy/v1beta1 PodDisruptionBudget | policy/v1 | 1.25 | Neredeyse: boş selector'ın anlamı değişti |
policy/v1beta1 PodSecurityPolicy | Karşılığı yok; Pod Security Admission | 1.25 | Hayır: tamamen farklı mekanizma |
autoscaling/v2beta1, autoscaling/v2beta2 HPA (Horizontal Pod Autoscaler) | autoscaling/v2 | 1.25 ve 1.26 | v2beta2'den evet, v2beta1'den metrik yapısı farklı |
Deployment: extensions/v1beta1'den apps/v1'e
2019'daki tipik manifest:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: api
namespace: bookstore
spec:
replicas: 2
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: bookstore-api:1.0.0
ports:
- containerPort: 8080
Dikkat: selector yok. Eski API onu template'in label'larından türetiyordu. İlk refleks apiVersion'ı elle apps/v1 yapmak:
The Deployment "api" is invalid:
* spec.selector: Required value
* spec.template.metadata.labels: Invalid value: {"app":"api"}: `selector` does not match template `labels`
apps/v1'de selector zorunlu ve bir kez yaratıldıktan sonra değiştirilemez (on dördüncü yazıdaki commonLabels tuzağı bu yüzden pahalıydı). kubectl convert eksik alanı doldurur; artık kubectl'in içinde gelmiyor, ayrı bir plugin (kubectl-convert) olarak kurulur ve --local ile cluster'a gitmeden çalışır:
$ kubectl convert -f old/api-deployment.yaml --local -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: api
name: api
namespace: bookstore
spec:
progressDeadlineSeconds: 2147483647
replicas: 2
revisionHistoryLimit: 2147483647
selector:
matchLabels:
app: api
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
...
Önce Tahmin Et'in cevabı burada. Selector geldi, ama yanında eski API'nin varsayılanları da geldi. Aynı Deployment'ı bugün apps/v1 ile sıfırdan yaratınca API server'ın verdiği değerlerle karşılaştırdık:
# kubectl convert çıktısı uygulandıktan sonra
2147483647 2147483647 {"maxSurge":1,"maxUnavailable":1}
# apps/v1 ile sıfırdan yaratılan Deployment
600 10 {"maxSurge":"25%","maxUnavailable":"25%"}
Üç fark da davranışı değiştirir. progressDeadlineSeconds: 2147483647 takılan bir rollout'un hiçbir zaman ProgressDeadlineExceeded olmaması demek; serinin dördüncü yazısında rollout'un başarısız sayılmasını bu alana bağlamıştık, pipeline'daki kubectl rollout status da sonsuza kadar bekler. revisionHistoryLimit: 2147483647 her deploy'dan bir ReplicaSet biriktirir. maxUnavailable: 1 ise iki replica'lı bir Deployment'ta rolling update sırasında kapasitenin yarısını aynı anda kapatabilir. Converter'ın çıktısını olduğu gibi commit etme; varsayılanlarını silip yalnız gerçekten istediğin alanları bırak.
Ingress: networking.k8s.io/v1 ve ingress.class
Eski Ingress:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: api
namespace: bookstore
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: bookstore.local
http:
paths:
- path: /api
backend:
serviceName: api
servicePort: 80
kubectl convert backend'i yeni yapıya çevirdi ve zorunlu pathType'ı ekledi:
spec:
rules:
- host: bookstore.local
http:
paths:
- backend:
service:
name: api
port:
number: 80
path: /api
pathType: ImplementationSpecific
ImplementationSpecific, yolun nasıl eşleşeceğini controller'a bırakır; aynı manifest iki farklı controller'da farklı davranabilir. Taşınabilir olması için Prefix ya da Exact seç. Uygulayınca cluster bir uyarı daha verdi:
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/api created
NAME CLASS HOSTS ADDRESS PORTS AGE
api <none> bookstore.local 80 0s
CLASS sütunu boş: annotation'ı hâlâ okuyan controller'lar var, ama yeni yazılan controller'lar yalnız spec.ingressClassName'e bakar. Daha büyük resim serinin altıncı yazısında: Ingress API'si donduruldu, ingress-nginx Mart 2026'da emekliye ayrıldı ve yeni kurulumlar için yol Gateway API. Eski bir Ingress'i Gateway API'ye çevirmek için ingress2gateway aracı var; bir sonraki yazıda güncel durumuna bakıyoruz.
dockershim'in Kaldırılması: Docker Image'ların Çalışmaya Devam Ediyor
Kubernetes 1.24 (Mayıs 2022) ile kubelet'in Docker Engine'le konuşmak için kullandığı dockershim katmanı kaldırıldı. O dönem "Kubernetes Docker'ı bırakıyor" başlıkları çok konuşuldu ve yanlış anlaşıldı. Değişen şey node'daki container runtime: kubelet artık CRI (Container Runtime Interface) uyumlu bir runtime ile konuşuyor, çoğunlukla containerd ile:
NAME VERSION RUNTIME
k8swork2-1-control-plane v1.37.0 containerd://2.3.4
k8swork2-1-worker v1.37.0 containerd://2.3.4
k8swork2-1-worker2 v1.37.0 containerd://2.3.4
Bu seride kullandığımız bütün image'ları Docker ile build ettik ve hepsi containerd'de sorunsuz çalıştı: Docker'ın ürettiği image'lar OCI (Open Container Initiative) standardındadır. Eski manifest'lerde gerçekten kırılan şeyler başka: node'daki /var/run/docker.sock'u pod'a mount edip "Docker-in-Docker" ile image build eden CI (Continuous Integration) pod'ları, docker ps ile node'daki container'lara bakan monitoring agent'ları ve Docker'ın log dosyası yolunu okuyan eski log toplayıcıları. Böyle bir hostPath görürsen, image build'ini cluster dışına (CI runner'ına) ya da daemon gerektirmeyen bir araca taşıman gerekir. Ayrıca containerd 1.x desteği de bitti: Kubernetes 1.35 onu destekleyen son sürümdü.
PodSecurityPolicy'den Pod Security Standards'a
PSP (PodSecurityPolicy) 1.21'de deprecated oldu, 1.25'te kaldırıldı. Eski bir PSP manifest'i:
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
runAsUser:
rule: MustRunAsNonRoot
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes: ["configMap", "secret", "emptyDir", "persistentVolumeClaim"]
Bunun bir "convert" karşılığı yok, araç da açıkça söylüyor:
error: unable to decode "old/restricted-psp.yaml": no kind "PodSecurityPolicy" is registered for version "policy/v1beta1" in scheme "pkg/api/legacyscheme/scheme.go:30"
PSP, pod'ları değiştirebilen (mutating) ve hangi policy'nin uygulanacağı RBAC'e bağlı olduğu için tahmin etmesi zor bir mekanizmaydı. Yerine gelen PSA (Pod Security Admission), on beşinci yazıda kullandığımız namespace label'larıyla çalışır ve pod'u değiştirmez, yalnız kabul ya da reddeder. Çeviri yaparken PSP'nin ne izin verdiğine bak ve en yakın PSS (Pod Security Standards) seviyesini seç: yukarıdaki PSP runAsNonRoot ve volume kısıtıyla restricted'a yakın, ama restricted ayrıca capability drop, seccomp ve allowPrivilegeEscalation: false ister. PSP'nin varsayılan değer atama işi (ör. runAsUser'ı otomatik doldurmak) artık pod spec'inde açıkça yazılmalı; PSS'nin karşılamadığı özel kurallar için Kyverno veya Gatekeeper gibi bir policy engine ya da yerleşik ValidatingAdmissionPolicy kullanılır.
CronJob, PDB ve HPA: Küçük Ama Sinsi Farklar
batch/v1beta1 CronJob'un batch/v1'e çevrilmesi gerçekten yalnız apiVersion işi; çevrilmiş manifest cluster'a hatasız uygulandı (cronjob.batch/cleanup-carts created). Ama eski tutorial'lar timeZone'u bilmez; on üçüncü yazıdaki gibi saat dilimini açıkça yaz. policy/v1 PodDisruptionBudget'ta (PDB) boş bir selector ({}) artık namespace'teki bütün pod'ları seçer; v1beta1'de hiçbirini seçmiyordu. HPA tarafında autoscaling/v2beta1'deki targetAverageUtilization alanı v2'de target.type: Utilization ve target.averageUtilization yapısına dönüştü; on birinci yazıdaki HPA bu yeni biçimde. Bir de kubectl'in kendisi: eski tutorial'lardaki kubectl run --replicas=3 komutu 1.18'den beri Deployment yaratmıyor, yalnız tek bir pod; Deployment için kubectl create deployment var.
Taramayı Otomatikleştirmek: pluto
Bir repo dolusu manifest'i tek tek okumak yerine pluto ile tarıyoruz. Yukarıdaki beş eski dosya için:
$ pluto detect-files -d old -o wide
NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN REPL AVAIL REPL AVAIL IN
api bookstore Deployment extensions/v1beta1 apps/v1 true v1.9.0 true v1.16.0 true v1.9.0
api bookstore Ingress extensions/v1beta1 networking.k8s.io/v1 true v1.14.0 true v1.22.0 true v1.19.0
api bookstore PodDisruptionBudget policy/v1beta1 policy/v1 true v1.21.0 true v1.25.0 true v1.21.0
cleanup-carts bookstore CronJob batch/v1beta1 batch/v1 true v1.21.0 true v1.25.0 true v1.21.0
restricted <UNKNOWN> PodSecurityPolicy policy/v1beta1 true v1.21.0 true v1.25.0 false
REPL AVAIL IN sütunu, yeni API'nin hangi sürümden beri var olduğunu söyler; eski ve yeni cluster'ları birlikte destekliyorsan önemli. pluto bulduğunda sıfırdan farklı bir exit code döner (bizde 4), yani CI'da bir adım olarak koymak yeter. Helm chart'ları için pluto detect-helm, cluster'daki nesneler için pluto detect-api-resources var; ama bir önceki yazıda gördüğümüz gibi cluster'a eski API'yle konuşan istemcileri yalnız API server metriği gösterir. Resmi kaynak, sürüm sürüm hangi API'nin kaldırıldığını listeleyen Kubernetes Deprecated API Migration Guide sayfası.
Üretimde Dikkat
Eski bir manifest'i çevirdiğinde onu yeni bir manifest gibi review et: converter'ın yazdığı varsayılanları sil, selector'ları ve label'ları serinin standardına (app.kubernetes.io/name) getir, probe, resource ve securityContext ekle. Çalışan bir cluster'daki eski API'li nesneleri ayrıca dert etme: API server onları depolama sürümünde tutar ve yeni API'den sunar; kırılan şey repodaki dosyalar ve onları uygulayan pipeline'lardır. Upgrade'den önce bütün repo'larda pluto taraması, cluster'da deprecated API metriği ve sağlayıcının upgrade insight'ları birlikte kontrol edilmeli. Selector değiştirmek gerektiğinde Deployment'ı yerinde düzeltemezsin; yeni isimle yaratıp trafiği taşımak ya da kısa bir kesintiyi planlayıp silip yeniden yaratmak gerekir.
Sık Düşülen Tuzaklar
Hata mesajındaki "ensure CRDs are installed first" önerisine inanıp CRD aramak. apiVersion'ı elle değiştirip selector'ı unutmak. kubectl convert çıktısını olduğu gibi commit edip sonsuz progressDeadlineSeconds ve sınırsız revision geçmişiyle yaşamak. Ingress'te pathType: ImplementationSpecific'i bırakıp controller değişince farklı eşleşme davranışıyla karşılaşmak. "Kubernetes Docker'ı bıraktı" diye Dockerfile'ları değiştirmeye kalkmak; asıl taranması gereken docker.sock mount'ları. PSP'yi birebir PSS'ye çevirmeye çalışmak. Boş selector'lı eski bir PDB'yi policy/v1'e çevirip bütün namespace'i kapsayan bir bütçe yaratmak. Ve eski bir tutorial'daki kubectl run --replicas'ın neden Deployment yaratmadığına şaşırmak.
Küçük Bir Deneme
★☆☆ Elindeki (ya da GitHub'da bulduğun) eski bir Kubernetes repo'sunda pluto detect-files -d . çalıştır. Kaç dosya, hangi sürümlerde kırılıyor?
★★☆ Bu yazıdaki eski Deployment'ı kubectl convert ile çevir, varsayılanları temizle, serinin label standardına ve dördüncü yazıdaki rollout ayarlarına getir. kubectl diff ile convert çıktısı ve temizlenmiş sürüm arasındaki farkı göster.
★★★ Boş selector'lı bir policy/v1 PodDisruptionBudget yarat, namespace'te iki farklı Deployment çalıştır ve kubectl get pdb çıktısındaki ALLOWED DISRUPTIONS değerini yorumla. Ardından bir node drain ederek hangi pod'ların bu bütçeye takıldığını gör.
Eski API'lerden bugüne geldik. Serinin son yazısında bugünden ileriye bakıyoruz: Kubernetes 1.33–1.37 arasında uygulama geliştiriciyi etkileyen yenilikler, ingress-nginx sonrası seçenekler ve Gateway API'nin güncel durumu.