Mert Özen Her Satırda Daha İleri
DevOps Kubernetes — #21

kubectl ile Verimli Çalışmak: Output Format'ları, jsonpath, custom-columns, Selector'lar, patch, events, k9s ve Plugin'ler

Mert Özen 15 Kas 2026 14 dk 6 görüntülenme
kubectl ile Verimli Çalışmak: Output Format'ları, jsonpath, custom-columns, Selector'lar, patch, events, k9s ve Plugin'ler

kubectl'i verimli kullanmak, okurken custom-columns, jsonpath, selector ve events ile tam istediğin cevabı almak, değiştirirken de doğru patch türünü ve güvenlik kilitlerini seçmek demek. JSON patch'in index yüzünden yanlış değişkeni ezip pod'u çökertmesini, server-side apply çatışmasını, kubectl edit drift'ini, kuberc ile onaylı delete'i, krew, k9s ve stern'i 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, k9s 0.51.0, krew 0.5.0, stern 1.35.0, PowerShell 7.7 ve .NET 10 ile build edilen Bookstore API image'ıyla denenmiştir.

Önce Tahmin Et

Bookstore API Deployment'ının container'ında tek bir environment variable var: Database__Provider=InMemory. Önce kubectl patch ile, strategic merge türünde, log seviyesini belirleyen Logging__LogLevel__Default=Warning değişkenini ekliyorsun. Hemen ardından bu yeni değişkeni JSON patch ile Information yapmak istiyorsun ve listenin ikinci elemanı olduğunu düşünerek /spec/template/spec/containers/0/env/1/value yolunu yazıyorsun. İkinci patch hangi değişkeni değiştirir ve production'da bunun bedeli ne olur? Cevap yazının ortasında, gerçek bir CrashLoop'la birlikte.

Okumak ve Seçmek: Output Format'ları ve Selector'lar

Serinin ikinci yazısında get, describe, explain ve --dry-run=server'ı gördük. Bu yazı onun devamı: kubectl'i hem okurken hem değiştirirken hızlı ve güvenli kullanmak. Lab, sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) üzerinde: bookstore namespace'inde 1.0.0, bookstore-staging'de 1.1.0 çalışıyor. API'yi önceki yazıdaki gibi in-memory modda çalıştırıyorum.

Tablo yetmediğinde ilk araç -o custom-columns. Sütun adı ve JSONPath ifadesi verirsin, kubectl tabloyu senin için kurar. "Hangi namespace'te hangi image tag'i çalışıyor?" sorusunun tek satırlık cevabı:

$ kubectl get deploy -A -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,IMAGE:.spec.template.spec.containers[*].image,READY:.status.readyReplicas'
NAMESPACE            NAME                     IMAGE                                                         READY
bookstore-staging    api                      k8sseries-bookstore-api:1.1.0                                 2
bookstore            api                      k8sseries-bookstore-api:1.0.0                                 3
kube-system          coredns                  registry.k8s.io/coredns/coredns:v1.14.6                       2
local-path-storage   local-path-provisioner   docker.io/kindest/local-path-provisioner:v20260820-69b56db7   1

Script'e girdi verecek, tab ile ayrılmış satırlar gerekiyorsa -o jsonpath ve range; cluster'daki bütün image'ları sayan versiyon da kısa:

$ kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}' | grep bookstore
bookstore-staging	api-d7f5cd5f8-2vcp2	k8sseries-bookstore-api:1.1.0
bookstore-staging	api-d7f5cd5f8-5c9bf	k8sseries-bookstore-api:1.1.0
bookstore	api-7dc5dc5b47-k7d4t	k8sseries-bookstore-api:1.0.0
bookstore	api-7dc5dc5b47-lrpdl	k8sseries-bookstore-api:1.0.0
bookstore	api-7dc5dc5b47-r5xhq	k8sseries-bookstore-api:1.0.0
$ kubectl get pods -A -o jsonpath='{.items[*].spec.containers[*].image}' | tr ' ' '\n' | sort | uniq -c
   3 docker.io/kindest/kindnetd:v20260820-69b56db7
   1 docker.io/kindest/local-path-provisioner:v20260820-69b56db7
   3 k8sseries-bookstore-api:1.0.0
   2 k8sseries-bookstore-api:1.1.0
   2 registry.k8s.io/coredns/coredns:v1.14.6
   1 registry.k8s.io/etcd:3.7.0-0
   1 registry.k8s.io/kube-apiserver:v1.37.0
   1 registry.k8s.io/kube-controller-manager:v1.37.0
   3 registry.k8s.io/kube-proxy:v1.37.0
   1 registry.k8s.io/kube-scheduler:v1.37.0
