色々といじってたら、繋がったので、書いてみる。
オートスケールの前に
リソースの制限
kubernetesにはコンテナ単位にリソースの制限を行うことができる。
コンテナ、ノードが適切にパフォーマンスを発揮するためにも必要なことである。
基本的にはCPUとメモリに制限をかけることができる。
CPUについては、1vCPUを1000millicores単位で指定する。 メモリについては、1Gi を 1024Mi単位で指定する。
構成ファイル例
apiVersion: apps/v1beta1 kind: Deployment metadata: namespace: wakashiyo name: wakashiyo-dev-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.12 ports: - containerPort: 80 resources: requests: memory: 1024Mi cpu: 500m limits: memory: 2048Mi cpu: 1000m
resources.requestsとresources.limitsの2種類が設定されている。
requestsは、リソース使用量の下限で、
limitsは、上限を指定している。
Podのスケジューリングは、ノードにrequestsで指定したリソースが残っているとスケジューリングされ、 残っていないとスケジューリングされない。
例えば、ノードAの割り当て可能なCPUが940mだった場合、
kube-systemの合計のrequestsが300mになっている。
これからデプロイしたいPodのrequestsのCPUリソースを100mにしているとする。(レプリカ数は、8にする)
この場合、6台までは割り当てられることができるが、残り割り当て可能なCPUリソースは40m しか残っていないため、
7台目以降はスケジューリングされない。
requestsの値を基準にリソースが残っているか判断しスケジューリングされるため、limitsの方で指定したリソースが残っていなくてもスケジューリングされる。
コンテナのリソース使用量がlimitsを超えた場合どうなってしまうのか?
limitに設定したメモリを超える構成ファイルを用意して、デプロイしてみた
apiVersion: v1 kind: Pod metadata: name: memory-over-pod namespace: wakashiyo spec: containers: - name: memory-over-pod image: polinux/stress resources: requests: memory: "50Mi" limits: memory: "100Mi" command: ["stress"] args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]
limitsのメモリを100Miにして250のメモリを確保する用コマンドを実行している。
memory-over-pod 0/1 OOMKilled 1 10s memory-over-pod 0/1 CrashLoopBackOff 1 11s
結果、上記のように強制終了し、再作成の繰り返しとなった。
limitsの値を実際に超えてしまうと、強制的に終了され、再作成のスケジューリングが実行されることがわかった。
CPUの場合、どうなるのかも試してみた。
apiVersion: apps/v1beta1 kind: Deployment metadata: namespace: wakashiyo name: cpu-over-pods spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.12 ports: - containerPort: 80 resources: requests: memory: 50Mi cpu: 5m limits: memory: 100Mi cpu: 10m
デプロイ後に、apach bench で負荷をかけてみた。(ingress , nodeportもデプロイしている)
NAME READY STATUS RESTARTS AGE cpu-over-pods-5579fd4fdd-v97zr 1/1 Running 0 3m16s
CPUのときも強制終了するかもしれないと期待したが、何も変化がなかった。。。(もしかしたらやり方が悪かった?)
あらかじめ、ノードより大きいリソースを割り当てられる設定を行うとスケジューリングされないとわかったが、
実際に使用している間にlimitsを超えたりするとどうなるのかがわかった。
オートスケールしてみる(hpaを使ってみる)
前述のリソースの制限にはLimitRangeやCluster Auto Scaler、Resource Quotaなどもあるが今回は割愛する。
hpa(Horizontal Pod AutoScaler)はCPU負荷に応じて、Deployment, ReplicaSet, Replication Controllerのレプリカ数を自動的にスケールしてくれるとても便利なリソースだ。
内部的には30秒に1回オートスケールするべきかのチェックが行われるらしい。
その際に実際に使用しているCPU使用率と
どれくらのCPU使用率に抑えたいか(targetAverageUtilization)
この2つの値を用いて必要なレプリカ数を算出し、スケールアウトしたり、スケールインしてくれる。
算出する計算式は、ドキュメントに記載されている。
Horizontal Pod Autoscaler - Kubernetes
実際にデプロイしてみた
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: namespace: wakashiyo name: wakashiyo-dev-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: wakashiyo-dev-deployment minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu targetAverageUtilization: 30
hpaを適用するdeployment
apiVersion: apps/v1beta1 kind: Deployment metadata: namespace: wakashiyo name: wakashiyo-dev-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.12 ports: - containerPort: 80 resources: requests: memory: 50Mi cpu: 5m limits: memory: 75Mi cpu: 10m
今回はスケールするのを簡単にみたいため、cpuのresourcesを小さくした。
targetAverageUtilization: 30ということで目標のCPU使用率平均の値を30%にした
これをapach benchで負荷をかけてみた
pods(負荷前)
% kubectl get pods -n wakashiyo NAME READY STATUS RESTARTS AGE wakashiyo-dev-deployment-6bdb498876-89dk2 1/1 Running 0 17m
pods(負荷後)
% kubectl get pods -n wakashiyo NAME READY STATUS RESTARTS AGE wakashiyo-dev-deployment-6bdb498876-5zpxf 1/1 Running 0 2m4s wakashiyo-dev-deployment-6bdb498876-774dv 1/1 Running 0 3m34s wakashiyo-dev-deployment-6bdb498876-89dk2 1/1 Running 0 17m wakashiyo-dev-deployment-6bdb498876-c9ps5 1/1 Running 0 3m4s wakashiyo-dev-deployment-6bdb498876-f7rl2 1/1 Running 0 3m34s wakashiyo-dev-deployment-6bdb498876-g95xg 1/1 Running 0 3m4s wakashiyo-dev-deployment-6bdb498876-p64wc 1/1 Running 0 2m4s wakashiyo-dev-deployment-6bdb498876-pp9sf 1/1 Running 0 3m4s wakashiyo-dev-deployment-6bdb498876-qqdms 1/1 Running 0 3m34s wakashiyo-dev-deployment-6bdb498876-tjxh9 1/1 Running 0 2m4s
hpa
% kubectl get horizontalpodautoscalers.autoscaling -n wakashiyo --watch NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 20%/30% 1 10 1 10m NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 0%/30% 1 10 1 10m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 100%/30% 1 10 1 11m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 100%/30% 1 10 4 11m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 200%/30% 1 10 4 11m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 200%/30% 1 10 7 12m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 26%/30% 1 10 7 12m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 46%/30% 1 10 7 12m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 46%/30% 1 10 10 13m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 48%/30% 1 10 10 13m wakashiyo-dev-hpa Deployment/wakashiyo-dev-deployment 20%/30% 1 10 10 13m
上記のように、CPU使用率の目標値を上回ってから、 podがオートスケールされていき、
目標値に近づいてきた。実際に正確な目標値におさまることは確認できなかったが、CPU使用率に応じて自動でスケールされることがわかった。
一つ気になること
hpaを使用することでオートスケールするのが確認できたが、実際ノードに割り振られているpod数を確認すると以下のようになっていた
% kubectl get pods -o wide -n wakashiyo NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES wakashiyo-dev-deployment-6bdb498876-5zpxf 1/1 Running 0 5m35s 10.48.2.11 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-774dv 1/1 Running 0 7m5s 10.48.2.6 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-89dk2 1/1 Running 0 20m 10.48.2.4 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-c9ps5 1/1 Running 0 6m35s 10.48.2.7 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-f7rl2 1/1 Running 0 7m5s 10.48.1.4 gke-wakashiyo-default-pool-8b127224-4pbq <none> <none> wakashiyo-dev-deployment-6bdb498876-g95xg 1/1 Running 0 6m35s 10.48.2.9 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-p64wc 1/1 Running 0 5m35s 10.48.2.10 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-pp9sf 1/1 Running 0 6m35s 10.48.2.8 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-qqdms 1/1 Running 0 7m5s 10.48.2.5 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none> wakashiyo-dev-deployment-6bdb498876-tjxh9 1/1 Running 0 5m35s 10.48.2.12 gke-wakashiyo-default-pool-8b127224-zx8q <none> <none>
ノード | Pod数 |
---|---|
A | 9 |
B | 1 |
C | 0 |
1つのノードでは全くPodが割り当てられていないことがわかった。
これで9台割り当てられているリソースで枯渇が起きたら、効率的にクラスタを利用できていないし、パフォーマンスも発揮できない。
できるのならCのノードに何台かPodを割り当てたい。
deschedulerを使用する
deschedulerとは?
GitHub - kubernetes-sigs/descheduler: Descheduler for Kubernetes
一部のノードが使用されていなかったり、使用されすぎたりしてしまっているとき、
ノードが新たに追加されたとき、
ラベルがノードに追加されたり、削除されたりして、今まで適用されていたNode Affinityの条件を満たさなくなってしまった
ノードに障害が起こり、他のノードに移動した
など、クラスタの状態が動的に変化するにもかかわらず、kube-schedulerは配置後の見直しは行わないので、
このdeschedulerによって意図しないPodのスケジューリングを見つけ、再配置をしてくれる。というこれまた便利なものだ。
実際に、スケジューリング自体はkube-systemが行い、deschedulerが行うのは、移動可能なPodを見つけ排除するところまでを行う。
そのため、スケジューリング自体はデフォルトのkube-systemに依存している。
deschedulerは5つのポリシーと戦略から構成される。(RemovePodsViolatingNodeTaints こちらについては今回省略する。)
これらは全てデフォルトではenabledになっているとのこと。
RemoveDuplicates
これは、Deployment, ReplicaSet, ReplicationController, Jobによって動いているPodが同じノード内で1台だけであることを示す。
Podが重複していたら、クラスター内のPodの展開を改善するために排除される。
例としては、あるノードが障害が落ちてしまい、他のノードに移動したとする。 その後、障害していたノードが復活した際に、復活したノードに再配置されるように、障害時に移動して、重複状態のPodを削除する。
LowNodeUtilization
これは、使用されていないノードを見つけ、使用されすぎているノードからPodを削除する。
その上で、使用されていないノードに削除されたPodがスケジュールされるようにする。
nodeResourceUtilizationThresholds
というパラメータを使用して、設定を行う。
nodeResourceUtilizationThresholds.thresholds
は、
使用されていないノードの条件を設定する。cpu, memory, Pod数から決める。ここで設定した値を下回ると(3つ全て)使用されていないノードとみなされ、他のノードで削除されたPodがあった際に、スケジュール対象ノードになる。
nodeResourceUtilizationThresholds.targetThresholds
は、
使用されすぎているノードの条件を設定する。cpu, memory, Pod数から決める。ここで設定した値を上回ると、そのノードに割り当てられているPodが削除される。
thresholdsとtargetThresholdsの間にあるノードはどちらの対象のノードにならない。
もう一つ numberOfNodes
というパラメータも存在する。
これは、使用されていないノード数が numberOfNodes
で設定した値を超えるとこの LowNodeUtilizationのポリシーが有効になるというもの。
これはデフォルトでは、0になっていて、
大規模なクラスタで運用していて、何台かのノードが、しばしば使用されなさすぎな状態になるとき、あるいは短期間使用されなさすぎな状態になるということがあると役立つらしい。
RemovePodsViolatingInterPodAntiAffinity
これは、anti affinityのルールに違反しているPodをそのノードから削除してくれるもの。
例えば、Pod A, B , C が同じノードで動いているとする。 後から、PodB, PodC にPodAとは同じノードで動いてはいけないというanti affinityルールを付与したとする。 このとき、PodAが削除される。
RemovePodsViolatingNodeAffinity
これは、node affinityのルールに違反しているPodをそのノードから削除してくれるもの。
例えば、ノードAではPodAがスケジュールされるために必要なnode affinityを満たしていて、PodAがノードAで動いていたとする。 時が経って、ノードAではその条件を満たさなくなり、ノードBで条件を満たす状態になったとする。 この時、PodAが削除される。
使ってみる
使用方法は、
GitHub - kubernetes-sigs/descheduler: Descheduler for Kubernetes
README にも記載している。
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: descheduler-cluster-role rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["pods"] verbs: ["get", "watch", "list", "delete"] - apiGroups: [""] resources: ["pods/eviction"] verbs: ["create"] --- apiVersion: v1 kind: ServiceAccount metadata: name: descheduler-sa namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: descheduler-cluster-role-binding subjects: - kind: ServiceAccount name: descheduler-sa namespace: kube-system roleRef: kind: ClusterRole name: descheduler-cluster-role apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 data: policy.yaml: | apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: "RemoveDuplicates": enabled: true "RemovePodsViolatingInterPodAntiAffinity": enabled: true "LowNodeUtilization": enabled: true params: nodeResourceUtilizationThresholds: thresholds: "cpu" : 10 "memory": 10 "pods": 0 targetThresholds: "cpu" : 25 "memory": 25 "pods": 5 kind: ConfigMap metadata: name: descheduler-policy-configmap namespace: kube-system
job
apiVersion: batch/v1 kind: Job metadata: name: descheduler-job namespace: kube-system spec: parallelism: 1 completions: 1 template: metadata: name: descheduler-pod annotations: scheduler.alpha.kubernetes.io/critical-pod: "" spec: containers: - name: descheduler image: gcr.io/wakashiyo-playground/descheduler:0.0.1 volumeMounts: - mountPath: /policy-dir name: policy-volume command: [ "/bin/descheduler", "--policy-config-file", "/policy-dir/policy.yaml", "-v", "1", ] restartPolicy: "Never" serviceAccountName: descheduler-sa volumes: - name: policy-volume configMap: name: descheduler-policy-configmap
これを適用したが、Podがうまく割り振られたかというと上手くいかなかった。
nodeのリソースをそれぞれ確認すると、
1台も割り当てられていないPodというのは、既にkube-systemで結構リソースが使用されていた。
だから、 LowNodeUtilization.nodeResourceUtilizationThresholds
の設定がよくなかったのかもしれない。
元々、そのリソース使用量だったから、偏ったPodの配置をkube-schedulerは行ったのかもしれない。。。(それだったら、kube-systemは適切な配置を行ったとも言うことができる。。。)
この辺よくわからなくなってきたので、今日はここまでにして、もっと調査したりして、理解を深めていきたいと思う。