This the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

스케줄링과 축출(eviction)

쿠버네티스에서, 스케줄링은 kubelet이 파드를 실행할 수 있도록 파드가 노드와 일치하는지 확인하는 것을 말한다. 축출은 리소스가 부족한 노드에서 하나 이상의 파드를 사전에 장애로 처리하는 프로세스이다.

1 - 쿠버네티스 스케줄러

쿠버네티스에서 스케줄링Kubelet이 파드를 실행할 수 있도록 파드노드에 적합한지 확인하는 것을 말한다.

스케줄링 개요

스케줄러는 노드가 할당되지 않은 새로 생성된 파드를 감시한다. 스케줄러가 발견한 모든 파드에 대해 스케줄러는 해당 파드가 실행될 최상의 노드를 찾는 책임을 진다. 스케줄러는 아래 설명된 스케줄링 원칙을 고려하여 이 배치 결정을 하게 된다.

파드가 특정 노드에 배치되는 이유를 이해하려고 하거나 사용자 정의된 스케줄러를 직접 구현하려는 경우 이 페이지를 통해서 스케줄링에 대해 배울 수 있을 것이다.

kube-scheduler

kube-scheduler는 쿠버네티스의 기본 스케줄러이며 컨트롤 플레인의 일부로 실행된다. kube-scheduler는 원하거나 필요에 따라 자체 스케줄링 컴포넌트를 만들고 대신 사용할 수 있도록 설계되었다.

새로 생성된 모든 파드 또는 예약되지 않은 다른 파드에 대해 kube-scheduler는 실행할 최적의 노드를 선택한다. 그러나 파드의 모든 컨테이너에는 리소스에 대한 요구사항이 다르며 모든 파드에도 요구사항이 다르다. 따라서 기존 노드들은 특정 스케줄링 요구사항에 따라 필터링 되어야 한다.

클러스터에서 파드에 대한 스케줄링 요구사항을 충족하는 노드를 실행 가능한(feasible) 노드라고 한다. 적합한 노드가 없으면 스케줄러가 배치할 수 있을 때까지 파드가 스케줄 되지 않은 상태로 유지된다.

스케줄러는 파드가 실행 가능한 노드를 찾은 다음 실행 가능한 노드의 점수를 측정하는 기능 셋을 수행하고 실행 가능한 노드 중에서 가장 높은 점수를 가진 노드를 선택하여 파드를 실행한다. 그런 다음 스케줄러는 바인딩 이라는 프로세스에서 이 결정에 대해 API 서버에 알린다.

스케줄링 결정을 위해 고려해야 할 요소에는 개별 및 집단 리소스 요구사항, 하드웨어 / 소프트웨어 / 정책 제한조건, 어피니티 및 안티-어피니티 명세, 데이터 지역성(data locality), 워크로드 간 간섭 등이 포함된다.

kube-scheduler에서 노드 선택

kube-scheduler는 2단계 작업에서 파드에 대한 노드를 선택한다.

  1. 필터링
  2. 스코어링(scoring)

필터링 단계는 파드를 스케줄링 할 수 있는 노드 셋을 찾는다. 예를 들어 PodFitsResources 필터는 후보 노드가 파드의 특정 리소스 요청을 충족시키기에 충분한 가용 리소스가 있는지 확인한다. 이 단계 다음에 노드 목록에는 적합한 노드들이 포함된다. 하나 이상의 노드가 포함된 경우가 종종 있을 것이다. 목록이 비어 있으면 해당 파드는 (아직) 스케줄링 될 수 없다.

스코어링 단계에서 스케줄러는 목록에 남아있는 노드의 순위를 지정하여 가장 적합한 파드 배치를 선택한다. 스케줄러는 사용 중인 스코어링 규칙에 따라 이 점수를 기준으로 필터링에서 통과된 각 노드에 대해 점수를 지정한다.

마지막으로 kube-scheduler는 파드를 순위가 가장 높은 노드에 할당한다. 점수가 같은 노드가 두 개 이상인 경우 kube-scheduler는 이들 중 하나를 임의로 선택한다.

스케줄러의 필터링 및 스코어링 동작을 구성하는 데 지원되는 두 가지 방법이 있다.

  1. 스케줄링 정책을 사용하면 필터링을 위한 단정(Predicates) 및 스코어링을 위한 우선순위(Priorities) 를 구성할 수 있다.
  2. 스케줄링 프로파일을 사용하면 QueueSort, Filter, Score, Bind, Reserve, Permit 등의 다른 스케줄링 단계를 구현하는 플러그인을 구성할 수 있다. 다른 프로파일을 실행하도록 kube-scheduler를 구성할 수도 있다.

다음 내용

2 - 노드에 파드 할당하기

특정한 노드(들) 집합에서만 동작하도록 파드를 제한할 수 있다. 이를 수행하는 방법에는 여러 가지가 있으며 권장되는 접근 방식은 모두 레이블 셀렉터를 사용하여 선택을 용이하게 한다. 보통 스케줄러가 자동으로 합리적인 배치(예: 자원이 부족한 노드에 파드를 배치하지 않도록 노드 간에 파드를 분배하는 등)를 수행하기에 이러한 제약 조건은 필요하지 않지만 간혹 파드가 배포할 노드를 제어해야 하는 경우가 있다. 예를 들어 SSD가 장착된 머신에 파드가 연결되도록 하거나 또는 동일한 가용성 영역(availability zone)에서 많은 것을 통신하는 두 개의 서로 다른 서비스의 파드를 같이 배치할 수 있다.

노드 셀렉터(nodeSelector)

nodeSelector 는 가장 간단하고 권장되는 노드 선택 제약 조건의 형태이다. nodeSelector 는 PodSpec의 필드이다. 이는 키-값 쌍의 매핑으로 지정한다. 파드가 노드에서 동작할 수 있으려면, 노드는 키-값의 쌍으로 표시되는 레이블을 각자 가지고 있어야 한다(이는 추가 레이블을 가지고 있을 수 있다). 일반적으로 하나의 키-값 쌍이 사용된다.

nodeSelector 를 어떻게 사용하는지 예시를 통해 알아보도록 하자.

0 단계: 사전 준비

이 예시는 쿠버네티스 파드에 대한 기본적인 이해를 하고 있고 쿠버네티스 클러스터가 설정되어 있다고 가정한다.

1 단계: 노드에 레이블 붙이기

kubectl get nodes 를 실행해서 클러스터 노드 이름을 가져온다. 이 중에 레이블을 추가하기 원하는 것 하나를 선택한 다음에 kubectl label nodes <노드 이름> <레이블 키>=<레이블 값> 을 실행해서 선택한 노드에 레이블을 추가한다. 예를 들어 노드의 이름이 'kubernetes-foo-node-1.c.a-robinson.internal' 이고, 원하는 레이블이 'disktype=ssd' 라면, kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssd 를 실행한다.

kubectl get nodes --show-labels 를 다시 실행해서 노드가 현재 가진 레이블을 확인하여, 이 작업을 검증할 수 있다. 또한 kubectl describe node "노드 이름" 을 사용해서 노드에 주어진 레이블의 전체 목록을 확인할 수 있다.

2 단계: 파드 설정에 nodeSelector 필드 추가하기

실행하고자 하는 파드의 설정 파일을 가져오고, 이처럼 nodeSelector 섹션을 추가한다. 예를 들어 이것이 파드 설정이라면,

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx

이 다음에 nodeSelector 를 다음과 같이 추가한다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

그런 다음에 kubectl apply -f https://k8s.io/examples/pods/pod-nginx.yaml 을 실행하면, 레이블이 붙여진 노드에 파드가 스케줄된다. kubectl get pods -o wide 를 실행해서 파드가 할당된 "NODE" 를 보면 작동하는지 검증할 수 있다.

