Kubernetes Kaynak Yönetimi

2017’de bir etkinlikte eski Kartaca çalışanlarından Bekir Doğan’ı dinleme şansı bulmuş ve daha 2005 yılında yönettikleri tüm servisleri OpenVZ ile konteynerler halinde kurup dağıttıklarını duyunca çok etkilenmiştim. Birileri gerçekten bu tip şeylere uğraşıyor muydu?
Görünüşe bakılırsa, evet. 2000’lerin başından beri sektörün büyük bir kısmı ve Linux camiası konteynerlerin günümüzdeki haline gelmesi için uğraşıyorlarmış. Özellikle de Google, yaptığı katkılarla konteynerlerin ana akım haline gelmesinde öncü olmuş. Aklınıza hemen Kubernetes gelmiş olabilir ama aslında cgroups teknolojisinden bahsediyorum.
Kısaca cgroups
Bir konteyner, tüm sistemi sanallaştırmak yerine bir grup işlemi (process) izole edip diğer konteynerler ve uygulamalar ile aynı çekirdek üzerinde çalıştırdığı için tüm sistemin tek sahibi olduğu yanılgısına kolayca düşebilir. Agresif şekilde kaynak tüketen bir konteyner de kardeş konteynerlerini kararsız hale getirerek sistemi kararsızlaştırabilir.
Bunun önüne geçmek için sanallaştırma ile bütün bir sistemi konteynere tahsis edebiliriz ama bu da zamanın büyük kısmında kaynak israfına yol açacaktır. Mesela, Borg’un dizayn dokümanlarında da kaynaklardan maksimum yararlanma projenin başlıca amaçlarından biri olarak açıklanıyor.
İşte cgroups
bu noktada devreye giriyor.
cgroups
, 2006 yılında Google mühendisleri tarafından geliştirilmeye başlanıp 2008 yılında Linux 2.6.24 sürümüne dahil edilmiş ve yarattığı domino etkisi ile ekosistemi alt üst etmiş bir özellik.
Kodun Linux’a dahil edilmesi ile sistem yöneticileri, sistemdeki işlemleri (process/task) gruplayarak ortak kısıtlara tabi tutabilir hale geldi. İşlemlerin öncelikleri, kaynak limitleri yapılandırılabilir ve muhasebesi tutulabilir oldu. Dahası, çekirdeğin bu yeteneği LXC ve sonrasında Docker gibi sistem yönetimini kökten değiştiren yazılımların önünü açtı.
Size küçük bir cgroups
demosu hazırladım.
Yukarıda neler oluyor?
fibtest
adında bir kontrol grubu yaratıyorum.- Yarattığım grup içinde yine
fibtest
adlı küçük C uygulamasını çalıştırıyorum. Bu uygulama sürekli bir şekilde Fibonacci dizisi üretiyor. systemd-cgtop
ile sistemdeki tüm gruplardaki kaynak tüketimlerini izliyorum.- Asıl olay burada dönüyor. Grup içinde iki değeri değiştiriyorum:
cpu.cfs_period_us
vecpu.cfs_quota_us
.cfs_period_us
ile her CPU döneminin kaç mikrosaniye süreceğini (50000) belirlerkencfs_quota_us
ile programın her dönemde maksimum kaç mikrosaniye CPU kullanabileceğini (1000) belirliyorum. Uzun lafın kısası programı boğuyorum. - Değiştirdiğim değerleri geri alıyorum ve
fibtest
yeniden nefes alıyor.
Ek olarak, demoda kullandığım cgcreate
ve cgexec
adlı araçları Ubuntu 18.04’te cgroup-tools
, Fedora 32’de libcgroup-tools
adlı paketi kurarak edinebilirsiz.
Kubernetes ve cgroups
Varsayılan olarak sistem programları ile konteynerler, makinedeki kaynaklar üzerinde rekabet halinde çalışıyor. Sistem işlemleri için kaynak ayrılmaması halinde konteynerlerin kaynak tüketimi, makineyi kararsız hale getirebilir.
Kubernetes de sistem ve kullanıcı işlemleri için kaynak izolasyonunu cgroups ile sağlıyor. Her makinede (eğer yoksa) kubepods
adında bir cgroup yaratılıyor. Sistem ve Kubernetes’e ait servisler için ise cgroup kendiliğinden yaratılmıyor. Bunun için sistem yöneticilerinin kubelet
yapılandırmasını değiştirmesi gerekiyor.
apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration systemReserved: cpu: 500m memory: 500M kubeReserved: cpu: 500m memory: 500M

