본문 바로가기

AWS EKS 실습/EKS Beginner

Assigning Pods to Nodes

이 파트에서는 pod 할당 전략의 작동 방식, 대안 및 권장 되는 접근 방식을 검토한다. 특정 노드에서만 실행되거나 특정 노드에서 실행되도록 pod를 제한 할 수 있다.

 

일반적으로 스케줄러가 자동으로 Pod의 적절한 배치를 수행하기 때문에 pod를 노드에 할당하는 제약 조건이 필요하지 않으나 Pod가 연결된 노드에 특성이 일부 있을 수 있어 이 기능도 필요하다.

 

ㅁ NODESELECTOR

 

nodeSelectorsms는 node를 선택하는 제한을 적용하기에 가장 추천하는 기능이다. nodeSelector는 PodSpec의 필드이다. kye-value 맵을 지정한다. Pod가 Node에서 실행될 수 있으려면 Node에 표시된 각 Key-Value Pair가 Label로 있어야 한다. 

 

ㅇ Attach a Label to the Node

 

구성되어 있는 Node 정보를 확인해 본다.

eksuser:~/environment $ kubectl get nodes
NAME                                                STATUS   ROLES    AGE   VERSION
ip-192-168-21-20.ap-northeast-2.compute.internal    Ready    <none>   9d    v1.17.12-eks-7684af
ip-192-168-40-208.ap-northeast-2.compute.internal   Ready    <none>   9d    v1.17.12-eks-7684af
ip-192-168-71-49.ap-northeast-2.compute.internal    Ready    <none>   9d    v1.17.12-eks-7684af

이제 node에 disktype=ssd라는 새로운 labe을 추가한다. 먼저 해당 label이 적용되어 있는지 확인한다.

eksuser:~/environment $ kubectl get nodes --selector disktype=ssd
No resources found in default namespace.
kubectl apply -f ~/environment/pod-nginx.yaml

첫번째 노드에만 disktype=ssd label을 적용하기 위해 아래와 같은 command를 적용한다.

 

# export the first node name as a variable
export FIRST_NODE_NAME=$(kubectl get nodes -o json | jq -r '.items[0].metadata.name')

# add the label to the node
kubectl label nodes ${FIRST_NODE_NAME} disktype=ssd

 

다시 disktype=ssd인 Label로 정의된 node만 조회해 본다.

eksuser:~/environment $ kubectl get nodes
NAME                                                STATUS   ROLES    AGE   VERSION
ip-192-168-21-20.ap-northeast-2.compute.internal    Ready    <none>   9d    v1.17.12-eks-7684af
ip-192-168-40-208.ap-northeast-2.compute.internal   Ready    <none>   9d    v1.17.12-eks-7684af
ip-192-168-71-49.ap-northeast-2.compute.internal    Ready    <none>   9d    v1.17.12-eks-7684af

eksuser:~/environment $ kubectl get nodes --selector disktype=ssd
NAME                                               STATUS   ROLES    AGE   VERSION
ip-192-168-21-20.ap-northeast-2.compute.internal   Ready    <none>   9d    v1.17.12-eks-7684af

 

ㅇ Deploys a nginx pod only to the node with the new label

 

pod spec에 nodeSelector를 갖은 단순한 pod create file을 생성한다.

cat <<EoF > ~/environment/pod-ngingx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagepullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd
EoF
kubectl apply -f ~/environment/pod-nginx.yaml

 

ㅁ Affinity And Anti-Affinity

 

nodeSelector는 특정 Label이 있는 노드로 Pod의 구성을 제한하는 매우 간단하 방법을 제공한다. Affinity/Anti-Affinity 기능은 표현할 수 있는 제약의 한계를 크게 확장한다.

 

주요 개선 사항은 다음과 같다.

 

- 단순 And or  정확한 일치 등이상으로 표현 방식이 뛰어나다.

-  rule이 업격한 요구 사항이 아니라 "soft/기본설정"임을 나타낼 수 있으므로 스케줄러가 이를 충족할 수 없더라도 Pod는 여전히 스케줄된다.

- Node 자체의 Label뿐만이 아닌 Ndoe에서 실행중인 다른 Pod의 라벨에 대해서도 제한 할 수 있다. 이렇게 하면 어떤 Pod랑 한 노드에 같이 배포가 되어야 하는지 말아야 하는지에 대한 규칙을 적용할 수 있다.

 

Affinity 기능은 node affinity와 inter-pod affinity/anti-affinity의 두 가지 타입이 있다. Node Affinity는 기존 nodeSelector와 비슷하지만 inter-pod affnity/anti-affinity는 node label 기준이라기 보다는 pod label 기준으로 제한한다. 

 

  ㅁ Node Affinity

 

Node Affinity는 kubernetes 1.2 alpha에서 처음 도입됬다. Node Affinity는 nodeSelector와 유사한 개념이다. 이를 통해 Node의 Label 기반으로 pod가 스케줄될 수 있는 node를 제한할 수 있다.

 