넘어가기 전에: 내장 노드 레이블들

붙인 레이블뿐만 아니라, 노드에는 표준 레이블 셋이 미리 채워져 있다. 이들 목록은 잘 알려진 레이블, 어노테이션 및 테인트를 참고한다.

참고: 이 레이블들의 값은 클라우드 공급자에 따라 다르고 신뢰성이 보장되지 않는다. 예를 들어 kubernetes.io/hostname 은 어떤 환경에서는 노드 이름과 같지만, 다른 환경에서는 다른 값일 수 있다.

노드 격리(isolation)/제한(restriction)

노드 오브젝트에 레이블을 추가하면 파드가 특정 노드 또는 노드 그룹을 목표 대상으로 할 수 있게 된다. 이는 특정 파드가 어떤 격리, 보안, 또는 규제 속성이 있는 노드에서만 실행되도록 사용할 수 있다. 이 목적으로 레이블을 사용하는 경우, 노드에서 kubelet 프로세스로 수정할 수 없는 레이블 키를 선택하는 것을 권장한다. 이렇게 하면 손상된 노드가 해당 kubelet 자격 증명을 사용해서 해당 레이블을 자체 노드 오브젝트에 설정하고, 스케줄러가 손상된 노드로 워크로드를 스케줄 하는 것을 방지할 수 있다.

NodeRestriction 어드미션 플러그인은 kubelet이 node-restriction.kubernetes.io/ 접두사로 레이블을 설정 또는 수정하지 못하게 한다. 노드 격리에 해당 레이블 접두사를 사용하려면 다음과 같이 한다.

  1. 노드 권한부여자를 사용하고 있고, NodeRestriction 어드미션 플러그인활성화 해야 한다.
  2. 노드 오브젝트의 node-restriction.kubernetes.io/ 접두사 아래에 레이블을 추가하고, 해당 레이블을 노드 셀렉터에서 사용한다. 예를 들어, example.com.node-restriction.kubernetes.io/fips=true 또는 example.com.node-restriction.kubernetes.io/pci-dss=true 이다.

어피니티(affinity)와 안티-어피니티(anti-affinity)

nodeSelector 는 파드를 특정 레이블이 있는 노드로 제한하는 매우 간단한 방법을 제공한다. 어피니티/안티-어피니티 기능은 표현할 수 있는 제약 종류를 크게 확장한다. 주요 개선 사항은 다음과 같다.

  1. 어피니티/안티-어피니티 언어가 더 표현적이다. 언어는 논리 연산자인 AND 연산으로 작성된 정확한 매칭 항목 이외에 더 많은 매칭 규칙을 제공한다.
  2. 규칙이 엄격한 요구 사항이 아니라 "유연한(soft)"/"선호(preference)" 규칙을 나타낼 수 있기에 스케줄러가 규칙을 만족할 수 없더라도, 파드가 계속 스케줄되도록 한다.
  3. 노드 자체에 레이블을 붙이기보다는 노드(또는 다른 토폴로지 도메인)에서 실행 중인 다른 파드의 레이블을 제한할 수 있다. 이를 통해 어떤 파드가 함께 위치할 수 있는지와 없는지에 대한 규칙을 적용할 수 있다.

어피니티 기능은 "노드 어피니티" 와 "파드 간 어피니티/안티-어피니티" 두 종류의 어피니티로 구성된다. 노드 어피니티는 기존 nodeSelector 와 비슷하지만(그러나 위에서 나열된 첫째와 두 번째 이점이 있다.), 파드 간 어피니티/안티-어피니티는 위에서 나열된 세번째 항목에 설명된 대로 노드 레이블이 아닌 파드 레이블에 대해 제한되고 위에서 나열된 첫 번째와 두 번째 속성을 가진다.

노드 어피니티

노드 어피니티는 개념적으로 nodeSelector 와 비슷하다 -- 이는 노드의 레이블을 기반으로 파드를 스케줄할 수 있는 노드를 제한할 수 있다.

여기에 현재 requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution 로 부르는 두 가지 종류의 노드 어피니티가 있다. 전자는 파드가 노드에 스케줄되도록 반드시 규칙을 만족해야 하는 것(nodeSelector 와 비슷하나 보다 표현적인 구문을 사용해서)을 지정하고, 후자는 스케줄러가 시도하려고는 하지만, 보증하지 않는 선호(preferences) 를 지정한다는 점에서 이를 각각 "엄격함(hard)" 과 "유연함(soft)" 으로 생각할 수 있다. 이름의 "IgnoredDuringExecution" 부분은 nodeSelector 작동 방식과 유사하게 노드의 레이블이 런타임 중에 변경되어 파드의 어피니티 규칙이 더 이상 충족되지 않으면 파드가 그 노드에서 동작한다는 의미이다. 향후에는 파드의 노드 어피니티 요구 사항을 충족하지 않는 노드에서 파드를 제거한다는 점을 제외하고는 preferredDuringSchedulingIgnoredDuringExecution 와 동일한 requiredDuringSchedulingIgnoredDuringExecution 를 제공할 계획이다.

따라서 requiredDuringSchedulingIgnoredDuringExecution 의 예로는 "인텔 CPU가 있는 노드에서만 파드 실행"이 될 수 있고, preferredDuringSchedulingIgnoredDuringExecution 의 예로는 "장애 조치 영역 XYZ에 파드 집합을 실행하려고 하지만, 불가능하다면 다른 곳에서 일부를 실행하도록 허용"이 있을 것이다.

노드 어피니티는 PodSpec의 affinity 필드의 nodeAffinity 필드에서 지정된다.

여기에 노드 어피니티를 사용하는 파드 예시가 있다.

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

이 노드 어피니티 규칙은 키가 kubernetes.io/e2e-az-name 이고 값이 e2e-az1 또는 e2e-az2 인 레이블이 있는 노드에만 파드를 배치할 수 있다고 말한다. 또한, 이 기준을 충족하는 노드들 중에서 키가 another-node-label-key 이고 값이 another-node-label-value 인 레이블이 있는 노드를 선호하도록 한다.

예시에서 연산자 In 이 사용되고 있는 것을 볼 수 있다. 새로운 노드 어피니티 구문은 다음의 연산자들을 지원한다. In, NotIn, Exists, DoesNotExist, Gt, Lt. NotInDoesNotExist 를 사용해서 안티-어피니티를 수행하거나, 특정 노드에서 파드를 쫓아내는 노드 테인트(taint)를 설정할 수 있다.

nodeSelectornodeAffinity 를 모두 지정한다면 파드가 후보 노드에 스케줄되기 위해서는 둘 다 반드시 만족해야 한다.

nodeAffinity 유형과 연관된 nodeSelectorTerms 를 지정하면, nodeSelectorTerms하나라도 만족시키는 노드에 파드가 스케줄된다.

nodeSelectorTerms 와 연관된 여러 matchExpressions 를 지정하면, 파드는 matchExpressions모두 만족하는 노드에만 스케줄된다.

파드가 스케줄된 노드의 레이블을 지우거나 변경해도 파드는 제거되지 않는다. 다시 말해서 어피니티 선택은 파드를 스케줄링 하는 시점에만 작동한다.

preferredDuringSchedulingIgnoredDuringExecutionweight 필드의 범위는 1-100이다. 모든 스케줄링 요구 사항 (리소스 요청, RequiredDuringScheduling 어피니티 표현식 등)을 만족하는 각 노드들에 대해 스케줄러는 이 필드의 요소들을 반복해서 합계를 계산하고 노드가 MatchExpressions 에 일치하는 경우 합계에 "가중치(weight)"를 추가한다. 이후에 이 점수는 노드에 대한 다른 우선순위 함수의 점수와 합쳐진다. 전체 점수가 가장 높은 노드를 가장 선호한다.