$ kubectl -n bookstore get pods -l app.kubernetes.io/name=api -o jsonpath='{.items[0].status.conditions[?(@.type=="Ready")].status}'
True

Son örnekteki ?(@.type=="Ready") bir filtre: condition listesinde index'e güvenmek yerine tipine göre seçer. Windows'ta PowerShell'de tırnaklar farklı çalışır; JSONPath'i tek tırnak içinde verip iç tırnakları korumak gerekir, sort | uniq -c yerine de Group-Object kullanılır.

Seçmenin üç yolu var. -l label selector'dır (in, !=, ! destekler); --field-selector ise yalnız API server'ın indekslediği birkaç alanla çalışır (status.phase, spec.nodeName, metadata.name…); -A bütün namespace'leri tarar. Bir node'u boşaltmadan önce üzerinde ne çalıştığını görmek:

$ kubectl get pods -A --field-selector=spec.nodeName=k8sseries-lab-worker2,status.phase=Running -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name
NS                  NAME
bookstore-staging   api-d7f5cd5f8-5c9bf
bookstore           api-7dc5dc5b47-r5xhq
kube-system         kindnet-2hhjh
kube-system         kube-proxy-lwdq6

-w listeyi açık tutar ve değişiklikleri satır satır basar; --output-watch-events her satırın başına olayın tipini ekler. Bir pod'u sildiğimde:

$ kubectl -n bookstore get pods -w --output-watch-events
EVENT      NAME                   READY   STATUS    RESTARTS   AGE
ADDED      api-7dc5dc5b47-2bpm8   1/1     Running   0          63s
ADDED      api-7dc5dc5b47-mwrdh   1/1     Running   0          63s
ADDED      api-7dc5dc5b47-tpvg6   1/1     Running   0          63s
MODIFIED   api-7dc5dc5b47-2bpm8   1/1     Terminating   0          65s
ADDED      api-7dc5dc5b47-fc77w   0/1     Pending       0          0s
...
DELETED    api-7dc5dc5b47-2bpm8   0/1     Completed           0          66s
MODIFIED   api-7dc5dc5b47-fc77w   1/1     Running             0          1s

Ne Oldu? events, explain ve get --raw

Takılan bir rollout'un hikâyesini en kısa yoldan kubectl events anlatır. describe'ın en altındaki event'lerden farkı, --for ile tek bir object'e, --types ile yalnız uyarılara süzebilmen ve zamana göre sıralı gelmesi. Var olmayan bir tag'e geçtim:

$ kubectl -n bookstore set image deploy/api api=k8sseries-bookstore-api:1.2.0
deployment.apps/api image updated
$ kubectl -n bookstore annotate deploy/api kubernetes.io/change-cause='set image api 1.2.0' --overwrite
deployment.apps/api annotated
$ kubectl -n bookstore rollout status deploy/api --timeout=25s
Waiting for deployment "api" rollout to finish: 1 out of 3 new replicas have been updated...
error: timed out waiting for the condition
$ kubectl -n bookstore events --for deploy/api
LAST SEEN   TYPE     REASON              OBJECT           MESSAGE
56s         Normal   ScalingReplicaSet   Deployment/api   Scaled up replica set api-7dc5dc5b47 from 0 to 3
26s         Normal   ScalingReplicaSet   Deployment/api   Scaled up replica set api-544f6fcf45 from 0 to 1
$ kubectl -n bookstore events --for pod/api-544f6fcf45-qx6g6
LAST SEEN           TYPE      REASON      OBJECT                     MESSAGE
26s                 Normal    Scheduled   Pod/api-544f6fcf45-qx6g6   Successfully assigned bookstore/api-544f6fcf45-qx6g6 to k8sseries-lab-worker
24s                 Normal    BackOff     Pod/api-544f6fcf45-qx6g6   Back-off pulling image "k8sseries-bookstore-api:1.2.0"
24s                 Warning   Failed      Pod/api-544f6fcf45-qx6g6   Error: ImagePullBackOff
11s (x2 over 25s)   Normal    Pulling     Pod/api-544f6fcf45-qx6g6   Pulling image "k8sseries-bookstore-api:1.2.0"
10s (x2 over 24s)   Warning   Failed      Pod/api-544f6fcf45-qx6g6   Failed to pull image "k8sseries-bookstore-api:1.2.0": failed to pull and unpack image "docker.io/library/k8sseries-bookstore-api:1.2.0": failed to resolve reference "docker.io/library/k8sseries-bookstore-api:1.2.0": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
10s (x2 over 24s)   Warning   Failed      Pod/api-544f6fcf45-qx6g6   Error: ErrImagePull

