쿠버네티스 패턴 - 6장 자동 배치

오정재·2021년 4월 28일
5

쿠버네티스 패턴

목록 보기
7/12
post-thumbnail

👉 자동 배치

자동 배치 패턴은 쿠버네티스의 스케줄러의 핵심 기능입니다.
스케줄러는 컨테이너의 자원 요청을 가장 적절한 노드에 할당해줍니다.

즉, 이 패턴은 쿠버네티스가 파드를 스케줄 하는 원리
쿠버네티스 외부에서 배치 결정에 영향을 주는 방법에 대해서 설명합니다.

🍏 문제점

마이크로 서비스 기반의 시스템은 수십 또는 수백 개의 격리된 프로세스로 구성됩니다.
이런 프로세스들을 여러 노드에 배치해야하는 경우 여러 가지 변수들이 있습니다.

특히 컨테이너를 배치하는데 신경써야하는 의존성, 자원 요구사항, 시간에 따른 변경사항들은
노드를 배치하는데 많은 영향을 주며 이를 관리 하지 않을 경우 어플리케이션이 느려지거나 종료될 수 있습니다.

🍏 해결책

쿠버네티스는 스케줄러를 통해서 이를 제어하며 적절한 노드를 선택할 수 있도록 도와줍니다.
따라서 이번 글에서는 노드에 스케줄하기 위해 고려해야하는 것스케줄의 원리에 대해서 이야기하겠습니다.

Resource

쿠버네티스에서 파드를 스케줄 하기위해 가장 먼저 고려해야하는 것은 자원입니다.
따라서 먼저 노드의 자원과 컨테이너의 자원에 대해서 알아보도록 하겠습니다.

1) 가용한 노드의 자원

새로운 파드를 실행하기 위해선 노드에 충분한 자원 용량이 확보되어야 합니다.
따라서 스케줄러는 파드가 요청한 자원의 총합보다 노드의 가용 자원이 더 큰지 확인해야합니다.
그렇기 때문에 각 노드의 자원에 대해서 계산할 줄 알아야합니다.

쿠버네티스 전용 노드만을 고려한다면 다음과 같은 공식으로 계산됩니다.

Allocatable [어플리케이션 파드에 대한 용량] = 
	Node Capacity [하나의 노드에 가용한 용량]
    	- Kube-Reserved [kubelet, CRI 같은 쿠버네티스 daemon]
        - System-REserved [sshd, udev 같은 OS System daemon]

OS쿠버네티스를 관리하는 Systemd의 자원을 예약하지 않으면
파드는 노드의 전체 용량을 사용할 때까지 스케줄링이 되고,
이로인해 System Error를 발생시킬 수 있습니다.

또한 쿠버네티스에 의해 관리되지 않는 컨테이너가 노드에서 실행 중이더라도,
쿠버네티스에 의한 노드 용량 계산에 반영됩니다.

2) 컨테이너의 요구 자원

효율적인 파드의 배치를 위해서는 컨테이너의 요구 자원을 파악하고 적절한 노드에 배치하는 것입니다.
이를 위해 컨테이너의 요구 자원requestlimit 필드를 통해서 의존성 선언을 할 수 있습니다.

이는 쿠버네티스 패턴 - 2장 예측 범위 내의 요구사항 에서 설명했기 때문에 넘어가도록 하겠습니다.

Placement policy

1) Scheduling Policies

쿠버네티스에서는 스케줄러를 실행할 때 기본 스케줄링 정책을 사용하여 노드를 필터링하고 스코어링 할 수 있도록 합니다.
스케줄링 정책을 적용하는 방법은 File을 적용하거나 configMap을 적용하는 두 가지 방법으로 다음과 같습니다.

  • kube-scheduler --policy-config-file <filename>
  • kube-scheduler --policy-configmap <ConfigMap>

스케줄러 정책에는 두 가지 필드가 있으며 다음 configMap을 통해서 자세히 설명하겠습니다.

kind: ConfigMap
apiVersion: v1
metadata:
  name: scheduler-policy
  namespace: kube-system