node affinity는 아래와 같이 2가지 타입이 있다.

 

- requiredDuringSchedulingIgnoredDuringExecution 

- preferredDuringSchedulingIgnoredDuringExecution.

 

requiredDuringSchedulingIngnoredDuringExecutiondms Nodedptj Pod를 스케줄하기 위해 충족해야 하는 규칙을 지정한다는 점에서 각각 "Hard" 및 "Soft"라고 생각할 수 있다. (NodeSelector와 비슷)

preferredDuringSchedulingIgnoredDuringExecution은 스케줄러가 적용하려고 하지만 보장하지는 않는다는 기본 설정을 지정한다.

이름의 IgnoredDUringExecution 부분은 nodeSelector가 작동하는 방식과 유사하게 노드의 Label이 런타임시 변경되어 Pod의 Affinity 규칙이 더 이상 충족되지 않는 경우에도 Pod가 Node에서 계속 실행된다는 것을 의미한다.

 

따라서 requiredDuringSchedulingIgnoredDuringExecution의 예는 "Intel CPU가 있는 노드에서만 포드 실행" 이고 preferredDUringSchedulingIgnoredDuringExecution의 예는 "가용성 영역 XYZ 에서 이 Pod 집합을 실행하려고 시도하지만 가능하지 않은 경우 일부 다른 곳에서 실행하도록 허용" 이다.

 

Node Affinity는 PodSpec의 field Afinity의 nodeAffinity 필드로 지정한다.

 

Node Affinity를 사용하는 Pod의 예는 아래와 같다.

# export the first node name as a variable
export FIRST_NODE_NAME=$(kubectl get nodes -o json | jq -r '.items[0].metadata.name')

kubectl label nodes ${FIRST_NODE_NAME} azname=az1
node/ip-192-168-21-20.ap-northeast-2.compute.internal labeled

192-168-21-20이 FIRST_NODE_NAME임을 볼 수 있다.

 

nodeAffnity를 생성한다. 이 nodeAffinty는 키가 azname이고 값이  az1 또는 az2 인 Label이 있는 Node에만 pod를 배치할 수 있게 한다.

또한 해당 기준을 충족하는 누드 중에서 키가  another-node-label-key이고 값이 another-node-label-value인 레이블이 있는 노드를 선호하게 한다. 

cat <<EoF > ~/environment/pod-with-node-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: azname
            operator: In
            values:
            - az1
            - az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: us.gcr.io/k8s-artifacts-prod/pause:2.0
EoF

 

kubectl apply -f ~/environment/pod-with-node-affinity.yaml

정상적으로 작동되는지 점검한다.

kubectl get pods -o wide
NAME                     READY   STATUS      RESTARTS   AGE     IP               NODE                                               NOMINATED NODE   READINESS GATES
nginx                    1/1     Running     0          25h     192.168.29.96    ip-192-168-21-20.ap-northeast-2.compute.internal   <none>           <none>
with-node-affinity       1/1     Running     0          45s     192.168.9.22     ip-192-168-21-20.ap-northeast-2.compute.internal   <none>           <none>

192-168-21-20 Node에 with-node-affinity Pod가 할당된 것을 볼 수 있다.

 

label을 다른 Node에 배치 할 것이므로 먼저 label을 정리하고 pod를 삭제

 

kubectl delete -f ~/environment/pod-with-node-affinity.yaml

kubectl label nodes ${FIRST_NODE_NAME} azname-

 

export SECOND_NODE_NAME=$(kubectl get nodes -o json | jq -r '.items[1].metadata.name')

kubectl label nodes ${SECOND_NODE_NAME} azname=az1
kubectl apply -f ~/environment/pod-with-node-affinity.yaml

Second Node가 무엇으로 되어 있는지 확인해 본다.

$ kubectl label nodes ${SECOND_NODE_NAME} azname=az1
node/ip-192-168-40-208.ap-northeast-2.compute.internal labeled

SECOND_NODE_NAME이 192-168-40-208임을 볼 수 있고.. 해당 Node에 Pod가 배포되었는지 본다.

 

kubectl get pods -o wide
NAME                     READY   STATUS      RESTARTS   AGE     IP               NODE                                                NOMINATED NODE   READINESS GATES
nginx                    1/1     Running     0          27h     192.168.29.96    ip-192-168-21-20.ap-northeast-2.compute.internal    <none>           <none>
with-node-affinity       1/1     Running     0          11s     192.168.53.76    ip-192-168-40-208.ap-northeast-2.compute.internal   <none>           <none>

정상적으로 잘 할당되었음을 확인 할 수 있다.

 

 

위의 Affinity 설정 Yaml 파일에서 In Operator를 볼 수 있다.

 

