블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 



#26 쿠버네티스 오토 스케일러

조대협 (http://bcho.tistory.com)


쿠버네티스에서는 리소스 부족을 처리하기 위해서, 오토 스케일러를 사용할 수 있다.. 쿠버네티스는 용도에 따라 몇가지 다른 오토스케일러를 제공하는데 각각을 살펴보도록 하자

Vertical Pod Auto-scaler (VPA)

Pod의 Resource(CPU,Memory)  적절 request를 결정하는 다른 방법으로는 Vertical Pod Auto-scaler(VPA)를 사용하는 방법이 있다. 현재 beta 기능인데, 쿠버네티스 1.11 버전 이상에 별도로 추가 설치해야 한다. 

참고 : https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler

VPA는 리소스로 설정해서 Deployment에 적용할 수 있다.


apiVersion: autoscaling.k8s.io/v1beta2

kind: VerticalPodAutoscaler

metadata:

  name: api-server

spec:

  targetRef:

    apiVersion: "extensions/v1beta1"

    kind:       Deployment

    name:       api-server

  updatePolicy:

    updateMode: "Auto"


위의 예제는 api-server라는 Deployment에 api-server 라는 이름의 VPA를 적용한 예제인데, VPA는 Auto 모드와 Manual 모드가 있다. 설정은 updatePolicy.updateMode에서 지정하면 된다. 


<그림 VPA의 동작 원리>

 

Auto 모드의 경에는 VPA는 Pod의 리소스 사용량을 일정 주기 이상 모니터링 한 후에, 그 Metric을 기반으로, Pod resource의 request값을 변경 해주는 방식이다. 

 대신 주의해야할 점은 resource의 request를 변경하는 유일한 방법은 Pod를 리스타트 하는 방법이기 때문에 VPA를 Auto 모드로 적용한 경우 원하지 않은 Pod 리스타트가 발생할 수 있다.


만약에 Manual 모드로 설정하고, Pod를 운영하면, VPA가 직접 request 내용을 변경하지 않고, 적절하게 필요한 request 양을 추천 해준다. VPA를 만들어서 Deployment에 적용한 후 수분 정도 운영하다가 kubetctl get vpa 명령으로 내용을 보면 추천되는 request 양을 알려준다. 

% kubectl get vpa [VPA 이름]  --output yaml

다음은 위의 명령을 실행한 결과 예제이다. 

....

  recommendation:

    containerRecommendations:

    - containerName: my-rec-container

      lowerBound:

        cpu: 25m

        memory: 262144k

      target:

        cpu: 25m

        memory: 262144k

     upperBound:

        cpu: 7931m

        memory: 8291500k


위의 결과에서 출력된 target 값이 추천 resource 양이다. CPU 25m, 메모리 262144k 가 추천되는 값이다.

Horizontal Pod Autoscaler (HPA)

VPA가 Pod의 리소스를 더 할당해서 스케일링을 하는 기능이라면, Horizontal Pod Autoscaler (이하 HPA)는 Pod의 수를 늘려서 스케일링을 하는 기능이다.


HPA의 동작 원리는 다음과 같다. Pod 에서 메트릭을 저장해서 메트릭 서버에 저장한다. CPU나 메모리와 같은 리소스 지표들이 해당한다. 지표들이 지정해놓은 임계점을 지나면 Pod의 수를 늘려서 스케일링을 한다.



<그림. Horizontal Pod Autoscaler의 개념>


HPA는 메트릭을 모니터링하고, 실제로 Pod의 수를 늘리거나 줄이는것은 ReplicaSet이나 Deployment에 의해서 컨트롤 된다. 

적용 방법을 살펴보자, 아래와 같이 YAML 파일을 이용해서 HPA 리소스를 생성할 수 있다. 


apiVersion: autoscaling/v2beta2

kind: HorizontalPodAutoscaler

metadata:

  name: api-server

  namespace: default

spec:

  scaleTargetRef:

    apiVersion: apps/v1

    kind: Deployment

    name: api-server

  minReplicas: 1

  maxReplicas: 10

  metrics:

  - type: Resource

    resource:

      name: cpu

      target:

        type: Utilization

        averageUtilization: 50


HPA 설정에는 HPA 이름, 그리고 적용하고자 하는 Deployment의 이름, min,max Pod수 그리고, 측정 메트릭이 정의된다. 위의 예제는 api-server라는 HPA 이름으로 api-server 라는 deployment에 HPA를 적용하였고, CPU 사용률이 50% 이상이 되면 최대 10개의 Pod까지 스케일 업을 하도록 한 설정이다.


HPA는 아직 VPA와 같이 사용할 수 없다. 개발/테스트 환경에서 VPA를 이용해서 적절 resource 양을 측정한 다음, 운영환경에서는 HPA 정도만 적용하는 것을 권장한다.  

Cloud Auto-scaler

앞서 살펴본 VPA와 HPA가 Pod의 리소스 양을 늘려주거나 Pod의 수를 조정해서 부하를 감당하는 Pod 기반의 오토 스케일러라면 Cloud Autoscaler (이하 CA), Node의 수를 조정하는 오토 스케일러이다. AWS, Azure, Google Cloud 등 클라우드 인프라와 연동해서 동작하도록 되어 있고, 각 클라우드 서비스 마다 설정 방법이나 동작 방식에 다소 차이가 있으니, 자세한 내용은 클라우드 벤더별 문서를 참고하기 바란다.


기본적인 동작 방법은 같은데, Pod를 생성할때, Node들의 리소스가 부족해서 Pod를 생성할 수 없으면, Pod 들은 생성되지 못하고 Pending status로 대기 상황이 된다. Pending status 된 Pod를 CA가 감지 하면, Node를 늘리도록 한다. CA는 Pending status가 있는지를 감지 하기 위해서 (디폴트로) 10초 단위로 Pending status가 있는 Pod가 있는지를 체크한다.


Node Autoscaling 과정에서 주의해야 하는 점은 scale up 보다는  Node 수를 줄이는 scale down 이다. 

CA는 10 초 단위로 scale up 이 필요한지를 체크한 후에, scale up이 필요하지 않으면, scale down 이 가능한지를 체크한다. 

Node의 리소스(CPU,Memory)의 request 총합이 node 물리 리소스의 일정량 (디폴트는 50%)보다 작으면 scale down 을 고려 한다. 이때 리소스의 현재 사용량을 기반으로 하지 않음에 주의하기 바란다. Request를 넘어서 limit  아래까지 리소스가 사용되더라도 CA는 Node에 배포된 Pod들의 request 총합을 기준으로 한다. 

Scale down은 scale down 대상 node의 Pod를 다른 node로 옮기고, scale down node를 삭제하는 방식으로 작동하는데,Scale down 조건이 되면, Node는 그 Node에서 돌고 있는 Pod들을 옮길 수 있는지를 먼저 확인한다. 


  • 컨트롤러 (ReplicationSet,Job 등)에 의해서 관리되지 않는 naked pod는 옮길 수 없다.

  • 로컬 디스크를 사용하는 Pod는 옮길 수 없다. 

  • Affinity,Taint 등의 조건에 의해서 옮길 수 없는 경우 

  • 만약 Pod를 CA에 의해서 이동하고 싶지 않은 경우, Pod를 “cluster-autoscaler.kubernetes.io/safe-to-evict:True” 옵션을 부여하여, CA에 의해서 Pod가 옮겨지는 것을 막을 수 있다.  

  • 그리고 뒤에서 소개하는 Pod Disruption Budget (PDB)에 따라서 Pod를 삭제를 조정한다.


조건에 부합하여 모든 Pod를 옮길 수 있다면, scale down node로 부터 해당 Pod를 삭제하고, 그 Pod 들을 다른 Node에 생성하고, scale down 대상 node를 삭제한다. 


경우에 따라 특정 node를 CA에 의해서 scale down을 하고 싶지 않은 경우가 있을 수 있는데, 그 경우에는 node 에 다음과 같은 레이블을 적용하면 해당 Node는 Scale down에서 제외된다. 

"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

쿠버네티스 고급 스케쥴링 기법

#2 Affinity
조대협 (http://bcho.tistory.com)

Affinity

Taint가 Pod가 배포되지 못하도록 하는 정책이라면, affinity는 Pod를 특정 Node에 배포되도록 하는 정책이다. affinity는 Node를 기준으로 하는 Node affinity와, 다른 Pod가 배포된 위치(node) 를 기준으로 하는 Pod affinity 두 가지가 있다. 

Node affinity 

Node affinity는 Pod가 특정 node로 배포되도록 하는 기능이다. 예전에 label에서 설명했던 node selector 도 node의 label과 pod의 selector label이 매칭되는 node에만 배포하도록 하기 때문에, 사실상 Node affinity와 같은 기능을 한다 

Node affinity는 Hard affinity와 Soft affinity가 있다. Node affinity는 Pod가 조건이 딱 맞는 node 에만 배포되도록 하는 기능이고, Soft affinity는 Pod가 조건에 맞는 node에 되도록(반드시가 아니라)이면 배포되도록 하는 기능이다. 앞에서 언급한 node selector는 Hard affinity에 해당한다. 


아래는 Pod 설정 YAML에서 node affinity를 적용한 예제이다. 


pod-with-node-affinity.yaml docs/concepts/configuration  

apiVersion: v1

kind: Pod

metadata:

  name: with-node-affinity

spec:

  affinity:

    nodeAffinity:

      requiredDuringSchedulingIgnoredDuringExecution:

        nodeSelectorTerms:

        - matchExpressions:

          - key: kubernetes.io/e2e-az-name

            operator: In

            values:

            - e2e-az1

            - e2e-az2

      preferredDuringSchedulingIgnoredDuringExecution:

      - weight: 1

        preference:

          matchExpressions:

          - key: another-node-label-key

            operator: In

            values:

            - another-node-label-value

  containers:

  - name: with-node-affinity

    image: k8s.gcr.io/pause:2.0



출처 : From : https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity


requiredDuringSchedulingIgnoredDuringExecution는 Hard affinity 정의 이다. nodeSelectorTerms 부분에, matchExpressions을 사용하여, label set-based selector 문법을 이용하면 된다.

위의 예제는 node에 label key “kubernetes.io/e2e-az-name” 의 값이 eze-az1 이나 eze-az2 인 node를 선택하도록 하는 설정이다. 


Node affinity는 여러 affinity를 동시에 적용할 수 있는데, 위의 예제에서는 두 개의 Affinity를 정의하였다. 두번째 Affinity는 Soft affinity로, preferredDuringSchedulingIgnoredDuringExecution: 으로 정의한다. Soft affinity는 앞서 언급한것과 같이 조건에 맞는 node로 되도록이면 배포될 수 있도록 그 node로 배포 선호도를 주는 기능이다. 이때 weight 필드를 이용해서 선호도를 조정할 수 있는데, weight은 1~100이고, node 의 soft affinity의 weight 값들을 합쳐서 그 값이 높은 node를 우선으로 고려하도록 우선 순위를 주는데 사용할 수 있다. 


특정 Node로 배포되게 하는 Affinity 설정도 있지만, 반대로 특정 Node로 배포되는 것을 피하도록 하는 AntiAffinity라는 설정도 있다. nodeAffinity 대신 nodeAntiAffinity라는 Notation을 사용하면 되고, Affinity와는 다르게 반대로, 조건에 맞는 Node를 피해서 배포하도록 되낟. 

Inter-Pod affinity

Node affinity가 node의 label을 기준으로 Pod가 배포될 node는 선택한다면, Inter pod affinity는 기존에 배포된 Pod를 기준으로 해서, 배포될 node를 결정한다. 

Pod affinity는 데이타 베이스의 Master / Slave pod 가 다른 node 에 배포되도록 하기 위해서 master pod가 배포된 node를 피해서 배포하게 한다던가. 클러스터 시스템에서 클러스터를 이루는 각각의 Pod가 다른 node에 배포도록 하는 등에 전략에 사용할 수 있다. 


Node affinity와 마찬가지로 Hard affinity와 Soft affinity가 있다. Node affinity아 마찬가지로 requiredDuringSchedulingIgnoredDuringExecution 로 hard affinity를,preferredDuringSchedulingIgnoredDuringExecution로 soft affinity를 정의한다.

Node를 선택할때 Inter pod affinity는 node affinity와 다르게 topology key 라는 것을 사용한다. 

Pod affinity는 Pod affinity에 의해서 해당 node를 선택한 후에, 그 node의 label을 하나 선택한다. 선택하는 label은 topology key로 지정하는데, 이 topology key에 매칭 되는 node 들을 배포 대상으로 선택한다. Node affinity와 마찬가지로 특정 Pod와 같이 배포되는 것을 피하는 AntiAffinity 도 있다. Pod affinity는 podAffinity라는 notation을 사용하고, Anti affinity는 PodAntiAffinity라는 Notation을 사용한다. 


개념이 복잡하기 때문에 아래 예제를 보면서 이해해보도록 하자


apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: cloud/zone
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0


출처  https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity



와 같이 Pod 설정 파일이 있다고 했을때, 배포는 다음 그림과 같다. 



  PodAffinity에서 requiredDuringSchedulingIgnoredDuringExecution 로 Hard affinity를 정의하였다. 이렇게 되면, 기존에 배포된 Pod 중에서, key가 “security”이고, value가 “S1”인, Pod1이 배포된 node인 Node 1을 기준으로 하는데, topologyKey로 정의된 cloud/zone label의 값”z1”을 기준 Node인 Node 1에서 읽어서, Node 들 중에 label이 “cloud/zone=z1” 인 Node 들만을 후보로 선택해서, Node 1, Node 2 를 배포 가능 Node 로 선택한다.


Inter-Pod affinity 예제

다른 예제를 보자. 아래 예제는 redis-cache Pod를 3개 배포하는데, 각각 다른 Node에 분산되서 배포되도록 하는 예제이다. 


apiVersion: apps/v1

kind: Deployment

metadata:

  name: redis-cache

spec:

  selector:

    matchLabels:

      app: store

  replicas: 3

  template:

    metadata:

      labels:

        app: store

    spec:

      affinity:

        podAntiAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - store

            topologyKey: "kubernetes.io/hostname"

      containers:

      - name: redis-server

        image: redis:3.2-alpine


출처 : https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity


PodAntiAffinity를 사용한 예로, requiredDuringSchedulingIgnoredDuringExecution 를 이용한 Hard affinity이다. Pod의 label이 “app=store”가 있는 Pod가 배포되어 있는 Node 중에서 topologyKey가 kubernetes.io/hostname (쿠버네티스에서 자동으로 미리 저장하는 label로 Node의 이름을 정의하는 label 이다.) 로 되어 있기 때문에, node의 kubernetes.io/hostname label 값이 이 Node와 다른 Node를 배포 타겟으로 설정한다. 즉 “app=store” 이름으로 배포된 Pod가 없는 Node에 배포하는 설정이다.

이 Redis pod는 “app=store” 라는 label을 가지고 있는 Pod이기 때문에, 이미 Node에 이 Redis Pod가 배포되어 있으면, 그 Node에는 배포되지 않기 때문에 Redis Pod가 배포되지 않은 다른 Node에 중첩되서 배포되지 않도록 해준다. 


이 상태에서 nginx 서버를 배포해보자. Nginx pod를 3개 배포하는데, 각각을  서로 다른 node에 배포하도록 하되, 대신 redis가 배포된 node에 배포하도록 하는 설정이다.  


apiVersion: apps/v1

kind: Deployment

metadata:

  name: web-server

spec:

  selector:

    matchLabels:

      app: web-store

  replicas: 3

  template:

    metadata:

      labels:

        app: web-store

    spec:

      affinity:

        podAntiAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - web-store

            topologyKey: "kubernetes.io/hostname"

        podAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - store

            topologyKey: "kubernetes.io/hostname"

      containers:

      - name: web-app

        image: nginx:1.12-alpine


출처  https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity


먼저 podAntiAffinity로 “app:web-store” Pod가 배포되어 있는 node를 찾은 후에, 그 Node이 kubernetes.io/hostname 를 topologyKey로 해서, 그 node들을 제외한다.

즉 “app:web-store” 라벨을 가지고 있는 Pod 들이 배포된 Node를 제외하는 설정이다. 그런데, 이 Pod는 label을 “app:web-store” 라벨을 가지도록 되어 있기 때문에, 이 Pod가 배포되어 있는 Node에 배포하지 말고, 다른 Node에 분산해서 배포하라는 내용이다.


여기에 PodAffinity로 “app:store”인 Pod가 배포되어 있는 Node에서 topologyKey로 “kubernetes.io/hostname”을 사용하였기 때문에, “app:store”인 라벨을 가지고 있는 Pod가 배포되어 있는 Node를 찾아서 배포하라는 내용이다. 다시 말해서 “app:store”인 Pod와 같은 Node에 배포하라는 의미이다. 


아래는 실행결과 인데, web-server가 각각 다른 Node에 겹치지 않고 분리되서 부탁되어 있는 것을 볼 수 있고, redis-cache Pod도 역시 서로 다른 Node에 겹치지 않게 배포되어 있는 것을 확인할 수 있다. 그리고 web-server와 redis-cache pod들은 하나씩 같은 Node에 배포된것을 확인할 수 있다. 


NAME                           READY STATUS RESTARTS AGE       IP NODE

redis-cache-1450370735-6dzlj   1/1 Running 0 8m        10.192.4.2 kube-node-3

redis-cache-1450370735-j2j96   1/1 Running 0 8m        10.192.2.2 kube-node-1

redis-cache-1450370735-z73mh   1/1 Running 0 8m        10.192.3.1 kube-node-2

web-server-1287567482-5d4dz    1/1 Running 0 7m        10.192.2.3 kube-node-1

web-server-1287567482-6f7v5    1/1 Running 0 7m        10.192.4.3 kube-node-3

web-server-1287567482-s330j    1/1 Running 0 7m        10.192.3.2 kube-node-2

출처  https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

쿠버네티스 고급 스케쥴링 기법

#1 스케쥴링과 Taint&Toleration

조대협 (http://bcho.tistory.com)

쿠버네티스 스케쥴링

쿠버네티스에서 Pod 를 생성 요청 했을때, Pod를 적정 node에 배치하는 것을 스케쥴링이라고 한다. Pod를 어느 node에 배치할것인가에 대해서는 생각 보다 많은 고려가 필요하다. 먼저 Pod가 생성하기 위한 충분한 리소스 (CPU와 메모리)가 남아 있어야 하고, 디스크 볼륨을 사용할 경우, attach하고자 하는 디스크 볼륨이 해당 node에서 접근이 가능해야 한다.

또한 애플리케이션 특성에 따라서, Pod의 배포에 대해 배려가 필요한 경우도 있다. 예를 들어 MySQL을 HA 모드로 배포하기 위해서 마스터, 슬레이브 노드 각각을 배포하고자 할때, 마스터 슬레이브가 같은 node에 배포되게 되면, 해당 node가 문제가 생기면 마스터,슬레이브 노드 모두가 서비스가 불가능해지기 때문에, HA에 의한 가용성을 지원할 수 없다. 그래서 마스터 슬레이브 노드를 다른 node에 배포해야 하고, 더 나아가 다른 rack, 클라우드의 경우에는 다른 Zone(존)에 배포될 필요가 있다. 

이 모든 것을 제어 하는 것을 스케쥴링이라고 한다. 

이 장에서는 쿠버네티스의 스케쥴링이 어떻게 작동을 하는지 그리고, 이 스케쥴링을 제어할 수 있는 고급 기법에 대해서 알아보고자 한다. 

스케쥴링 작동의 기본 원리

(참고 : 쿠버네티스의 스케쥴링 정책은 이 에 매우 잘 설명되어 있다. )

Pod 생성이 요청 되면, 쿠버네티스 스케쥴러는 먼저 필터라는 것을 이용해서, Pod가 배포될 수 있는 Node를 선정하는 작업을 한다.

크게 보면 세 종류의 필터를 사용하는데, 다음과 같다.

  • 볼륨 필터

  • 리소스 필터

  • 토폴로지 필터

각각을 살펴보자

볼륨필터

Pod를 생성할 때, 생성하고자 하는 Pod의 디스크 볼륨에 대해서 Node가 지원할 수 있는지를 확인한다. 

예를 들어 클라우드에서 생성되는 Pod가 zone 1에 생성된 디스크를 attach해야 하는 조건을 가지고 있을때, 특정 클라우드들의 경우 다른 zone의 디스크를 attach할 수 없기 때문에, zone 1 이외에 있는 Node들을 후보에서 제외하고, 해당 볼륨을 attach할 수 있는 Node 들만 후보로 남긴다.

또는 쿠버네티스에서는 사용자가 볼륨에 node-affinity를 정의해서 특정 node 에만 그 볼륨을 attach할 수 있도록 하는데, 이러한 조건에 부합하지 않는 Node들을 제거하고 후보 Node 리스트를 만든다.

리소스 필터

다음으로 적용되는 필터가 리소스 필터인데, 해당 Node 들이 Pod를 배포할만한 충분한 리소스 (CPU,Memory,Disk)가 있는지를 확인하는 단계이다.

CPU 와 메모리 여유분이 Pod가 요청한 만큼 충분한지, 그리고 Node의 디스크 공간도 확인하는데, 앞에서 언급한 디스크 볼륨과 다소 차이가 있는 것이, Node가 Pod를 실행하기 위해서는 Pod를 실행하기 위한 디스크 공간이 필요하다. Pod 의 컨테이너 이미지를 저장하기 위한 공간등이 이에 해당한다. 

CPU,Memory,Disk 뿐 아니라 네트워크 포트도 체크를 하는데, Pod가 Node 포트를 사용하는 Pod 일 경우, 예를 들어 Pod가  Node의 8080 포트를 사용하고자 하는데, 이미 해당 Node의 8080 포트가 다른 Pod에 의해서 점유된 경우, 새로운 Pod를 생성할 수 없기 때문에 그 Node를 Pod를 생성하기 위한 Pod 리스트에서 제외한다. 


일반적인 경우에는 볼륨 필터와 리소스 필터를 거친 Node들을 후보로 두고 이 중에서 적절한 Node를 선택해서 Pod를 배포한다. 

고급 스케쥴링 정책

Pod를 배포할때, 사용자가 특정 Node를 선택할 수 있도록 정책을 정의할 수 있다. 예를 들어 앞에서 언급한것과 같이 MySQL의 마스터, 슬레이브가 같은 Node에 배포되지 않도록 Pod의 스케쥴링 정책을 인위적으로 조정할 수 있다. 이를 고급 스케쥴링 기법이라고 하는데, 자세한 설명은 이 문서를 참고하기 바란다. 

Taint & Toleration

먼저 살펴볼 스케쥴링 정책은 Taint와 Toleration이다.

Taint는 Node에 정의할 수 있고, Toleration은 Pod에 정의할 수 있는데, 한마디로 쉽게 설명하면, Taint 처리가 되어 있는 Node에는 Pod가 배포되지 않는다. Taint 처리가 되어 있는 Node에는 Taint에 맞는 Toleration을 가지고 있는 Pod 만 배포될 수 있다.

Taint

Taint는 label과 유사하게 <key>=<value>:<effect> 형태로 정의되서 node에 적용된다. key와 value는 사용자가 마음대로 정할 수 있으며, effect는 NoSchedule, PreferNoSchedule,NoExecute 3가지로 정의할 수 있다. NoSchedule은 taint 처리가 되어 있는 node에 대해서는 Pod가 이에 맞는 toleration을 가지고 있다면 이 Node에는 그 Pod를 배포하지 못하도록 막는 effect 이다. (나머지 2가지 effect에 대해서는 뒤에서 설명한다.)

Node에 taint 를 적용하는 방법은 다음과 같다.

%kubectl taint node [NODE_NAME] [KEY]=[VALUE]:[EFFECT]

형태로 적용하면 된다.

예를 들어 gke-terry-gke11-default-pool-317bb64b-21kd Node에 key가 “node-type”이고, value가 “production”이고, Effect가 NoSchedule인 Taint를 적용하고자 하면 다음과 같이 명령을 실행하면 된다. 

 

%kubectl taint node gke-terry-gke11-default-pool-317bb64b-21kd node-type=production:NoSchedule

node/gke-terry-gke11-default-pool-317bb64b-21kd tainted


이렇게 taint를 적용한 후, Taint가 제대로 적용이 되었는지, kubectl get nodes gke-terry-gke11-default-pool-317bb64b-21kd -o yaml 명령을 이용해서 확인해보면 다음과 같이 taint가 적용되어 있는 것을 확인할 수 있다. 


apiVersion: v1

kind: Node

metadata:

: (중략)

   name: gke-terry-gke11-default-pool-317bb64b-21kd

: (중략)

spec:

: (중략)

  taints:

  - effect: NoSchedule

    key: node-type

    value: production


이렇게 Taint 처리가 된 Node는 알맞은 Toleration이 정의되지 않은 Pod는 배포될 수 없다. 


Node에 Taint를 적용하는 방법은 앞에서 설명한 것과 같이 node 이름을 정의해서 하나의 특정 Node에 적용하는 방법도 있지만, node 에 적용된 label을 이용하여, label이 일치 하는 여러개의 node에 동시에도 적용할 수 있다. 

방법은 아래와 같이 -l 옵션을 이용해서 적용하고자 하는 node의 label의 key/value를 적용하면 된다. 

%kubectl taint node -l [LABEL_KEY]=[LABEL_VALUE] [KEY]=[VALUE]:[EFFECT]


예를 들어서 아래와 같이 -l  옵션을 적용하면,

%kubectl taint node -l node-label=zone1 node-type=production:NoSchedule

Node 중에서 label이 node-label=zone1인 모든 node에, node-type=production:NoSchedule 인 Taint가 적용된다. 

Toleration

그러면 Taint 처리가되어 있는 Node에 Pod를 배포하기 위해서 사용하는 Toleration이란 무엇인가?

Toleration처리가 되어 있는 Node에 배포될 수 있는 일종의 티켓과 같은 개념이라고 생각하면 된다. Taint 처리가 되어 있는 Node에 Toleration이라는 티켓을 가지고 있으면, 그 Node에 Pod가 배포될 수 있다. (“배포된다"가 아니라, “배포될 수 있다" 라는 의미에 주의하도록 하자. 그 Node가 아니라 다른 조건에 맞는 Node가 있다면, 배포될 수 있다.)

Toleration의 정의는 Match operator를 사용하여 Pod Spec에 정의한다.

tolerations:

- key: "key"

  operator: "Equal"

  value: "value

  effect: "NoSchedule"


이렇게 정의하면, key,value,effect 3개가 Taint와 일치하는 Node에 Pod가 배포될 수 있다. 

조금 더 광범위하게 정의를 하려면, “Exist”를 사용하면 된다. 

tolerations:

- key: "key"

  operator: "Exists

  effect: "NoSchedule"


이렇게 정의하면, Taint에 위에서 정의한 Key가 있고, effect가 “NoSchedule”로 설정된 Node에 value 값에 상관 없이 배포될 수 있다.

또는 아래와 같이 tolerations 절에서 effect 항목을 제외하면, 해당 key로 Taint가 적용되어 있는 모든 Node에 대해서 이  Pod를 배포하는 것이 가능하다. 

tolerations:

- key: "key"

  operator: "Exists


Taints는 특정 nodes에 일반적인 Pod가 배폭되는 것을 막을 수 있다. 가장 좋은 예로는 쿠버네티스의 마스터 Node에 적용된 Taints가 이에 해당한다. 쿠버네티스 마스터 Node에는 관리를 위한 Pod만이 배포되어야 하기 때문에, 일반적인 Pod를 배포할 수 없도록 Taints가 이미 적용되어 있고, 마스터 Node에 Pod를 배포하기 위해서는 이에 맞는 Toleration을 가지고 있어야 한다. 

이 외에도 운영용 Node로 특정 Node들을 적용해놓고, 개발이나 스테이징 환경용 Pod이 (실수로라도) 배포되지 못하게 한다는 것등에 사용할 수 있다. 

Taint와 Toleration 개념 정리

앞에서 Taint와 Toleration의 개념과 사용법에 대해서 설명하였는데, 이를 이해하기 편하게 그림으로 정리해서 보자




<그림. Taints와 Toleration의 개념>

출처 : https://livebook.manning.com/#!/book/kubernetes-in-action/chapter-16/section-16-3-1


Master node에는 node-role.kubernetes.io/master 라는 key로 value 없이 effect만 “NoSchedule”로 Taint를 정의하였다. toleration을 가지지 못한 일반적인 Pod는 Master node에는 배포될 수 없고, Taint 처리가 되어 있지 않은 regular node에만 배포가 가능한다.

System pod의 경우 node-role.kubernetes.io/master 라는 key로, effect가 “NoSchedule”인 toleration을 가지고 있기 때문에, Taint가 없는 Regular node에는 당연히 배포가 가능하고, Toleration에 맞는 Taint를 가지고 있는 Master node에 배포될 수 있다. 

Taint Effect 

Taint와 Toleration에 대한 사용법과 개념을 이해하였으면, 이제 Taint effect에 대해서 조금 더 자세하게 알아보도록 하자. 앞에서도 설명했듯이 Taint에 적용할 수 있는 effect는 아래와 같이 3가지가 있다. 

  • NoSchedule : Pod가 배포되지 못한다. (Toleration이 일치하면 배포됨)

이 effect로 Taint가 적용된 Node는 일치하는 Toleration을 가지고 있는 Pod가 아닌 경우에는 배포되지 못한다. 단, 이는 새롭게 배포되는 Pod에만 적용되고 이미 배포되어 있는 Pod에는 적용되지 않는다. 다시 말해서, Node 1에 Pod 1이 돌고 있는데, 이 Node 1에 Taint를 적용하면, Taint 적용전에 배포되서 돌고 있는 Pod 1에는 영향을 주지 않는다. Pod 1는 알맞은 Toleration이 없더라도, 종료되서 새롭게 스케쥴링이 되지 않는 이상 Node 1에 배포된 상태로 동작한다. 


만약에 이미 돌고 있는 Pod들에게도 영향을 주려면 NoExecute 라는 effect 를 사용하면 된다. 

  • NoExecute :  돌고 있던 Pod들을 evit 하고(다른 node로 옮김), 새것들은 못들어 오게 한다.

이 effect는 NoSchedule과 유사하지만, 새롭게 배포되는 Pod 뿐만 아니라, 이미 그 Node 에서 돌고 있는 Pod 들에게도 영향을 줘서, NoExecute로 Taint가 적용되면, 이에 해당하는 Toleration을 가지고 있지 않는 Pod는 모두 evict 되서 그 node에서 삭제 된다. 물론 ReplicaSet/Deployment 등 Controller에 의해서 관리되는 Pod의 경우에는 Taint 처리가 되어 있지 않은 다른 Node에서 새롭게 생성된다. 


이 effect에 대해서는 tolerationSeconds 라는 패러미터를 고려해야 하는데, 이 Taint가 적용된 Node에 맞는 toleration 을 가지고 있는 Pod의 경우, 이 Node에 영구적으로 남아 있지만, Pod의 toleration에 tolerationSeconds 패러미터가 정의되어 있으면 이 시간만큼만 남아 있다가 evit 된다. 즉 Pod 1, Pod 2,Pod 3가 Node 1에서 돌다가,  Node 1 에 NoExecute effect로 Taint가 적용되었다고 했을때, Pod 1은 이 Taint에 맞는 Toleration을 가지고 있고

Pod 2는 이 Taint에 맞는 Toleration을 tolerationSeconds=300(초) 패러미터와 함께 정의되어 가지고 있다면

Pod 3는 아마 Toleration이 없다면 

Pod 1은 계속 Node1에 남아 있게 되고,  Pod 2는 Node 2에 300초 동안 남아있다가 evit (강제 종료)되며, Pod 3는 바로 강제 종료가 된다. 


  • PreferNoSchedule : 가급적 Pod 배포하지 않는다. 


마지막으로 소개할 effect는 PerferNoSchedule인데, NoSchedule의 소프트 버전으로 생각히면 된다. NoSchedule로  Taint 처리가 되어 있는 Node 라면, 스케쥴시에, Toleration을 가지고 있지 않은 Pod는 무조건 배포가 불가능하지만, PreferNoSchedule의 경우에는 Toleration이 없는 Pod의 경우에는 되도록이면 배포되지 않지만 리소스가 부족한 상황등에는 우선순위를 낮춰서, Toleration이 없는 Pod도 배포될 수 있도록 한다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요