$ kubectl describe node ... Capacity: attachable-volumes-gce-pd: 127 cpu: 8 ephemeral-storage: 47259264Ki hugepages-2Mi: 0 memory: 30879764Ki pods: 110 Allocatable: attachable-volumes-gce-pd: 127 cpu: 7 ephemeral-storage: 18858075679 hugepages-2Mi: 0 memory: 29831188Ki pods: 110 ...
Capacity
makinede Kubernetes’in gördüğü toplam kaynakları, Allocatable
ise kullanıcı pod’larına ayrılmış toplam kaynağı gösteriyor.
Kaynak istekleri ve limitler
Kubernetes’te kaynak yönetiminde iki kavram hemen karşımıza çıkıyor: Kaynak talebi ve kaynak limiti.
Kaynak talebi, podların makinelere yerleştirilmesi için planlayıcı (scheduler) tarafından dikkate alınıyor ve kullanıcı pod’larına ayrılmış kaynakta yeterli yer olduğu müddetçe pod bir makineye atanıyor. Pod bir makineye atandıktan sonra kubelet
de talep edilen kaynağı daima konteynerin kullanabileceğini garanti ediyor. “Pending” durumu ise pod’un atanmayı beklediği anlamına geliyor. Her pod’un yaşam döngüsünde “Pending” durumunda bulunmak var ama pod’ların bu durumda uzun süre geçiriyor olması halinde kaynak taleplerini gözden geçirmekte fayda var. Bir pod’un kaynak talebi içindeki konteynerlerin taleplerinin toplamından oluşuyor.
Kullanıcı pod’ları için ayrılan toplam kaynaktan verimli şekilde yararlanmak adına her konteyner için kaynak isteği tanımlamak önem taşıyor. Planlayıcı, pod’ları makinelere atarken makinedeki kullanılabilecek kapasitenin makineye atanmış pod’ların toplam kaynak talebinden daha yüksek olup olmadığını kontrol ediyor. Pratikte, makinedeki pod’lar taleplerinden daha az kaynak tüketiyor olsalar bile, taleplerinin toplamı kullanılabilir kaynağa eşitse, bu makineye yeni pod atanamayacaktır. Zira, yukarıda bahsettiğim gibi konteynerin talep ettiği kaynak kubelet
tarafından her zaman garanti ediliyor.
Kaynak limiti, bir pod’un sistemdeki tüm kaynağı tüketmesini engellemek için kubelet
tarafından dikkate alınıyor.
Bir konteyner talep ettiğinden daha fazla kaynak tüketebilir. Ancak, talebinden daha fazla bellek (memory) tüketen bir konteyner, makinede belleğin azalması halinde tahliye (evict) edilecektir.
Limitinden daha fazla kaynak tüketen bir konteynere ne olacağı ise ilgili kaynağa bağlı:
- Bellek limitinin aşılması durumunda konteyner sonlandırılabilir ve mümkünse yeniden başlatılabilir.
- CPU limitinin aşılması durumunda ise konteyner sonlandırılmaz, sadece CPU kullanımı daraltılır.
Pratikte, makinedeki tüm konteynerlerin toplam kaynak talebi kullanıcı pod’larına ayrılan kaynağı aşamaz ama kaynak limitlerinin toplamı kullanılabilecek kaynağın çok üstünde olabilir.
Uçak şirketlerinin nasıl olsa birkaç fire çıkar diyerek fazladan bilet satması gibi, toplam kaynak limiti için de maksimum kaynağın üstüne çıkılabilir. Bu durumda yukarıda anlattığım şekilde sistem ve Kubernetes işlemlerine kaynak ayırmak daha fazla önem taşıyacaktır.
Namespace seviyesinde kaynak yönetimi
Servislerinizi veya ortamlarınızı (test, qa gibi) ayırmak için Kubernetes namespace’lerini kullanıyorsanız, her namespace için ön tanımlı kaynak talebi ve limitleri belirleyebilirsiniz. Böylece her konteyner için ayrı ayrı kaynak yapılandırması yapmadan ön tanımlı değerleri kullanabilirsiniz.
Ön tanımlı kaynak yapılandırması için bir LimitRange
tanımlamak gerekiyor:
apiVersion: v1 kind: LimitRange metadata: name: qa-limitrange spec: limits: - default: cpu: 1 memory: 512Mi defaultRequest: cpu: 500m memory: 256Mi type: Container $ kubectl describe limits Name: qa-limitrange Namespace: qa Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio ---- -------- --- --- --------------- ------------- ----------------------- Container cpu - - 500m 1 - Container memory - - 256Mi 512Mi -
Gün içinde küçük ağlama krizleri yaşayacak kadar YAML dosyası ile uğraştığımız için her dosyaya dört satır eksik yazmak çok çekici geliyor. Ancak, LimitRange
bundan daha fazlasını da yapıyor. Özellikle çok kiracılı (multi tenancy) Kubernetes cluster’larında kaynak talep ve limitlerinin onaylanması için de LimitRange
tanımlayabiliyoruz.
apiVersion: v1 kind: LimitRange metadata: name: dev-limitrange spec: limits: - max: cpu: 2 memory: 1Gi min: cpu: 1 memory: 500Mi type: Container $ kubectl describe limits Name: dev-limitrange Namespace: dev Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio ---- -------- --- --- --------------- ------------- ----------------------- Container cpu 500m 2 2 2 - Container memory 256Mi 1Gi 1Gi 1Gi -
Namespace için LimitRange
tanımladığımızda, aslında bir kabul denetçisi (admission controller) oluşturmuş oluyoruz. Bu sayede cluster’a eklenmek istenen her pod, kaynak yapılandırması bakımından onaylanarak içeri alınıyor. LimitRange
kurallarına uymayanlar ise reddediliyor. Bu sayede, sistem yöneticisinden bağımsız üçüncü kişilerin diğer servisleri kararsız hale getirmeyecek şekilde yeni pod’lar kurabilmesinin önü açılmış oluyor.
Namespace üzerindeki kontrolümüz bu kadarla da kalmıyor. Bir ResourceQuota
tanımlayarak namespace’in toplam kaynağını da sınırlayabiliyoruz.
apiVersion: v1 kind: ResourceQuota metadata: name: qa-resourcequota namespace: qa spec: hard: requests.cpu: "2" requests.memory: 8Gi limits.cpu: "2" limits.memory: 8Gi --- apiVersion: v1 kind: ResourceQuota metadata: name: test-resourcequota namespace: test spec: hard: requests.cpu: "1" requests.memory: 4Gi limits.cpu: "1" limits.memory: 4Gi

