kubernetes에서 자동적으로 pod를 스케줄링할 수 있는 혁신적인 방법은 없을까? (feat. descheduler) (2) - 실습과 유의사항

6 minute read

해당 블로그에서 사용된 코드는 해당 링크에서 보실수 있습니다.

TL;DR

  1. descheduler은 여러 방법을 통해서 내 cluster에 설치할 수 있다.
  2. descheduler는 실제 도입하기에 생각보다 더 많은 것을 고려해야되며 큰 문제를 만들 수도 있다.
  3. 도입시 세밀한 옵션을 통해서 문제를 막을수 있고 모니터링을 제공해줘서 활용할 필요가 있다.

이전 내용

  1. kubernetes 운영중 pod이 node에 적절히 분산되지 않는 문제를 경험했고 해당 문제를 해결할 수 있는 방법을 찾았다.
  2. 그 중 descheduler는 Pod이 배포된 이후에도 지속적으로 Node에 대해서 관리해주는 장점이 있다.
  3. descheduler는 Node 사용량을 기반으로 Pod 분산 외에도 다양한 정책을 지원해준다.

개요

이전 글에서는 실제 제가 k8s에 application을 동작시키고 운영했을때 발생했던 문제를 공유하고 그 해결책으로 descheduler에 대해서 이야기했습니다. 그리고 추가적으로 descheduler가 어떻게 해결책이 되면 어떤 장점이 있는지에 대해서 적었습니다.

이번에는 실제로 어떻게 descheduler를 cluster에 설치하고 실제 동작하는 모습에 대해서 이야기해보겠습니다.

설치

descheduler은 3가지 k8s resource를 제공하고 있습니다. Job, CronJob, Deployment.

다들 아시는것처럼 Job은 일회성, CronJob은 Job을 주기적으로 실행시켜주기 때문에 반복적으로 실행하도록 설정가능합니다. Deployment는 지속적으로 동작하고 있지만 container을 실행할때 interval을 arguments로 주입해서 사용하고 있습니다.

실질적으로 k8s resource를 확인하고 싶은 경우는 해당 링크를 통해서 보실수 있습니다.

버전

version에 따라 문서가 달라질 수 있으므로 반드시 tag 정보를 변경해서 그것과 맞는 문서와 사용법을 숙지하기를 바랍니다.

k8s version와 descheduler의 version을 맞추는게 좋습니다. descheduler readme에서도 해당 정보를 제공하고 있습니다. 만약 cluster version에 맞는 descheduler를 설치하고 싶은 경우 해당 링크를 확인해서 설치하는게 좋습니다.

k8s 호환성 스샷

저는 현재 local에 k8s cluste version이 1.31 이기 때문에 descheduler 0.31 version을 설치하겠습니다.

설치 방법

설치에 다양한 방법에 구체적인 방법은 해당 링크를 확인하십시오.

helm과 kustomize를 통해서 거의 바로 사용가능합니다. 저는 직접 resource file들을 관리하는 것이 좋아서 실제로 해당 파일들을 다운로드해서 사용하겠습니다. deployment를 직접 실행해보겠습니다.

  1. fork를 한다.
  2. 밑에 있는 코드를 실행한다.
kubectl create -f kubernetes/base/rbac.yaml
kubectl create -f kubernetes/base/configmap.yaml
kubectl create -f kubernetes/deployment/deployment.yaml

configmap/descheduler-policy-configmap created
deployment.apps/descheduler created
clusterrole.rbac.authorization.k8s.io/descheduler-cluster-role created
serviceaccount/descheduler-sa created
clusterrolebinding.rbac.authorization.k8s.io/descheduler-cluster-role-binding created

설치시 주의사항

descheduler이 처리하는 Pod의 수가 적다면 문제가 되지 않지만 만약 해당 대상이 많다면 성능적으로 문제를 만들수 있습니다. 그것에 맞춰서 descheduler가 실행되는 interval과 실행되는 Pod의 리소스 관리를 해야됩니다.

실제 동작 실험

모든 policy에 대해서 테스트하면 좋겠지만 제가 경험한 상황을 실험해보기 위해서 특정 node에 사용량을 높은 상태에서 추후 node가 추가되는 상황을 연출하겠습니다.

실험 환경 및 상황