스케줄링 프로파일당 노드 어피니티

FEATURE STATE: Kubernetes v1.20 [beta]

여러 스케줄링 프로파일을 구성할 때 노드 어피니티가 있는 프로파일을 연결할 수 있는데, 이는 프로파일이 특정 노드 집합에만 적용되는 경우 유용하다. 이렇게 하려면 스케줄러 구성에 있는 NodeAffinity 플러그인의 인수에 addedAffinity를 추가한다. 예를 들면

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration

profiles:
  - schedulerName: default-scheduler
  - schedulerName: foo-scheduler
    pluginConfig:
      - name: NodeAffinity
        args:
          addedAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: scheduler-profile
                  operator: In
                  values:
                  - foo

addedAffinity.spec.schedulerNamefoo-scheduler로 설정하는 모든 파드에 적용되며 PodSpec에 지정된 NodeAffinity도 적용된다. 즉, 파드를 매칭시키려면, 노드가 addedAffinity와 파드의 .spec.NodeAffinity를 충족해야 한다.

addedAffinity는 엔드 유저에게 표시되지 않으므로, 예상치 못한 동작이 일어날 수 있다. 프로파일의 스케줄러 이름과 명확한 상관 관계가 있는 노드 레이블을 사용하는 것이 좋다.

참고: 데몬셋용 파드를 생성하는 데몬셋 컨트롤러는 스케줄링 프로파일을 인식하지 못한다. 따라서 addedAffinity없이 default-scheduler와 같은 스케줄러 프로파일을 유지하는 것이 좋다. 그런 다음 데몬셋의 파드 템플릿이 스케줄러 이름을 사용해야 한다. 그렇지 않으면, 데몬셋 컨트롤러에 의해 생성된 일부 파드가 스케줄되지 않은 상태로 유지될 수 있다.

파드간 어피니티와 안티-어피니티

파드간 어피니티와 안티-어피니티를 사용하면 노드의 레이블을 기반으로 하지 않고, 노드에서 이미 실행 중인 파드 레이블을 기반으로 파드가 스케줄될 수 있는 노드를 제한할 수 있다. 규칙은 "X가 규칙 Y를 충족하는 하나 이상의 파드를 이미 실행중인 경우 이 파드는 X에서 실행해야 한다(또는 안티-어피니티가 없는 경우에는 동작하면 안된다)"는 형태이다. Y는 선택적으로 연관된 네임스페이스 목록을 가진 LabelSelector로 표현된다. 노드와는 다르게 파드는 네임스페이스이기에 (그리고 따라서 파드의 레이블은 암암리에 네임스페이스이다) 파드 레이블위의 레이블 셀렉터는 반드시 셀렉터가 적용될 네임스페이스를 지정해야만 한다. 개념적으로 X는 노드, 랙, 클라우드 공급자 영역, 클라우드 공급자 지역 등과 같은 토폴로지 도메인이다. 시스템이 이런 토폴로지 도메인을 나타내는 데 사용하는 노드 레이블 키인 topologyKey 를 사용하여 이를 표현한다. 예: 넘어가기 전에: 빌트인 노드 레이블 섹션 위에 나열된 레이블 키를 본다.

참고: 파드간 어피니티와 안티-어피니티에는 상당한 양의 프로세싱이 필요하기에 대규모 클러스터에서는 스케줄링 속도가 크게 느려질 수 있다. 수백 개의 노드를 넘어가는 클러스터에서 이를 사용하는 것은 추천하지 않는다.
참고: 파드 안티-어피니티에서는 노드에 일관된 레이블을 지정해야 한다. 즉, 클러스터의 모든 노드는 topologyKey 와 매칭되는 적절한 레이블을 가지고 있어야 한다. 일부 또는 모든 노드에 지정된 topologyKey 레이블이 없는 경우에는 의도하지 않은 동작이 발생할 수 있다.

노드 어피니티와 마찬가지로 현재 파드 어피니티와 안티-어피니티로 부르는 "엄격함" 대 "유연함"의 요구사항을 나타내는 requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution 두 가지 종류가 있다. 앞의 노드 어피니티 섹션의 설명을 본다. requiredDuringSchedulingIgnoredDuringExecution 어피니티의 예시는 "서로 많은 통신을 하기 때문에 서비스 A와 서비스 B를 같은 영역에 함께 위치시키는 것"이고, preferredDuringSchedulingIgnoredDuringExecution 안티-어피니티의 예시는 "서비스를 여러 영역에 걸쳐서 분배하는 것"이다 (엄격한 요구사항은 영역보다 파드가 더 많을 수 있기 때문에 엄격한 요구사항은 의미가 없다).

파드간 어피니티는 PodSpec에서 affinity 필드 중 podAffinity 필드로 지정한다. 그리고 파드간 안티-어피니티는 PodSpec에서 affinity 필드 중 podAntiAffinity 필드로 지정한다.

파드 어피니티를 사용하는 파드의 예시

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0

이 파드의 어피니티는 하나의 파드 어피니티 규칙과 하나의 파드 안티-어피니티 규칙을 정의한다. 이 예시에서 podAffinityrequiredDuringSchedulingIgnoredDuringExecution 이고 podAntiAffinitypreferredDuringSchedulingIgnoredDuringExecution 이다. 파드 어피니티 규칙에 의하면 키 "security" 와 값 "S1"인 레이블이 있는 하나 이상의 이미 실행 중인 파드와 동일한 영역에 있는 경우에만 파드를 노드에 스케줄할 수 있다. (보다 정확하게는, 클러스터에 키 "security"와 값 "S1"인 레이블을 가지고 있는 실행 중인 파드가 있는 키 topology.kubernetes.io/zone 와 값 V인 노드가 최소 하나 이상 있고, 노드 N이 키 topology.kubernetes.io/zone 와 일부 값이 V인 레이블을 가진다면 파드는 노드 N에서 실행할 수 있다.) 파드 안티-어피니티 규칙에 의하면 파드는 키 "security"와 값 "S2"인 레이블을 가진 파드와 동일한 영역의 노드에 스케줄되지 않는다. 디자인 문서를 통해 requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution 의 파드 어피니티와 안티-어피니티에 대한 많은 예시를 맛볼 수 있다.

파드 어피니티와 안티-어피니티의 적합한 연산자는 In, NotIn, Exists, DoesNotExist 이다.

원칙적으로, topologyKey 는 적법한 어느 레이블-키도 될 수 있다. 하지만, 성능과 보안상의 이유로 topologyKey에는 몇 가지 제약조건이 있다.

  1. 파드 어피니티에서 requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutiontopologyKey 의 빈 값을 허용하지 않는다.
  2. 파드 안티-어피니티에서도 requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutiontopologyKey 의 빈 값을 허용하지 않는다.
  3. requiredDuringSchedulingIgnoredDuringExecution 파드 안티-어피니티에서 topologyKeykubernetes.io/hostname 로 제한하기 위해 어드미션 컨트롤러 LimitPodHardAntiAffinityTopology 가 도입되었다. 사용자 지정 토폴로지를 사용할 수 있도록 하려면, 어드미션 컨트롤러를 수정하거나 아니면 이를 비활성화해야 한다.
  4. 위의 경우를 제외하고, topologyKey 는 적법한 어느 레이블-키도 가능하다.

labelSelectortopologyKey 외에도 labelSelector 와 일치해야 하는 네임스페이스 목록 namespaces 를 선택적으로 지정할 수 있다(이것은 labelSelectortopologyKey 와 같은 수준의 정의이다). 생략되어있거나 비어있을 경우 어피니티/안티-어피니티 정의가 있는 파드의 네임스페이스가 기본 값이다.