새 노드 선호도 구문은 In, NotIn, Exists, DoesNotExist, Gt, Lt 연산자를 지원한다. NotIn 및 DoesNotExist를 사용하여 anit-Affinity 행위에 대한 동작을 수행할 수 있다.

 

nodeSelector 및 nodeAffinity를 모두 지정하는 경우 Pod가 후보 Node에 스케줄 되도록 하려면 두 조건이 충족되어야 한다.

 

nodeAffinity 유형과 연관된 여러 nodeSelector Terms를 지정하는 경우 nodeSelector Terms 중 하나가 충족되면 Node에 포드를 스케줄 할 수 있다

 

Pode가 스케줄 된 Node의 Label을 제거하거나 변경해도 Pod가 제거되진 않는다. 즉 Affinity 선택은 Pod를 스케줄 할 때만 작동한다.

 

preferredDuringSchedulingIgnoredDuringExecution의 가중치 필드는 1-100 범위에 있다. 모든 스케줄링 요구 사항(리로스 요청, RequiredDuringScheduling 선호도 표현식 등)을 충족하는 각 Node에 대해 Scheduller는 이 Field의 요소를 반복하고 Node가 해당하는 것과 일치하는 경우 합계에 가중치를 추가하여 합계를 계산한다. MatchExpression. 이 점수는 Node에 대한 다른 우선 순위 함수의 점수와 결합되며 총 점수가 가장 높은 노드가 가장 선호되게 된다.

 

위에서 만든 두 노드를 삭제한다.

kubectl delete -f ~/environment/pod-nginx.yaml
kubectl delete -f ~/environment/pod-with-node-affinity.yaml

 

ㅁ Practical Use-Cases

 

ㅇ 항상 같은 Node에 위치

 

3Node Cluster에서 웹 애플리케이션은 redis와 같은 인메모리 캐시가 있다. 일반적으로 웹 서버가 가능한 한 캐시와 함께 배치되기를 원한다.

아래 예에서는 3개의 replica와 selector label app=store가 있는 간단한 redis 배포를 위한 yaml 파일이다. 이 Deployment는 스케줄러가 하나의 싱글 node에 같이 구성되지 않게 하기 위하여 구성한 PodAnitiAffinity를 갖는다.

cat <<EoF > ~/environment/redis-with-node-affinity.yaml
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
EoF

 

아래 yaml은 podAntiAffinity및 podAffinity로 구성되어 스케줄러에 모든 replicas가 selector label app=store가 있는 pod와 함께 배치됨을 알린다.  이렇게 하면 각 웹 서버 복제본이 단일 노드에 함께 배치되지 않는다. 

 

cat <<EoF > ~/environment/web-with-node-affinity.yaml
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.12-alpine
EoF

 

이 Deployment를 적용한다.

kubectl apply -f ~/environment/redis-with-node-affinity.yaml
kubectl apply -f ~/environment/web-with-node-affinity.yaml

 

 

아래와 같이 구성되는것이 목표이다.

 

node-1 - webserver-1 - cache-1

node-2 - webserver-2 - cache-2

node-3 - webserver-3 - cache-3

 

Node별로 어떻게 구성되어 있는지 확인해 본다.

# We will use --sort-by to filter by nodes name
 kubectl get pods -o wide --sort-by='.spec.nodeName'

 

$  kubectl get pods -o wide --sort-by='.spec.nodeName'
NAME                           READY   STATUS      RESTARTS   AGE     IP               NODE                                                NOMINATED NODE   READINESS GATES
web-server-655bf8bdf4-t7vxk    1/1     Running     0          25s     192.168.29.96    ip-192-168-21-20.ap-northeast-2.compute.internal    <none>           <none>
redis-cache-6bc7d5b59d-rdk4v   1/1     Running     0          26s     192.168.21.194   ip-192-168-21-20.ap-northeast-2.compute.internal    <none>           <none>
web-server-655bf8bdf4-lm2zs    1/1     Running     0          25s     192.168.53.76    ip-192-168-40-208.ap-northeast-2.compute.internal   <none>           <none>
redis-cache-6bc7d5b59d-mnsh7   1/1     Running     0          26s     192.168.52.23    ip-192-168-40-208.ap-northeast-2.compute.internal   <none>           <none>
web-server-655bf8bdf4-tddxd    1/1     Running     0          25s     192.168.76.10    ip-192-168-71-49.ap-northeast-2.compute.internal    <none>           <none>
redis-cache-6bc7d5b59d-vvlsr   1/1     Running     0          26s     192.168.92.91    ip-192-168-71-49.ap-northeast-2.compute.internal    <none>           <none>

 

테스트 Cleaning Up script

kubectl delete -f ~/environment/redis-with-node-affinity.yaml
kubectl delete -f ~/environment/web-with-node-affinity.yaml
kubectl label nodes --all azname-
kubectl label nodes --all disktype-