
Kubernetes Best Practices
Merhaba, bu yazımızda Google Cloud Platform kanalının Kubernetes Best Practices adlı oynatma listesinden öğrendiklerimizi paylaşacağım , bu çoğu pratikleri Trendyol içerisinde de uygulamaya gayret ediyoruz.
Building Small Containers
* Kubernetes uygulamaları deploy etmenin yolu uygulamayı container içerisine koymaktır.
* Default base image kullanmak büyük image oluşmasına ve bir sürü güvenlik açığına sebebiyet verebilir.
* Çoğu Docker varsayılan image base image olarak Debian veya Ubuntuyu kullanır ve bu base imageler yüzlerce MB ek yük getirebilir.Tüm bu ek yük , güvenlik açıkları ve buglar için harika bir saklanma yeri olabilir.
* Image boyutlarını azaltmanın iki yolu var.
- “Small Base Images”
- “Builder Pattern”
* Küçük base imageler kullanmak(alpine versiyonlar) muhtemelen image boyunu düşürmenin en kolay yolu.
* Senin uygulamanın kullandığı dilin küçük base image seçeneği yoksa imageni, “raw linux alpine” imagesini base olarak kullanarak inşa edebilirsin.Bu sayede container içerisine nelerin gideceği konusunda tamamen kontrol sahibi olur
- Yorumlanan(interpreted) dillerde source code direkt olarak yorumlayıcıya(interpreter) gider ve çalıştırılır.Fakat derlenen(compiled) dillerde kod önceden compile edilir ve bu derleme adımı kodu çalıştırmaya ihtiyaç duymadığımız bazı araçlara ihtiyaç duyar.Bu da demek oluyor ki sen bu araçları final container içerisine koymak zorunda değilsin.Bunu yapmak için de “builder pattern” kullanılır.Temel amaç şudur kod ilk container içerisinde inşa edilir ve daha sonra inşa edilmiş yani compile edilmiş kod final container içerisine eklenir bu sayede compile sürecindeki compiler ve diğer toollar final container içerisine eklenmemiş olur bu sayede containerin boyutu gereksiz büyümemiş olur.Builder pattern uyguladığımız Dockerfile içerisinde 2 tane “FROM” statementi olur ve bu statementlere “as veya AS” şeklinde alias verilir.
FROM golang:alpine AS build
.
.
.
FROM alpine
COPY --from=build ...* Küçük base imagelerin ve builder patternin ölçülebilir avantajlarının görüldüğü iki alan var.
- “Security”
- “Performance”
* “Performance” tarafına bakarsak imagelerin build edilmesi , pull edilmesi ve push edilmesi süreleri çok daha azalacaktır.Imagelerin build ve push sürelerinin çok önemi olmasa da pull edilme süresi oldukça önemlidir çünkü şöyle bir senaryoda clusterdaki down olan bir node tekrardan tüm imageleri pull etmek zorunda ve bu süre ne kadar kısa olursa node kendini o kadar kısa sürede toparlayacaktır.
* “Security” tarafına bakarsak küçük imageler daha küçük yüzeye(surface) e sahip olduklarından ataklar konusunda daha güvenli olacaktır.(Google Container Vulnerability Scanner)
# Organizing Kubernetes with Namespaces
* K8s üzerinde daha fazla servis geliştirmeye başladığımız zaman en basit işler bile karmaşıklaşmaya başlar.Örneğin takımlar aynı isimli deployment veya service oluşturamaz, eğer binlerce podun varsa bunları listelemek uzun zaman alabilir.
Bu gibi durumları basitleştirmek için “namespaceler” kullanılır.
* Namespace’i k8s clusterındaki sanal bir cluster olarak düşünebilirsin.
* Tek k8s clusterı içerisinde namespacelerin olabilir ve bunlar birbirinden tamamen izoledir ayrıca sana güvenlik ve performans açısından yardımcı olur.
* k8s temel olarak 3 namespace ile birlikte gelir. default , kube-system ve kube-public.
* kube-system ve kube-public genelde Kubernetes ile alakalı workloadları(pod) barındırır dolayısıyla bizim workloadlarımız için tek yer “default” kalıyor.
* kubectl create namespace test
* kubectl get namespaces
* kubectl apply -f test.yaml — namespace=test
* normalde tüm kubectl komutları current active namespace de çalışır.(eğer herhangi bir namespace belirtilmez ise bu “default”’dur.)
* namespaceler arasındaki serviceler ile konuşmak mümkündür.Normalde uygulaman bir Kubernetes servicesine erişmek isterse yapısal olarak desteklenen “DNS Service Discovery” i kullanabilir ve uygulamaya bu service ismi ile erişebilir.Fakat farklı namespacelerde aynı service ismine sahip uygulamalar olabilir bunu da “Expanded DNS Address” ile çözebiliriz.(Cross Namespace Communication)
- <service name>.<namespace name>.svc.cluster.local
şeklinde, buna common DNS pattern de denir.
Kubernetes Health Checks with Readiness and Livenes Probes
* Healthcheckler sistemin , uygulamanın çalışıp/çalışmadığını anlamasına imkan verir.
* Eğer uygulaman çalışmıyorsa , diğer servisler bu uygulamaya erişememeli ve request iletememelidir.Bunun yerine requestler uygulamanın diğer sağlıklı instancelarına iletilmelidir.Ayrıca system uygulamayı tekrardan sağlıklı hale getirebilmelidir.
* Varsayılan olarak k8s , pod içerisindeki tüm containerlar çalıştığı zaman poda trafik yollamaya başlar ve containerlar crash olduğu zaman da restart eder.
* Deploymentlarını custom health checkler ile daha güçlü hale getirebilirsin.
* k8s’de iki tip health-check vardır.
- “Readiness”
* k8s’e uygulamanın ne zaman trafik almaya hazır olduğunu bilgisini verir.
* k8s pod trafik iletmeye başlamadan önce “Readiness Probe”’dan başarılı şekilde geçtiğinden emin olur.
* Eğer “Readiness Probe” fail ederse k8s uygulamaya trafik yollamayı tekrardan ilgili health-check başarılı olana kadar keser.
- “Liveness”
* k8s’e uygulamanın canlı olup/olmadığı bilgisini verir.
* Eğer uygulaman sağlıksız ise k8s podu siler ve podun yerine yeni bir tane başlatır.
* Şimdi ise sıra bu health checkleri test edeceğimiz probelara geldi , 3 tip probe var.
1) “HTTP”
livenessProbe:
httpGet:
path: /healthz
port: 80802) “command”
livenessProbe:
exec:
command:
— myprogram3) “TCP”
livenessProbe:
tcpSocket:
port: 8080
* Probelar konfigüre edilebilir.
- initialDelaySeconds: ne kadar süre sonra bu health checkler test edilmeye başlanacak.
- periodSeconds: ne kadar süre aralıklarıyla test için bu istekler iletilecek.
- timeoutSeconds: ne kadar süre isteğin cevabı beklenecek.
- successThreshold: kaç tane başarılı istekten sonra başarılı sayılacak.
- failureThreshold: kaç tane fail istekten sonra hatalı sayılacak.
Setting Resource Requests and Limits in Kubernetes
* Uygulamaların çalışabilmesi için containerlara yeterli kaynakların verilmesi gereklidir.Eğer büyük bir uygulamayı küçük kaynaklarla çalıştırmaya çalışırsan out of memory hataları alınabilir.
* Requests ve Limits , örneğin CPU ve memory gibi kaynakları kontrol etmek için kullandığı mekanizmalardır.
* Requests , containerların ilgili kaynağı almayı garanti ettiği tanımdır.Eğer container bir kaynak talep ederse , talep ettiği kaynağı k8s ona vererek node üzerine schedule eder.
* Limit ise containerin asla üstüne çıkmayacağı değeri belirler.
2 tip resource vardır.
- “CPU” ve “Memory”.
!* Eğer “requests” edilen kaynaklar sahip olunan nodeun veya nodeların toplam kaynaklarından büyük olursa pod asla schedule edilemez.
containers:
- name: container1
image: busybox
resources:
requests:
memory: “32Mi”
cpu: “200m”
limits:
memory: “64Mi”
cpu: “250m”* Pod içerisindeki tüm containerlar kendi requests ve limit’lerini belirleyebilir.
* CPU resourceları milicore olarak tanımlanır.Eğer container 2 full core a ihtiyaç duyuyorsa çalışmak için sen bu değeri “2000m” olarak vermelisin.
“2 core == 2000m”
Eğer 1/4 ü kadar core ihtiyacı varsa o zaman da “250m” olarak bu değeri set etmelisin.
* Memory resourceları byte olarak tanımlanır.
* CPU sıkıştırabilir bir kaynak olarak bilinir,dolayısıyla uygulaman bu CPU limitlerine dayandığında k8s uygulamanın CPU kullanımını kısmaya başlar böylece uygulamanın yalnızca performansı düşecektir uygulama terminate edilmeyecektir.Fakat Memory sıkıştırabilir bir kaynak olmadığından verilen limite dayandığında veya bu limiti aştığında uygulama terminate edilecektir.
* ResourceQuota ve LimitRange benzer işler için kullanılır.
ResoureQuota aslında namespace bazlı kaynak sınırlandırmasının tanımıdır.
kind: ResourceQuota
metadata:
name: demo
spec:
hard:
requests.cpu: 500m
requests.memory: 100Mib
limits.cpu: 700m
limits.memory: 500Mibaslında tüm bu değerler namespace altında tanımlı olan tüm podların containerlarının maks sahip olabileceği ve maks talep edebileceği kaynak değerleri.
LimitRange ise namespace içerisindeki bireysel olarak containierin maks ve min request/limit kaynak değerlerini belirler, yani bu değerden düşük talep edemezsin veya bu kadarden daha yüksek talep edemezsin gibi.
kind: LimitRange
metadata:
name: limit-mem-cpu-per-container
spec:
limits:
— max: → max cpu and memory limit
cpu: “800m”
memory: “1Gi”
min: → min cpu and memory limit
cpu: “100m”
memory: “99Mi”
default: → default limits
cpu: “700m”
memory: “900Mi”
defaultRequest: → default request
cpu: “110m”
memory: “111Mi”
type: Container* Tüm bu requests ve limits aslında scheduler tarafından bir node a podun schedule edilmesi için kullanılır , scheduler aslında round robin load balancing kullanarak podu çalıştırmak için bir node arar ve node podun talep ettiği kadar yeterli kaynağı var mı yok mu kontrol eder, eğer hiçbir node da yeterli kaynak yoksa pod “Pending State”de bekler.
Terminating with Grace
* Uygulamalarımız SIGTERM mesajını handle etmeli ve bu mesajı gördüğünde shutdown işlemini başlatmalı.
Kubernetes Termination Lifecycle
— — — — — — — — — — — — — — — —
* bir podu terminate etmek istediğimizi düşünelim, bu noktada pod trafik almayı durduracaktır.
Terminating statedeki bir pod için önce
“preStop” hook çalıştırılır, SIGTERM signal yollanır poda.
terminationGracePeriodSeconds: 60 → paralelde olur bu diğer işlemlerle.yani preStop hook veya sigterm signal bitmesini beklemez.Tüm bu işlemlerden sonra uygulama halen kapanmadıysa SIGKILL signal yollanır ve force kill e zorlanır.
Mapping External Services
* External servicelere de erişmek için service ve endpoint oluşturabiliyorsun.Endpoint içerisinde bu external servicenin ipsini tanımlamak zorundasın.
Örneğin bir MongoDB serverimiz olsun external olarak.IP’si 10.12.232.424 olsun.
Önce bir service oluşturuyoruz.
kind: Service
metadata:
name: demo
spec:
type: ClusterIP
ports:
— port: 27017
targetPort:27017görüldüğü gibi herhangi bir pod selector yok burada , peki service trafiği nereye yönlendireceğini nereden bilecek ? işte bu noktada manual olarak bir Endpoint oluşturuyoruz.
kind: Endpoints
metadata:
name: mongo
subsets:
— addresses:
— ip: 10.12.232.424
ports:
— port: 27017veya external services using domain name diye bir yapı var bu konuda da service type olarak “ExternalName” kullanılır.
Node Updating process
- kubectl drain <node_name> — force → node üzerindeki tüm podları siliyorsun.
- kubectl cordon <node_name> → node’u yönetimden çıkarıyorsun, yani artık podlar bu node üzerine deploy olmuyor.
küçük bir not ilk işlemden sonra yani node üzerinde tüm podları sildikten sonra bu podların diğer nodelar üzerine schedule olduğundan emin olduktan sonra nodeu yönetimden çıkarmamızda fayda var.