파드를 노드에 스케줄하려면 requiredDuringSchedulingIgnoredDuringExecution 어피니티와 안티-어피니티와 연관된 matchExpressions 가 모두 충족되어야 한다.

더 실용적인 유스케이스

파드간 어피니티와 안티-어피니티는 레플리카셋, 스테이트풀셋, 디플로이먼트 등과 같은 상위 레벨 모음과 함께 사용할 때 더욱 유용할 수 있다. 워크로드 집합이 동일한 노드와 같이 동일하게 정의된 토폴로지와 같은 위치에 배치되도록 쉽게 구성할 수 있다.

항상 같은 노드에 위치시키기

세 개의 노드가 있는 클러스터에서 웹 애플리케이션에는 redis와 같은 인-메모리 캐시가 있다. 웹 서버가 가능한 캐시와 함께 위치하기를 원한다.

다음은 세 개의 레플리카와 셀렉터 레이블이 app=store 가 있는 간단한 redis 디플로이먼트의 yaml 스니펫이다. 디플로이먼트에는 스케줄러가 단일 노드에서 레플리카를 함께 배치하지 않도록 PodAntiAffinity 가 구성되어 있다.

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

아래 yaml 스니펫의 웹서버 디플로이먼트는 podAntiAffinitypodAffinity 설정을 가지고 있다. 이렇게 하면 스케줄러에 모든 레플리카는 셀렉터 레이블이 app=store 인 파드와 함께 위치해야 한다. 또한 각 웹 서버 레플리카가 단일 노드의 같은 위치에 있지 않도록 한다.

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.16-alpine

만약 위의 두 디플로이먼트를 생성하면 세 개의 노드가 있는 클러스터는 다음과 같아야 한다.

node-1node-2node-3
webserver-1webserver-2webserver-3
cache-1cache-2cache-3

여기서 볼 수 있듯이 web-server 의 세 레플리카들이 기대했던 것처럼 자동으로 캐시와 함께 위치하게 된다.

kubectl get pods -o wide

출력은 다음과 유사할 것이다.

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
절대 동일한 노드에 위치시키지 않게 하기

위의 예시에서 topologyKey:"kubernetes.io/hostname" 과 함께 PodAntiAffinity 규칙을 사용해서 두 개의 인스터스가 동일한 호스트에 있지 않도록 redis 클러스터를 배포한다. 같은 기술을 사용해서 고 가용성을 위해 안티-어피니티로 구성된 스테이트풀셋의 예시는 ZooKeeper 튜토리얼을 본다.

nodeName

nodeName 은 가장 간단한 형태의 노트 선택 제약 조건이지만, 한계로 인해 일반적으로는 사용하지 않는다. nodeName 은 PodSpec의 필드이다. 만약 비어있지 않으면, 스케줄러는 파드를 무시하고 명명된 노드에서 실행 중인 kubelet이 파드를 실행하려고 한다. 따라서 만약 PodSpec에 nodeName 가 제공된 경우, 노드 선텍을 위해 위의 방법보다 우선한다.

nodeName 을 사용해서 노드를 선택할 때의 몇 가지 제한은 다음과 같다.

  • 만약 명명된 노드가 없으면, 파드가 실행되지 않고 따라서 자동으로 삭제될 수 있다.
  • 만약 명명된 노드에 파드를 수용할 수 있는 리소스가 없는 경우 파드가 실패하고, 그 이유는 다음과 같이 표시된다. 예: OutOfmemory 또는 OutOfcpu.
  • 클라우드 환경의 노드 이름은 항상 예측 가능하거나 안정적인 것은 아니다.

여기에 nodeName 필드를 사용하는 파드 설정 파일 예시가 있다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-01

위 파드는 kube-01 노드에서 실행될 것이다.

다음 내용

테인트는 노드가 특정 파드들을 쫓아낼 수 있다.

노드 어피니티파드간 어피니티/안티-어피니티에 대한 디자인 문서에는 이러한 기능에 대한 추가 배경 정보가 있다.

파드가 노드에 할당되면 kubelet은 파드를 실행하고 노드의 로컬 리소스를 할당한다. 토폴로지 매니저는 노드 수준의 리소스 할당 결정에 참여할 수 있다.

3 - 파드 오버헤드

FEATURE STATE: Kubernetes v1.18 [beta]

노드 위에서 파드를 구동할 때, 파드는 그 자체적으로 많은 시스템 리소스를 사용한다. 이러한 리소스는 파드 내의 컨테이너들을 구동하기 위한 리소스 이외에 추가적으로 필요한 것이다. 파드 오버헤드 는 컨테이너 리소스 요청과 상한 위에서 파드의 인프라에 의해 소비되는 리소스를 계산하는 기능이다.

쿠버네티스에서 파드의 오버헤드는 파드의 런타임클래스 와 관련된 오버헤드에 따라 어드미션 이 수행될 때 지정된다.

파드 오버헤드가 활성화 되면, 파드를 노드에 스케줄링 할 때 컨테이너 리소스 요청의 합에 파드의 오버헤드를 추가해서 스케줄링을 고려한다. 마찬가지로, kubelet은 파드의 cgroups 크기를 변경하거나 파드의 축출 등급을 부여할 때에도 파드의 오버헤드를 포함하여 고려한다.

파드 오버헤드 활성화하기

기능 활성화를 위해 클러스터에서 PodOverhead 기능 게이트가 활성화되어 있고(1.18 버전에서는 기본적으로 활성화), overhead 필드를 정의하는 RuntimeClass 가 사용되고 있는지 확인해야 한다.

사용 예제

파드 오버헤드 기능을 사용하기 위하여, overhead 필드를 정의하는 런타임클래스가 필요하다. 예를 들어, 가상 머신 및 게스트 OS에 대하여 파드 당 120 MiB를 사용하는 가상화 컨테이너 런타임의 런타임클래스의 경우 다음과 같이 정의 할 수 있다.

---
kind: RuntimeClass
apiVersion: node.k8s.io/v1
metadata:
    name: kata-fc
handler: kata-fc
overhead:
    podFixed:
        memory: "120Mi"
        cpu: "250m"

kata-fc 런타임클래스 핸들러를 지정하는 워크로드는 리소스 쿼터 계산, 노드 스케줄링 및 파드 cgroup 크기 조정을 위하여 메모리와 CPU 오버헤드를 고려한다.

주어진 예제 워크로드 test-pod의 구동을 고려해보자.

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  runtimeClassName: kata-fc
  containers:
  - name: busybox-ctr
    image: busybox
    stdin: true
    tty: true
    resources:
      limits:
        cpu: 500m
        memory: 100Mi
  - name: nginx-ctr
    image: nginx
    resources:
      limits:
        cpu: 1500m
        memory: 100Mi

어드미션 수행 시에, 어드미션 컨트롤러는 런타임클래스에 기술된 overhead 를 포함하기 위하여 워크로드의 PodSpec 항목을 갱신한다. 만약 PodSpec이 이미 해당 필드에 정의되어 있으면, 파드는 거부된다. 주어진 예제에서, 오직 런타임클래스의 이름만이 정의되어 있기 때문에, 어드미션 컨트롤러는 파드가 overhead 를 포함하도록 변경한다.

런타임클래스의 어드미션 수행 후에, 파드의 스펙이 갱신된 것을 확인할 수 있다.

kubectl get pod test-pod -o jsonpath='{.spec.overhead}'

명령 실행 결과는 다음과 같다.

map[cpu:250m memory:120Mi]

만약 리소스쿼터 항목이 정의되어 있다면, 컨테이너의 리소스 요청의 합에는 overhead 필드도 추가된다.

kube-scheduler 는 어떤 노드에 파드가 기동 되어야 할지를 정할 때, 파드의 overhead 와 해당 파드에 대한 컨테이너의 리소스 요청의 합을 고려한다. 이 예제에서, 스케줄러는 리소스 요청과 파드의 오버헤드를 더하고, 2.25 CPU와 320 MiB 메모리가 사용 가능한 노드를 찾는다.