data:
  policy.cfg: |
      {
        "kind": "Policy",
        "apiVersion": "v1",
        "predicates": [
          {"name": "PodFitsHostPorts"},
          {"name": "PodFitsResources"},
          {"name": "NoDiskConflict"},
          {"name": "NoVolumeZoneConflict"},
          {"name": "MatchNodeSelector"},
          {"name": "HostName"},
        ],
        "priorities": [
          {"name": "LeastRequestedPriority", "weight": 2},
          {"name": "BalancedResourceAllocation", "weight": 1},
          {"name": "ServiceSpreadingPriority", "weight": 2},
          {"name": "EqualPriority", "weight": 1}
        ]
      }

predicates

  • 필터링을 위한 필드로 각 name 에 해당하는 값을 통해서 필터링 함
  • PodFitsHostsPorts 는 파드가 요청하는 포트를 노드가 사용할 수 있는지 확인

priorities

  • 스코어링을 위한 필드로 각 name 에 해당하는 값을 weight 에 해당하는 값으로 스코어링 함
  • LeastRequestedPriority 는 파드의 requests 필드의 리소스가 적은 노드를 선호하며, 우선순위 값은 2 로 설정

👌 name 에 해당하는 Policy는 Scheduling Policies 에서 자세하게 확인할 수 있습니다.

2) Scheduling Process

다음은 kube-scheduler 가 파드가 배치될 노드를 어떤 절차로 선택하는지 알아보도록 하겠습니다.

1. Filter the Nodes

  • 사용 가능한 노드를 지정된 제약 조건 또는 요구 사항에 따라 필터링
  • predicates에 선언되어있는 필터 함수 목록에서 실행하여 필터링

2. Prioritize the Filtered List of Nodes

  • 필터링을 통과한 노드를 0 ~ 10 사이의 점수로 할당
  • 숫자가 클수록 우선 순위가 높으며 같을 수 있음

3. Select the Best Fit Node

  • 점수에 따라 정렬된 노드 중 가장 높은 점수를 받은 노드를 선택
  • 같은 점수인 경우 랜덤하게 선택

Scheduling Pods

1) Node Name

Node Name 은 가장 간단한 형태의 노드 선택 제약 조건이지만 한계로 인해서 사용하지 않습니다.
이를 사용하기 위해서는 nodeName 필드에 노드의 이름을 추가하면 됩니다.

이 때 스케줄러는 파드를 무시하고 nodeName 필드에 명시된 노드에서 실행 중인 kubelet이 파드를 실행하려고 합니다.

몇 가지 제한사항은 다음과 같습니다.

  • 명시된 노드가 없으면 파드가 실행되지 않고 자동으로 삭제될 수 있음
  • 명시된 노드에 충분한 공간이 없으면 파드가 실패하고 OutOfmemory 또는 OutOfcpu 로 이유가 표시됨
  • 위의 이유로 클라우드 환경에서 Node Name은 항상 예측 가능하지도 안정적이지도 않음

다음은 nodeName 필드를 사용하여 kube-01 노드에서 실행되기 위한 yaml 예시입니다.

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

2) Node Selector

Node Selector 는 가장 간단하고 권장되는 노드 선택 제약 조건입니다.
이를 사용하기 위해서는 nodeSelector 필드에 key-value 형태의 매핑으로 지정하면 됩니다.

이때 nodeSelector 필드에 있는 key-value 레이블이 지정된 노드만 파드를 실행할 수 있도록 필터링 합니다.

다음 yaml 예시를 보겠습니다.

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

위의 예시뿐만 아니라 기본적으로 쿠버네티스의 노드에는 표준 레이블 셋이 미리 채워져있습니다.
해당 목록은 Well-Known Labels, Annotations and Taints 를 참고하면 됩니다.

3) Node Affinity

Node Affinity 는 Node Selector 접근 방식을 세분화하여 사용할 수 있도록 하였습니다.
이를 사용하기 위해서는 requiredpreferred 방식을 이해해야합니다.

requiredFiltering 에 해당하며 조건을 만족하는 노드만 파드를 스케줄 하도록 합니다.
preferredScoring 에 해당하며 우선 순위를 통해서 보장하지 않지만 조건을 만족하는 파드를 최대한 스케줄 하도록 합니다.
Node Selector 와의 차이점은 matchExpressions 필드를 사용하여 정밀하게 노드를 선택할 수 있도록 하였습니다.

다음 yaml 예시를 보겠습니다.

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

