혼자 정리하기 위한 쿠버네티스 pod scheduling - affinity 편

쿠버네티스엔 pod을 어떤 노드에 실행시킬 것인지 scheduling 하는 여러 옵션들이 있다.
특정 node를 선택해서 pod를 scheduling 할 수도 있고 ( node selector)
특정 pod들을 같은 node에 모아놓거나 (podAffinity) 반대로 흩어지게 하거나 (podAntiAffinity)
특정 node에 있는 pod들을 다른 node로 옮길 수도 있다(drain)

nodeSelctor

POD가 어떤 node에서 실행될지를 nodeSelector 기능을 통해 key-value 값으로 설정

  • node label 확인
$ kubectl get nodes --show-labels
NAME       STATUS   ROLES    AGE     VERSION   LABELS
minikube   Ready    master   2d23h   v1.17.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=minikube,kubernetes.io/os=linux,node-role.kubernetes.io/master=

=> 테스트 환경이 minikube라 하나의 노드만 있으며 LABELS 탭의 beta.kubernetes.io/arch=amd64 같은 값들이 각각 key=value를 의미함

  • node label 추가
$ kubectl label nodes minikube disktype=ssd
node/minikube labeled

$ kubectl get nodes --show-labels
NAME       STATUS   ROLES    AGE   VERSION   LABELS
minikube   Ready    master   3d    v1.17.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=minikube,kubernetes.io/os=linux,node-role.kubernetes.io/master=

=> minikube node에 disktype=ssd 라는 key value label을 추가함

  • nodeSelector로 pod scheduling 해보자
---
apiVersion: v1
kind: Pod
metadata:
  name: nodeSelector-test-pod
spec:
  containers:
  - name: nginx-pod
    image: nginx
    ports:
    - containerPort: 80
  nodeSelector:
    disktype: hdd

$ kubectl apply -f nodeselector.yaml

$ kubectl get pod -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
nodeselector-test-pod      0/1     Pending   0          45s   <none>       <none>     <none>           <none>

=> nodeselector.yaml에서 .spec.nodeSelector의 값으로 disktype: hdd를 설정했으나
현재 클러스터 내 disktype: hdd label을 가진 node는 없다. 그래서 pod이 생성되지 못하고 pending 상태임
disktype: ssd 로 변경하면 잘 생성됨

nodeAffinity

Pod들을 동일 node에 실행하도록 설정하는 기능

  • nodeAffinity 로 pod scheduling 해보자
---
apiVersion: v1
kind: Pod
metadata: 
  name: nodeaffinity-pod
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: beta.kubernetes.io/os
            operator: In 
            values:
            - linux
            - window
          - key: disktype
            operator: Exists
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 10
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: In 
            values: 
            - worker-node01

=> .spec.affinity.nodeAffinity.requireDuringSchedulingIgnoreDuringExecution : pod scheduling 시 반드시 필요한 조건
.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoreDuringExecution : pod scheduling 시 만족하면 좋은 조건
requireDuringSchedulingIgnoreDuringExecution 설정 시엔 해당하는 node가 없으면 pending 상태로 pod이 생성되지 않지만
preferredDuringSchedulingIgnoreDuringExecution 설정 시엔 만족하는 node가 없더라도 weight 가중치 점수가 높은 node에 pod scheduling 수행함

.nodeSelectorTerm[].matchExpressions[]의 하위 필드로 key,operator,values가 있음

  • key: node의 label key 중 하나를 설정

  • operator: .key에 대한 조건식

    • In: .values[] 필드에 설정한 값 중 label key의 value와 하나라도 일치하는지 체크
    • NotIn: .values[] 필드에 있는 값이 label key의 value와 모두 불일치해야함
    • Exists: .key 필드에 설정한 값이 label의 key 중 존재하는지 확인, .values[] 필드 필요없게됨
    • DoesNotExist : .key 필드 값이 label의 key 중에 없는지 확인
    • Gt : greater than, .key 필드에 설정한 label key의 value가 .values[] 필드의 값보다 큰 지 체크
    • Lt : lower than
  • nodeAffinity 로 pod scheduling 해보자-2