일단 파드가 특정 노드에 스케줄링 되면, 해당 노드에 있는 kubelet 은 파드에 대한 새로운 cgroup을 생성한다. 기본 컨테이너 런타임이 만들어내는 컨테이너들은 이 파드 안에 존재한다.

만약 각 컨테이너에 대하여 QoS가 보장되었거나 향상이 가능하도록 QoS 의 리소스 상한 제한이 걸려있으면, kubelet 은 해당 리소스(CPU의 경우 cpu.cfs_quota_us, 메모리의 경우 memory.limit_in_bytes)와 연관된 파드의 cgroup 의 상한선을 설정한다. 이 상한선은 컨테이너 리소스 상한과 PodSpec에 정의된 overhead 의 합에 기반한다.

CPU의 경우, 만약 파드가 보장형 또는 버스트형 QoS로 설정되었으면, kubelet은 PodSpec에 정의된 overhead 에 컨테이너의 리소스 요청의 합을 더한 값을 cpu.shares 로 설정한다.

다음의 예제를 참고하여, 워크로드에 대하여 컨테이너의 리소스 요청을 확인하자.

kubectl get pod test-pod -o jsonpath='{.spec.containers[*].resources.limits}'

컨테이너 리소스 요청의 합은 각각 CPU 2000m 와 메모리 200MiB 이다.

map[cpu: 500m memory:100Mi] map[cpu:1500m memory:100Mi]

노드에서 측정된 내용과 비교하여 확인해보자.

kubectl describe node | grep test-pod -B2

CPU 2250m와 메모리 320MiB 가 리소스로 요청되었으며, 이 결과는 파드의 오버헤드를 포함한다.

  Namespace                   Name                CPU Requests  CPU Limits   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------   ---------------  -------------  ---
  default                     test-pod            2250m (56%)   2250m (56%)  320Mi (1%)       320Mi (1%)     36m

파드 cgroup 상한 확인하기

워크로드가 실행 중인 노드에서 파드의 메모리 cgroup들을 확인 해보자. 다음의 예제에서, crictl은 노드에서 사용되며, CRI-호환 컨테이너 런타임을 위해서 노드에서 사용할 수 있는 CLI 를 제공한다. 파드의 오버헤드 동작을 보여주는 좋은 예이며, 사용자가 노드에서 직접 cgroup들을 확인하지 않아도 된다.

먼저 특정 노드에서 파드의 식별자를 확인해 보자.

# 파드가 스케줄 된 노드에서 이것을 실행
POD_ID="$(sudo crictl pods --name test-pod -q)"

여기에서, 파드의 cgroup 경로를 확인할 수 있다.

# 파드가 스케줄 된 노드에서 이것을 실행
sudo crictl inspectp -o=json $POD_ID | grep cgroupsPath

명령의 결과로 나온 cgroup 경로는 파드의 pause 컨테이너를 포함한다. 파드 레벨의 cgroup은 하나의 디렉터리이다.

        "cgroupsPath": "/kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2/7ccf55aee35dd16aca4189c952d83487297f3cd760f1bbf09620e206e7d0c27a"

아래의 특정한 경우에, 파드 cgroup 경로는 kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2 이다. 메모리의 파드 레벨 cgroup 설정을 확인하자.

# 파드가 스케줄 된 노드에서 이것을 실행.
# 또한 사용자의 파드에 할당된 cgroup 이름에 맞춰 해당 이름을 수정.
 cat /sys/fs/cgroup/memory/kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2/memory.limit_in_bytes

예상대로 320 MiB 이다.

335544320

관찰성

kube_pod_overhead 항목은 kube-state-metrics 에서 사용할 수 있어, 파드 오버헤드가 사용되는 시기를 식별하고, 정의된 오버헤드로 실행되는 워크로드의 안정성을 관찰할 수 있다. 이 기능은 kube-state-metrics 의 1.9 릴리스에서는 사용할 수 없지만, 다음 릴리스에서는 가능할 예정이다. 그 전까지는 소스로부터 kube-state-metric 을 빌드해야 한다.

다음 내용

4 - 확장된 리소스를 위한 리소스 빈 패킹(bin packing)

FEATURE STATE: Kubernetes v1.16 [alpha]

kube-scheduler는 RequestedToCapacityRatioResourceAllocation 우선 순위 기능을 사용해서 확장된 리소스와 함께 리소스의 빈 패킹이 가능하도록 구성할 수 있다. 우선 순위 기능을 사용해서 맞춤 요구에 따라 kube-scheduler를 미세 조정할 수 있다.

RequestedToCapacityRatioResourceAllocation을 사용해서 빈 패킹 활성화하기

쿠버네티스를 사용하면 사용자가 각 리소스에 대한 가중치와 함께 리소스를 지정하여 용량 대비 요청 비율을 기반으로 노드의 점수를 매기는 것을 허용한다. 이를 통해 사용자는 적절한 파라미터를 사용해서 확장된 리소스를 빈 팩으로 만들 수 있어 대규모의 클러스터에서 부족한 리소스의 활용도가 향상된다. RequestedToCapacityRatioResourceAllocation 우선 순위 기능의 동작은 requestedToCapacityRatioArguments라는 구성 옵션으로 제어할 수 있다. 이 인수는 shaperesources 두 개의 파라미터로 구성된다. shape 파라미터는 사용자가 utilizationscore 값을 기반으로 최소 요청 또는 최대 요청된 대로 기능을 조정할 수 있게 한다. resources 파라미터는 점수를 매길 때 고려할 리소스의 name 과 각 리소스의 가중치를 지정하는 weight 로 구성된다.

다음은 확장된 리소스 intel.com/foointel.com/bar 에 대한 requestedToCapacityRatioArguments 를 빈 패킹 동작으로 설정하는 구성의 예시이다.

apiVersion: v1
kind: Policy
# ...
priorities:
  # ...
  - name: RequestedToCapacityRatioPriority
    weight: 2
    argument:
      requestedToCapacityRatioArguments:
        shape:
          - utilization: 0
            score: 0
          - utilization: 100
            score: 10
        resources:
          - name: intel.com/foo
            weight: 3
          - name: intel.com/bar
            weight: 5

이 기능은 기본적으로 비활성화되어 있다.

우선 순위 기능 튜닝하기

shapeRequestedToCapacityRatioPriority 기능의 동작을 지정하는 데 사용된다.

shape:
 - utilization: 0
   score: 0
 - utilization: 100
   score: 10

위의 인수는 utilization 이 0%인 경우 score 는 0, utilization 이 100%인 경우 10으로 하여, 빈 패킹 동작을 활성화한다. 최소 요청을 활성화하려면 점수 값을 다음과 같이 변경해야 한다.

shape:
  - utilization: 0
    score: 10
  - utilization: 100
    score: 0

resources 는 기본적으로 다음과 같이 설정되는 선택적인 파라미터이다.

resources:
  - name: CPU
    weight: 1
  - name: Memory
    weight: 1

다음과 같이 확장된 리소스를 추가하는 데 사용할 수 있다.

resources:
  - name: intel.com/foo
    weight: 5
  - name: CPU
    weight: 3
  - name: Memory
    weight: 1

weight 파라미터는 선택 사항이며 지정되지 않은 경우 1로 설정 된다. 또한, weight 는 음수로 설정할 수 없다.

용량 할당을 위해 노드에 점수 매기기

이 섹션은 이 기능 내부의 세부적인 사항을 이해하려는 사람들을 위한 것이다. 아래는 주어진 값의 집합에 대해 노드 점수가 계산되는 방법의 예시이다.

요청된 리소스는 다음과 같다.

