쿠버네티스에서, 스케줄링은 kubelet이 파드를 실행할 수 있도록 파드를 노드에 할당하는 것을 말한다. 선점은 우선순위가 높은 파드가 노드에 스케줄될 수 있도록 우선순위가 낮은 파드를 종료시키는 과정을 말한다. 축출은 리소스가 부족한 노드에서 하나 이상의 파드를 사전에 종료시키는 프로세스이다.
쿠버네티스에서, 스케줄링은 kubelet이 파드를 실행할 수 있도록
파드를
노드에 할당하는 것을 말한다.
선점은 우선순위가 높은 파드가 노드에 스케줄될 수 있도록
우선순위가 낮은 파드를 종료시키는 과정을 말한다.
축출은 리소스가 부족한 노드에서 하나 이상의 파드를 사전에 종료시키는 프로세스이다.
쿠버네티스에서 스케줄링 은 Kubelet이
파드를 실행할 수 있도록 파드가
노드에 적합한지 확인하는 것을 말한다.
스케줄링 개요
스케줄러는 노드가 할당되지 않은 새로 생성된 파드를 감시한다.
스케줄러가 발견한 모든 파드에 대해 스케줄러는 해당 파드가 실행될
최상의 노드를 찾는 책임을 진다. 스케줄러는
아래 설명된 스케줄링 원칙을 고려하여 이 배치 결정을
하게 된다.
파드가 특정 노드에 배치되는 이유를 이해하려고 하거나
사용자 정의된 스케줄러를 직접 구현하려는 경우 이
페이지를 통해서 스케줄링에 대해 배울 수 있을 것이다.
kube-scheduler
kube-scheduler는
쿠버네티스의 기본 스케줄러이며 컨트롤 플레인의
일부로 실행된다.
kube-scheduler는 원하거나 필요에 따라 자체 스케줄링 컴포넌트를
만들고 대신 사용할 수 있도록 설계되었다.
kube-scheduler는 새로 생성되었거나 아직
예약되지 않은(스케줄링되지 않은) 파드를 실행할 최적의 노드를 선택한다. 파드의 컨테이너와 파드 자체는
서로 다른 요구사항을 가질 수 있으므로, 스케줄러는 파드의 특정 스케줄링 요구사항을
충족하지 않는 모든 노드를 필터링한다. 또는 API를 사용하면 파드를 생성할 때 노드를 지정할 수 있지만, 이는 드문 경우이며
특별한 경우에만 수행된다.
클러스터에서 파드에 대한 스케줄링 요구사항을 충족하는 노드를
실행 가능한(feasible) 노드라고 한다. 적합한 노드가 없으면 스케줄러가
배치할 수 있을 때까지 파드가 스케줄 되지 않은 상태로 유지된다.
스케줄러는 파드가 실행 가능한 노드를 찾은 다음 실행 가능한 노드의
점수를 측정하는 기능 셋을 수행하고 실행 가능한 노드 중에서 가장 높은 점수를
가진 노드를 선택하여 파드를 실행한다. 그런 다음 스케줄러는
바인딩 이라는 프로세스에서 이 결정에 대해 API 서버에 알린다.
스케줄링 결정을 위해 고려해야 할 요소에는
개별 및 집단 리소스 요구사항, 하드웨어 / 소프트웨어 /
정책 제한조건, 어피니티 및 안티-어피니티 명세, 데이터
지역성(data locality), 워크로드 간 간섭 등이 포함된다.
kube-scheduler에서 노드 선택
kube-scheduler는 2단계 작업에서 파드에 대한 노드를 선택한다.
필터링
스코어링(scoring)
필터링 단계는 파드를 스케줄링 할 수 있는 노드 셋을
찾는다. 예를 들어 PodFitsResources 필터는
후보 노드가 파드의 특정 리소스 요청을 충족시키기에 충분한 가용 리소스가
있는지 확인한다. 이 단계 다음에 노드 목록에는 적합한 노드들이
포함된다. 하나 이상의 노드가 포함된 경우가 종종 있을 것이다. 목록이 비어 있으면
해당 파드는 (아직) 스케줄링 될 수 없다.
스코어링 단계에서 스케줄러는 목록에 남아있는 노드의 순위를 지정하여
가장 적합한 파드 배치를 선택한다. 스케줄러는 사용 중인 스코어링 규칙에 따라
이 점수를 기준으로 필터링에서 통과된 각 노드에 대해 점수를 지정한다.
마지막으로 kube-scheduler는 파드를 순위가 가장 높은 노드에 할당한다.
점수가 같은 노드가 두 개 이상인 경우 kube-scheduler는
이들 중 하나를 임의로 선택한다.
스케줄러의 필터링 및 스코어링 동작을 구성하는 데 지원되는 두 가지
방법이 있다.
스케줄링 정책을 사용하면 필터링을 위한 단정(Predicates) 및 스코어링을 위한 우선순위(Priorities) 를 구성할 수 있다.
스케줄링 프로파일을 사용하면 QueueSort, Filter, Score, Bind, Reserve, Permit 등의 다른 스케줄링 단계를 구현하는 플러그인을 구성할 수 있다. 다른 프로파일을 실행하도록 kube-scheduler를 구성할 수도 있다.
특정한 노드(들) 집합에서만
동작하거나 특정한 노드 집합에서 동작하는 것을 선호하도록 파드를
제한할 수 있다.
이를 수행하는 방법에는 여러 가지가 있으며 권장되는 접근 방식은 모두
레이블 셀렉터를 사용하여 선택을 용이하게 한다.
보통은 스케줄러가
자동으로 합리적인 배치(예: 자원이 부족한 노드에 파드를 배치하지 않도록
노드 간에 파드를 분배)를 수행하기에 이러한 제약 조건을 설정할 필요는 없다.
그러나, 예를 들어 SSD가 장착된 머신에 파드가 배포되도록 하거나 또는
많은 통신을 하는 두 개의 서로 다른 서비스의 파드를 동일한 가용성 영역(availability zone)에 배치하는 경우와 같이,
파드가 어느 노드에 배포될지를 제어해야 하는 경우도 있다.
쿠버네티스가 특정 파드를 어느 노드에 스케줄링할지 고르는
다음의 방법 중 하나를 골라 사용할 수 있다.
다른 쿠버네티스 오브젝트와 마찬가지로, 노드도 레이블을 가진다.
레이블을 수동으로 추가할 수 있다.
또한 쿠버네티스도 클러스터의 모든 노드에 표준화된 레이블 집합을 적용한다.
잘 알려진 레이블, 어노테이션, 테인트에서 널리 사용되는 노드 레이블의 목록을 확인한다.
참고:
이러한 레이블에 대한 값은 클라우드 제공자별로 다르며 정확하지 않을 수 있다.
예를 들어, kubernetes.io/hostname에 대한 값은 특정 환경에서는 노드 이름과 동일할 수 있지만
다른 환경에서는 다른 값일 수도 있다.
노드 격리/제한
노드에 레이블을 추가하여
파드를 특정 노드 또는 노드 그룹에 스케줄링되도록 지정할 수 있다.
이 기능을 사용하여 특정 파드가 특정 격리/보안/규제 속성을 만족하는 노드에서만
실행되도록 할 수 있다.
노드 격리를 위해 레이블을 사용할 때, kubelet이 변경할 수 없는 레이블 키를 선택한다.
그렇지 않으면 kubelet이 해당 레이블을 변경하여 노드가 사용 불가능(compromised) 상태로 빠지고
스케줄러가 이 노드에 워크로드를 스케줄링하는 일이 발생할 수 있다.
NodeRestriction 어드미션 플러그인은
kubelet이 node-restriction.kubernetes.io/ 접두사를 갖는 레이블을
설정하거나 변경하지 못하도록 한다.
노드 격리를 위해 레이블 접두사를 사용하려면,
노드 인가자(authorizer)를 사용하고 있는지, 그리고 NodeRestriction 어드미션 플러그인을 활성화 했는지 확인한다.
노드에 node-restriction.kubernetes.io/ 접두사를 갖는 레이블을 추가하고, 노드 셀렉터에서 해당 레이블을 사용한다.
예: example.com.node-restriction.kubernetes.io/fips=true 또는 example.com.node-restriction.kubernetes.io/pci-dss=true
노드셀렉터(nodeSelector)
nodeSelector는 노드 선택 제약사항의 가장 간단하면서도 추천하는 형태이다.
파드 스펙에 nodeSelector 필드를 추가하고,
타겟으로 삼고 싶은 노드가 갖고 있는 노드 레이블을 명시한다.
쿠버네티스는 사용자가 명시한 레이블을 갖고 있는 노드에만
파드를 스케줄링한다.
각 preferredDuringSchedulingIgnoredDuringExecution 어피니티 타입 인스턴스에 대해
1-100 범위의 weight를 명시할 수 있다.
스케줄러가 다른 모든 파드 스케줄링 요구 사항을 만족하는 노드를 찾으면,
스케줄러는 노드가 만족한 모든 선호하는(preferred) 규칙에 대해
합계 계산을 위한 weight 값을 각각 추가한다.
최종 합계는 해당 노드에 대한 다른 우선 순위 함수 점수에 더해진다.
스케줄러가 파드에 대한 스케줄링 판단을 할 때,
총 점수가 가장 높은 노드가 우선 순위를 갖는다.
preferredDuringSchedulingIgnoredDuringExecution 규칙을 만족하는 노드가 2개 있고,
하나에는 label-1:key-1 레이블이 있고 다른 하나에는 label-2:key-2 레이블이 있으면,
스케줄러는 각 노드의 weight를 확인한 뒤
해당 노드에 대한 다른 점수에 가중치를 더하고,
최종 점수가 가장 높은 노드에 해당 파드를 스케줄링한다.
참고:
이 예시에서 쿠버네티스가 정상적으로 파드를 스케줄링하려면,
보유하고 있는 노드에 kubernetes.io/os=linux 레이블이 있어야 한다.
스케줄링 프로파일당 노드 어피니티
기능 상태:Kubernetes v1.20 [beta]
여러 스케줄링 프로파일을 구성할 때
노드 어피니티가 있는 프로파일을 연결할 수 있는데, 이는 프로파일이 특정 노드 집합에만 적용되는 경우 유용하다.
이렇게 하려면 다음과 같이 스케줄러 구성에 있는
NodeAffinity 플러그인의 args 필드에 addedAffinity를 추가한다.
addedAffinity는 .spec.schedulerName을 foo-scheduler로 설정하는 모든 파드에 적용되며
PodSpec에 지정된 NodeAffinity도 적용된다.
즉, 파드를 매칭시키려면, 노드가 addedAffinity와
파드의 .spec.NodeAffinity를 충족해야 한다.
addedAffinity는 엔드 유저에게 표시되지 않으므로,
예상치 못한 동작이 일어날 수 있다.
스케줄러 프로파일 이름과 명확한 상관 관계가 있는 노드 레이블을 사용한다.
참고:
데몬셋 파드를 생성하는 데몬셋 컨트롤러는
스케줄링 프로파일을 지원하지 않는다.
데몬셋 컨트롤러가 파드를 생성할 때, 기본 쿠버네티스 스케줄러는 해당 파드를 배치하고
데몬셋 컨트롤러의 모든 nodeAffinity 규칙을 준수한다.
파드간 어피니티와 안티-어피니티
파드간 어피니티와 안티-어피니티를 사용하여,
노드 레이블 대신, 각 노드에 이미 실행 중인 다른 파드 의 레이블을 기반으로
파드가 스케줄링될 노드를 제한할 수 있다.
파드간 어피니티와 안티-어피니티 규칙은
"X가 규칙 Y를 충족하는 하나 이상의 파드를 이미 실행중인 경우 이 파드는 X에서 실행해야 한다(또는
안티-어피니티의 경우에는 "실행하면 안 된다")"의 형태이며,
여기서 X는 노드, 랙, 클라우드 제공자 존 또는 리전 등이며
Y는 쿠버네티스가 충족할 규칙이다.
이러한 규칙(Y)은 레이블 셀렉터 형태로 작성하며,
연관된 네임스페이스 목록을 선택적으로 명시할 수도 있다.
쿠버네티스에서 파드는 네임스페이스에 속하는(namespaced) 오브젝트이므로,
파드 레이블도 암묵적으로 특정 네임스페이스에 속하게 된다.
파드 레이블에 대한 모든 레이블 셀렉터는 쿠버네티스가 해당 레이블을 어떤 네임스페이스에서 탐색할지를 명시해야 한다.
topologyKey를 사용하여 토폴로지 도메인(X)를 나타낼 수 있으며,
이는 시스템이 도메인을 표시하기 위해 사용하는 노드 레이블의 키이다.
이에 대한 예시는 잘 알려진 레이블, 어노테이션, 테인트를 참고한다.
참고:
파드간 어피니티와 안티-어피니티에는 상당한 양의 프로세싱이 필요하기에
대규모 클러스터에서는 스케줄링 속도가 크게 느려질 수 있다.
수백 개의 노드를 넘어가는 클러스터에서 이를 사용하는 것은 추천하지 않는다.
참고:
파드 안티-어피니티에서는 노드에 일관된 레이블을 지정해야 한다.
즉, 클러스터의 모든 노드는 topologyKey 와 매칭되는 적절한 레이블을 가지고 있어야 한다.
일부 또는 모든 노드에 지정된 topologyKey 레이블이 없는 경우에는
의도하지 않은 동작이 발생할 수 있다.
파드간 어피니티 및 안티-어피니티 종류
노드 어피니티와 마찬가지로
파드 어피니티 및 안티-어피니티에는 다음의 2 종류가 있다.
requiredDuringSchedulingIgnoredDuringExecution
preferredDuringSchedulingIgnoredDuringExecution
예를 들어, requiredDuringSchedulingIgnoredDuringExecution 어피니티를 사용하여
서로 통신을 많이 하는 두 서비스의 파드를
동일 클라우드 제공자 존에 배치하도록 스케줄러에게 지시할 수 있다.
비슷하게, preferredDuringSchedulingIgnoredDuringExecution 안티-어피니티를 사용하여
서비스의 파드를
여러 클라우드 제공자 존에 퍼뜨릴 수 있다.
파드간 어피니티를 사용하려면, 파드 스펙에 affinity.podAffinity 필드를 사용한다.
파드간 안티-어피니티를 사용하려면,
파드 스펙에 affinity.podAntiAffinity 필드를 사용한다.
이 예시는 하나의 파드 어피니티 규칙과
하나의 파드 안티-어피니티 규칙을 정의한다.
파드 어피니티 규칙은 "하드" requiredDuringSchedulingIgnoredDuringExecution을,
안티-어피니티 규칙은 "소프트" preferredDuringSchedulingIgnoredDuringExecution을 사용한다.
위의 어피니티 규칙은 security=S1 레이블이 있는 하나 이상의 기존 파드의 존와 동일한 존에 있는 노드에만
파드를 스케줄링하도록 스케줄러에 지시한다.
더 정확히 말하면, 만약 security=S1 파드 레이블이 있는 하나 이상의 기존 파드를 실행하고 있는 노드가
zone=V에 하나 이상 존재한다면,
스케줄러는 파드를 topology.kubernetes.io/zone=V 레이블이 있는 노드에 배치해야 한다.
위의 안티-어피니티 규칙은 security=S2 레이블이 있는 하나 이상의 기존 파드의 존와 동일한 존에 있는 노드에는
가급적 파드를 스케줄링하지 않도록 스케줄러에 지시한다.
더 정확히 말하면, 만약 security=S2 파드 레이블이 있는 파드가 실행되고 있는 zone=R에
다른 노드도 존재한다면,
스케줄러는 topology.kubernetes.io/zone=R 레이블이 있는 노드에는 가급적 해당 파드를 스케줄링하지 않야아 한다.
파드 어피니티와 안티-어피니티의 operator 필드에
In, NotIn, Exists 및 DoesNotExist 값을 사용할 수 있다.
원칙적으로, topologyKey 에는 성능과 보안상의 이유로 다음의 예외를 제외하면
어느 레이블 키도 사용할 수 있다.
파드 어피니티 및 안티-어피니티에 대해, 빈 topologyKey 필드는
requiredDuringSchedulingIgnoredDuringExecution 및 preferredDuringSchedulingIgnoredDuringExecution 내에 허용되지 않는다.
requiredDuringSchedulingIgnoredDuringExecution 파드 안티-어피니티 규칙에 대해,
LimitPodHardAntiAffinityTopology 어드미션 컨트롤러는
topologyKey를 kubernetes.io/hostname으로 제한한다.
커스텀 토폴로지를 허용하고 싶다면 어드미션 컨트롤러를 수정하거나 비활성화할 수 있다.
labelSelector와 topologyKey에 더하여 선택적으로,
labelSelector가 비교해야 하는 네임스페이스의 목록을
labelSelector 및 topologyKey 필드와 동일한 계위의 namespaces 필드에 명시할 수 있다.
생략하거나 비워 두면,
해당 어피니티/안티-어피니티 정의가 있는 파드의 네임스페이스를 기본값으로 사용한다.
네임스페이스 셀렉터
기능 상태:Kubernetes v1.24 [stable]
네임스페이스 집합에 대한 레이블 쿼리인 namespaceSelector 를 사용하여 일치하는 네임스페이스를 선택할 수도 있다.
namespaceSelector 또는 namespaces 필드에 의해 선택된 네임스페이스 모두에 적용된다.
빈 namespaceSelector ({})는 모든 네임스페이스와 일치하는 반면,
null 또는 빈 namespaces 목록과 null namespaceSelector 는 규칙이 적용된 파드의 네임스페이스에 매치된다.
더 실제적인 유스케이스
파드간 어피니티와 안티-어피니티는 레플리카셋, 스테이트풀셋, 디플로이먼트 등과 같은
상위 레벨 모음과 함께 사용할 때 더욱 유용할 수 있다.
이러한 규칙을 사용하면, 워크로드 집합이 예를 들면
서로 연관된 두 개의 파드를 동일한 노드에 배치하는 것과 같이 동일하게 정의된 토폴로지와
같은 위치에 배치되도록 쉽게 구성할 수 있다.
세 개의 노드로 구성된 클러스터를 상상해 보자. 이 클러스터에서 redis와 같은 인-메모리 캐시를 이용하는 웹 애플리케이션을 실행한다.
또한 이 예에서 웹 애플리케이션과 메모리 캐시 사이의 대기 시간은 될 수 있는 대로 짧아야 한다고 가정하자.
이 때 웹 서버를 가능한 한 캐시와 같은 위치에서 실행되도록 하기 위해
파드간 어피니티/안티-어피니티를 사용할 수 있다.
다음의 redis 캐시 디플로이먼트 예시에서, 레플리카는 app=store 레이블을 갖는다.
podAntiAffinity 규칙은 스케줄러로 하여금
app=store 레이블을 가진 복수 개의 레플리카를 단일 노드에 배치하지 않게 한다.
이렇게 하여 캐시 파드를 각 노드에 분산하여 생성한다.
웹 서버를 위한 다음의 디플로이먼트는 app=web-store 레이블을 갖는 레플리카를 생성한다.
파드 어피니티 규칙은 스케줄러로 하여금 app=store 레이블이 있는 파드를 실행 중인 노드에 각 레플리카를 배치하도록 한다.
파드 안티-어피니티 규칙은 스케줄러로 하여금 app=web-store 레이블이 있는 서버 파드를
한 노드에 여러 개 배치하지 못하도록 한다.
위의 두 디플로이먼트를 생성하면 다음과 같은 클러스터 형상이 나타나는데,
세 노드에 각 웹 서버가 캐시와 함께 있는 형상이다.
node-1
node-2
node-3
webserver-1
webserver-2
webserver-3
cache-1
cache-2
cache-3
전체적인 효과는 각 캐시 인스턴스를 동일한 노드에서 실행 중인 단일 클라이언트가 액세스하게 될 것 같다는 것이다.
이 접근 방식은 차이(불균형 로드)와 대기 시간을 모두 최소화하는 것을 목표로 한다.
파드간 안티-어피니티를 사용해야 하는 다른 이유가 있을 수 있다.
ZooKeeper 튜토리얼에서
위 예시와 동일한 기술을 사용해
고 가용성을 위한 안티-어피니티로 구성된 스테이트풀셋의 예시를 확인한다.
nodeName
nodeName은 어피니티 또는 nodeSelector보다 더 직접적인 형태의 노드 선택 방법이다.
nodeName은 파드 스펙의 필드 중 하나이다.
nodeName 필드가 비어 있지 않으면, 스케줄러는 파드를 무시하고,
명명된 노드의 kubelet이 해당 파드를 자기 노드에 배치하려고 시도한다.
nodeName은 nodeSelector 또는 어피니티/안티-어피니티 규칙보다 우선적으로 적용(overrule)된다.
nodeName 을 사용해서 노드를 선택할 때의 몇 가지 제한은 다음과 같다.
만약 명명된 노드가 없으면, 파드가 실행되지 않고
따라서 자동으로 삭제될 수 있다.
만약 명명된 노드에 파드를 수용할 수 있는
리소스가 없는 경우 파드가 실패하고, 그 이유는 다음과 같이 표시된다.
예: OutOfmemory 또는 OutOfcpu.
_토폴로지 분배 제약 조건_을 사용하여 지역(regions), 영역(zones), 노드 또는 사용자가 정의한 다른 토폴로지 도메인과 같은 장애 도메인 사이에서 파드가 클러스터 전체에 분산되는 방식을 제어할 수 있다. 성능, 예상 가용성 또는 전체 활용도를 개선하기 위해 이 작업을 수행할 수 있다.
노드 상에서 파드를 구동할 때, 파드는 그 자체적으로 많은 시스템 리소스를 사용한다.
이러한 리소스는 파드 내의 컨테이너들을 구동하기 위한 리소스 이외에 추가적으로 필요한 것이다.
쿠버네티스에서, 파드 오버헤드 는 리소스 요청 및 상한 외에도
파드의 인프라에 의해 소비되는 리소스를 계산하는 방법 중 하나이다.
쿠버네티스에서 파드의 오버헤드는 파드의
런타임클래스 와 관련된 오버헤드에 따라
어드미션
이 수행될 때 지정된다.
파드를 노드에 스케줄링할 때, 컨테이너 리소스 요청의 합 뿐만 아니라 파드의 오버헤드도 함께 고려된다.
마찬가지로, kubelet은 파드의 cgroups 크기를 변경하거나 파드의 축출 등급을 부여할 때에도
파드의 오버헤드를 포함하여 고려한다.
파드 오버헤드 환경 설정하기
overhead 필드를 정의하는 RuntimeClass 가 사용되고 있는지 확인해야 한다.
사용 예제
파드 오버헤드를 활용하려면, overhead 필드를 정의하는 런타임클래스가 필요하다.
예를 들어, 가상 머신 및 게스트 OS에 대하여 파드 당 120 MiB를 사용하는
가상화 컨테이너 런타임의 런타임클래스의 경우 다음과 같이 정의 할 수 있다.
어드미션 수행 시에, 어드미션 컨트롤러는
런타임클래스에 기술된 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을 생성한다.
기본 컨테이너 런타임이 만들어내는 컨테이너들은 이 파드 안에 존재한다.
만약 각 컨테이너에 대하여 리소스 상한 제한이 걸려있으면
(제한이 걸려있는 보장된(Guaranteed) Qos 또는 향상 가능한(Burstable) 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}'
결과를 보면 2250 m의 CPU와 320 MiB의 메모리가 리소스로 요청되었다. 여기에는 파드 오버헤드가 포함되어 있다.
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은 하나의 디렉터리이다.
아래의 특정한 경우에, 파드 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 에서 사용할 수 있어,
파드 오버헤드가 사용되는 시기를 식별하고, 정의된 오버헤드로 실행되는 워크로드의 안정성을 관찰할 수 있다.
파드는 생성되면 스케줄링 될 준비가 된 것으로 간주된다. 쿠버네티스 스케줄러는
모든 Pending 중인 파드를 배치할 노드를 찾기 위한 철저한 조사 과정을 수행한다. 그러나 일부 파드는
오랜 기간 동안 "필수 리소스 누락" 상태에 머물 수 있다.
이러한 파드는 실제로 스케줄러(그리고 클러스터 오토스케일러와 같은 다운스트림 통합자)
불필요한 방식으로 작동하게 만들 수 있다.
파드의 .spec.schedulingGates를 지정하거나 제거함으로써,
파드가 스케줄링 될 준비가 되는 시기를 제어할 수 있다.
파드 스케줄링게이트(schedulingGates) 구성하기
스케줄링게이트(schedulingGates) 필드는 문자열 목록을 포함하며, 각 문자열 리터럴은
파드가 스케줄링 가능한 것으로 간주되기 전에 충족해야 하는 기준으로 인식된다. 이 필드는
파드가 생성될 때만 초기화할 수 있다(클라이언트에 의해 생성되거나, 어드미션 중에 변경될 때).
생성 후 각 스케줄링게이트는 임의의 순서로 제거될 수 있지만, 새로운 스케줄링게이트의 추가는 허용되지 않는다.
사용 예시
파드가 스케줄링 될 준비가 되지 않았다고 표시하려면, 다음과 같이 하나 이상의 스케줄링 게이트를 생성하여 표시할 수 있다.
schedulingGates가 지워졌는지 확인하려면 다음 명령을 실행하여 확인할 수 있다.
kubectl get pod test-pod -o jsonpath='{.spec.schedulingGates}'
출력은 비어 있을 것이다. 그리고 다음 명령을 실행하여 최신 상태를 확인할 수 있다.
kubectl get pod test-pod -o wide
test-pod가 CPU/메모리 리소스를 요청하지 않았기 때문에, 이 파드의 상태는 이전의 SchedulingGated에서
Running으로 전환됐을 것이다.
NAME READY STATUS RESTARTS AGE IP NODE
test-pod 1/1 Running 0 15s 10.0.0.4 node-2
가시성(Observability)
스케줄링을 시도했지만 스케줄링할 수 없다고 클레임되거나 스케줄링할 준비가 되지 않은 것으로 명시적으로 표시된 파드를
구분하기 위해 scheduler_pending_pods 메트릭은 ”gated"라는 새로운 레이블과 함께 제공된다.
scheduler_pending_pods{queue="gated"}를 사용하여 메트릭 결과를 확인할 수 있다.
사용자는 토폴로지 분배 제약 조건 을 사용하여
지역(region), 존(zone), 노드 및 기타 사용자 정의 토폴로지 도메인과 같이
장애 도메인으로 설정된 클러스터에 걸쳐
파드가 분배되는 방식을 제어할 수 있다.
이를 통해 고가용성뿐만 아니라 효율적인 리소스 활용의 목적을 이루는 데에도 도움이 된다.
클러스터-수준 제약을 기본값으로 설정하거나,
개별 워크로드마다 각각의 토폴로지 분배 제약 조건을 설정할 수 있다.
동기(motivation)
최대 20 노드로 이루어진 클러스터가 있고, 레플리카 수를 자동으로 조절하는
워크로드를
실행해야 하는 상황을 가정해 보자.
파드의 수는 2개 정도로 적을 수도 있고, 15개 정도로 많을 수도 있다.
파드가 2개만 있는 상황에서는, 해당 파드들이 동일 노드에서 실행되는 것은 원치 않을 것이다.
단일 노드 장애(single node failure)로 인해
전체 워크로드가 오프라인이 될 수 있기 때문이다.
이러한 기본적 사용 뿐만 아니라, 고가용성(high availability) 및
클러스터 활용(cluster utilization)으로부터 오는 장점을 워크로드가 누리도록 하는 고급 사용 예시도 존재한다.
워크로드를 스케일 업 하여 더 많은 파드를 실행함에 따라, 중요성이 부각되는 다른 요소도 존재한다.
3개의 노드가 각각 5개의 파드를 실행하는 경우를 가정하자.
각 노드는 5개의 레플리카를 실행하기에 충분한 성능을 갖고 있다.
하지만, 이 워크로드와 통신하는 클라이언트들은
3개의 서로 다른 데이터센터(또는 인프라스트럭처 존(zone))에 걸쳐 존재한다.
이제 단일 노드 장애에 대해서는 덜 걱정해도 되지만, 지연 시간(latency)이 증가하고,
서로 다른 존 간에 네트워크 트래픽을 전송하기 위해 네트워크 비용을 지불해야 한다.
정상적인 동작 중에는 각 인프라스트럭처 존에
비슷한 수의 레플리카가 스케줄되고,
클러스터에 문제가 있는 경우 스스로 치유하도록 설정할 수 있다.
파드 토폴로지 분배 제약 조건은 이러한 설정을 할 수 있도록 하는 선언적인 방법을 제공한다.
topologySpreadConstraints 필드
파드 API에 spec.topologySpreadConstraints 필드가 있다. 이 필드는 다음과 같이
쓰인다.
---apiVersion:v1kind:Podmetadata:name:example-podspec:# 토폴로지 분배 제약 조건을 구성한다.topologySpreadConstraints:- maxSkew:<integer>minDomains:<integer># 선택 사항이며, v1.25에서 베타 기능으로 도입되었다.topologyKey:<string>whenUnsatisfiable:<string>labelSelector:<object>matchLabelKeys:<list># 선택 사항이며, v1.25에서 알파 기능으로 도입되었다.nodeAffinityPolicy:[Honor|Ignore]# 선택 사항이며, v1.26에서 베타 기능으로 도입되었다.nodeTaintsPolicy:[Honor|Ignore]# 선택 사항이며, v1.26에서 베타 기능으로 도입되었다.### 파드의 다른 필드가 이 아래에 오게 된다.
kubectl explain Pod.spec.topologySpreadConstraints 명령을 실행하거나 파드에 관한 API 레퍼런스의
스케줄링 섹션을 참조해서 이 필드에 대해 좀 더 알아볼 수 있다.
분배 제약 조건 정의
하나 또는 다중 topologySpreadConstraint 를 정의하여,
kube-scheduler가 어떻게 클러스터 내에서 기존 파드와의 관계를 고려하며
새로운 파드를 배치할지를 지시할 수 있다. 각 필드는 다음과 같다.
maxSkew 는 파드가 균등하지 않게 분산될 수 있는 정도를 나타낸다.
이 필드는 필수이며, 0 보다는 커야 한다.
이 필드 값의 의미는 whenUnsatisfiable 의 값에 따라 다르다.
whenUnsatisfiable: DoNotSchedule을 선택했다면,
maxSkew는 대상 토폴로지에서 일치하는 파드 수와
전역 최솟값(global minimum) (적절한 도메인 내에서 일치하는 파드의 최소 수, 또는 적절한 도메인의 수가 minDomains보다 작은 경우에는 0)
사이의 최대 허용 차이를 나타낸다.
예를 들어, 3개의 존에 각각 2, 2, 1개의 일치하는 파드가 있으면,
maxSkew는 1로 설정되고 전역 최솟값은 1로 설정된다.
whenUnsatisfiable: ScheduleAnyway를 선택하면,
스케줄러는 차이(skew)를 줄이는 데 도움이 되는 토폴로지에 더 높은 우선 순위를 부여한다.
minDomains 는 적합한(eligible) 도메인의 최소 수를 나타낸다. 이 필드는 선택 사항이다.
도메인은 토폴로지의 특정 인스턴스 중 하나이다.
도메인의 노드가 노드 셀렉터에 매치되면 그 도메인은 적합한 도메인이다.
참고:
minDomains 필드는 1.25에서 기본적으로 사용하도록 설정된 베타 필드이다. 사용을 원하지 않을 경우
MinDomainsInPodTopologySpread기능 게이트를 비활성화한다.
minDomains 값을 명시하는 경우, 이 값은 0보다 커야 한다.
minDomains는 whenUnsatisfiable: DoNotSchedule일 때에만 지정할 수 있다.
매치되는 토폴로지 키의 적합한 도메인 수가 minDomains보다 적으면,
파드 토폴로지 스프레드는 전역 최솟값을 0으로 간주한 뒤, skew 계산을 수행한다.
전역 최솟값은 적합한 도메인 내에 매치되는 파드의 최소 수이며,
적합한 도메인 수가 minDomains보다 적은 경우에는 0이다.
매치되는 토폴로지 키의 적합한 도메인 수가 minDomains보다 크거나 같으면,
이 값은 스케줄링에 영향을 미치지 않는다.
minDomains를 명시하지 않으면, 분배 제약 조건은 minDomains가 1이라고 가정하고 동작한다.
topologyKey 는 노드 레이블의 키(key)이다. 이 키와 동일한 값을 가진
레이블이 있는 노드는 동일한 토폴로지에 있는 것으로 간주된다.
토폴로지의 각 인스턴스(즉, <키, 값> 쌍)를 도메인이라고 한다. 스케줄러는
각 도메인에 균형잡힌 수의 파드를 배치하려고 시도할 것이다.
또한, 노드가 nodeAffinityPolicy 및 nodeTaintsPolicy의 요구 사항을 충족하는 도메인을
적절한 도메인이라고 정의한다.
whenUnsatisfiable 는 분산 제약 조건을 만족하지 않을 경우에 파드를 처리하는 방법을 나타낸다.
DoNotSchedule(기본값)은 스케줄러에 스케줄링을 하지 말라고 알려준다.
ScheduleAnyway는 스케줄러에게 차이(skew)를 최소화하는 노드에 높은 우선 순위를 부여하면서, 스케줄링을 계속하도록 지시한다.
labelSelector 는 일치하는 파드를 찾는 데 사용된다.
이 레이블 셀렉터와 일치하는 파드의 수를 계산하여
해당 토폴로지 도메인에 속할 파드의 수를 결정한다.
자세한 내용은
레이블 셀렉터를 참조한다.
matchLabelKeys 는 분배도(spreading)가 계산될 파드를 선택하기 위한 파드 레이블
키 목록이다. 키는 파드 레이블에서 값을 조회하는 데 사용되며, 이러한 키-값 레이블은 labelSelector와 AND 처리되어 들어오는 파드(incoming pod)에 대해 분배도가 계산될 기존 파드 그룹의 선택에 사용된다. 파드 레이블에 없는 키는 무시된다. null 또는 비어 있는 목록은 labelSelector와만 일치함을 의미한다.
matchLabelKeys를 사용하면, 사용자는 다른 리비전 간에 pod.spec을 업데이트할 필요가 없다. 컨트롤러/오퍼레이터는 다른 리비전에 대해 동일한 label키에 다른 값을 설정하기만 하면 된다. 스케줄러는 matchLabelKeys를 기준으로 값을 자동으로 가정할 것이다. 예를 들어 사용자가 디플로이먼트를 사용하는 경우, 디플로이먼트 컨트롤러에 의해 자동으로 추가되는 pod-template-hash로 키가 지정된 레이블을 사용함으로써 단일 디플로이먼트에서 서로 다른 리비전을 구별할 수 있다.
matchLabelKeys 필드는 1.25에서 추가된 알파 필드이다. 이 필드를 사용하려면
MatchLabelKeysInPodTopologySpread기능 게이트
를 활성화시켜야 한다.
nodeAffinityPolicy는 파드 토폴로지의 스프레드 스큐(spread skew)를 계산할 때
파드의 nodeAffinity/nodeSelector를 다루는 방법을 나타낸다. 옵션은 다음과 같다.
Honor: nodeAffinity/nodeSelector와 일치하는 노드만 계산에 포함된다.
Ignore: nodeAffinity/nodeSelector는 무시된다. 모든 노드가 계산에 포함된다.
옵션의 값이 null일 경우, Honor 정책과 동일하게 동작한다.
참고:
nodeAffinityPolicy 필드는 베타 필드이고 1.26에서 기본적으로 활성화되어 있다. 이 필드를 비활성화하려면
NodeInclusionPolicyInPodTopologySpread기능 게이트
를 비활성화하면 된다.
nodeTaintsPolicy는 파드 토폴로지의 스프레드 스큐(spread skew)를 계산할 때 노드 테인트(taint)를
다루는 방법을 나타낸다. 옵션은 다음과 같다.
Honor: 테인트가 없는 노드, 그리고 노드가 톨러레이션이 있는 들어오는 파드(incoming pod)를 위한 테인트가 설정된
노드가 포함된다.
Ignore: 노드 테인트는 무시된다. 모든 노드가 포함된다.
옵션의 값이 null일 경우, Ignore 정책과 동일하게 동작한다.
참고:
nodeTaintsPolicy 필드는 베타 필드이고 1.26에서 기본적으로 활성화되어 있다. 이 필드를 비활성화하려면
NodeInclusionPolicyInPodTopologySpread기능 게이트
를 비활성화하면 된다.
파드에 2개 이상의 topologySpreadConstraint가 정의되어 있으면,
각 제약 조건은 논리 AND 연산으로 조합되며,
kube-scheduler는 새로운 파드의 모든 제약 조건을 만족하는 노드를 찾는다.
노드 레이블
토폴로지 분배 제약 조건은 노드 레이블을 이용하여
각 노드가 속한 토폴로지 도메인(들)을 인식한다.
예를 들어, 노드가 다음과 같은 레이블을 가질 수 있다.
region:us-east-1zone:us-east-1a
참고:
간결함을 위해, 이 예시에서는
잘 알려진 레이블 키인
topology.kubernetes.io/zone 및 topology.kubernetes.io/region을 사용하지 않는다.
그러나 그렇더라도, 아래에 등장하는 프라이빗(비공인된) 레이블 키인 region 및 zone보다는
위와 같은 공인된 레이블 키를 사용하는 것을 권장한다.
다양한 각 상황에 대해, 프라이빗 레이블 키의 의미가
모두 우리의 생각과 같을 것이라고 가정할 수는 없다.
각각 다음과 같은 레이블을 갖는 4개의 노드로 구성된 클러스터가 있다고 가정한다.
NAME STATUS ROLES AGE VERSION LABELS
node1 Ready <none> 4m26s v1.16.0 node=node1,zone=zoneA
node2 Ready <none> 3m58s v1.16.0 node=node2,zone=zoneA
node3 Ready <none> 3m17s v1.16.0 node=node3,zone=zoneB
node4 Ready <none> 2m43s v1.16.0 node=node4,zone=zoneB
그러면 클러스터는 논리적으로 다음과 같이 보이게 된다.
일관성(consistency)
그룹 내의 모든 파드에는 동일한 파드 토폴로지 분배 제약 조건을 설정해야 한다.
일반적으로, 디플로이먼트와 같은 워크로드 컨트롤러를 사용하는 경우,
파드 템플릿이 이 사항을 담당한다.
여러 분배 제약 조건을 혼합하는 경우, 쿠버네티스는 해당 필드의 API 정의를 따르기는 하지만,
동작이 복잡해질 수 있고 트러블슈팅이 덜 직관적이게 된다.
동일한 토폴로지 도메인(예: 클라우드 공급자 리전)에 있는 모든 노드가
일관적으로 레이블되도록 하는 메카니즘이 필요하다.
각 노드를 수동으로 레이블하는 것을 피하기 위해,
대부분의 클러스터는 topology.kubernetes.io/hostname와 같은 잘 알려진 레이블을 자동으로 붙인다.
이용하려는 클러스터가 이를 지원하는지 확인해 본다.
토폴로지 분배 제약 조건 예시
예시: 단수 토폴로지 분배 제약 조건
foo:bar 가 레이블된 3개의 파드가 4개 노드를 가지는 클러스터의
node1, node2 및 node3에 각각 위치한다고 가정한다.
신규 파드가 기존 파드와 함께 여러 존에 걸쳐 균등하게 분배되도록 하려면,
다음과 같은 매니페스트를 사용할 수 있다.
위의 매니페스트에서, topologyKey: zone이 의미하는 것은 zone: <any value>로 레이블된
노드에 대해서만 균등한 분배를 적용한다는 뜻이다(zone 레이블이 없는 노드는 무시된다).
whenUnsatisfiable: DoNotSchedule 필드는 만약 스케줄러가 신규 파드에 대해 제약 조건을
만족하는 스케줄링 방법을 찾지 못하면 이 신규 파드를 pending 상태로 유지하도록 한다.
만약 스케줄러가 이 신규 파드를 A 존에 배치하면 파드 분포는 [3, 1]이 된다.
이는 곧 실제 차이(skew)가 2(3-1)임을 나타내는데, 이는 maxSkew: 1을 위반하게 된다.
이 예시에서 제약 조건과 상황을 만족하려면,
신규 파드는 B 존의 노드에만 배치될 수 있다.
또는
사용자는 파드 스펙을 조정하여 다음과 같은 다양한 요구사항을 충족할 수 있다.
maxSkew 를 더 큰 값(예: 2)으로 변경하여
신규 파드가 A 존에도 배치할 수 있도록 한다.
topologyKey를 node로 변경하여 파드가 존이 아닌 노드에 걸쳐 고르게 분산되도록 한다.
위의 예시에서, 만약 maxSkew를 1로 유지한다면,
신규 파드는 오직 node4에만 배치될 수 있다.
whenUnsatisfiable: DoNotSchedule를 whenUnsatisfiable: ScheduleAnyway로 변경하면
신규 파드가 항상 스케줄링되도록 보장할 수 있다(다른 스케줄링 API를 충족한다는 가정 하에).
그러나, 매칭되는 파드의 수가 적은 토폴로지 도메인에 배치되는 것이 선호된다.
(이 선호도는 리소스 사용 비율과 같은 다른 내부 스케줄링 우선순위와 함께 정규화된다는 것을
알아두자.)
예시: 다중 토폴로지 분배 제약 조건
이 예시는 위의 예시에 기반한다. foo:bar 가 레이블된 3개의 파드가
4개 노드를 가지는 클러스터의 node1, node2 그리고 node3에 각각 위치한다고 가정한다.
사용자는 2개의 토폴로지 분배 제약 조건을 사용하여
노드 및 존 기반으로 파드가 분배되도록 제어할 수 있다.
이 경우에는, 첫 번째 제약 조건에 부합하려면, 신규 파드는 오직 B 존에만 배치될 수 있다.
한편 두 번째 제약 조건에 따르면 신규 파드는 오직 node4 노드에만 배치될 수 있다.
스케줄러는 모든 정의된 제약 조건을 만족하는 선택지만 고려하므로,
유효한 유일한 선택지는 신규 파드를 node4에 배치하는 것이다.
예시: 상충하는 토폴로지 분배 제약 조건
다중 제약 조건이 서로 충돌할 수 있다. 3개의 노드를 가지는 클러스터 하나가 2개의 존에 걸쳐 있다고 가정한다.
만약
two-constraints.yaml
(위 예시의 매니페스트)을
이 클러스터에 적용하면,
mypod 파드가 Pending 상태로 유지되는 것을 볼 수 있을 것이다.
이러한 이유는, 첫 번째 제약 조건을 충족하려면 mypod 파드는 B 존에만 배치될 수 있지만,
두 번째 제약 조건에 따르면 mypod 파드는 node2 노드에만 스케줄링될 수 있기 때문이다.
두 제약 조건의 교집합이 공집합이므로, 스케줄러는 파드를 배치할 수 없다.
이 상황을 극복하기 위해, maxSkew 값을 증가시키거나,
제약 조건 중 하나를 whenUnsatisfiable: ScheduleAnyway 를 사용하도록 수정할 수 있다.
상황에 따라, 기존 파드를 수동으로 삭제할 수도 있다.
예를 들어, 버그픽스 롤아웃이 왜 진행되지 않는지 트러블슈팅하는 상황에서 이 방법을 활용할 수 있다.
노드 어피니티(Affinity) 및 노드 셀렉터(Selector)와의 상호 작용
스케줄러는 신규 파드에 spec.nodeSelector 또는 spec.affinity.nodeAffinity가 정의되어 있는 경우,
부합하지 않는 노드들을 차이(skew) 계산에서 생략한다.
예시: 토폴로지 분배 제약조건과 노드 어피니티
5개의 노드를 가지는 클러스터가 A 존에서 C 존까지 걸쳐 있다고 가정한다.
그리고 C 존은 파드 배포에서 제외해야 한다는 사실을 사용자가 알고 있다고 가정한다.
이 경우에, 다음과 같이 매니페스트를 작성하여, mypod 파드가 C 존 대신 B 존에 배치되도록 할 수 있다.
유사하게, 쿠버네티스는 spec.nodeSelector 필드도 고려한다.
topologySpreadConstraints[*].topologyKey 가 없는 노드는 무시된다.
이것은 다음을 의미한다.
이러한 노드에 위치한 파드는 maxSkew 계산에 영향을 미치지 않는다.
위의 예시에서, node1 노드는 "zone" 레이블을 가지고 있지 않다고 가정하면,
파드 2개는 무시될 것이고, 이런 이유로 신규 파드는 A 존으로 스케줄된다.
신규 파드는 이런 종류의 노드에 스케줄 될 기회가 없다.
위의 예시에서, 잘못 타이핑된zone-typo: zoneC 레이블을 갖는 node5 노드가 있다고 가정하자(zone 레이블은 설정되지 않음).
node5 노드가 클러스터에 편입되면, 해당 노드는 무시되고
이 워크로드의 파드는 해당 노드에 스케줄링되지 않는다.
신규 파드의 topologySpreadConstraints[*].labelSelector가 자체 레이블과 일치하지 않을 경우 어떻게 되는지 알고 있어야 한다.
위의 예시에서, 신규 파드의 레이블을 제거해도,
제약 조건이 여전히 충족되기 때문에 이 파드는 B 존의 노드에 배치될 수 있다.
그러나, 배치 이후에도 클러스터의 불균형 정도는 변경되지 않는다.
여전히 A 존은 foo: bar 레이블을 가지는 2개의 파드를 가지고 있고,
B 존도 foo: bar 레이블을 가지는 1개의 파드를 가지고 있다.
만약 결과가 예상과 다르다면,
워크로드의 topologySpreadConstraints[*].labelSelector를 파드 템플릿의 레이블과 일치하도록 업데이트한다.
클러스터 수준의 기본 제약 조건
클러스터에 대한 기본 토폴로지 분배 제약 조건을 설정할 수 있다.
기본 토폴로지 분배 제약 조건은 다음과 같은 경우에만 파드에 적용된다.
.spec.topologySpreadConstraints 에 어떠한 제약 조건도 정의되어 있지 않는 경우.
서비스, 레플리카셋(ReplicaSet), 스테이트풀셋(StatefulSet), 또는 레플리케이션컨트롤러(ReplicationController)에 속해있는 경우.
기본 제약 조건은
스케줄링 프로파일내의 플러그인 인자 중 하나로 설정할 수 있다.
제약 조건은 위에서 설명한 것과 동일한 API를 이용하여 정의되는데,
다만 labelSelector는 비워 두어야 한다.
셀렉터는 파드가 속한 서비스, 레플리카셋, 스테이트풀셋, 또는 레플리케이션 컨트롤러를 바탕으로 계산한다.
파드어피니티(PodAffinity) 및 파드안티어피니티(PodAntiAffinity)와의 비교
쿠버네티스에서, 파드간 어피니티 및 안티 어피니티는
파드가 다른 파드에 서로 어떤 연관 관계를 지니며 스케줄링되는지를 제어하며,
이는 파드들이 서로 더 밀집되도록 하거나 흩어지도록 하는 것을 의미한다.
podAffinity
파드를 끌어당긴다. 조건이 충족되는 토폴로지 도메인에는
원하는 수의 파드를 얼마든지 채울 수 있다.
podAntiAffinity
파드를 밀어낸다.
이를 requiredDuringSchedulingIgnoredDuringExecution 모드로 설정하면
각 토폴로지 도메인에는 하나의 파드만 스케줄링될 수 있다.
반면 preferredDuringSchedulingIgnoredDuringExecution로 설정하면 제약 조건이 강제성을 잃게 된다.
더 세밀한 제어를 위해,
토폴로지 분배 제약 조건을 지정하여 다양한 토폴로지 도메인에 파드를 분배할 수 있고,
이를 통해 고 가용성 또는 비용 절감을 달성할 수 있다.
이는 또한 워크로드의 롤링 업데이트와 레플리카의 원활한 스케일링 아웃에 도움이 될 수 있다.
더 자세한 내용은
파드 토폴로지 분배 제약 조건에 대한 개선 제안의
동기(motivation) 섹션을 참고한다.
알려진 제한사항
파드가 제거된 이후에도 제약 조건이 계속 충족된다는 보장은 없다.
예를 들어 디플로이먼트를 스케일링 다운하면 그 결과로 파드의 분포가 불균형해질 수 있다.
스케줄러는 클러스터가 갖는 모든 존 또는 다른 토폴로지 도메인에 대한 사전 지식을 갖고 있지 않다.
이 정보들은 클러스터의 기존 노드로부터 획득된다.
이로 인해 오토스케일된 클러스터에서 문제가 발생할 수 있는데,
예를 들어 노드 풀(또는 노드 그룹)이 0으로 스케일 다운되고,
클러스터가 다시 스케일 업 되기를 기대하는 경우,
해당 토폴로지 도메인은 적어도 1개의 노드가 존재하기 전에는 고려가 되지 않을 것이다.
이를 극복하기 위해, 파드 토폴로지 분배 제약 조건과
전반적인 토폴로지 도메인 집합에 대한 정보를 인지하고 동작하는
클러스터 오토스케일링 도구를 이용할 수 있다.
톨러레이션은, 키와 이펙트가 동일한 경우에 테인트와 "일치"한다. 그리고 다음의 경우에도 마찬가지다.
operator 가 Exists 인 경우(이 경우 value 를 지정하지 않아야 함), 또는
operator 는 Equal 이고 value 는 value 로 같다.
참고:
두 가지 특별한 경우가 있다.
operator Exists 가 있는 비어있는 key 는 모든 키, 값 및 이펙트와 일치하므로
모든 것이 톨러레이션 된다.
비어있는 effect 는 모든 이펙트를 키 key1 와 일치시킨다.
위의 예는 NoSchedule 의 effect 를 사용했다. 또는, PreferNoSchedule 의 effect 를 사용할 수 있다.
이것은 NoSchedule 의 "기본 설정(preference)" 또는 "소프트(soft)" 버전이다. 시스템은 노드의 테인트를 허용하지 않는
파드를 배치하지 않으려고 시도 하지만, 필요하지는 않다. 세 번째 종류의 effect 는
나중에 설명할 NoExecute 이다.
동일한 노드에 여러 테인트를, 동일한 파드에 여러 톨러레이션을 둘 수 있다.
쿠버네티스가 여러 테인트 및 톨러레이션을 처리하는 방식은 필터와 같다.
모든 노드의 테인트로 시작한 다음, 파드에 일치하는 톨러레이션이 있는 것을 무시한다.
무시되지 않은 나머지 테인트는 파드에 표시된 이펙트를 가진다. 특히,
NoSchedule 이펙트가 있는 무시되지 않은 테인트가 하나 이상 있으면 쿠버네티스는 해당 노드에
파드를 스케줄하지 않는다.
NoSchedule 이펙트가 있는 무시되지 않은 테인트가 없지만 PreferNoSchedule 이펙트가 있는
무시되지 않은 테인트가 하나 이상 있으면 쿠버네티스는 파드를 노드에 스케쥴하지 않으려고 시도 한다
NoExecute 이펙트가 있는 무시되지 않은 테인트가 하나 이상 있으면
파드가 노드에서 축출되고(노드에서 이미 실행 중인 경우), 노드에서
스케줄되지 않는다(아직 실행되지 않은 경우).
이 경우, 세 번째 테인트와 일치하는 톨러레이션이 없기 때문에, 파드는
노드에 스케줄 될 수 없다. 그러나 세 번째 테인트가 파드에서 용인되지 않는 세 가지 중
하나만 있기 때문에, 테인트가 추가될 때 노드에서 이미 실행 중인 경우,
파드는 계속 실행할 수 있다.
일반적으로, NoExecute 이펙트가 있는 테인트가 노드에 추가되면, 테인트를
용인하지 않는 파드는 즉시 축출되고, 테인트를 용인하는 파드는
축출되지 않는다. 그러나 NoExecute 이펙트가 있는 톨러레이션은
테인트가 추가된 후 파드가 노드에 바인딩된 시간을 지정하는
선택적 tolerationSeconds 필드를 지정할 수 있다. 예를 들어,
이것은 이 파드가 실행 중이고 일치하는 테인트가 노드에 추가되면,
파드는 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 어드미션 컨트롤러가
파드에 올바른 톨러레이션을 자동으로 추가하고 해당 파드는
특별한 하드웨어 노드에서 스케줄된다. 이렇게 하면 이러한 특별한 하드웨어 노드가
해당 하드웨어를 요청하는 파드가 전용으로 사용하며 파드에 톨러레이션을
수동으로 추가할 필요가 없다.
테인트 기반 축출: 노드 문제가 있을 때 파드별로
구성 가능한 축출 동작은 다음 섹션에서 설명한다.
테인트 기반 축출
기능 상태: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/memory-pressure: 노드에 메모리 할당 압박이 있다.
node.kubernetes.io/disk-pressure: 노드에 디스크 할당 압박이 있다.
node.kubernetes.io/pid-pressure: 노드에 PID 할당 압박이 있다.
node.kubernetes.io/network-unavailable: 노드의 네트워크를 사용할 수 없다.
node.kubernetes.io/unschedulable: 노드를 스케줄할 수 없다.
node.cloudprovider.kubernetes.io/uninitialized: "외부" 클라우드 공급자로
kubelet을 시작하면, 이 테인트가 노드에서 사용 불가능으로 표시되도록
설정된다. 클라우드-컨트롤러-관리자의 컨트롤러가 이 노드를 초기화하면,
kubelet이 이 테인트를 제거한다.
노드가 축출될 경우, 노드 컨트롤러 또는 kubelet은 NoExecute 이펙트로 관련
테인트를 추가한다. 장애 상태가 정상으로 돌아오면 kubelet 또는 노드 컨트롤러가
관련 테인트를 제거할 수 있다.
참고:
콘트롤 플레인은 노드에 새 테인트를 추가하는 비율을 제한한다.
이 비율-제한은 많은 노드가 동시에 도달할 수 없을 때(예를 들어, 네트워크 중단으로)
트리거될 축출 개수를 관리한다.
이 기능을 tolerationSeconds 와 함께 사용하면, 파드에서
이러한 문제 중 하나 또는 둘 다가 있는 노드에 바인딩된 기간을 지정할 수 있다.
예를 들어, 로컬 상태가 많은 애플리케이션은 네트워크 분할의 장애에서
네트워크가 복구된 후에 파드가 축출되는 것을 피하기 위해
오랫동안 노드에 바인딩된 상태를 유지하려고 할 수 있다.
이 경우 파드가 사용하는 톨러레이션은 다음과 같다.
쿠버네티스는 사용자나 컨트롤러에서 명시적으로 설정하지 않았다면, 자동으로
node.kubernetes.io/not-ready 와 node.kubernetes.io/unreachable 에 대해
tolerationSeconds=300 으로
톨러레이션을 추가한다.
자동으로 추가된 이 톨러레이션은 이러한 문제 중 하나가 감지된 후 5분 동안
파드가 노드에 바인딩된 상태를 유지함을 의미한다.
데몬셋 파드는 tolerationSeconds 가 없는
다음 테인트에 대해 NoExecute 톨러레이션를 가지고 생성된다.
node.kubernetes.io/unreachable
node.kubernetes.io/not-ready
이렇게 하면 이러한 문제로 인해 데몬셋 파드가 축출되지 않는다.
컨디션(condition)을 기준으로 노드 테인트하기
컨트롤 플레인은 노드 컨트롤러를 이용하여
노드 컨디션에 대한
NoSchedule 효과를 사용하여 자동으로 테인트를 생성한다.
스케줄러는 스케줄링 결정을 내릴 때 노드 컨디션을 확인하는 것이 아니라 테인트를 확인한다.
이렇게 하면 노드 컨디션이 스케줄링에 직접적인 영향을 주지 않는다.
예를 들어 DiskPressure 노드 컨디션이 활성화된 경우
컨트롤 플레인은 node.kubernetes.io/disk-pressure 테인트를 추가하고 영향을 받는 노드에 새 파드를 할당하지 않는다.
MemoryPressure 노드 컨디션이 활성화되면
컨트롤 플레인이 node.kubernetes.io/memory-pressure 테인트를 추가한다.
새로 생성된 파드에 파드 톨러레이션을 추가하여 노드 컨디션을 무시하도록 할 수 있다.
또한 컨트롤 플레인은 BestEffort 이외의
QoS 클래스를 가지는 파드에
node.kubernetes.io/memory-pressure 톨러레이션을 추가한다.
이는 쿠버네티스가 Guaranteed 또는 Burstable QoS 클래스를 갖는 파드(메모리 요청이 설정되지 않은 파드 포함)를
마치 그 파드들이 메모리 압박에 대처 가능한 것처럼 다루는 반면,
새로운 BestEffort 파드는 영향을 받는 노드에 할당하지 않기 때문이다.
데몬셋 컨트롤러는 다음의 NoSchedule 톨러레이션을
모든 데몬에 자동으로 추가하여, 데몬셋이 중단되는 것을 방지한다.
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는
더 실행 가능한 노드를 찾는 검색을 중지하고
스코어링 단계를 진행한다.
percentageOfNodesToScore는 1과 100 사이의 값이어야 하며
기본값은 클러스터 크기에 따라 계산된다. 또한 100 노드로 하드 코딩된
최솟값도 있다.
참고:
클러스터에서 적합한 노드가 100 미만인 경우, 스케줄러는 여전히
모든 노드를 확인한다. 그 이유는 스케줄러가 탐색을 조기 중단하기에는 적합한
노드의 수가 충분하지 않기 때문이다.
규모가 작은 클러스터에서는 percentageOfNodesToScore 에 낮은 값을 설정하면,
비슷한 이유로 변경 사항이 거의 또는 전혀 영향을 미치지 않게 된다.
클러스터에 수백 개 이하의 노드가 있는 경우 이 구성 옵션을
기본값으로 둔다. 이는 변경사항을 적용하더라도 스케줄러의
성능이 크게 향상되지 않는다.
이 값을 세팅할 때 중요하고 자세한 사항은, 클러스터에서
적은 수의 노드에 대해서만 적합성을 확인하면, 주어진 파드에 대해서
일부 노드의 점수는 측정이되지 않는다는 것이다. 결과적으로, 주어진 파드를 실행하는데
가장 높은 점수를 가질 가능성이 있는 노드가 점수 측정 단계로 조차 넘어가지
않을 수 있다. 이것은 파드의 이상적인 배치보다 낮은 결과를 초래할 것이다.
percentageOfNodesToScore 를 매우 낮게 설정해서 kube-scheduler가
파드 배치 결정을 잘못 내리지 않도록 해야 한다. 스케줄러의 처리량에
대해 애플리케이션이 중요하고 노드 점수가 중요하지 않은 경우가 아니라면
백분율을 10% 미만으로 설정하지 말아야 한다. 즉, 가능한 한
모든 노드에서 파드를 실행하는 것이 좋다.
스케줄러가 노드 탐색을 반복(iterate)하는 방법
이 섹션은 이 특징의 상세한 내부 방식을 이해하고 싶은 사람들을
위해 작성되었다.
클러스터의 모든 노드가 파드 실행 대상으로 고려되어 공정한 기회를
가지도록, 스케줄러는 라운드 로빈(round robin) 방식으로 모든 노드에 대해서 탐색을
반복한다. 모든 노드가 배열에 나열되어 있다고 생각해보자. 스케줄러는 배열의
시작부터 시작하여 percentageOfNodesToScore에 명시된 충분한 수의 노드를
찾을 때까지 적합성을 확인한다. 그 다음 파드에 대해서는, 스케줄러가
이전 파드를 위한 노드 적합성 확인이 마무리된 지점인 노드 배열의 마지막
포인트부터 확인을 재개한다.
만약 노드들이 다중의 영역(zone)에 있다면, 다른 영역에 있는 노드들이 적합성
확인의 대상이 되도록 스케줄러는 다양한 영역에 있는 노드에 대해서
탐색을 반복한다. 예제로, 2개의 영역에 있는 6개의 노드를 생각해보자.
RequestedToCapacityRatio 전략은 사용자가 각 리소스에 대한 가중치와 함께 리소스를 지정하여
용량 대비 요청 비율을 기반으로 노드의 점수를 매길 수 있게 한다.
이를 통해 사용자는 적절한 파라미터를 사용하여 확장된 리소스를 빈 팩으로 만들 수 있어
대규모의 클러스터에서 부족한 리소스의 활용도를 향상시킬 수 있다. 이 전략은
할당된 리소스의 구성된 기능에 따라 노드를 선호하게 한다. NodeResourcesFit점수 기능의
RequestedToCapacityRatio 동작은 scoringStrategy필드를
이용하여 제어할 수 있다.
scoringStrategy 필드에서 requestedToCapacityRatio와 resources라는 두 개의 파라미터를
구성할 수 있다. requestedToCapacityRatio파라미터의
shape를 사용하면 utilization과 score 값을 기반으로
최소 요청 혹은 최대 요청된 대로 기능을 조정할 수 있게 한다.
resources 파라미터는 점수를 매길 때 고려할 리소스의 name 과
각 리소스의 가중치를 지정하는 weight 로 구성된다.
다음은 requestedToCapacityRatio 를 이용해
확장된 리소스 intel.com/foo 와 intel.com/bar 에 대한 빈 패킹 동작을
설정하는 구성의 예시이다.
프라이어리티클래스는 프라이어리티클래스 이름에서 우선순위의 정수 값으로의 매핑을
정의하는 네임스페이스가 아닌(non-namespaced) 오브젝트이다. 이름은
프라이어리티클래스 오브젝트의 메타데이터의 name 필드에 지정된다. 값은
필수 value 필드에 지정되어 있다. 값이 클수록, 우선순위가
높다.
프라이어리티클래스 오브젝트의 이름은 유효한
DNS 서브 도메인 이름이어야 하며,
system- 접두사를 붙일 수 없다.
프라이어리티클래스 오브젝트는 10억 이하의 32비트 정수 값을 가질
수 있다. 일반적으로 선점하거나 축출해서는 안되는 중요한 시스템 파드에는 더 큰 숫자가
예약되어 있다. 클러스터 관리자는 원하는 각 매핑에 대해 프라이어리티클래스 오브젝트를
하나씩 생성해야 한다.
프라이어리티클래스에는 globalDefault 와 description 두 개의 선택적인 필드도 있다.
globalDefault 필드는 이 프라이어리티클래스의 값을 priorityClassName 이 없는
파드에 사용해야 함을 나타낸다. 시스템에서 globalDefault 가 true 로 설정된
프라이어리티클래스는 하나만 존재할 수 있다. globalDefault 가 설정된
프라이어리티클래스가 없을 경우, priorityClassName 이 없는 파드의
우선순위는 0이다.
description 필드는 임의의 문자열이다. 이 필드는 이 프라이어리티클래스를 언제
사용해야 하는지를 클러스터 사용자에게 알려주기 위한 것이다.
PodPriority와 기존 클러스터에 대한 참고 사항
이 기능없이 기존 클러스터를 업그레이드 하는 경우, 기존 파드의
우선순위는 사실상 0이다.
globalDefault 가 true 로 설정된 프라이어리티클래스를 추가해도 기존 파드의
우선순위는 변경되지 않는다. 이러한 프라이어리티클래스의 값은
프라이어리티클래스를 추가한 후 생성된 파드에만 사용된다.
프라이어리티클래스를 삭제하면, 삭제된 프라이어리티클래스의 이름을 사용하는
기존 파드는 변경되지 않고 남아있지만, 삭제된 프라이어리티클래스의 이름을
사용하는 파드는 더 이상 생성할 수 없다.
프라이어리티클래스 예제
apiVersion:scheduling.k8s.io/v1kind:PriorityClassmetadata:name:high-priorityvalue:1000000globalDefault:falsedescription:"이 프라이어리티클래스는 XYZ 서비스 파드에만 사용해야 한다."
비-선점 프라이어리티클래스
기능 상태:Kubernetes v1.24 [stable]
preemptionPolicy: Never 를 가진 파드는 낮은 우선순위 파드의 스케줄링 대기열의
앞쪽에 배치되지만,
그 파드는 다른 파드를 축출할 수 없다.
스케줄링 대기 중인 비-선점 파드는 충분한 리소스가 확보되고
스케줄링될 수 있을 때까지
스케줄링 대기열에 대기한다.
다른 파드와 마찬가지로,
비-선점 파드는
스케줄러 백오프(back-off)에 종속된다.
이는 스케줄러가 이러한 파드를 스케줄링하려고 시도하고 스케줄링할 수 없는 경우,
더 적은 횟수로 재시도하여,
우선순위가 낮은 다른 파드를 미리 스케줄링할 수 있음을 의미한다.
비-선점 파드는 다른 우선순위가 높은 파드에 의해
축출될 수 있다.
preemptionPolicy 는 기본값으로 PreemptLowerPriority 로 설정되어,
해당 프라이어리티클래스의 파드가 우선순위가 낮은 파드를 축출할 수
있다(기존의 기본 동작과 동일).
preemptionPolicy 가 Never 로 설정된 경우,
해당 프라이어리티클래스의 파드는 비-선점될 것이다.
예제 유스케이스는 데이터 과학 관련 워크로드이다.
사용자는 다른 워크로드보다 우선순위가 높은 잡(job)을 제출할 수 있지만,
실행 중인 파드를 축출하여 기존의 작업을 삭제하지는 않을 것이다.
클러스터 리소스가 "자연스럽게" 충분히 사용할 수 있게 되면,
preemptionPolicy: Never 의 우선순위가 높은 잡이
다른 대기 중인 파드보다 먼저 스케줄링된다.
비-선점 프라이어리티클래스 예제
apiVersion:scheduling.k8s.io/v1kind:PriorityClassmetadata:name:high-priority-nonpreemptingvalue:1000000preemptionPolicy:NeverglobalDefault:falsedescription:"이 프라이어리티클래스는 다른 파드를 축출하지 않는다."
파드 우선순위
프라이어리티클래스가 하나 이상 있으면, 그것의 명세에서 이들 프라이어리티클래스 이름 중 하나를
지정하는 파드를 생성할 수 있다. 우선순위 어드미션
컨트롤러는 priorityClassName 필드를 사용하고 우선순위의 정수 값을
채운다. 프라이어리티클래스를 찾을 수 없으면, 파드가 거부된다.
다음의 YAML은 이전 예제에서 생성된 프라이어리티클래스를
사용하는 파드 구성의 예이다. 우선순위 어드미션 컨트롤러는
명세를 확인하고 파드의 우선순위를 1000000으로
해석한다.
파드 우선순위가 활성화되면, 스케줄러가 우선순위에 따라 보류 중인
파드를 주문하고 보류 중인 파드는 스케줄링 대기열에서
우선순위가 낮은 다른 보류 중인 파드보다 우선한다. 결과적으로, 스케줄링
요구 사항이 충족되는 경우 우선순위가 더 낮은 파드보다 우선순위가 높은 파드가
더 빨리 스케줄링될 수 있다. 이러한 파드를 스케줄링할 수 없는 경우,
스케줄러는 계속 진행하고 우선순위가 낮은 다른 파드를 스케줄링하려고 한다.
선점
파드가 생성되면, 대기열로 이동하여 스케줄링을 기다린다.
스케줄러가 대기열에서 파드를 선택하여 노드에 스케줄링하려고 한다.
파드의 지정된 모든 요구 사항을 충족하는 노드가 없으면,
보류 중인 파드에 대해 선점 로직이 트리거된다. 보류 중인 파드를 P라 하자.
선점 로직은 P보다 우선순위가 낮은 하나 이상의 파드를 제거하면
해당 노드에서 P를 스케줄링할 수 있는 노드를 찾는다. 이러한
노드가 발견되면, 하나 이상의 우선순위가 낮은 파드가 노드에서 축출된다.
파드가 축출된 후, P는 노드에 스케줄링될 수 있다.
사용자 노출 정보
파드 P가 노드 N에서 하나 이상의 파드를 축출할 경우, 파드 P의 상태 nominatedNodeName
필드는 노드 N의 이름으로 설정된다. 이 필드는 스케줄러가 파드 P에
예약된 리소스를 추적하는 데 도움이 되고 사용자에게 클러스터의 선점에 대한
정보를 제공한다.
파드 P는 반드시 "지정된 노드"로 스케줄링되지는 않는다.
스케줄러는 다른 노드에 스케줄링을 시도하기 전에 항상 "지정된 노드"부터 시도한다.
피해자 파드가 축출된 후, 그것은 정상적(graceful)으로 종료되는 기간을 갖는다.
스케줄러가 종료될 피해자 파드를 기다리는 동안 다른 노드를 사용할 수
있게 되면, 스케줄러는 파드 P를 스케줄링하기 위해 다른 노드를 사용할 수 있다. 그 결과,
파드 스펙의 nominatedNodeName 과 nodeName 은 항상 동일하지 않다. 또한,
스케줄러가 노드 N에서 파드를 축출했지만, 파드 P보다 우선순위가 높은 파드가
도착하면, 스케줄러가 노드 N에 새로운 우선순위가 높은 파드를 제공할 수 있다. 이러한
경우, 스케줄러는 파드 P의 nominatedNodeName 을 지운다. 이렇게하면, 스케줄러는
파드 P가 다른 노드에서 파드를 축출할 수 있도록 한다.
선점의 한계
선점 피해자의 정상적인 종료
파드가 축출되면, 축출된 피해자 파드는
정상적인 종료 기간을 가진다.
피해자 파드는 작업을 종료하고 빠져나가는 데(exit) 많은 시간을 가진다. 그렇지 않으면,
파드는 강제종료(kill) 된다. 이 정상적인 종료 기간은 스케줄러가 파드를 축출하는
지점과 보류 중인 파드(P)를 노드(N)에서 스케줄링할 수 있는 시점 사이의
시간 간격을 만든다. 그 동안, 스케줄러는 보류 중인 다른 파드를
계속 스케줄링한다. 피해자 파드가 빠져나가거나 종료되면, 스케줄러는 보류 대기열에서
파드를 스케줄하려고 한다. 따라서, 일반적으로 스케줄러가 피해자 파드를 축출하는 시점과
파드 P가 스케줄링된 시점 사이에 시간 간격이 있다.
이러한 차이를 최소화하기 위해, 우선순위가 낮은 파드의 정상적인 종료 기간을 0 또는
작은 수로 설정할 수 있다.
PodDisruptionBudget을 지원하지만, 보장하지 않음
Pod Disruption Budget(PDB)은
애플리케이션 소유자가 자발적 중단에서 동시에 다운된 복제된
애플리케이션의 파드 수를 제한할 수 있다. 쿠버네티스는 파드를
선점할 때 PDB를 지원하지만, PDB를 따르는 것이 최선의 노력이다. 스케줄러는
선점에 의해 PDB를 위반하지 않은 피해자 파드를 찾으려고 하지만, 그러한 피해자 파드가
발견되지 않으면, 선점은 여전히 발생하며, PDB를 위반하더라도 우선순위가
낮은 파드는 제거된다.
우선순위가 낮은 파드에 대한 파드-간 어피니티
이 질문에 대한 답변이 '예'인 경우에만 노드가 선점 대상으로 간주된다.
"대기 중인 파드보다 우선순위가 낮은 모든 파드가 노드에서
제거되면, 보류 중인 파드를 노드에 스케줄링할 수 있습니까?"
참고:
선점으로 우선순위가 낮은 모든 파드를 반드시 제거할 필요는
없다. 우선순위가 낮은 모든 파드보다 적은 수를 제거하여 보류 중인 파드를
스케줄링할 수 있는 경우, 우선순위가 낮은 파드의 일부만 제거된다.
그럼에도 불구하고, 앞의 질문에 대한 대답은 '예'여야 한다. 답변이 '아니오'인 경우,
노드가 선점 대상으로 간주되지 않는다.
보류 중인 파드가 노드에 있는 하나 이상의 우선순위가 낮은 파드에 대한 파드-간 어피니티를
가진 경우에, 우선순위가 낮은 파드가 없을 때 파드-간 어피니티 규칙을
충족할 수 없다. 이 경우, 스케줄러는 노드의 파드를 축출하지
않는다. 대신, 다른 노드를 찾는다. 스케줄러가
적합한 노드를 찾거나 찾지 못할 수 있다. 보류 중인 파드를 스케줄링할 수 있다는
보장은 없다.
이 문제에 대한 권장 솔루션은 우선순위가 같거나 높은 파드에 대해서만 파드-간 어피니티를
생성하는 것이다.
교차 노드 선점
보류 중인 파드 P가 노드 N에 스케줄링될 수 있도록 노드 N이 선점 대상으로 고려되고
있다고 가정한다. 다른 노드의 파드가 축출된 경우에만 P가 N에서 실행 가능해질 수
있다. 예를 들면 다음과 같다.
파드 P는 노드 N에 대해 고려된다.
파드 Q는 노드 N과 동일한 영역의 다른 노드에서 실행 중이다.
파드 P는 파드 Q(topologyKey: topology.kubernetes.io/zone)와 영역(zone) 전체의 안티-어피니티를 갖는다.
영역에서 파드 P와 다른 파드 간의 안티-어피니티에 대한 다른 경우는
없다.
노드 N에서 파드 P를 스케줄링하기 위해, 파드 Q를 축출할 수 있지만, 스케줄러는
교차-노드 선점을 수행하지 않는다. 따라서, 파드 P가 노드 N에서
스케줄링할 수 없는 것으로 간주된다.
파드 Q가 노드에서 제거되면, 파드 안티-어피니티 위반이
사라지고, 파드 P는 노드 N에서 스케줄링될 수 있다.
수요가 충분하고 합리적인 성능의 알고리즘을 찾을 경우
향후 버전에서 교차 노드 선점의 추가를 고려할 수 있다.
문제 해결
파드 우선순위와 선점은 원하지 않는 부작용을 가질 수 있다. 다음은
잠재적 문제의 예시와 이를 해결하는 방법이다.
파드가 불필요하게 선점(축출)됨
선점은 우선순위가 높은 보류 중인 파드를 위한 공간을 만들기 위해 리소스 압박을 받고 있는
클러스터에서 기존 파드를 제거한다. 실수로 특정 파드에 높은 우선순위를
부여하면, 의도하지 않은 높은 우선순위 파드가 클러스터에서
선점을 유발할 수 있다. 파드 우선순위는 파드 명세에서
priorityClassName 필드를 설정하여 지정한다. 그런 다음
우선순위의 정수 값이 분석되어 podSpec 의 priority 필드에 채워진다.
문제를 해결하기 위해, 해당 파드가 우선순위가 낮은 클래스를 사용하도록 priorityClassName 을
변경하거나, 해당 필드를 비워둘 수 있다. 빈
priorityClassName 은 기본값이 0으로 해석된다.
파드가 축출되면, 축출된 파드에 대한 이벤트가 기록된다.
선점은 클러스터가 파드에 대한 리소스를 충분히 가지지 못한 경우에만
발생한다. 이러한 경우, 선점은 보류 중인 파드(선점자)의 우선순위가
피해자 파드보다 높은 경우에만 발생한다. 보류 중인 파드가 없거나,
보류 중인 파드의 우선순위가 피해자 파드와 같거나 낮은 경우
선점이 발생하지 않아야 한다. 그러한 시나리오에서 선점이 발생하면, 이슈를 올리기 바란다.
파드가 축출되었지만, 선점자는 스케줄링되지 않음
파드가 축출되면, 요청된 정상적인 종료
기간(기본적으로 30초)이 주어진다. 이 기간 내에 대상 파드가
종료되지 않으면, 강제 종료된다. 모든 피해자 파드가 사라지면,
선점자 파드를 스케줄링할 수 있다.
선점자 파드가 피해자 파드가 없어지기를 기다리는 동안, 동일한 노드에
적합한 우선순위가 높은 파드가 생성될 수 있다. 이 경우, 스케줄러는
선점자 대신 우선순위가 높은 파드를 스케줄링한다.
이것은 예상된 동작이다. 우선순위가 높은 파드는 우선순위가 낮은 파드를
대체해야 한다.
우선순위가 높은 파드는 우선순위가 낮은 파드보다 우선함
스케줄러가 보류 중인 파드를 실행할 수 있는 노드를 찾으려고 한다. 노드를 찾지
못하면, 스케줄러는 임의의 노드에서 우선순위가 낮은 파드를 제거하여
보류 중인 파드를 위한 공간을 만든다.
우선순위가 낮은 파드가 있는 노드가 보류 중인 파드를 실행할 수 없는 경우, 스케줄러는
선점을 위해 우선순위가 높은 다른 노드(다른 노드의 파드와 비교)를
선택할 수 있다. 피해자 파드는 여전히 선점자 파드보다 우선순위가
낮아야 한다.
선점할 수 있는 여러 노드가 있는 경우, 스케줄러는
우선순위가 가장 낮은 파드 세트를 가진 노드를 선택하려고 한다. 그러나,
이러한 파드가 위반될 PodDisruptionBudget을 가지고 있고 축출된 경우
스케줄러는 우선순위가 높은 파드를 가진 다른 노드를 선택할 수 있다.
선점을 위해 여러 개의 노드가 존재하고 위의 시나리오 중 어느 것도 적용되지 않는 경우,
스케줄러는 우선순위가 가장 낮은 노드를 선택한다.
파드 우선순위와 서비스 품질 간의 상호 작용
파드 우선순위와 QoS 클래스는
상호 작용이 거의 없고 QoS 클래스를 기반으로 파드 우선순위를 설정하는 데 대한
기본 제한이 없는 두 개의 직교(orthogonal) 기능이다. 스케줄러의
선점 로직은 선점 대상을 선택할 때 QoS를 고려하지 않는다.
선점은 파드 우선순위를 고려하고 우선순위가 가장 낮은 대상 세트를
선택하려고 한다. 우선순위가 가장 높은 파드는 스케줄러가
선점자 파드를 스케줄링할 수 없거나 우선순위가 가장 낮은 파드가
PodDisruptionBudget 으로 보호되는 경우에만, 우선순위가 가장 낮은 파드를
축출 대상으로 고려한다.
kubelet은 우선순위를 사용하여 파드의 노드-압박(node-pressure) 축출 순서를 결정한다.
사용자는 QoS 클래스를 사용하여 어떤 파드가 축출될 것인지
예상할 수 있다. kubelet은 다음의 요소들을 통해서 파드의 축출 순위를 매긴다.
kubelet은 이전에 설정된 PodDisruptionBudget 값이나 파드의 terminationGracePeriodSeconds 값을 따르지 않는다.
소프트 축출 임계값을 사용하는 경우,
kubelet은 이전에 설정된 eviction-max-pod-grace-period 값을 따른다.
하드 축출 임계값을 사용하는 경우, 파드 종료 시 0s 만큼 기다린 후 종료한다(즉, 기다리지 않고 바로 종료한다).
이 표에서, 설명 열은 kubelet이 축출 신호 값을 계산하는 방법을 나타낸다.
각 축출 신호는 백분율 또는 숫자값을 지원한다.
kubelet은 총 용량 대비 축출 신호의 백분율 값을
계산한다.
memory.available 값은 free -m과 같은 도구가 아니라 cgroupfs로부터 도출된다.
이는 free -m이 컨테이너 안에서는 동작하지 않고, 또한 사용자가
node allocatable
기능을 사용하는 경우 자원 부족에 대한 결정은 루트 노드뿐만 아니라
cgroup 계층 구조의 최종 사용자 파드 부분에서도 지역적으로 이루어지기 때문에 중요하다.
이 스크립트는
kubelet이 memory.available을 계산하기 위해 수행하는 동일한 단계들을 재현한다.
kubelet은 메모리 압박 상황에서 메모리가 회수 가능하다고 가정하므로,
inactive_file(즉, 비활성 LRU 목록의 파일 기반 메모리 바이트 수)을
계산에서 제외한다.
kubelet은 다음과 같은 파일시스템 파티션을 지원한다.
nodefs: 노드의 메인 파일시스템이며, 로컬 디스크 볼륨, emptyDir,
로그 스토리지 등에 사용된다. 예를 들어 nodefs는 /var/lib/kubelet/을 포함한다.
imagefs: 컨테이너 런타임이 컨테이너 이미지 및
컨테이너 쓰기 가능 레이어를 저장하는 데 사용하는 선택적 파일시스템이다.
kubelet은 이러한 파일시스템을 자동으로 검색하고 다른 파일시스템은 무시한다.
kubelet은 다른 구성은 지원하지 않는다.
아래의 kubelet 가비지 수집 기능은 더 이상 사용되지 않으며 축출로 대체되었다.
기존 플래그
새로운 플래그
이유
--image-gc-high-threshold
--eviction-hard 또는 --eviction-soft
기존의 축출 신호가 이미지 가비지 수집을 트리거할 수 있음
--image-gc-low-threshold
--eviction-minimum-reclaim
축출 회수도 동일한 작업을 수행
--maximum-dead-containers
-
오래된 로그들이 컨테이너의 컨텍스트 외부에 저장된 이후로 사용되지 않음
--maximum-dead-containers-per-container
-
오래된 로그들이 컨테이너의 컨텍스트 외부에 저장된 이후로 사용되지 않음
--minimum-container-ttl-duration
-
오래된 로그들이 컨테이너의 컨텍스트 외부에 저장된 이후로 사용되지 않음
축출 임계값
kubelet이 축출 결정을 내릴 때 사용하는 축출 임계값을
사용자가 임의로 설정할 수 있다.
축출 임계값은 [eviction-signal][operator][quantity] 형태를 갖는다.
quantity에는 1Gi와 같이 축출 임계값 수치를 적는다.
quantity에 들어가는 값은 쿠버네티스가 사용하는 수치 표현 방식과 맞아야 한다.
숫자값 또는 백분율(%)을 사용할 수 있다.
예를 들어, 노드에 총 10Gi의 메모리가 있고
1Gi 아래로 내려갔을 때 축출이 시작되도록 만들고 싶으면, 축출 임계값을
memory.available<10% 또는 memory.available<1Gi 형태로 정할 수 있다. 둘을 동시에 사용할 수는 없다.
소프트 축출 임계값과 하드 축출 임계값을 설정할 수 있다.
소프트 축출 임계값
소프트 축출 임계값은 관리자가 설정하는 유예 시간(필수)과 함께 정의된다.
kubelet은 유예 시간이 초과될 때까지 파드를 제거하지 않는다.
유예 시간이 지정되지 않으면 kubelet 시작 시
오류가 반환된다.
kubelet이 축출 과정에서 사용할 수 있도록,
'소프트 축출 임계값'과 '최대 허용 파드 종료 유예 시간' 둘 다를 설정할 수 있다.
'최대 허용 파드 종료 유예 시간'이 설정되어 있는 상태에서 '소프트 축출 임계값'에 도달하면,
kubelet은 두 유예 시간 중 작은 쪽을 적용한다.
'최대 허용 파드 종료 유예 시간'을 설정하지 않으면,
kubelet은 축출된 파드를 유예 시간 없이 즉시 종료한다.
소프트 축출 임계값을 설정할 때 다음과 같은 플래그를 사용할 수 있다.
eviction-soft: 축출 임계값(예: memory.available<1.5Gi)의 집합이며,
지정된 유예 시간동안 이 축출 임계값 조건이 충족되면 파드 축출이 트리거된다.
eviction-soft-grace-period: 축출 유예 시간의 집합이며,
소프트 축출 임계값 조건이 이 유예 시간동안 충족되면 파드 축출이 트리거된다.
eviction-max-pod-grace-period: '최대 허용 파드 종료 유예 시간(단위: 초)'이며,
소프트 축출 임계값 조건이 충족되어 파드를 종료할 때 사용한다.
하드 축출 임계값
하드 축출 임계값에는 유예 시간이 없다. 하드 축출 임계값 조건이 충족되면,
kubelet은 고갈된 자원을 회수하기 위해 파드를 유예 시간 없이
즉시 종료한다.
eviction-hard 플래그를 사용하여 하드 축출
임계값(예: memory.available<1Gi)을 설정할 수 있다.
kubelet은 다음과 같은 하드 축출 임계값을 기본적으로 설정하고 있다.
memory.available<100Mi
nodefs.available<10%
imagefs.available<15%
nodefs.inodesFree<5% (리눅스 노드)
이러한 하드 축출 임계값의 기본값은
매개변수가 변경되지 않은 경우에만 설정된다. 어떤 매개변수의 값을 변경한 경우,
다른 매개변수의 값은 기본값으로 상속되지 않고
0으로 설정된다. 사용자 지정 값을 제공하려면,
모든 임계값을 각각 제공해야 한다.
축출 모니터링 시간 간격
kubelet은 housekeeping-interval에 설정된 시간 간격(기본값: 10s)마다
축출 임계값을 확인한다.
노드 컨디션
kubelet은 하드/소프트 축출 임계값 조건이 충족되어
노드 압박이 발생했다는 것을 알리기 위해,
설정된 유예 시간과는 관계없이 노드 컨디션을 보고한다.
kubelet은 다음과 같이 노드 컨디션과 축출 신호를 매핑한다.
노드 컨디션
축출 신호
설명
MemoryPressure
memory.available
노드의 가용 메모리 양이 축출 임계값에 도달함
DiskPressure
nodefs.available, nodefs.inodesFree, imagefs.available, 또는 imagefs.inodesFree
노드의 루트 파일시스템 또는 이미지 파일시스템의 가용 디스크 공간 또는 inode의 수가 축출 임계값에 도달함
PIDPressure
pid.available
(리눅스) 노드의 가용 프로세스 ID(PID)가 축출 임계값 이하로 내려옴
kubelet은 --node-status-update-frequency에 설정된
시간 간격(기본값: 10s)마다 노드 컨디션을 업데이트한다.
노드 컨디션 진동(oscillation)
경우에 따라, 노드의 축출 신호값이 사전에 설정된 유예 시간 동안 유지되지 않고
소프트 축출 임계값을 중심으로 진동할 수 있다. 이로 인해 노드 컨디션이 계속
true와 false로 바뀌며, 잘못된 축출 결정을 야기할 수 있다.
이러한 진동을 방지하기 위해, eviction-pressure-transition-period 플래그를
사용하여 kubelet이 노드 컨디션을 다른 상태로 바꾸기 위해 기다려야 하는 시간을
설정할 수 있다. 기본값은 5m이다.
노드-수준 자원 회수하기
kubelet은 최종 사용자 파드를 축출하기 전에 노드-수준 자원 회수를 시도한다.
DiskPressure 노드 컨디션이 보고되면,
kubelet은 노드의 파일시스템을 기반으로 노드-수준 자원을 회수한다.
imagefs가 있는 경우
컨테이너 런타임이 사용할 전용 imagefs 파일시스템이 노드에 있으면,
kubelet은 다음 작업을 수행한다.
nodefs 파일시스템이 축출 임계값 조건을 충족하면,
kubelet은 종료된 파드와 컨테이너에 대해 가비지 수집을 수행한다.
imagefs 파일시스템이 축출 임계값 조건을 충족하면,
kubelet은 모든 사용중이지 않은 이미지를 삭제한다.
imagefs가 없는 경우
노드에 nodefs 파일시스템만 있고 이것이 축출 임계값 조건을 충족한 경우,
kubelet은 다음 순서로 디스크 공간을 확보한다.
종료된 파드와 컨테이너에 대해 가비지 수집을 수행한다.
사용중이지 않은 이미지를 삭제한다.
kubelet 축출을 위한 파드 선택
kubelet이 노드-수준 자원을 회수했음에도 축출 신호가 임계값 아래로 내려가지 않으면,
kubelet은 최종 사용자 파드 축출을 시작한다.
결과적으로, kubelet은 다음과 같은 순서로 파드의 축출 순서를 정하고 축출을 수행한다.
BestEffort 또는 Burstable 파드 중 자원 사용량이 요청량을 초과한 파드.
이 파드들은 파드들의 우선순위, 그리고 자원 사용량이 요청량을
얼마나 초과했는지에 따라 축출된다.
Guaranteed, Burstable 파드 중 자원 사용량이 요청량보다 낮은 파드는
우선순위에 따라 후순위로 축출된다.
참고:
kubelet이 파드 축출 순서를 결정할 때 파드의 QoS 클래스는 이용하지 않는다.
메모리 등의 자원을 회수할 때, QoS 클래스를 이용하여 가장 가능성이 높은 파드 축출 순서를 예측할 수는 있다.
QoS는 EphemeralStorage 요청에 적용되지 않으므로,
노드가 예를 들어 DiskPressure 아래에 있는 경우 위의 시나리오가 적용되지 않는다.
Guaranteed 파드는 모든 컨테이너에 대해 자원 요청량과 제한이 명시되고
그 둘이 동일할 때에만 보장(guaranteed)된다. 다른 파드의 자원 사용으로 인해
Guaranteed 파드가 축출되는 일은 발생하지 않는다. 만약 시스템 데몬(예:
kubelet, journald)이 system-reserved 또는 kube-reserved
할당을 통해 예약된 것보다 더 많은 자원을 소비하고, 노드에는 요청량보다 적은 양의
자원을 사용하고 있는 Guaranteed / Burstable 파드만 존재한다면,
kubelet은 노드 안정성을 유지하고 자원 고갈이 다른 파드에 미칠 영향을 통제하기 위해
이러한 파드 중 하나를 골라 축출해야 한다.
이 경우, 가장 낮은 Priority를 갖는 파드가 선택된다.
inodes와 PIDs에 대한 요청량은 정의하고 있지 않기 때문에, kubelet이 inode
또는 PID 고갈 때문에 파드를 축출할 때에는 파드의 Priority를 이용하여 축출
순위를 정한다.
노드에 전용 imagefs 파일시스템이 있는지 여부에 따라 kubelet이 파드 축출 순서를
정하는 방식에 차이가 있다.
imagefs가 있는 경우
nodefs로 인한 축출의 경우, kubelet은 nodefs
사용량(모든 컨테이너의 로컬 볼륨 + 로그)을 기준으로 축출 순서를 정한다.
imagefs로 인한 축출의 경우, kubelet은 모든 컨테이너의
쓰기 가능한 레이어(writable layer) 사용량을 기준으로 축출 순서를 정한다.
imagefs가 없는 경우
nodefs로 인한 축출의 경우, kubelet은 각 파드의 총
디스크 사용량(모든 컨테이너의 로컬 볼륨 + 로그 + 쓰기 가능한 레이어)을 기준으로 축출 순서를 정한다.
최소 축출 회수량
경우에 따라, 파드를 축출했음에도 적은 양의 자원만이 회수될 수 있다.
이로 인해 kubelet이 반복적으로 축출 임계값 도달을 감지하고
여러 번의 축출을 수행할 수 있다.
--eviction-minimum-reclaim 플래그 또는
kubelet 설정 파일을 이용하여
각 자원에 대한 최소 회수량을 설정할 수 있다. kubelet이 자원 부족 상황을 감지하면,
앞서 설정한 최소 회수량에 도달할때까지 회수를 계속 진행한다.
또한, kubelet은 system-node-critical파드 우선 순위(Priority)를 갖는 파드의 컨테이너에
oom_score_adj 값을 -997로 설정한다.
노드가 OOM을 겪기 전에 kubelet이 메모리를 회수하지 못하면, oom_killer가 노드의
메모리 사용률 백분율을 이용하여 oom_score를 계산하고, 각 컨테이너의 실질
oom_score를 구하기 위해 oom_score_adj를 더한다. 그 뒤 oom_score가 가장 높은
컨테이너부터 종료시킨다.
이는 곧, 스케줄링 요청에 비해 많은 양의 메모리를 사용하면서
QoS가 낮은 파드에 속한 컨테이너가 먼저 종료됨을 의미한다.
파드 축출과 달리, 컨테이너가 OOM으로 인해 종료되면,
kubelet이 컨테이너의 RestartPolicy를 기반으로 컨테이너를 다시 실행할 수 있다.
추천 예시
아래 섹션에서 축출 설정에 대한 추천 예시를 소개한다.
스케줄 가능한 자원과 축출 정책
kubelet에 축출 정책을 설정할 때, 만약 어떤 파드 배치가 즉시 메모리 압박을
야기하기 때문에 축출을 유발한다면 스케줄러가 그 파드 배치를 수행하지 않도록
설정해야 한다.
다음 시나리오를 가정한다.
노드 메모리 용량: 10Gi
운영자는 시스템 데몬(커널, kubelet 등)을 위해 메모리 용량의 10%를 확보해 놓고 싶어 한다.
운영자는 시스템 OOM 발생을 줄이기 위해 메모리 사용률이 95%인 상황에서 파드를 축출하고 싶어한다.
이 환경 설정에서, --system-reserved 플래그는 시스템 용으로 1.5Gi 메모리를
확보하는데, 이는 총 메모리의 10% + 축출 임계값에 해당된다.
파드가 요청량보다 많은 메모리를 사용하거나 시스템이 1Gi 이상의 메모리를
사용하여, memory.available 축출 신호가 500Mi 아래로 내려가면 노드가 축출
임계값에 도달할 수 있다.
데몬셋(DaemonSet)
파드 우선 순위(Priority)는 파드 축출 결정을 내릴 때의 주요 요소이다.
kubelet이 DaemonSet에 속하는 파드를 축출하지 않도록 하려면
해당 파드의 파드 스펙에 충분히 높은 priorityClass를 지정한다.
또는 낮은 priorityClass나 기본값을 사용하여
리소스가 충분할 때만 DaemonSet 파드가 실행되도록 허용할 수도 있다.
알려진 이슈
다음 섹션에서는 리소스 부족 처리와 관련된 알려진 이슈에 대해 다룬다.
kubelet이 메모리 압박을 즉시 감지하지 못할 수 있음
기본적으로 kubelet은 cAdvisor를 폴링하여
일정한 간격으로 메모리 사용량 통계를 수집한다.
해당 타임 윈도우 내에서 메모리 사용량이 빠르게 증가하면 kubelet이
MemoryPressure를 충분히 빠르게 감지하지 못해 OOMKiller가 계속 호출될 수 있다.
--kernel-memcg-notification 플래그를 사용하여
kubelet의 memcg 알림 API가 임계값을 초과할 때 즉시 알림을 받도록
할 수 있다.
사용률(utilization)을 극단적으로 높이려는 것이 아니라 오버커밋(overcommit)에 대한 합리적인 조치만 원하는 경우,
이 문제에 대한 현실적인 해결 방법은 --kube-reserved 및
--system-reserved 플래그를 사용하여 시스템에 메모리를 할당하는 것이다.
active_file 메모리가 사용 가능한 메모리로 간주되지 않음
리눅스에서, 커널은 활성 LRU 목록의 파일 지원 메모리 바이트 수를 active_file
통계로 추적한다. kubelet은 active_file 메모리 영역을 회수할 수 없는 것으로
취급한다. 임시 로컬 스토리지를 포함하여 블록 지원 로컬 스토리지를 집중적으로
사용하는 워크로드의 경우 파일 및 블록 데이터의 커널 수준 캐시는 최근에 액세스한
많은 캐시 페이지가 active_file로 계산될 가능성이 있음을 의미한다. 활성 LRU
목록에 이러한 커널 블록 버퍼가 충분히 많으면, kubelet은 이를 높은 자원 사용
상태로 간주하고 노드가 메모리 압박을 겪고 있다고 테인트를 표시할 수 있으며, 이는
파드 축출을 유발한다.
API를 사용하여 축출을 요청하면,
API 서버는 인가 확인(admission checks)를 수행하고 다음 중 하나로 응답한다.
200 OK: 축출 요청이 허용되었고, Eviction 서브리소스가 생성되었고,
(마치 파드 URL에 DELETE 요청을 보낸 것처럼) 파드가 삭제되었다.
429 Too Many Requests: 현재 설정된
PodDisruptionBudget 때문에
축출이 현재 허용되지 않는다.
또는 API 요청 속도 제한(rate limiting) 때문에 이 응답을 받았을 수도 있다.
500 Internal Server Error: 잘못된 환경 설정(예:
여러 PodDisruptionBudget이 하나의 동일한 파드를 참조함)으로 인해 축출이 허용되지 않는다.
축출하려는 파드가
PodDisruptionBudget이 설정된 워크로드에 속하지 않는다면,
API 서버는 항상 200 OK를 반환하고 축출을 허용한다.
API 서버가 축출을 허용하면, 파드는 다음과 같이 삭제된다.
API 서버 내 Pod 리소스의 삭제 타임스탬프(deletion timestamp)가 업데이트되며,
이 타임스탬프에 명시된 시각이 경과하면 API 서버는 해당 Pod 리소스를 종료 대상으로 간주한다.
또한 설정된 그레이스 시간(grace period)이 Pod 리소스에 기록된다.
로컬 파드가 실행되고 있는 노드의 kubelet이
Pod가 종료 대상으로 표시된 것을 감지하고
로컬 파드의 그레이스풀 셧다운을 시작한다.
kubelet이 파드를 종료하는 와중에, 컨트롤 플레인은
엔드포인트 및
엔드포인트슬라이스 오브젝트에서 파드를 삭제한다.
이 결과, 컨트롤러는 파드를 더 이상 유효한 오브젝트로 간주하지 않는다.
파드의 그레이스 시간이 만료되면,
kubelet이 로컬 파드를 강제로 종료한다.
kubelet이 API 서버에 Pod 리소스를 삭제하도록 지시한다.
API 서버가 Pod 리소스를 삭제한다.
문제가 있어 중단된 축출 트러블슈팅하기
일부 경우에, 애플리케이션이 잘못된 상태로 돌입하여,
직접 개입하기 전에는 축출 API가 429 또는 500 응답만 반환할 수 있다.
이러한 현상은, 예를 들면 레플리카셋이 애플리케이션을 서비스할 파드를 생성했지만
새 파드가 Ready로 바뀌지 못하는 경우에 발생할 수 있다.
또는 마지막으로 축출된 파드가 긴 종료 그레이스 시간을 가진 경우에 이러한 현상을 목격할 수도 있다.
문제가 있어 중단된 축출을 발견했다면, 다음 해결책 중 하나를 시도해 본다.
이 문제를 발생시키는 자동 동작(automated operation)을 중단하거나 일시 중지한다.
해당 동작을 재시작하기 전에, 문제가 있어 중단된 애플리케이션을 조사한다.
잠시 기다린 뒤, 축출 API를 사용하는 것 대신
클러스터 컨트롤 플레인에서 파드를 직접 삭제한다.