실제 실험은 k3s를 이용했으며 cpu stress pod을 실행하여서 부하를 강제로 만들었습니다.

현재 강제로 k3d-descheduler-test-agent-0 서버에는 Taint를 사용해서 스케쥴링은 막은 상태입니다.

$ k top node
NAME                            CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
k3d-descheduler-test-agent-0    44m          2%     270Mi           6%        
k3d-descheduler-test-agent-1    1464m        73%    509Mi           13%       
k3d-descheduler-test-server-0   40m          2%     513Mi           13% 
  • server component로 가정한 deployment에 topologySpreadConstraints을 설정해뒀습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: continuous-cpu-stress-deployment
  namespace: default
spec:
  replicas: 6
  selector:
    matchLabels:
      app: continuous-cpu-stress
  template:
    metadata:
      labels:
        app: continuous-cpu-stress
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          whenUnsatisfiable: ScheduleAnyway
          topologyKey: kubernetes.io/hostname
          labelSelector:
            matchLabels:
              app: continuous-cpu-stress
      containers:
        - name: cpu-stressor
          image: narmidm/k8s-pod-cpu-stressor
          args: ["-cpu", "0.5", "-forever"]

새로운 스케쥴링 가능한 노드 추가

  • 만약 여기서 새로운 node를 추가한다고 한다면 어떻게 될까요?

일부러 스케쥴링을 막았던 Node에 taint를 삭제해보겠습니다. k3d-descheduler-test-agent-0 node에 taint를 없애더라도 현재 기존에 스케줄링된 pod들이 재스케쥴링되지는 않습니다.

$ kubectl describe node k3d-descheduler-test-agent-0 | grep Taint

Taints:             <none>

$ k get pods -o wide
NAME                                                READY   STATUS    RESTARTS   AGE   IP           NODE                           NOMINATED NODE   READINESS GATES
continuous-cpu-stress-deployment-5fc774cd59-b4qxn   1/1     Running   0          18m   10.42.1.36   k3d-descheduler-test-agent-1   <none>           <none>
continuous-cpu-stress-deployment-5fc774cd59-b4sz8   1/1     Running   0          18m   10.42.1.34   k3d-descheduler-test-agent-1   <none>           <none>
continuous-cpu-stress-deployment-5fc774cd59-fr4xz   1/1     Running   0          18m   10.42.1.39   k3d-descheduler-test-agent-1   <none>           <none>
continuous-cpu-stress-deployment-5fc774cd59-k86x4   1/1     Running   0          18m   10.42.1.35   k3d-descheduler-test-agent-1   <none>           <none>
continuous-cpu-stress-deployment-5fc774cd59-vtnfh   1/1     Running   0          18m   10.42.1.37   k3d-descheduler-test-agent-1   <none>           <none>
continuous-cpu-stress-deployment-5fc774cd59-z9hlb   1/1     Running   0          18m   10.42.1.38   k3d-descheduler-test-agent-1   <none>           <none>

LowNodeUtilization 설정

  • LowNodeUtilization을 설정된 configmap과 descheduler deployment, 그리고 rbac를 설정한다. (해당 내용은 위에 설명한 것과 같습니다.)
---
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha2"
    kind: "DeschedulerPolicy"
    profiles:
      - name: ProfileName
        pluginConfig:
          - name: "DefaultEvictor"
          - name: "LowNodeUtilization"
            args:
              thresholds:
                "cpu" : 10
              targetThresholds:
                "cpu" : 20
        plugins:
          balance:
            enabled:
              - "LowNodeUtilization"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: descheduler
  namespace: kube-system
  labels:
    app: descheduler
spec:
  replicas: 1
  selector:
    matchLabels:
      app: descheduler
  template:
    metadata:
      labels:
        app: descheduler
    spec:
      priorityClassName: system-cluster-critical
      serviceAccountName: descheduler-sa
      containers:
        - name: descheduler
          image: registry.k8s.io/descheduler/descheduler:v0.31.0
          imagePullPolicy: IfNotPresent
          command:
            - "/bin/descheduler"
          args:
            - "--policy-config-file"
            - "/policy-dir/policy.yaml"
            - "--descheduling-interval"
            - "5m"
            - "--v"
            - "3"
          ports:
            - containerPort: 10258
              protocol: TCP
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10258
              scheme: HTTPS
            initialDelaySeconds: 3
            periodSeconds: 10
          resources:
            requests:
              cpu: 500m
              memory: 256Mi
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            privileged: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
          volumeMounts:
            - mountPath: /policy-dir
              name: policy-volume
      volumes:
        - name: policy-volume
          configMap:
            name: descheduler-policy-configmap