Deployment'ın event'leri yalnız "yeni ReplicaSet'i 1'e çıkardım" diyor; asıl sebep pod'un event'lerinde. Takılan rollout'ta zincir hep aynı: Deployment → yeni ReplicaSet → yeni pod. Bir not: event'ler object adına göre eşleşir ve varsayılan olarak bir saat saklanır; aynı adla silinip yeniden oluşturulan bir Deployment'ın eski event'leri de listede çıkar. Geri almak için:

$ kubectl -n bookstore rollout undo deploy/api
Warning: resource deployments/api was previously managed with 'kubectl apply'. Rolling back will not update the kubectl.kubernetes.io/last-applied-configuration annotation, which may cause unexpected behavior on future 'kubectl apply' operations. Consider using 'kubectl apply' with your previous configuration file instead.
deployment.apps/api rolled back
$ kubectl -n bookstore rollout history deploy/api
deployment.apps/api
REVISION  CHANGE-CAUSE
2         set image api 1.2.0
3         <none>

Uyarıyı ciddiye al: set image ve rollout undo cluster'ı değiştirir, manifest dosyanı değiştirmez. Acil durumda doğru araçlar, ama hemen ardından aynı değişikliği Git'teki manifest'e de yapman gerekir.

İki okuma aracı daha. kubectl explain deployment.spec.strategy --recursive alt alanları tek ekranda ağaç olarak gösterir; kubectl'in cluster'dan aldığı şemayı okuduğu için CRD'lerde (Custom Resource Definition; Kubernetes'e senin eklediğin resource tipleri) de çalışır. kubectl get --raw ise API server'a doğrudan istek atar: /readyz?verbose control plane'in sağlık kontrollerini, /apis/apps/v1 o gruptaki resource'ları, /api/v1/namespaces/bookstore/pods?limit=1 ise sayfalamayı (continue token'ı) gösterir. Bir aracın hangi isteği attığını merak ettiğinde -v=8 URL'leri de yazdırır.

Değiştiren Komutlar: create, apply, set, scale, drain

kubectl create object yoksa oluşturur, varsa hata verir (AlreadyExists); kubectl replace object'in tamamını dosyadakiyle değiştirir ve dosyada olmayan alanları siler; kubectl apply dosyayı istenen durum olarak alır ve farkı uygular. Günlük işte apply, tek seferlik kurulum script'lerinde create kullan. replace'i yalnız ne sileceğini bildiğinde kullan.

Küçük değişiklik komutlarının çoğunda bir güvenlik kilidi var ve onu kullanmak alışkanlık olmalı. label ve annotate var olan bir değeri --overwrite olmadan değiştirmez; scale --current-replicas beklediğin sayı değilse hiçbir şey yapmaz; drain server dry run'la neyi çıkaracağını önceden söyler:

$ kubectl -n bookstore label deploy/api tier=backend
deployment.apps/api labeled
$ kubectl -n bookstore label deploy/api tier=api
error: 'tier' already has a value (backend), and --overwrite is false
$ kubectl -n bookstore scale deploy/api --current-replicas=2 --replicas=4
error: Expected replicas to be 2, was 3
$ kubectl drain k8sseries-lab-worker2 --ignore-daemonsets --dry-run=server
node/k8sseries-lab-worker2 cordoned (server dry run)
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-2hhjh, kube-system/kube-proxy-lwdq6
evicting pod bookstore/api-db78cc5b8-2lnmt (server dry run)
evicting pod bookstore-staging/api-d7f5cd5f8-5c9bf (server dry run)
node/k8sseries-lab-worker2 drained (server dry run)