👌 requiredpreferredIgnoredDuringExecution 이라는 접미사를 붙인 이유는 확장성 때문입니다. 현재는 해당 규칙이 유효하지 않아도 파드는 계속 실행하지만 향후에는 런타임으로 변경되는 것도 고려 대상입니다.

4) Pod Affinity

Pod Affinity 는 노드의 레이블을 기반으로 하는 것이 아닌,
노드에서 이미 실행 중인 파드 레이블을 기반으로 파드를 스케줄 될 수 있도록 합니다.
Pod AffinitypodAffinitypodAntiAffinity 가 있습니다.

podAffinity 는 노드 중 해당 레이블을 가지고 있는 파드가 있는 곳에 스케줄 될 수 있도록 합니다.
podAntiAffinity 는 노드 중 해당 레이블을 가지고 있는 파드가 없는 곳에 스케줄 될 수 있도록 합니다.

또한 파드는 노드와 다르게 셀렉터가 적용될 네임스페이스가 필요하기 때문에 topologyKey 를 사용하여
토폴로지 도메인(node, rack, cloud provider zone, cloud provider region, etc.) 을 나타냅니다.

Pod Affinity 도 마찬가지로 Node Affinity 처럼 requiredpreferred 를 사용합니다.

다음 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

5) Taints & Tolerations

Node Affinity 는 파드가 원하는 노드를 필터링하는 것이었다면
반대로 Taints 는 노드에 적용되어 파드 셋을 제외할 수 있도록 값을 지정합니다.
그리고 Tolerations 은 파드에 적용되어 노드에 적용된 Taints 와 비교하여 스케줄을 제외될 수 있도록 합니다.

다음은 kubectl 을 통해서 Taints 를 추가하는 방법입니다.

  • Taints 추가 : kubectl taint nodes node1 key1=value1:NoSchedule
  • Taints 제거 : kubectl taint nodes node1 key1=value1:NoSchedule-

다음은 Taints 를 사용하는 yaml 예시입니다.

apiVersion: v1
kind: Node
metadata:
  name: node1
spec:
  taints:
  - effect: NoSchedule
    key: key1

effect 필드에 해당하는 값은 세 가지가 있으며 다음과 같습니다.

  • NoSchedule
    • Taints 에 일치하지 않는 파드는 해당 노드에 예약되지 않음
    • 노드의 기존 파드들은 유지
  • PreferNoSchedule
    • Taints 에 일치하지 않는 파드는 해당 노드에 최대한 예약 되지 않도록 함
    • 다른 노드의 자원 부족 시 예약 될 수도 있음
  • NoExecute
    • Taints 에 일치하지 않는 파드는 해당 노드에 예약되지 않음
    • 노드의 기존 파드들도 Taints 에 일치하지 않으면 제거

다음은 Tolerations 을 사용하는 yaml 예시입니다.

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

operator 필드에 해당하는 값은 두 가지가 있으며 다음과 같습니다.

  • Equal
    • key / value / effect 매개 변수가 모두 일치해야 함
    • Operator기본값
  • Exists
    • key / value 매개 변수가 일치해야 함
    • effect상관하지 않음

마지막으로 Taints & Tolerations 를 사용하는 예시는 다음과 같습니다.

  • 전용노드
    • 특정 사용자들이 노드를 독점적으로 사용하도록 하기 위해 사용
  • 특별한 하드웨어가 있는 노드
    • 작은 노드 셋에 특별한 하드웨어(GPU) 가 있는 경우 해당 하드웨어를 필요로 하는 파드만 사용하기 위해
  • Taints 기반 파드 제거
    • NoExecute 를 사용하여 특정 파드를 제거하는 경우 사용

Descheduler

지금까지는 새로운 파드를 스케줄하기위해 적절한 노드를 찾는 방법에 대해서 알아보았습니다.
하지만 노드 내에서 변경사항은 계속해서 일어나며 노드의 스케일이 변할 때에도 기존의 파드는 유지가 됩니다.

이를 해결하기 위해 Descheduler 는 특정 정책에 따라 파드를 제거하고 다른 적절한 노드로 다시 예약할 수 있도록 합니다.

다음은 Descheduling 을 위해 지원하는 4가지의 전략을 설명하도록 하겠습니다.

1) RemoveDuplicates