intel.com/foo : 2
Memory: 256MB
CPU: 2

리소스의 가중치는 다음과 같다.

intel.com/foo : 5
Memory: 1
CPU: 3

FunctionShapePoint {{0, 0}, {100, 10}}

노드 1의 사양은 다음과 같다.

Available:
  intel.com/foo: 4
  Memory: 1 GB
  CPU: 8

Used:
  intel.com/foo: 1
  Memory: 256MB
  CPU: 1

노드 점수는 다음과 같다.

intel.com/foo  = resourceScoringFunction((2+1),4)
               = (100 - ((4-3)*100/4)
               = (100 - 25)
               = 75                       # requested + used = 75% * available
               = rawScoringFunction(75)
               = 7                        # floor(75/10)

Memory         = resourceScoringFunction((256+256),1024)
               = (100 -((1024-512)*100/1024))
               = 50                       # requested + used = 50% * available
               = rawScoringFunction(50)
               = 5                        # floor(50/10)

CPU            = resourceScoringFunction((2+1),8)
               = (100 -((8-3)*100/8))
               = 37.5                     # requested + used = 37.5% * available
               = rawScoringFunction(37.5)
               = 3                        # floor(37.5/10)

NodeScore   =  (7 * 5) + (5 * 1) + (3 * 3) / (5 + 1 + 3)
            =  5

노드 2의 사양은 다음과 같다.

Available:
  intel.com/foo: 8
  Memory: 1GB
  CPU: 8
Used:
  intel.com/foo: 2
  Memory: 512MB
  CPU: 6

노드 점수는 다음과 같다.

intel.com/foo  = resourceScoringFunction((2+2),8)
               =  (100 - ((8-4)*100/8)
               =  (100 - 50)
               =  50
               =  rawScoringFunction(50)
               = 5

Memory         = resourceScoringFunction((256+512),1024)
               = (100 -((1024-768)*100/1024))
               = 75
               = rawScoringFunction(75)
               = 7

CPU            = resourceScoringFunction((2+6),8)
               = (100 -((8-8)*100/8))
               = 100
               = rawScoringFunction(100)
               = 10

NodeScore   =  (5 * 5) + (7 * 1) + (10 * 3) / (5 + 1 + 3)
            =  7

다음 내용

5 - 테인트(Taints)와 톨러레이션(Tolerations)

노드 어피니티노드 셋을 (기본 설정 또는 어려운 요구 사항으로) 끌어들이는 파드의 속성이다. 테인트 는 그 반대로, 노드가 파드 셋을 제외할 수 있다.

톨러레이션 은 파드에 적용되며, 파드를 일치하는 테인트가 있는 노드에 스케줄되게 하지만 필수는 아니다.

테인트와 톨러레이션은 함께 작동하여 파드가 부적절한 노드에 스케줄되지 않게 한다. 하나 이상의 테인트가 노드에 적용된다. 이것은 노드가 테인트를 용인하지 않는 파드를 수용해서는 안 되는 것을 나타낸다.

개요

kubectl taint를 사용하여 노드에 테인트을 추가한다. 예를 들면 다음과 같다.

kubectl taint nodes node1 key1=value1:NoSchedule

node1 노드에 테인트을 배치한다. 테인트에는 키 key1, 값 value1 및 테인트 이펙트(effect) NoSchedule 이 있다. 이는 일치하는 톨러레이션이 없으면 파드를 node1 에 스케줄할 수 없음을 의미한다.

위의 명령으로 추가한 테인트를 제거하려면, 다음을 실행한다.

kubectl taint nodes node1 key1=value1:NoSchedule-

PodSpec에서 파드에 대한 톨러레이션를 지정한다. 다음의 톨러레이션은 위의 kubectl taint 라인에 의해 생성된 테인트와 "일치"하므로, 어느 쪽 톨러레이션을 가진 파드이던 node1 에 스케줄 될 수 있다.

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
tolerations:
- key: "key1"
  operator: "Exists"
  effect: "NoSchedule"

톨러레이션을 사용하는 파드의 예는 다음과 같다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"

지정하지 않으면 operator 의 기본값은 Equal 이다.

톨러레이션은 키가 동일하고 이펙트가 동일한 경우, 테인트와 "일치"한다. 그리고 다음의 경우에도 마찬가지다.

  • operatorExists 인 경우(이 경우 value 를 지정하지 않아야 함), 또는
  • operatorEqual 이고 valuevalue 로 같다.
참고:

두 가지 특별한 경우가 있다.

operator Exists 가 있는 비어있는 key 는 모든 키, 값 및 이펙트와 일치하므로 모든 것이 톨러레이션 된다.

비어있는 effect 는 모든 이펙트를 키 key1 와 일치시킨다.

위의 예는 NoScheduleeffect 를 사용했다. 또는, PreferNoScheduleeffect 를 사용할 수 있다. 이것은 NoSchedule 의 "기본 설정(preference)" 또는 "소프트(soft)" 버전이다. 시스템은 노드의 테인트를 허용하지 않는 파드를 배치하지 않으려고 시도 하지만, 필요하지는 않다. 세 번째 종류의 effect 는 나중에 설명할 NoExecute 이다.

동일한 노드에 여러 테인트를, 동일한 파드에 여러 톨러레이션을 둘 수 있다. 쿠버네티스가 여러 테인트 및 톨러레이션을 처리하는 방식은 필터와 같다. 모든 노드의 테인트로 시작한 다음, 파드에 일치하는 톨러레이션이 있는 것을 무시한다. 무시되지 않은 나머지 테인트는 파드에 표시된 이펙트를 가진다. 특히,

  • NoSchedule 이펙트가 있는 무시되지 않은 테인트가 하나 이상 있으면 쿠버네티스는 해당 노드에 파드를 스케줄하지 않는다.
  • NoSchedule 이펙트가 있는 무시되지 않은 테인트가 없지만 PreferNoSchedule 이펙트가 있는 무시되지 않은 테인트가 하나 이상 있으면 쿠버네티스는 파드를 노드에 스케쥴하지 않으려고 시도 한다
  • NoExecute 이펙트가 있는 무시되지 않은 테인트가 하나 이상 있으면 파드가 노드에서 축출되고(노드에서 이미 실행 중인 경우), 노드에서 스케줄되지 않는다(아직 실행되지 않은 경우).

예를 들어, 이와 같은 노드를 테인트하는 경우는 다음과 같다.

kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

그리고 파드에는 두 가지 톨러레이션이 있다.

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"

이 경우, 세 번째 테인트와 일치하는 톨러레이션이 없기 때문에, 파드는 노드에 스케줄 될 수 없다. 그러나 세 번째 테인트가 파드에서 용인되지 않는 세 가지 중 하나만 있기 때문에, 테인트가 추가될 때 노드에서 이미 실행 중인 경우, 파드는 계속 실행할 수 있다.

일반적으로, NoExecute 이펙트가 있는 테인트가 노드에 추가되면, 테인트를 용인하지 않는 파드는 즉시 축출되고, 테인트를 용인하는 파드는 축출되지 않는다. 그러나 NoExecute 이펙트가 있는 톨러레이션은 테인트가 추가된 후 파드가 노드에 바인딩된 시간을 지정하는 선택적 tolerationSeconds 필드를 지정할 수 있다. 예를 들어,

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

이것은 이 파드가 실행 중이고 일치하는 테인트가 노드에 추가되면, 파드는 3600초 동안 노드에 바인딩된 후, 축출된다는 것을 의미한다. 그 전에 테인트를 제거하면, 파드가 축출되지 않는다.

유스케이스 예시

테인트 및 톨러레이션은 파드를 노드에서 멀어지게 하거나 실행되지 않아야 하는 파드를 축출할 수 있는 유연한 방법이다. 유스케이스 중 일부는 다음과 같다.

  • 전용 노드: 특정 사용자들이 독점적으로 사용하도록 노드 셋을 전용하려면, 해당 노드에 테인트를 추가(예: kubectl taint nodes nodename dedicated=groupName:NoSchedule)한 다음 해당 톨러레이션을 그들의 파드에 추가할 수 있다(사용자 정의 [어드미션 컨트롤러] (/docs/reference/access-authn-authz/admission-controllers/)를 작성하면 가장 쉽게 수행할 수 있음). 그런 다음 톨러레이션이 있는 파드는 테인트된(전용) 노드와 클러스터의 다른 노드를 사용할 수 있다. 노드를 특정 사용자들에게 전용으로 지정하고 그리고 그 사용자들이 전용 노드 사용하려면, 동일한 노드 셋에 테인트와 유사한 레이블을 추가해야 하고(예: dedicated=groupName), 어드미션 컨트롤러는 추가로 파드가 dedicated=groupName 으로 레이블이 지정된 노드에만 스케줄될 수 있도록 노드 어피니티를 추가해야 한다.

  • 특별한 하드웨어가 있는 노드: 작은 서브셋의 노드에 특별한 하드웨어(예: GPU)가 있는 클러스터에서는, 특별한 하드웨어가 필요하지 않는 파드를 해당 노드에서 분리하여, 나중에 도착하는 특별한 하드웨어가 필요한 파드를 위한 공간을 남겨두는 것이 바람직하다. 이는 특별한 하드웨어가 있는 노드(예: kubectl taint nodes nodename special=true:NoSchedule 또는 kubectl taint nodes nodename special=true:PreferNoSchedule)에 테인트를 추가하고 특별한 하드웨어를 사용하는 파드에 해당 톨러레이션을 추가하여 수행할 수 있다. 전용 노드 유스케이스에서와 같이, 사용자 정의 어드미션 컨트롤러를 사용하여 톨러레이션를 적용하는 것이 가장 쉬운 방법이다. 예를 들어, 확장된 리소스를 사용하여 특별한 하드웨어를 나타내고, 확장된 리소스 이름으로 특별한 하드웨어 노드를 테인트시키고 ExtendedResourceToleration 어드미션 컨트롤러를 실행하는 것을 권장한다. 이제, 노드가 테인트되었으므로, 톨러레이션이 없는 파드는 스케줄되지 않는다. 그러나 확장된 리소스를 요청하는 파드를 제출하면, ExtendedResourceToleration 어드미션 컨트롤러가 파드에 올바른 톨러레이션을 자동으로 추가하고 해당 파드는 특별한 하드웨어 노드에서 스케줄된다. 이렇게 하면 이러한 특별한 하드웨어 노드가 해당 하드웨어를 요청하는 파드가 전용으로 사용하며 파드에 톨러레이션을 수동으로 추가할 필요가 없다.

  • 테인트 기반 축출: 노드 문제가 있을 때 파드별로 구성 가능한 축출 동작은 다음 섹션에서 설명한다.

테인트 기반 축출

FEATURE STATE: Kubernetes v1.18 [stable]

앞에서 우리는 노드에서 이미 실행 중인 파드에 영향을 주는 NoExecute 테인트 이펙트를 다음과 같이 언급했다.

  • 테인트를 용인하지 않는 파드는 즉시 축출된다.
  • 톨러레이션 명세에 tolerationSeconds 를 지정하지 않고 테인트를 용인하는 파드는 계속 바인딩된다.
  • tolerationSeconds 가 지정된 테인트를 용인하는 파드는 지정된 시간 동안 바인딩된 상태로 유지된다.

노드 컨트롤러는 특정 조건이 참일 때 자동으로 노드를 테인트시킨다. 다음은 빌트인 테인트이다.

  • node.kubernetes.io/not-ready: 노드가 준비되지 않았다. 이는 NodeCondition Ready 가 "False"로 됨에 해당한다.
  • node.kubernetes.io/unreachable: 노드가 노드 컨트롤러에서 도달할 수 없다. 이는 NodeCondition Ready 가 "Unknown"로 됨에 해당한다.
  • node.kubernetes.io/out-of-disk: 노드에 디스크가 부족하다.
  • node.kubernetes.io/memory-pressure: 노드에 메모리 할당 압박이 있다.
  • node.kubernetes.io/disk-pressure: 노드에 디스크 할당 압박이 있다.
  • node.kubernetes.io/network-unavailable: 노드의 네트워크를 사용할 수 없다.
  • node.kubernetes.io/unschedulable: 노드를 스케줄할 수 없다.
  • node.cloudprovider.kubernetes.io/uninitialized: "외부" 클라우드 공급자로 kubelet을 시작하면, 이 테인트가 노드에서 사용 불가능으로 표시되도록 설정된다. 클라우드-컨트롤러-관리자의 컨트롤러가 이 노드를 초기화하면, kubelet이 이 테인트를 제거한다.

노드가 축출될 경우, 노드 컨트롤러 또는 kubelet은 NoExecute 이펙트로 관련 테인트를 추가한다. 장애 상태가 정상으로 돌아오면 kubelet 또는 노드 컨트롤러가 관련 테인트를 제거할 수 있다.

참고: 콘트롤 플레인은 노드에 새 테인트를 추가하는 비율을 제한한다. 이 비율-제한은 많은 노드가 동시에 도달할 수 없을 때(예를 들어, 네트워크 중단으로) 트리거될 축출 개수를 관리한다.

이 기능을 tolerationSeconds 와 함께 사용하면, 파드에서 이러한 문제 중 하나 또는 둘 다가 있는 노드에 바인딩된 기간을 지정할 수 있다.

예를 들어, 로컬 상태가 많은 애플리케이션은 네트워크 분할의 장애에서 네트워크가 복구된 후에 파드가 축출되는 것을 피하기 위해 오랫동안 노드에 바인딩된 상태를 유지하려고 할 수 있다. 이 경우 파드가 사용하는 톨러레이션은 다음과 같다.

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000
참고:

쿠버네티스는 사용자나 컨트롤러에서 명시적으로 설정하지 않았다면, 자동으로 node.kubernetes.io/not-readynode.kubernetes.io/unreachable 에 대해 tolerationSeconds=300 으로 톨러레이션을 추가한다.

자동으로 추가된 이 톨러레이션은 이러한 문제 중 하나가 감지된 후 5분 동안 파드가 노드에 바인딩된 상태를 유지함을 의미한다.

데몬셋 파드는 tolerationSeconds 가 없는 다음 테인트에 대해 NoExecute 톨러레이션를 가지고 생성된다.

  • node.kubernetes.io/unreachable
  • node.kubernetes.io/not-ready

이렇게 하면 이러한 문제로 인해 데몬셋 파드가 축출되지 않는다.

컨디션별 노드 테인트하기

노드 라이프사이클 컨트롤러는 NoSchedule 이펙트가 있는 노드 컨디션에 해당하는 테인트를 자동으로 생성한다. 마찬가지로 스케줄러는 노드 컨디션을 확인하지 않는다. 대신 스케줄러는 테인트를 확인한다. 이렇게 하면 노드 컨디션이 노드에 스케줄된 내용에 영향을 미치지 않는다. 사용자는 적절한 파드 톨러레이션을 추가하여 노드의 일부 문제(노드 컨디션으로 표시)를 무시하도록 선택할 수 있다.

쿠버네티스 1.8 버전부터 데몬셋 컨트롤러는 다음의 NoSchedule 톨러레이션을 모든 데몬에 자동으로 추가하여, 데몬셋이 중단되는 것을 방지한다.

  • node.kubernetes.io/memory-pressure
  • node.kubernetes.io/disk-pressure
  • node.kubernetes.io/out-of-disk (중요한 파드에만 해당)
  • node.kubernetes.io/unschedulable (1.10 이상)
  • node.kubernetes.io/network-unavailable (호스트 네트워크만 해당)

이러한 톨러레이션을 추가하면 이전 버전과의 호환성이 보장된다. 데몬셋에 임의의 톨러레이션을 추가할 수도 있다.

다음 내용

6 - 스케줄러 성능 튜닝

FEATURE STATE: Kubernetes 1.14 [beta]

kube-scheduler는 쿠버네티스의 기본 스케줄러이다. 그것은 클러스터의 노드에 파드를 배치하는 역할을 한다.

파드의 스케줄링 요건을 충족하는 클러스터의 노드를 파드에 적합한(feasible) 노드라고 한다. 스케줄러는 파드에 대해 적합한 노드를 찾고 기능 셋을 실행하여 해당 노드의 점수를 측정한다. 그리고 스케줄러는 파드를 실행하는데 적합한 모든 노드 중 가장 높은 점수를 가진 노드를 선택한다. 이후 스케줄러는 바인딩 이라는 프로세스로 API 서버에 해당 결정을 통지한다.

본 페이지에서는 상대적으로 큰 규모의 쿠버네티스 클러스터에 대한 성능 튜닝 최적화에 대해 설명한다.

큰 규모의 클러스터에서는 스케줄러의 동작을 튜닝하여 응답 시간 (새 파드가 빠르게 배치됨)과 정확도(스케줄러가 배치 결정을 잘 못하는 경우가 드물게 됨) 사이에서의 스케줄링 결과를 균형 잡을 수 있다.

kube-scheduler 의 percentageOfNodesToScore 설정을 통해 이 튜닝을 구성 한다. 이 KubeSchedulerConfiguration 설정에 따라 클러스터의 노드를 스케줄링할 수 있는 임계값이 결정된다.

임계값 설정하기

percentageOfNodesToScore 옵션은 0과 100 사이의 값을 허용한다. 값 0은 kube-scheduler가 컴파일 된 기본값을 사용한다는 것을 나타내는 특별한 숫자이다. percentageOfNodesToScore 를 100 보다 높게 설정해도 kube-scheduler는 마치 100을 설정한 것처럼 작동한다.

값을 변경하려면, kube-scheduler 구성 파일을 편집한 다음 스케줄러를 재시작한다. 대부분의 경우, 구성 파일은 /etc/kubernetes/config/kube-scheduler.yaml 에서 찾을 수 있다.

이를 변경한 후에 다음을 실행해서

kubectl get pods -n kube-system | grep kube-scheduler

kube-scheduler 컴포넌트가 정상인지 확인할 수 있다.

노드 스코어링(scoring) 임계값

스케줄링 성능을 향상시키기 위해 kube-scheduler는 실행 가능한 노드가 충분히 발견되면 이를 찾는 것을 중단할 수 있다. 큰 규모의 클러스터에서는 모든 노드를 고려하는 고지식한 접근 방식에 비해 시간이 절약된다.

클러스터에 있는 모든 노드의 정수 백분율로 충분한 노두의 수에 대한 임계값을 지정한다. kube-scheduler는 이 값을 노드의 정수 값(숫자)로 변환 한다. 스케줄링 중에 kube-scheduler가 구성된 비율을 초과 할만큼 충분히 실행 가능한 노드를 식별한 경우, kube-scheduler는 더 실행 가능한 노드를 찾는 검색을 중지하고 스코어링 단계를 진행한다.

스케줄러가 노드 탐색을 반복(iterate)하는 방법 은 이 프로세스를 자세히 설명한다.

기본 임계값

임계값을 지정하지 않으면 쿠버네티스는 100 노드 클러스터인 경우 50%, 5000 노드 클러스터인 경우 10%를 산출하는 선형 공식을 사용하여 수치를 계산한다. 자동 값의 하한선은 5% 이다.

즉, percentageOfNodesToScore 를 명시적으로 5보다 작게 설정하지 않은 경우 클러스터가 아무리 크더라도 kube-scheduler는 항상 클러스터의 최소 5%를 스코어링을 한다.

스케줄러가 클러스터의 모든 노드에 스코어링을 하려면 percentageOfNodesToScore 를 100으로 설정 한다.

예시

아래는 percentageOfNodesToScore를 50%로 설정하는 구성 예시이다.

apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
algorithmSource:
  provider: DefaultProvider

...

percentageOfNodesToScore: 50

percentageOfNodesToScore 튜닝

percentageOfNodesToScore는 1과 100 사이의 값이어야 하며 기본값은 클러스터 크기에 따라 계산된다. 또한 50 노드로 하드 코딩된 최솟값도 있다.

참고:

클러스터에서 적합한 노드가 50 미만인 경우, 스케줄러는 여전히 모든 노드를 확인한다. 그 이유는 스케줄러가 탐색을 조기 중단하기에는 적합한 노드의 수가 충분하지 않기 때문이다.

규모가 작은 클러스터에서는 percentageOfNodesToScore 에 낮은 값을 설정하면, 비슷한 이유로 변경 사항이 거의 또는 전혀 영향을 미치지 않게 된다.

클러스터에 수백 개 이하의 노드가 있는 경우 이 구성 옵션을 기본값으로 둔다. 이는 변경사항을 적용하더라도 스케줄러의 성능이 크게 향상되지 않는다.

이 값을 세팅할 때 중요하고 자세한 사항은, 클러스터에서 적은 수의 노드에 대해서만 적합성을 확인하면, 주어진 파드에 대해서 일부 노드의 점수는 측정이되지 않는다는 것이다. 결과적으로, 주어진 파드를 실행하는데 가장 높은 점수를 가질 가능성이 있는 노드가 점수 측정 단계로 조차 넘어가지 않을 수 있다. 이것은 파드의 이상적인 배치보다 낮은 결과를 초래할 것이다.

percentageOfNodesToScore 를 매우 낮게 설정해서 kube-scheduler가 파드 배치 결정을 잘못 내리지 않도록 해야 한다. 스케줄러의 처리량에 대해 애플리케이션이 중요하고 노드 점수가 중요하지 않은 경우가 아니라면 백분율을 10% 미만으로 설정하지 말아야 한다. 즉, 가능한 한 모든 노드에서 파드를 실행하는 것이 좋다.

스케줄러가 노드 탐색을 반복(iterate)하는 방법

이 섹션은 이 특징의 상세한 내부 방식을 이해하고 싶은 사람들을 위해 작성되었다.

클러스터의 모든 노드가 파드 실행 대상으로 고려되어 공정한 기회를 가지도록, 스케줄러는 라운드 로빈(round robin) 방식으로 모든 노드에 대해서 탐색을 반복한다. 모든 노드가 배열에 나열되어 있다고 생각해보자. 스케줄러는 배열의 시작부터 시작하여 percentageOfNodesToScore에 명시된 충분한 수의 노드를 찾을 때까지 적합성을 확인한다. 그 다음 파드에 대해서는, 스케줄러가 이전 파드를 위한 노드 적합성 확인이 마무리된 지점인 노드 배열의 마지막 포인트부터 확인을 재개한다.

만약 노드들이 다중의 영역(zone)에 있다면, 다른 영역에 있는 노드들이 적합성 확인의 대상이 되도록 스케줄러는 다양한 영역에 있는 노드에 대해서 탐색을 반복한다. 예제로, 2개의 영역에 있는 6개의 노드를 생각해보자.

영역 1: 노드 1, 노드 2, 노드 3, 노드 4
영역 2: 노드 5, 노드 6

스케줄러는 노드의 적합성 평가를 다음의 순서로 실행한다.

노드 1, 노드 5, 노드 2, 노드 6, 노드 3, 노드 4

모든 노드를 검토한 후, 노드 1로 돌아간다.

다음 내용