cordon node'a yeni pod gelmesini durdurur, drain buna ek olarak pod'ları eviction API'siyle çıkarır (PDB'lere uyarak), uncordon node'u yeniden açar. Drain'in PDB ve anti-affinity ile nasıl takıldığını önceki yazıda gördük.

kubectl patch: strategic, merge ve json

Önce Tahmin Et'in cevabı. Strategic merge patch, Kubernetes'in kendi type'larını bilen bir birleştirme: container listesini name'e göre eşleştirir, env listesine yeni elemanı ekler.

$ kubectl -n bookstore patch deploy api --type=strategic -p '{"spec":{"template":{"spec":{"containers":[{"name":"api","env":[{"name":"Logging__LogLevel__Default","value":"Warning"}]}]}}}}'
deployment.apps/api patched
$ kubectl -n bookstore get deploy api -o jsonpath='{.spec.template.spec.containers[0].env}'
[{"name":"Logging__LogLevel__Default","value":"Warning"},{"name":"Database__Provider","value":"InMemory"}]

Yeni değişken listenin başına eklendi. Aynı patch'i --type=merge ile, yani RFC (internet standartlarını tanımlayan belge) 7386'daki JSON merge patch biçimiyle göndermek de işe yaramıyor, çünkü merge patch listeleri birleştirmez, olduğu gibi değiştirir; container listesi image'sız tek bir elemana iner ve API server reddeder:

$ kubectl -n bookstore patch deploy api --type=merge -p '{"spec":{"template":{"spec":{"containers":[{"name":"api","env":[{"name":"Logging__LogLevel__Default","value":"Information"}]}]}}}}'
The Deployment "api" is invalid: spec.template.spec.containers[0].image: Required value
$ kubectl -n bookstore patch deploy api --type=json -p '[{"op":"replace","path":"/spec/template/spec/containers/0/env/1/value","value":"Information"}]'
deployment.apps/api patched
$ kubectl -n bookstore get deploy api -o jsonpath='{.spec.template.spec.containers[0].env}'
[{"name":"Logging__LogLevel__Default","value":"Warning"},{"name":"Database__Provider","value":"Information"}]
$ kubectl -n bookstore logs pod/api-65c947849d-kh28w | tail -3
Unhandled exception. System.InvalidOperationException: Connection string 'BookstoreDb' is missing. Set it with user-secrets or the ConnectionStrings__BookstoreDb environment variable (see README.md).
   at Program.<Main>$(String[] args) in /src/src/Bookstore.Api/Program.cs:line 18
   at Program.<Main>(String[] args)

RFC 6902'deki JSON patch ise index'le çalışır ve index 1 artık Database__Provider. Değeri "Information" olunca uygulama in-memory moddan çıktı, connection string aradı ve çöktü. Yeni pod hiç ready olmadığı ve maxUnavailable 0'a yuvarlandığı için eski üç pod trafiği taşımaya devam etti; readiness probe ve rolling update ayarı bu sefer kurtardı. JSON patch kullanacaksan önce test işlemiyle yolun beklediğin şeyi gösterdiğini doğrula; yanlış eleman varsa patch'in tamamı reddedilir:

$ kubectl -n bookstore patch deploy api --type=json -p '[{"op":"test","path":"/spec/template/spec/containers/0/env/1/name","value":"Logging__LogLevel__Default"},{"op":"replace","path":"/spec/template/spec/containers/0/env/1/value","value":"Information"}]'
The request is invalid: the server rejected our request due to an error in our request

Kural: listelerde strategic merge (Kubernetes'in kendi type'ları için) ya da server-side apply; merge patch'i yalnız skaler alanlar ve CRD'ler için; JSON patch'i test ile birlikte. PowerShell'de bu iç içe tırnaklarla uğraşmak yerine patch'i bir dosyaya yazıp --patch-file ile ver:

spec:
  template:
    spec:
      containers:
        - name: api
          env:
            - name: Logging__LogLevel__Default
              value: Warning
$ kubectl -n bookstore-staging patch deploy api --patch-file log-level-patch.yaml
deployment.apps/api patched

Server-Side Apply, kubectl edit ve Drift

Server-side apply'da birleştirmeyi API server yapar ve her alanın bir sahibi (field manager) olur. Bu, aynı object'e birden fazla aracın yazdığı durumları görünür kılar:

$ kubectl -n bookstore apply --server-side -f api.yaml
deployment.apps/api serverside-applied
$ kubectl -n bookstore scale deploy/api --replicas=5
deployment.apps/api scaled
$ kubectl -n bookstore get deploy api --show-managed-fields -o jsonpath='{range .metadata.managedFields[*]}{.manager}{" "}{.operation}{" "}{.subresource}{"\n"}{end}'
kubectl Apply
kubectl Update scale
kube-controller-manager Update status
$ kubectl -n bookstore apply --server-side -f api.yaml
error: Apply failed with 1 conflict: conflict with "kubectl" with subresource "scale" using apps/v1: .spec.replicas
...
$ kubectl -n bookstore apply --server-side --force-conflicts -f api.yaml
deployment.apps/api serverside-applied

Çatışma bir hata değil, bir soru: "bu alanı başka biri de yönetiyor, ezeyim mi?" HPA'nın (yüke göre pod sayısını ayarlayan bileşen) yönettiği bir Deployment'ta cevap genelde hayır; doğrusu manifest'ten replicas'ı çıkarmak. --force-conflicts alanın sahipliğini alır ve dosyadaki değeri yazar.

kubectl edit de bir field manager olarak kaydedilir. Bir memory limit'ini elle 512Mi'ye çıkardım (etkileşimsiz olsun diye editor olarak sed verdim) ve Git'teki dosyayla farkına baktım:

$ KUBE_EDITOR="sed -i.bak -e 's/memory: 256Mi/memory: 512Mi/'" kubectl -n bookstore edit deploy/api
deployment.apps/api edited
$ kubectl -n bookstore diff --server-side -f api.yaml | grep -E '^[-+] '
W1115 10:35:11.692059   52103 diff.go:723] Object (apps/v1, Kind=Deployment: api) keeps changing, diffing without lock
Error from server (Conflict): Apply failed with 1 conflict: conflict with "kubectl-edit" using apps/v1: .spec.template.spec.containers[name="api"].resources.limits.memory
$ kubectl -n bookstore diff --server-side --force-conflicts -f api.yaml | grep -E '^[-+] |^[-+]{3}'
--- <tmp>/LIVE-685834022/apps.v1.Deployment.bookstore.api	2026-11-15 10:35:17
+++ <tmp>/MERGED-2491651083/apps.v1.Deployment.bookstore.api	2026-11-15 10:35:17
-  generation: 4
+  generation: 5
-            memory: 512Mi
+            memory: 256Mi

Cluster'daki durum artık Git'tekinden farklı (drift). GitOps ve Argo CD serisinin üçüncü yazısındaki self-heal açıksa Argo CD bu farkı görüp birkaç dakika içinde 256Mi'ye geri yazar; kapalıysa uygulama "OutOfSync" görünür ve bir sonraki sync elle yaptığın değişikliği siler. Her iki durumda da kubectl edit production'da kalıcı bir değişiklik yolu değil; acil bir müdahaleyse aynı değişikliği hemen manifest'e taşı.

Verimlilik: Completion, kuberc, kubectx ve krew

İlk adım alias ve completion (Terminal ve Command Line serisinin ikinci yazısı). PowerShell 7'de profile'a üç satır yeter; bunu PowerShell 7.7'de TabExpansion2 ile denedim, k get deployment -n bookstore-st sonrası Tab bookstore-staging'i tamamladı:

kubectl completion powershell | Out-String | Invoke-Expression
Set-Alias -Name k -Value kubectl
Register-ArgumentCompleter -CommandName k -ScriptBlock $__kubectlCompleterBlock

Bash'te karşılığı source <(kubectl completion bash), alias k=kubectl ve complete -o default -F __start_kubectl k (bash satırlarını bu yazı için ayrıca denemedim). kubectl 1.34'ten beri beta olan kuberc ise kubectl'in kendi tercih dosyası: komut başına varsayılan flag'ler ve alias'lar. İki varsayılan production'da çok işe yarıyor: her delete önce onay istesin, apply server-side olsun.