RemoveDuplicates 전략은 Replica Set, Deployment로 생성된
같은 종류의 파드들이 같은 노드에 없도록 하는 전략입니다.

예를 들어 하나의 노드가 이상이 있어 해당 노드에 있던 파드들이 다른 노드로 옮겨가는 경우가 있습니다.
그때 이미 같은 종류의 파드가 실행되고 있는 노드에 스케줄 될 수 있습니다.

만약 이상이 있던 노드가 복구가 되었을 때 같은 노드에 실행되고 있는 같은 종류의 파드를 옮긴다면
RemoveDuplicates 전략을 사용하면 유용합니다.

다음은 yaml 예시입니다.

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemoveDuplicates":
     enabled: true

2) LowNodeUtilization

LowNodeUtilization 전략은 자원 활용률이 과도하게 높거나 낮은 노드를 분산시키는데 사용합니다.

즉, 해당 전략이 지정이 되어있다면 자원 활용률이 높은 노드의 파드를 제거하여
자원 활용률이 낮은 노드에 배치되도록 합니다.

다음은 yaml 예시입니다.

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "LowNodeUtilization":
     enabled: true
     params:
       nodeResourceUtilizationThresholds:
         thresholds: 
           "cpu" : 20
           "memory": 20
           "pods": 20
         targetThresholds: 
           "cpu" : 50
           "memory": 50
           "pods": 50
         numberOfNodes: 3 

위의 예시를 보면 thresholds 필드와 targetThresholds 가 있습니다.
thresholds 는 활용률이 낮은 노드를 찾기 위한 필드이며
targetThresholds 는 활용률이 높은 노드를 찾기 위한 필드입니다.

numberOfNodes 필드는 LowNodeUtilization 전략이 활성화 되는 순간은
활용도가 낮은 노드가 최소 이 값을 초과할 때 활성화 된다는 것을 나타내기 위한 필드입니다.

위의 예시에서 thresholds 의 값보다 낮은 자원 활용도를 가진 노드가
3 개를 초과해야 Descheduling 되는 것을 나타냅니다.
기본적으로 numberOfNodes0 입니다.

3) RemovePodsViolatingInterPodAntiAffinity

RemovePodsViolatingInterPodAntiAffinity 전략은 Pod Affinity 규칙이 추가될 때
기존 노드에 있던 파드들을 추가된 Pod Affinity 에 맞게 Descheduling 할 때 사용합니다.

예를 들어 Node1podA, podB, podC 가 실행중이며
podApodBpodC 에 대한 podAntiAffinity 가 추가된다면
podApodAntiAffinity 에 맞게 Descheduling 됩니다.

다음은 yaml 예시입니다.

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemovePodsViolatingInterPodAntiAffinity": 
     enabled: true

4) RemovePodsViolatingNodeAffinity

RemovePodsViolatingNodeAffinity 전략은 Node Affinity 규칙을 위반하는 파드를 Descheduling 할 때 사용합니다.

다음은 yaml 예시입니다.

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemovePodsViolatingNodeAffinity":
    enabled: true
    params:
      nodeAffinityType:
      - "requiredDuringSchedulingIgnoredDuringExecution"

다음은 위의 정책과 관계 없이 Descheduling 되지 않는 파드들입니다.

  • scheduler.alpha.kubernetes.io/critical-pod 어노테이션으로 표시된 중요한 파드
  • Replica Set, Deployment, Job, DaemonSet 에 의해 관리되지 않는 파드
  • 로컬 스토리지를 갖는 파드
  • 파드를 축출 할 때 PodDisruptionBudget 규칙을 위반하게 되는 PodDisruptionBudget을 사용한 파드
  • Descheduler 파드 자신

🍏 정리

지금까지 Kubernetes 에서 사용하는 Scheduler 가 노드에 스케줄하기 위해
고려해야하는 것스케줄의 원리에 대해서 이야기하였습니다.

하지만 파드를 배치하는 것에 개입하는 것은 최소한으로 줄이는 것이 좋습니다.
특히 쿠버네티스 패턴 - 2장 예측 범위 내의 요구사항 에서 정리한 패턴을 잘 따른다면
Scheduler 는 가능한 가장 적절한 노드에 파드를 배치할 것입니다.

👉 Reference

profile
ohhong

0개의 댓글