Yukarıdaki yapılandırma ile aşağıdaki hususları garanti etmiş oluyoruz:
- Namespace’deki pod’ların toplam bellek talebi ve limiti
qa
için 8 GB,test
için 4 GB’ı geçemez. - Namespace’deki pod’ların toplam CPU talebi ve limiti
qa
için 2,test
için 1 CPU’dan fazla olamaz.
QoS sınıfları
Podlara ait QoS (Quality of Service) sınıfları hem planlama (scheduling) hem de tahliye (eviction) için önem taşıyor. Kullanabileceğimiz üç QoS sınıfı mevcut:
- Guaranteed: Pod içindeki tüm konteynerlerin kaynak talep ve limitleri eşitse.
- Burstable: Pod, Guaranteed sınıfına girmiyor ve en az bir konteyner kaynak talebinde bulunuyorsa.
- BestEffort: Pod içindeki hiçbir konteyner herhangi bir kaynak talebi veya limitine sahip değilse.
Anlayacağınız üzere bu sınıflar pod’ların kaynak yapılandırmasına göre Kubernetes tarafından atanıyor.
$ kubectl get pod <pod> -o yaml apiVersion: v1 kind: Pod metadata: name: pod spec: containers: - name: container-1 image: ... resources: limits: memory: 1Gi requests: cpu: 100m memory: 512Mi status: ... phase: Running qosClass: Burstable
Tahliye
Yukarıda anlattığım tüm yapılandırmalara rağmen makinelerimizdeki kaynaklar tükenebilir ve cluster kararsızlaşabilir. Bu durumda kubelet
hızlıca kaynak geri kazanmaya çalışacaktır. Çabalarının nafile kalması durumunda ise tahliye
süreci başlar.
Tahliye için kubelet
pod’ları bir sıraya koyar:
- BestEffort veya Burstable QoS sınıfına ait pod’lardan kaynak talebinden fazlasını kullananlar önceliklerine ve taleplerinden ne kadar fazla kaynak tükettiklerine göre sıralanır ve tahliye edilir.
- Guaranteed pod’lar ile taleplerinden daha az kaynak tüketen Burstable pod’lar en son tahliye edilir. Guaranteed pod’lara, adı üstünde, başka bir pod’un kaynak tüketiminden dolayı tahliyeye maruz kalmayacağı garanti ediliyor. Ancak, sistem veya Kubernetes’e ayırdığımız kaynakların tükenmeye başlaması durumunda en düşük önceliğe sahip pod’dan başlayarak bunlar da tahliye edilebilir.
Öncelik, yine Kubernetes yöneticileri olarak PriorityClass
ile ayarlayabileceğimiz bir değer. Yazıyı daha da dallandırmamak adına sizi ilgili dokümanlara yönlendirmekle yetiniyorum.
Umarım yazı sizin için faydalı olmuştur.
Yazan: Ege Güneş
Yayınlanma Tarihi: 13.11.2020