$ kubectl kuberc set --section defaults --command delete --option interactive=true
Updated /home/user/.kube/kuberc
$ kubectl kuberc set --section defaults --command apply --option server-side=true
Updated /home/user/.kube/kuberc
$ kubectl kuberc set --section aliases --name images --command get --prependarg deployments --option all-namespaces=true --option 'output=custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,IMAGE:.spec.template.spec.containers[*].image'
Updated /home/user/.kube/kuberc
$ kubectl images
NAMESPACE            NAME                     IMAGE
bookstore-staging    api                      k8sseries-bookstore-api:1.1.0
bookstore            api                      k8sseries-bookstore-api:1.0.0
kube-system          coredns                  registry.k8s.io/coredns/coredns:v1.14.6
local-path-storage   local-path-provisioner   docker.io/kindest/local-path-provisioner:v20260820-69b56db7
$ echo n | kubectl -n bookstore-staging delete deploy api
You are about to delete the following 1 resource(s):
deployment.apps/api
Do you want to continue? (y/N): deletion is cancelled

Dosya ~/.kube/kuberc'de durur (Windows'ta %USERPROFILE%\.kube\kuberc); KUBECTL_KUBERC=false ile kapatılır, CI'da bu yüzden script'lerin davranışı değişmez. Context ve namespace değiştirmek için kubectx ve kubens yaygın; krew üzerinden ctx ve ns plugin'leri olarak kurulabiliyor. krew, kubectl plugin'lerinin paket yöneticisi; plugin'i indirir, checksum'ını kontrol eder ve kubectl-<ad> binary'sini PATH'e koyar. Kurulum sonrası verdiği uyarı önemli:

$ kubectl krew install ctx ns tree
...
WARNING: You installed plugin "tree" from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.
$ kubectl ctx
kind-k8sseries-lab
staging
$ kubectl ctx staging
Switched to context "staging".
$ kubectl get deploy
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
api    2/2     2            2           3m45s
$ kubectl ctx -
Switched to context "kind-k8sseries-lab".

Plugin, kubectl'in senin kubeconfig'inle, yani senin yetkilerinle çalışan bir programdır. Production cluster'a erişen makinede yalnız tanıdığın ve sürümünü sabitlediğin plugin'leri kur.

k9s ve stern

k9s, cluster'ı terminalde tam ekran gezdiren bir arayüz: resource tipleri arasında :deploy, :pods gibi komutlarla geçersin, l log'u, d describe'ı, shift-f port-forward'ı açar. Aşağıdaki ekran k9s -n bookstore --readonly ile açıldı ve bir pseudo-terminal'den metin olarak yakalandı (boş tablo satırlarını çıkardım, pod IP'lerini maskeledim):

 Context: kind-k8sseries-lab [R]                   <0> all         <d>       Describe        … ____  __ ________
 Cluster: kind-k8sseries-lab                       <1> bookstore   <?>       Help            <|    |/  /   __   \______
 User:    kind-k8sseries-lab                       <2> default     <shift-j> Jump Owner      <|       /\____    /  ___/
 K9s Rev: v0.51.0                                                  <l>       Logs            <|    \   \  /    /\___  \
 K8s Rev: v1.37.0                                                  <p>       Logs Previous    |____|\__ \/____//____  /
 CPU:     n/a                                                      <shift-f> Port-Forward              \/           \/
 MEM:     n/a
┌───────────────────────────────────────────────── pods(bookstore)[3] ─────────────────────────────────────────────────┐
│ NAME↑                  PF   READY   STATUS         RESTARTS IP              NODE                            AGE      │
│ api-db78cc5b8-2lnmt    ●    1/1     Running               0 <pod-ip>        k8sseries-lab-worker2           49s      │
│ api-db78cc5b8-5nm8p    ●    1/1     Running               0 <pod-ip>        k8sseries-lab-worker            60s      │
│ api-db78cc5b8-khgsf    ●    1/1     Running               0 <pod-ip>        k8sseries-lab-control-plane     48s      │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  <pod>

Başlıktaki [R] read-only modu gösteriyor: bu modda k9s silme, edit ve shell gibi değiştiren kısayolları kapatır. Production context'inde k9s'i bu modda açmak, yanlış tuşa basma riskini ortadan kaldırır. CPU ve MEM "n/a" çünkü bu cluster'da metrics-server yok.

stern ise birden fazla pod'un log'unu tek akışta, pod adıyla birlikte gösterir ve yeni açılan pod'ları kendiliğinden ekler. kubectl logs -l rollout sırasında yeni pod'ları kaçırır; stern kaçırmaz. Bir rollout restart sırasında:

$ stern -n bookstore api --color never --since 1s --template '{{.PodName}} {{.ContainerName}} {{.Message}}{{"\n"}}'
+ api-7dc5dc5b47-4bd4m › api
+ api-7dc5dc5b47-jctrr › api
+ api-7dc5dc5b47-kwqf5 › api
+ api-db78cc5b8-5nm8p › api
api-db78cc5b8-5nm8p api info: Microsoft.EntityFrameworkCore.Update[30100]
api-db78cc5b8-5nm8p api       Saved 62 entities to in-memory store.
api-db78cc5b8-5nm8p api info: Microsoft.Hosting.Lifetime[14]
api-db78cc5b8-5nm8p api       Now listening on: http://[::]:8080
...
api-7dc5dc5b47-kwqf5 api info: Microsoft.Hosting.Lifetime[0]
api-7dc5dc5b47-kwqf5 api       Application is shutting down...
+ api-db78cc5b8-2lnmt › api
...
- api-7dc5dc5b47-kwqf5 › api

+ satırları stern'in izlemeye başladığı, - satırları bıraktığı pod'lar. Eski pod'un "Application is shutting down" satırı ile yeni pod'un "Now listening" satırı aynı akışta: onuncu yazıdaki graceful shutdown'ı canlı izlemenin en kolay yolu. Uzun süreli log arama ve saklama için bu araçlar değil, Observability serisindeki log backend'leri gerekir.

Üretimde Dikkat

Production'a erişimi geliştirme makinesinden ayır. Her cluster için ayrı kubeconfig dosyası tut ve KUBECONFIG'i yalnız o terminal oturumunda ver; tek dosyada yirmi context, yanlış cluster'da komut çalıştırmanın en kısa yolu. Prompt'ta context ve namespace'i göster (kube-ps1 ya da Oh My Posh ve Starship gibi araçların Kubernetes segmenti), production context'inde rengi değiştir. Production için kullanıcıya cluster-admin değil, okuma yetkisi ver; değişiklikler pipeline ya da GitOps üzerinden gelsin. kuberc'te delete --interactive varsayılanı ve k9s'in --readonly modu, kalan riski azaltır. Script'lerde ise kubectl çıktısını tablo olarak parse etme; sütunlar sürümler arasında değişebilir, -o jsonpath ve -o json değişmez.

Sık Düşülen Tuzaklar

Yanlış context'te delete: kubectl delete -f . ya da delete ns, onay sormadan çalışır; kuberc'teki interactive varsayılanı veya ayrı kubeconfig bunu yakalar. JSON patch'te index'e güvenmek: liste sırası her strategic merge'den sonra değişebilir. Merge patch ile liste güncellemeye çalışıp listenin tamamını ezmek. kubectl edit ya da set image ile yapılan değişikliği manifest'e taşımamak; GitOps onu geri alır, GitOps yoksa bir sonraki apply. HPA'nın yönettiği replicas'ı manifest'te tutup her apply'da replica sayısını sıfırlamak. Field selector'ı her alan için çalışır sanmak. Ve doğrulamadan kurulan krew plugin'leri: uyarı metni bunu açıkça söylüyor.

Küçük Bir Deneme

★☆☆ Kendi cluster'ında bütün namespace'lerdeki container image'larını tag'leriyle listeleyen bir -o custom-columns komutu yaz, sonra aynısını kuberc'te bir alias yap.

★★☆ Bir Deployment'a strategic merge patch ile iki environment variable ekle, sonra birinin değerini JSON patch ile değiştir. test işlemini önce yanlış, sonra doğru index'le dene ve --patch-file ile PowerShell'de aynı patch'i uygula.

★★★ Bir Deployment'ı server-side apply ile oluştur, bir alanı kubectl edit ile, bir alanı kubectl scale ile değiştir. --show-managed-fields çıktısından hangi alanın kime ait olduğunu çıkar, kubectl diff --server-side ile drift'i gör ve --force-conflicts olmadan çatışmayı çözmenin yolunu bul.

Bu yazıda kubectl tree plugin'ini kurduk ama kullanmadık. Bir sonraki ve serinin son yazısında onunla Deployment → ReplicaSet → Pod sahiplik zincirini göreceğiz; CRD ile Kubernetes'e yeni bir resource tipi ekleyip .NET ile küçük bir controller yazacak, finalizer'ların neden "Terminating"de takılan namespace'lere yol açtığını inceleyeceğiz.