쿠버네티스 - 노드에 파드를 할당하는 방법 nodeSelector, affinity

Hoonkii·2022년 2월 8일
0

사내 ML Worker는 하나의 노드에 동일한 하나의 pod만 스케줄링되어야 한다(훈련 시 CPU를 100% 사용해야하는 전제가 깔려있다). 그 조건을 어떻게 설정하였는지 분석하면서 쿠버네티스에서 노드에 Pod을 어떻게 할당하는지 공부하였다. 오늘은 쿠버네티스에서 노드에 Pod을 할당하는 방법을 포스팅하려고 한다.

  • nodeSelector

nodeSelector는 가장 간단한 노드 선택 제약조건이다. 키 값 쌍의 매핑으로 지정되며 어떤 pod이 특정 노드에서 동작하도록 하려면 노드는 키-값 쌍으로 표시되는 라벨을 가지고 있어야 한다.

GKE 기준으로 아래와 같이 노드 풀에 kubernetes 라벨을 설정한다.


kubernetes 라벨에 app:train-worker을 붙였다.

우리는 ML worker pod을 app이라는 키에 train-worker라는 값으로 라벨이 붙어있는 노드에 배포하고 싶다.

그러면 다음과 같이 deployment yaml을 작성하면 된다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: train-worker-deployment
spec:
  selector:
    matchLabels:
      app: train-worker
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: train-worker
    spec:
      nodeSelector:
        app: train-worker

이렇게하고 쿠버네티스 yaml을 적용하면 train-worker deployment에 명시된 Pod들은 위에 설정된 노드풀에만 배포된다.

  • affinity와 anti-affinity

nodeSelector는 pod가 특정 라벨이 있는 노드로 스케줄링을 제한하는 간단한 방법이었다. affinity, anti-affinity는 표현할 수 있는 제약조건을 좀 더 유연하고 확장성있게 제공한다.
Affinity는 노드 affinity 와 pod 간 affinity/anti-affinity 로 구성된다.

노드 Affinity

노드 Affinity는 개념적으로 nodeSelector와 비슷하며 다만 requiredDuringSchedulingIgnoredDuringExecution, preferredDuringSchedulingIgnoredDuringExecution 와 같이 두 가지 종류의 노드 어피니티 기능을 제공한다. requiredDuringSchedulingIgnoredDuringExecution은 pod이 노드에 스케줄 되도록 반드시 규칙을 만족해야 하는 것을 의미하며, preferredDuringSchedulingIgnoredDuringExecution는 스케줄러가 pod을 조건에 따라 노드에 배포하려고 시도는 하지만 보증할 수 없다는 것을 의미한다. (즉 상황에 따라 다른 노드에 스케줄 될 수 있다는 것을 의미한다. 어쨌든 Best Effort...)

사용 예시는 다음과 같다. k8s 공식 문서를 참고했다.

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

위 yaml을 분석하면 pod은 kubernetes.io/e2e-az-name 라는 키에 e2e-az1, e2e-az2 값의 라벨을 가진 노드에 반드시 스케줄링되어야 하며, 그 노드 풀 중에서도 되도록이면 another-node-label-key 키에 another-node-label-value 값의 라벨을 가진 노드에 우선적으로 스케줄링 되는 것을 선호한다는 뜻이다.

여기서 중요한 것은 pod이 스케줄된 노드의 라벨을 지우거나 변경해도 pod은 제거되지 않는다는 점이다. 즉 만약에 내가 특정 노드의 라벨을 변경한다고 스케줄된 pod이 조정되는 것이 아니기 때문에 재 배포가 필요하다. Affinity의 선택은 pod을 스케줄링하는 시점에만 작동한다는 것을 명심하자.

  • pod간 affinity와 anti-affinity

pod간 affinity와 anti-affinity를 사용하면 노드의 라벨을 기반으로 스케줄링 정책을 지정하는 것이 아니라, 노드에서 이미 실행중인 pod의 라벨을 기반으로 pod이 스케줄될 수 있는 노드를 제한할 수 있다. 예를 들어 어떤 pod이 어떤 노드에 이미 스케줄되어 있다면, 이 pod은 해당 노드에 스케줄되면 안된다 라는 식의 조건이 가능하다. (서두에서 언급한 한 노드에 한 Pod만 할당할 수 있는 것이 가능하다. )

자 yaml 예시를 보자 먼저 쿠버네티스 공식 문서에 있는 예시이다.

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

위 pod은 키 security 와 값 S1인 라벨이 있는 하나 이상의 이미 실행중인 pod과 동일한 영역에 있는 경우에만 pod을 노드에 스케줄 할 수 있다. 또한 pod은 키 security와 값 S2인 라벨이 있는 pod과 동일한 영역의 노드에 스케줄 되지 않는다는 의미이다.

여기서 topologyKey라는 개념이 나오는데 pod간 affinity, anti-affinity에서 반드시 설정해야 하는 값이다.

노드와는 다르게 Pod은 네임스페이스이기 때문에 Pod 라벨위의 라벨셀렉터는 반드시 셀렉터가 적용될 네임스페이스를 지정해야만 한다. 개념적으로 노드는 서버 랙, 클라우드 공급자 영역, 클라우드 공급자 지역 등과 같은 토폴로지 도메인이다.

쿠버네티스에서는 모든 라벨과 어노테이션들을 kubernetes.io 네임스페이스 아래에 정의해놓았으며, 노드의 상태를 읽어서 알아서 지정한다.

한가지 예시를 더 보자.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: train-worker-deployment
spec:
  selector:
    matchLabels:
      app: train-worker
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: train-worker
    spec:
      nodeSelector:
        app: train-worker
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - train-worker
              topologyKey: "kubernetes.io/hostname"

app:train-worker라고 라벨링된 pod이 특정 노드에 있는 경우 해당 노드에 동일한 pod이 배포되지 않도록 제한한다. 이 때 topologyKey는 hostname인데, 각 노드의 hostname은 독립적이기 때문에 개별 노드로 스케줄링을 제한할 수 있다.

위 쿠버네티스 공식 문서 예시에서는 anti-affinity가 pod과 동일한 영역의 노드에 스케줄 되지 않는다는 조건이었고, 아래 예시에서는 특정 개별 노드 하나 에 스케줄되지 않는다는 상이한 조건을 가지는데 이는 topologyKey로 구분된다.


오늘은 쿠버네티스에서 노드에 Pod을 어떻게 할당할 수 있는지에 대해 알아보았다. 간단하게는 노드 셀렉터 좀 더 세분화된 할당 제어가 필요하다면 Affinity, Anti-Affinity를 활용할 수 있을 것이다.

예를 들어 특정 백엔드 노드에 Redis 캐시 서버가 1-1로 할당되기를 원하며 각 쌍은 하나의 노드안에 절대 같이 배포되지 않기를 원할 수 있다. Affinity, Anti-Affinity를 잘 활용하면 위와 같은 제약사항을 쉽게 구현할 수 있을 것이다.

profile
개발 공부 내용 정리

0개의 댓글