$ k apply -f ./descheduler
configmap/descheduler-policy-configmap created
deployment.apps/descheduler created
clusterrole.rbac.authorization.k8s.io/descheduler-cluster-role created
serviceaccount/descheduler-sa created
clusterrolebinding.rbac.authorization.k8s.io/descheduler-cluster-role-binding created
  • descheduler deployment log
0216 13:54:09.710040       1 descheduler.go:173] Setting up the pod evictor
I0216 13:54:09.757825       1 nodeutilization.go:207] "Node is overutilized" node="k3d-descheduler-test-agent-0" usage={"cpu":"600m","memory":"326Mi","pods":"3"} usagePercentage={"cpu":30,"memory":8.33,"pods":2.73}
I0216 13:54:09.764642       1 nodeutilization.go:204] "Node is underutilized" node="k3d-descheduler-test-agent-1" usage={"cpu":"100m","memory":"70Mi","pods":"10"} usagePercentage={"cpu":5,"memory":1.79,"pods":9.09}
I0216 13:54:09.764890       1 nodeutilization.go:204] "Node is underutilized" node="k3d-descheduler-test-server-0" usage={"cpu":"0","memory":"0","pods":"1"} usagePercentage={"cpu":0,"memory":0,"pods":0.91}
I0216 13:54:09.769163       1 lownodeutilization.go:135] "Criteria for a node under utilization" CPU=10 Mem=100 Pods=100
I0216 13:54:09.770894       1 lownodeutilization.go:136] "Number of underutilized nodes" totalNumber=2
I0216 13:54:09.771432       1 lownodeutilization.go:149] "Criteria for a node above target utilization" CPU=20 Mem=100 Pods=100
I0216 13:54:09.771482       1 lownodeutilization.go:150] "Number of overutilized nodes" totalNumber=1
I0216 13:54:09.780813       1 nodeutilization.go:261] "Total capacity to be moved" CPU=700 Mem=8137900032 Pods=209
I0216 13:54:09.781167       1 nodeutilization.go:264] "Evicting pods from node" node="k3d-descheduler-test-agent-0" usage={"cpu":"600m","memory":"326Mi","pods":"3"}
I0216 13:54:09.786729       1 nodeutilization.go:267] "Pods on node" node="k3d-descheduler-test-agent-0" allPods=3 nonRemovablePods=3 removablePods=0
I0216 13:54:09.787907       1 nodeutilization.go:270] "No removable pods on node, try next node" node="k3d-descheduler-test-agent-0"
I0216 13:54:09.792150       1 profile.go:345] "Total number of pods evicted" extension point="Balance" evictedPods=0
I0216 13:54:09.792289       1 descheduler.go:179] "Number of evicted pods" totalEvicted=0

위와 같은 실험을 하는데 환경 문제로 판단되는데 실제 pod이 있는 node와 사용량이 올라간 node가 달라서 실질적으로 evict가 발생하지 않습니다. 추후 minikube나 node를 여러개 둘 수 있는 상황에서 추가 테스트를 진행해서 업데이트하겠습니다.

실제 descheduler를 설정할때 꼭 주의해야되는 것.

  • PDB(PodDisruptionBudget) 설정할 것

descheduler은 schedulering이 필요하다고 판단되면 모든 pod들을 eviction합니다. 그 효과로 인해서 갑자기 특정 component에 Pod이 모두 evict되거나 혹은 한번에 Pod이 사라져서 가용성이 떨어질 가능성이 존재합니다.

그래서 PDB를 설정하여서 descheduler 과정에서 문제를 발생하지 않도록 할수 있습니다.