---
apiVersion: v1
kind: Pod
metadata: 
  name: nodeaffinity-pod
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: beta.kubernetes.io/os
            operator: In 
            values:
            - linux
            - window
          - key: disktype
            operator: Exists
          - key: core
            operator: Gt
            values:
            - "40"
      preferredDuringSchedulingIgnoreDuringExecution:
      - weight: 10
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: In 
            values: 
            - worker-node01

=> requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[].matchExpressions[].key = core 부분에 걸려서 pod이 생성되지 않음

$ kubectl get pod
NAME                       READY   STATUS    RESTARTS   AGE
nodeaffinity-pod           0/1     Pending   0          6s


Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/1 nodes are available: 1 node(s) didn't match node selector.

podAffinity & podAntiaffinity

podAffinity 와 podAntiAffinity는 각각의 pod 사이의 관계를 정의하는 기능으로
podAffinity는 서비스1 과 서비스2의 pod를 같은 node에 생성하여 네트워크 통신비용을 줄일 때 사용함
podAntiAffinity는 CPU나 네트워크 같은 서버 리소스를 많이 사용하는 pod을 여러 node로 분산할 때 사용함
podAnitiAffinity를 설정하지 않으면 서비스 사용량에 맞춰 pod를 scaleout 해도 이미 사용률이 높은 node에 생성하게 되기 때문에 효과가 없을 수 있음

  • podAntiAffinity.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
spec:
  selector:
    matchLabels:
      app: redis
  replicas: 3
  template:
    metadata:
      labels:
        app: redis
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In 
                values: 
                - redis 
          topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis

=> podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution 를 사용하여 하위 필드의 조건이 모두 만족되는 node에만 pod을 생성하는데
label.app의 값이 redis인 pod가 속하지 않은 node에만 pod을 생성하겠다는 조건임

$ kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
redis-deployment-7869db744c-ck472   0/1     Pending   0          38s
redis-deployment-7869db744c-m7x4l   0/1     Pending   0          38s
redis-deployment-7869db744c-zrzw7   1/1     Running   0          38s

=> 테스트 환경에서는 node가 하나이기 때문에 pod 하나만 생성되고 나머지는 labels.app의 값이 redis가 속하지 않은 node를 찾을 수 없기 때문에 생성되지않음

  • podaffinity.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In 
                values:
                - web 
          topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In 
                values:
                - redis 
          topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx

=> podAntiAffinity 설정으로 pod의 labels.app이 web인 pod이 속한 node에는 pod을 생성하지 않겠다
추가로, podAffinity 설정으로 pod의 labels.app이 redis인 pod이 속한 node에 pod을 생성하겠다는 의미
podAntiAffinity, podAffinity의 .topologyKey 필드는 labelSelector 로 걸러진 node 중에서 해당 node가 맞는지 확인하는 기준이 되는 기능으로
topologyKey: “kubernetes.io/hostname” 에서 hostname이 다르면 서로 다른 node => podAntiAffinity 같으면 서로 같은 node => podAffinity 로 동작하게 하는 기준이 되는 기능임

$ kubectl get pod --show-labels
NAME                                READY   STATUS    RESTARTS   AGE    LABELS
redis-deployment-7869db744c-ck472   0/1     Pending   0          36m    app=redis,pod-template-hash=7869db744c
redis-deployment-7869db744c-m7x4l   0/1     Pending   0          36m    app=redis,pod-template-hash=7869db744c
redis-deployment-7869db744c-zrzw7   1/1     Running   0          36m    app=redis,pod-template-hash=7869db744c
web-server-555659bf88-q6s2q         0/1     Pending   0          2m1s   app=web,pod-template-hash=555659bf88
web-server-555659bf88-trtl2         0/1     Pending   0          2m1s   app=web,pod-template-hash=555659bf88
web-server-555659bf88-vdmf8         1/1     Running   0          2m1s   app=web,pod-template-hash=555659bf88

=> 수행 결과는 web도 한개만 뜨게됨