서비스의 가용성을 향상하기 위해 여러 노드(node)와 존(zone)을 걸쳐 파드를 분산하여 배포합니다.
이때 사용할 수 있는 podAntiAffinity와 topologySpreadConstraints에 대해 알아보겠습니다.
podAntiAffinity
: 하나 이상의 파드를 이미 실행 중인 노드나 존에 대해 파드를 실행하지 않도록 설정하는 기능
- 옵션
requiredDuringSchedulingIgnoredDuringExecution
: 조건에 맞게 스케쥴링을 할 수 없을 때 pending 파드 발생(hard)preferredDuringSchedulingIgnoredDuringExecution
: 조건에 맞게 스케쥴링 하도록 시도하지만, 맞지 않더라도 스케쥴링 가능(soft)
- zone, 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: topology.kubernetes.io/zone
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: kubernetes.io/hostname
containers:
- name: web-app
image: nginx:1.16-alpine
requiredDuringSchedulingIgnoredDuringExecution
옵션을 사용할 때 주의점- 동일한 topology(노드, 존 등)에 하나의 파드를 배포하는 것이기 때문에, 존에 대해 podAntiAffinity를 설정하면 zone 당 하나의 파드 생성만 허용되어, 존의 수보다 많은 수의 파드를 배포할 수 없음
- 존에 대해 설정하지 않는 것을 권장하고, 노드당 단 하나의 파드만 배포가 필요한 특수한 상황에서 활용 가능
- podAntiAffinity는 대형 클러스터(100개 이상의 노드)에서 스케쥴링 속도 문제로 권장하지 않는다고 함
topologySpreadConstraints
: 리전, 존, 노드 등 여러 topology 도메인에 대해 파드를 분산하여 배포하도록 설정하는 기능
- 옵션
whenUnsatisfiable
DoNotSchedule
: 조건에 맞게 스케쥴링을 할 수 없을 때 pending 파드 발생(hard)ScheduleAnyway
: 조건에 맞게 스케쥴링 하도록 시도하지만, 맞지 않더라도 스케쥴링
maxSkew
: 파드가 얼마나 고르게 분산되어야 하는지에 대한 설정. 작게 설정할수록 고르게 분산minDomains
(선택)
: 최소 분산이 필요한 topology 도메인의 수를 설정할 수 있으며, 최소 분산 필요한 노드 또는 존의 수를 설정하는데 활용할 수 있음(현재 kubernetes 버전이 v1.25 이상이라면 사용 가능)nodeAffinityPolicy
(선택)
: nodeAffinity/nodeSelector를 기반으로 매칭되는 노드만 고려하여 파드 분산 skew를 계산할지 말지에 대한 설정nodeTaintsPolicy
(선택)
: taint가 없는 노드와 taint가 있더라도 파드의 toleration이 있어 스케쥴링이 가능한 노드만 고려하여 파드 분산 skew를 계산할지 말지에 대한 설정matchLabelKeys
(선택)
: labelSelector에 설정하지 않은 파드의 label을 추가적으로 고려하여 분산 대상 파드를 선택할 때 사용
deployment 등에 변경사항이 있어 rollingupdate가 필요할 때, rollingupdate 이후에도 파드의 분산을 고르게 하기 위해 사용할 수 있음(현재 kubernetes 버전이 v1.27 이상이라면 사용 가능)
- skew를 계산하는 방식
: 여러 노드가 있을 때 최종적으로 노드에 배포된 파드의 수의 차이가 maxSkew를 넘지 않는 것을 의도하는 것이 아니며, 파드 스케줄링 당시에 모든 topology 중 매칭되는 파드의 수가 가장 적은 topology와의 파드 수 차이가 maxSkew를 만족하는 노드를 선택해 파드 배포
새로운 파드 한 개 생성이 필요할 때:
- nodeA에 배치할 때 :
- node skew : 1(nodeA) - 0(nodeY)
- zone skew : 4(Zone 1) - 2(Zone 2)
- nodeY에 배치할 때 :
- node skew : 1(nodeY) - 0(nodeA)
- zone skew : 3(Zone 2) - 3(Zone 1)
- nodeB에 배치할 때 :
- node skew : 4(nodeB) - 0(nodeA or nodeY),
- zone skew : 4(Zone 1) - 2(Zone 2)
- nodeX에 배치할 때 :
- node skew : 3(nodeX) - 0(nodeA or nodeY)
- zone skew : 3(Zone 1) - 1(Zone 1)
-> nodeY가 모든 skew 조건을 만족하고, 파드 생성 후 최종적인 파드 수는 아래와 같음
nodeA : 0, nodeB : 3, nodeX : 2, nodeY : 1 / Zone1 : 3, Zone2 : 3
- 제약 사항
- 스케쥴러는 클러스터가 가진 topology 도메인에 대해 미리 알지 못하며, 해당 topology 도메인에 노드가 있을 때 인식할 수 있음(어떤 topology 도메인이 있는지 모두 알 수 없음)
: 노드 수가 적을 때 해당 노드에 집중되어 파드가 생성될 수 있으며, 최소 분산이 필요한 노드 또는 존이 있다면 minDomains를 설정하여 어느 정도 분산을 보장할 수 있음 - 파드 삭제 시(scale-in/evict 등) 분산 조건을 고려하지 않음
: scale-in 시 특정 노드나 존에 파드가 아예 없을 수도 있음
- 스케쥴러는 클러스터가 가진 topology 도메인에 대해 미리 알지 못하며, 해당 topology 도메인에 노드가 있을 때 인식할 수 있음(어떤 topology 도메인이 있는지 모두 알 수 없음)
- zone, 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:
topologySpreadConstraints:
- labelSelector:
matchLabels:
app: web-store
maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
nodeAffinityPolicy: Honor
nodeTaintsPolicy: Honor
minDomains: 3 # 3개의 AZ 사용을 위한 설정
matchLabelKeys:
- pod-template-hash
- labelSelector:
matchLabels:
app: web-store
maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
nodeAffinityPolicy: Honor
nodeTaintsPolicy: Honor
minDomains: 3 # 최소 3개의 노드 사용을 위한 설정
matchLabelKeys:
- pod-template-hash
containers:
- name: web-app
image: nginx:1.16-alpine
podAntiAffinity와 topologySpreadConstraints를 비교 및 사용해보며 느낀점
처음에 파드 분산 배포에 있어서 podAntiAffinity와 topologySpreadConstraints의 기능적 차이가 없다고 판단하였고, 둘 중 하나의 방법으로 통일하여 사용하기 위해 비교를 진행했습니다.
이번 기회에 공식 문서를 읽어보며 기존에 잘못 이해하고 있었던 부분을 발견했습니다.
podAntiAffinity의 경우, deployment에 존과 노드에 대해 podAntiAffinity를 설정(hard)하고 두 개의 파드를 생성한 후에 rollingupdate를 진행했고, pending 파드가 발생하여 원인을 파악하던 중, 두 개의 존을 사용하기 때문에 이미 두 개의 존에 파드가 하나씩 있어 새로운 파드를 생성하지 못하는 것을 확인했습니다. 이는 podAntiAffinity는 topology 하나에 파드 하나만 배포하는 것을 의도하기 때문인 것을 알았습니다.
topologySpreadConstraints의 경우, 전체 노드 간 조건에 맞는 파드의 수의 차이가 최종적으로 maxSkew를 넘지 않아야 한다고 이해하고 있었습니다. 테스트 중 hard 조건을 설정하였지만 노드 간 파드의 수의 차이가 많이 발생했음에도 파드 스케줄링에 이슈가 없는 것을 보고 설정한 대로 동작하지 않는다고 생각했었는데, skew를 계산하는 방식을 잘못 이해하고 있었던 것을 알았습니다.
또한, kubernetes 신규 버전에 여러 필드들이 업데이트되었고, 그 중 minDomains과 matchLabelKeys의 경우 추가 시 개선되는 점을 확인하여 유용하게 사용할 예정입니다.
maxSkew를 1로 지정하고 DoNotSchedule 옵션을 사용할 때, 최초 배포 시 노드 별로 단 하나의 파드만 생성되는 경향이 있습니다(노드 scale-out이 가능할 때). 따라서, 최초에 배포할 파드가 다수 필요하거나 파드 수 급증이 예상된다면, 비용을 고려하여 skew 수 상향 조정 또는 ScheduleAnyway 사용을 검토해야 합니다. 또한, 최초에 배포하는 파드의 수가 적고 HPA를 통해 점진적으로 증감된다면 maxSkew : 1, DoNotSchedule 옵션을 사용하여도 비용 관점에서 큰 낭비가 없습니다. 현재 생성된 노드에 스케줄링이 가능하고 minDomains를 만족한다면 새로운 노드를 생성하지 않고도 기존 노드에 파드 생성이 가능하기 때문입니다.
결론
대부분의 경우 파드 분산 배포를 위해서는 podAntiAffinity 보다는 topologySpreadConstraints를 사용하는 것이 좋습니다.
다만, topologySpreadConstraints을 통해 하나의 노드에 단 하나의 파드를 생성하도록 강제하는 것은 불가능하기 때문에, 이때는 podAntiAffinity를 사용해야 합니다.
topologySpreadConstraints를 사용하더라도 서비스 배포 후 파드 scale-in/out 이 반복되면서 특정 노드 또는 존에 파드가 밀집되어 생성될 수 있기 때문에, 주기적으로 rollout이 필요합니다.
이를 개선하기 위해서 descheduler를 활용할 수 있지만, 추가적인 설치가 필요하여 실제 동작 방식과 사용 사례를 찾아볼 필요가 있어 보입니다.
참고
'Kubernetes' 카테고리의 다른 글
EKS Node의 최대 Pod 수 - Security Groups per Pod 사용 시 (0) | 2024.11.18 |
---|---|
노드의 memory에 대하여 - cgroup과 OOM killer에 대해 알아보기 (4) | 2024.07.31 |
파드의 컨테이너 pid 및 cgroup에 할당된 cpu, mem 확인 방법(containerd) (1) | 2024.03.12 |
컨테이너 root로 접속하는 방법(containerd) (3) | 2023.11.07 |
EKS Node의 최대 Pod 수 (0) | 2022.08.01 |