만약 PDB가 설정하지 않은 경우 descheduler에 대상에서 제거하는 옵션이 존재합니다. 혹은 gracePeriodSeconds 설정을 통해서 바로 Pod을 삭제하는 것이 아닌 시간을 제공하도록 할수도 있습니다. 밑에는 예시입니다.

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha2"
    kind: "DeschedulerPolicy"
    gracePeriodSeconds: 60
    profiles:
      - name: ProfileName
        pluginConfig:
          - name: "DefaultEvictor"
            args:
              ignorePodsWithoutPDB: true
          - name: "LowNodeUtilization"
            args:
              thresholds:
                "cpu" : 10
              targetThresholds:
                "cpu" : 20
        plugins:
          balance:
            enabled:
              - "LowNodeUtilization"
  • 원하는 Pod이나 Node에 대해서 설정하기

labelSelector, nodeSelector 설정을 통해서 특정 label이 붙어있는 Pod이나 Node에 대해서 처리할 수 있습니다. 해당 옵션을 통해서 원치않은 Pod들에 대해서 descheduling 되는것을 막을 수 있습니다.

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha2"
    kind: "DeschedulerPolicy"
    gracePeriodSeconds: 60
    profiles:
      - name: ProfileName
        pluginConfig:
          - name: "DefaultEvictor"
            args:
              ignorePodsWithoutPDB: true
              labelSelector: "app=continuous-cpu-stress"
              nodeSelector: "node-role.kubernetes.io/worker"
          ...

더욱 다양한 설정은 top lovel configurationdefault evictor을 참고하십시오.

내가 도입한다면 꼭 고민해야될 것들

  • descheduler은 scheduler가 아닙니다.

scheduler처럼 Pod을 어느 node에서 실행시킬지 처리하는 것이 아닌 Pod을 Eviction해줄 뿐입니다. 그 이후 kube-scheduler가 일합니다. 여기서 중요한 것은 pod이 evict 당한다는 것입니다. 위에서 말했듯이 특별한 설정이 없다면 문제가 충분히 발생할 수 있습니다.

  • k8s 운영에 디버깅이 어려울 수 있습니다.

k8s에 문제가 발생했거나 Pod이 갑자기 죽었는데 해당 문제에 대한 정보를 잘 제공해주지 못할수도 있습니다.

  • 특별한 룰이 없는 상태로 운영하면 더 큰 문제를 만들 수 있습니다.

특정 policy에 대해서 주의사항이 존재합니다. 특히 가용성이 높이 필요한 DB라거나 지속적으로 실행되어야될 가능성이 높은 PVC가 붙은 stateful한 application에 대해서 조심할 필요성이 있습니다.

특정 룰이 없는 상태에서 도입시 가용성이 반드시 필요한 Pod에게도 영향을 줄수 있습니다. 그래서 ignorePvcPods와 같은 옵션도 제공해주고 있습니다.

  • descheduler는 리소스를 사용합니다.

특정 아티클에서는 너무 많은 pod들에 대해서 조사를하다보니 너무 많은 리소스와 시간이 걸릴 수 있다는 내용을 본적이 있습니다. 많은 pod들과 node를 운영하고 있다면 충분히 가용할수 있는지 파악할 필요가 있습니다.

개인적인 결론

  • 반드시 도입해야되는 Pod들을 지정해야된다.

작은 실수로 큰 문제를 만들 수 있기 때문에 명확하게 어떤 Pod들을 관리할지 혹은 어떤 Node에 있는 Pod들을 관리할지 지정하는 것이 중요하다고 생각합니다.

  • 모니터링에 대한 필요성

설정이 잘못된 경우 빠르게 파악하기 위해서 혹은 설정 문제를 파악하기 위해서 모니터링을 할 필요가 있습니다. 실제 descheduler에서는 해당 정보에 대한 설정을 통해서 메트릭을 제공해주고 있습니다.

마무리

이번에는 descheduler를 실제 도입해보고 어떻게 동작하는지에 대해서 이야기해봤습니다. 실습 과정에서 환경에 문제로 정상적으로 잘 동작하지 않았지만 log를 보면서 어떤 과정으로 node와 pod을 선정하는지 알수 있었습니다.

이번 정리와 결론으로 descheduler을 사용할때 어떤 것을 주의하고 어떤 것을 신경써야하는지 명확하게 알 수 있는 시간이 되었기를 바랍니다.

ref

  • https://github.com/kubernetes-sigs/descheduler
  • https://www.gomgomshrimp.com/posts/k8s/topology-spread-constraints
  • https://docs.redhat.com/ko/documentation/openshift_container_platform/4.7/html-single/nodes/index#nodes-descheduler

Leave a comment