記録。

めも。

kubernetesの個人的メモ(オートスケールとdescheduler)

色々といじってたら、繋がったので、書いてみる。

オートスケールの前に

リソースの制限

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は適切な配置を行ったとも言うことができる。。。)

この辺よくわからなくなってきたので、今日はここまでにして、もっと調査したりして、理解を深めていきたいと思う。