티스토리 뷰

728x90
반응형

서론

본 포스팅에서는 Kubernetes 특정 node에 Pod를 배치하는 방법에 대해 살펴보겠습니다.

Kubernetes의 구조는 크게 MaterNode / WorkerNode / InfraNode / Ingress Node 등으로 구분할 수 있습니다.

- MasterNode에는 Kubernetes를 관리하는 관리노드가 설치

- InfraNode에는 각종 Echo System(Monitoring, Logging, Tracing 등)이 설치

- WorkerNode에는 실제 Application이 배포되는 노드

- IngressNode에는 Ingress Controller가 배포되는 노드

...

위와 같은 용도로 Node를 구성하게 됩니다.

예를 들어 Nginx를 사용하는 홈페이지 환경을 Docker 이미지로 생성하여 Kubernetes에 배포할 경우 해당 Pod는 WorkerNode에 구성되어야 합니다.

또한 모니터링을 위해 Prometheus Docker Image를 배포할 경우 InfraNode에 Pod가 배포되어야 합니다.

이와같이 용도에 맞게 Pod를 배치 시킬 수 있는 Label 기능과 Label을 선택할 수 있는 NodeSelector & Affinity에 대해 테스트를 진행하겠습니다.

본문

1. label 관리

1) node label info

kubectl get nodes --show-labels

먼저 현재 구성되어 있는 node들의 label 정보를 확인해 보겠습니다.

[root@kubemaster ~]# kubectl get nodes --show-labels

NAME         STATUS   ROLES    AGE   VERSION   LABELS

kubemaster   Ready    master   16d   v1.16.3  

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kubemaster,

kubernetes.io/os=linux,node-role.kubernetes.io/master=

kubeworker   Ready       16d   v1.16.3   

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kubeworker,

kubernetes.io/os=linux

[root@kubemaster ~]#

kubemaster, kubeworker 노드의 label 정보가 위와 같이 구성되어 있으며, 특이점은 MasterNode에는 node-role.kubernetes.io/master= Label이 등록되어 있습니다.

 

2) node label add

kubectl label nodes [node_name] [key]=[value]

다음으로 label을 추가해 보도록 하겠습니다.

[root@kubemaster ~]# kubectl label nodes kubeworker key=worker
node/kubeworker labeled
[root@kubemaster ~]# kubectl get nodes --show-labels
NAME         STATUS   ROLES    AGE   VERSION   LABELS
kubemaster   Ready    master   16d   v1.16.3   

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kubemaster,

kubernetes.io/os=linux,node-role.kubernetes.io/master=
kubeworker   Ready       16d   v1.16.3   

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,key=worker,kubernetes.io/arch=amd64,

kubernetes.io/hostname=kubeworker,kubernetes.io/os=linux
[root@kubemaster ~]# kubectl describe nodes kubeworker
Name:               kubeworker
Roles:              
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    key=worker
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=kubeworker
                    kubernetes.io/os=linux
Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Wed, 25 Dec 2019 22:17:47 +0900
Taints:             
Unschedulable:      false
Conditions:
  Type                 Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----                 ------  -----------------                 ------------------                ------                       -------
  NetworkUnavailable   False   Sat, 11 Jan 2020 15:33:07 +0900   Sat, 11 Jan 2020 15:33:07 +0900   

CalicoIsUp                   Calico is running on this node
  MemoryPressure       False   Sat, 11 Jan 2020 22:28:21 +0900   Wed, 25 Dec 2019 22:17:48 +0900   

KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure         False   Sat, 11 Jan 2020 22:28:21 +0900   Wed, 25 Dec 2019 22:17:48 +0900   

KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure          False   Sat, 11 Jan 2020 22:28:21 +0900   Wed, 25 Dec 2019 22:17:48 +0900   

KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready                True    Sat, 11 Jan 2020 22:28:21 +0900   Wed, 25 Dec 2019 22:18:08 +0900   

KubeletReady                 kubelet is posting ready status
Addresses:
  InternalIP:  192.168.56.103
  Hostname:    kubeworker
Capacity:
 cpu:                2
 ephemeral-storage:  49250820Ki
 hugepages-2Mi:      0
 memory:             1882148Ki
 pods:               110
Allocatable:
 cpu:                1900m
 ephemeral-storage:  45389555637
 hugepages-2Mi:      0
 memory:             1529748Ki
 pods:               110
System Info:
 Machine ID:                 7e9b604579b5441988f6d3ad8f70c343
 System UUID:                7E9B6045-79B5-4419-88F6-D3AD8F70C343
 Boot ID:                    5fe7f547-c84c-4ba7-b6ae-24bb2ef1defc
 Kernel Version:             3.10.0-957.el7.x86_64
 OS Image:                   CentOS Linux 7 (Core)
 Operating System:           linux
 Architecture:               amd64
 Container Runtime Version:  docker://18.9.7
 Kubelet Version:            v1.16.3
 Kube-Proxy Version:         v1.16.3
PodCIDR:                     10.233.65.0/24
PodCIDRs:                    10.233.65.0/24
Non-terminated Pods:         (8 in total)
  Namespace                  Name                                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                         ------------  ----------  ---------------  -------------  ---
  ingress-nginx              nginx-ingress-controller-6fbff54876-lsd4l    0 (0%)        0 (0%)      0 (0%)           0 (0%)         17d
  kube-system                calico-kube-controllers-584dcf688b-qj7vb     30m (1%)      100m (5%)   64M (4%)         256M (16%)     17d
  kube-system                calico-node-q254k                            150m (7%)     300m (15%)  64M (4%)         500M (31%)     17d
  kube-system                coredns-58687784f9-z9gdk                     100m (5%)     0 (0%)      70Mi (4%)        170Mi (11%)    17d
  kube-system                kube-proxy-l9zzx                             0 (0%)        0 (0%)      0 (0%)           0 (0%)         17d
  kube-system                kubernetes-dashboard-556b9ff8f8-pkxw7        50m (2%)      100m (5%)   64M (4%)         256M (16%)     17d
  kube-system                nginx-proxy-kubeworker                       25m (1%)      0 (0%)      32M (2%)         0 (0%)         17d
  kube-system                nodelocaldns-j64jh                           100m (5%)     0 (0%)      70Mi (4%)        170Mi (11%)    17d
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests         Limits
  --------           --------         ------
  cpu                455m (23%)       500m (26%)
  memory             370800640 (23%)  1368515840 (87%)
  ephemeral-storage  0 (0%)           0 (0%)
Events:              
[root@kubemaster ~]# 

위와 같이 kubeworker node에 key=worker라는 label이 추가된 것을 확인할 수 있습니다.

 

3) node label delete

kubectl label nodes [node_name] [key]-

다음으로 추가한 label을 삭제해 보도록 하겠습니다.

[root@kubemaster ~]# kubectl label nodes kubeworker key-

node/kubeworker labeled

[root@kubemaster ~]# kubectl get nodes --show-labels

NAME         STATUS   ROLES    AGE   VERSION   LABELS

kubemaster   Ready    master   17d   v1.16.3   

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kubemaster,

kubernetes.io/os=linux,node-role.kubernetes.io/master=

kubeworker   Ready       17d   v1.16.3   

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kubeworker,

kubernetes.io/os=linux

[root@kubemaster ~]# 

위와 같이 추가한 key를 삭제할 수 있습니다.

2. Label을 이용한 특정 Node에 Pod 배포

이제부터는 추가한 Label을 Deployment에 적용하는 방법에 대해 살펴보겠습니다.

크게 nodeSelector와 affinity를 이용할 수 있습니다.

1) nodeSelector

아래는 생성한 label을 기준으로 deployment에 nodeSelector를 추가한 yaml 파일입니다.

apiVersion: apps/v1

kind: Deployment

metadata:

  name: wildfly-app-deployment

  labels:

    app: wildfly-app

spec:

  replicas: 1

  selector:

    matchLabels:

      app: wildfly-app

  template:

    metadata:

      labels:

        app: wildfly-app

    spec:

      containers:

      - name: wildfly-app

        image: 192.168.56.100:5000/middleware/wildfly/wildfly_custom_image:latest

        imagePullPolicy: Always

        ports:

        - containerPort: 8080

      nodeSelector:

        key: worker

---

apiVersion: v1

kind: Service

metadata:

  name: wildfly-app

spec:

  selector:

    app: wildfly-app

  ports:

    - protocol: TCP

      port: 8080

      targetPort: 8080

  type: ClusterIP

---

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  name: wildfly-app-ingress

  namespace: default

spec:

  rules:

  - host: test.nrson.co.kr

    http:

      paths:

      - backend:

          serviceName: wildfly-app

          servicePort: 8080

        path: /

  tls:

  - hosts:

    - test.nrson.co.kr

    secretName: nrson-tls

위와 같이 deployment에

      nodeSelector:

        key: worker

형식으로 nodeSelector와 key, value를 지정하면 해당 label을 갖고 있는 node에만 배포가 진행됩니다.

그럼 실제로 배포를 진행해 보도록 하겠습니다.

[root@kubemaster deployment]# kubectl create -f deployment.yaml 

deployment.apps/wildfly-app-deployment created

service/wildfly-app created

ingress.extensions/wildfly-app-ingress created

[root@kubemaster deployment]# kubectl get pods -o wide

NAME                                      READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES

wildfly-app-deployment-84b77d7c98-5p49p   1/1     Running   0          83s   10.233.104.45   kubeworker              

[root@kubemaster deployment]#

위와 같이 key=worker label을 포함하고 있는 kubeworker node에 pods가 배치된 것을 확인할 수 있습니다.

3) affinity

affinity는 뜻 그대로 유연한 Scheduling이 가능하도록 구성할 수 있습니다.

앞서 살펴본 nodeSelector의 경우 label naming을 기준으로 Pod를 특정 노드 또는 특정 노드 그룹에 배포하는 방식을 사용하였다면, affinity는 다양한 조건을 제시할 수 있고 유연하게 처리할 수 있습니다.

affinity는 크게 노드를 기준으로 하는 node affinity와 pod 레벨에서 label을 관리할 수 있는 pod affinity으로 구분할 수 있습니다.

 

a) node affinity

node affinity가 node level에서 배치를 관리하는다는점에서 nodeSelector와는 비슷하지만 다양한 조건을 명시할 수 있다는 유연성에서 차이가 있습니다.

node affinity는 크게 아래와 같이 4가지로 분류할 수 있습니다.

requiredDuringSchedulingIgnoredDuringExecution 

preferredDuringSchedulingIgnoredDuringExecution 

requiredDuringSchedulingRequiredDuringExecution 

preferredDuringSchedulingRequiredDuringExecution

서두의 빨간색 required(hard affinity) & preferred(soft affinity)는 반드시 포함해야 하는지 또는 우선시하되 필수는 아닌지를 결정하는 조건이며, 중간의 파란색 Ignored & Required는 운영 중(Runtime) Node의 Label이 변경되면 무시할 것인지(Ignore) 또는 즉시 Eviction 처리(Required)하여 재기동을 수행할 것인지를 결정합니다.

앞서 nodeSelector로 생성한 pod는 초기화를 진행하고 yaml 파일을 수정해 보도록 하겠습니다.

apiVersion: apps/v1

kind: Deployment

metadata:

  name: wildfly-app-deployment

  labels:

    app: wildfly-app

spec:

  replicas: 1

  selector:

    matchLabels:

      app: wildfly-app

  template:

    metadata:

      labels:

        app: wildfly-app

    spec:

      containers:

      - name: wildfly-app

        image: 192.168.56.100:5000/middleware/wildfly/wildfly_custom_image:latest

        imagePullPolicy: Always

        ports:

        - containerPort: 8080

      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: key
                operator: In
                values:
                - worker

---

apiVersion: v1

kind: Service

metadata:

  name: wildfly-app

spec:

  selector:

    app: wildfly-app

  ports:

    - protocol: TCP

      port: 8080

      targetPort: 8080

  type: ClusterIP

---

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  name: wildfly-app-ingress

  namespace: default

spec:

  rules:

  - host: test.nrson.co.kr

    http:

      paths:

      - backend:

          serviceName: wildfly-app

          servicePort: 8080

        path: /

  tls:

  - hosts:

    - test.nrson.co.kr

    secretName: nrson-tls

위와 같이 deployment에 nodeAffinity를 추가하였습니다.

nodeAffinity가 의미하는 내용을 살펴보자면

      affinity: 
        nodeAffinity: 
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms: 
            - matchExpressions: 
              - key: key 
                operator: In 
                values: 
                - worker

requiredDuringSchedulingIgnoredDuringExecution의 경우 아래 요구조건이 일치하는 node에 적용하겠다는 의미로 matchExpressions로 정의 된 key 값이 label의 [key], values 값이 label의 [value]와 일치해야 합니다. 여기서 처음 정의되는 operator라는 항목이 있는데 nodeAffinity에서 사용할 수 있는 operator에는 In 외에도 NotIn, Exists, DoesNotExist, Gt, Lt 등을 활용할 수 있습니다.

가장 많이 사용하는 In의 경우 두개 이상의 values가 명시되어 있을 때 values 중 하나의 값을 가질 경우를 의미합니다.

#참고

a. 여기서 주의 할 점은 2개 이상의 matchExpressions가 정의되어 있을 경우 각각의 matchExpressions를 만족하는 Pod가 기동되지만, matchExpressions 내부의 matching 조건이 여러개 일 경우 모든 조건을 만족해야 pod가 기동된다는 점입니다.

b. nodeAffinity와 반대로 특정 노드에만 할당하지 않도록 하는 nodeAntiAffinity를 지원합니다. matchExpressions를 만족할 경우 해당 노드는 pod를 배포하는 대상에서 제외됩니다.

c. matchExpressions에서 key는 단일 값을 갖지만, values는 여러개의 값이 나열될 수 있습니다.

d. preferred와 required는 스케줄링이 이루어지는 순간 즉 create or apply 시점에만 반영되는 정보입니다. 이를 Pod의 label 정보가 변경되는 순간에 반영할 것인지를 결정하는 ignore or required를 적용할 수 있습니다. Required가 적용될 경우 Node label이 변경되면 즉시 Eviction 상태로 변경될 수 있으니 설정에 유의해야 합니다.

 

b) pod affinity

pod affinity는 node affinity가 node의 label을 기준으로 기동했던것 처럼 pod의 label을 기준으로 조건을 만족할 경우 pod를 기동하는 방식으로 작성됩니다.

예시 1) requiredDuringSchedulingIgnoredDuringExecution

apiVersion: apps/v1

kind: Deployment

metadata:

  name: wildfly-app-deployment

  labels:

    app: wildfly-app

spec:

  replicas: 1

  selector:

    matchLabels:

      app: wildfly-app

  template:

    metadata:

      labels:

        app: wildfly-app

    spec:

      containers:

      - name: wildfly-app

        image: 192.168.56.100:5000/middleware/wildfly/wildfly_custom_image:latest

        imagePullPolicy: Always

        ports:

        - containerPort: 8080

      affinity:

        podAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - wildfly-app

            topologyKey: kubernetes.io/hostname

---

apiVersion: v1

kind: Service

metadata:

  name: wildfly-app

spec:

  selector:

    app: wildfly-app

  ports:

    - protocol: TCP

      port: 8080

      targetPort: 8080

  type: ClusterIP

---

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  name: wildfly-app-ingress

  namespace: default

spec:

  rules:

  - host: test.nrson.co.kr

    http:

      paths:

      - backend:

          serviceName: wildfly-app

          servicePort: 8080

        path: /

  tls:

  - hosts:

    - test.nrson.co.kr

    secretName: nrson-tls

podAffinity는 위와 같이 작성됩니다.

내용을 살펴보자면, "pod의 label key가 app이고, label value가 wildfly-app인 pods가 기동된 서버에 topologyKey 값과 같은 node에 해당 deployment을 배포해라" 정도로 이해 할 수 있습니다.

nodeAffinity와 마찬가지로 특정 pod가 배포된 node를 제외하라는 의미의 podAntiAffinity를 지원합니다.

약간 내용이 어렵게 느껴질 수 있어서 이를 도식화 해보도록 하겠습니다.

node와 pod의 label 정보입니다.

[root@kubemaster deployment]# kubectl get nodes --show-labels -o wide
NAME         STATUS   ROLES    AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION          CONTAINER-RUNTIME   LABELS
kubemaster   Ready    master   17d   v1.16.3   192.168.56.102           CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://18.9.7     

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kubemaster,

kubernetes.io/os=linux,node-role.kubernetes.io/master=
kubeworker1   Ready       17d   v1.16.3   192.168.56.103           CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://18.9.7     

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,key=worker,kubernetes.io/arch=amd64,

kubernetes.io/hostname=kubeworker1,kubernetes.io/os=linux

kubeworker2   Ready       17d   v1.16.3   192.168.56.104           CentOS Linux 7 (Core)   3.10.0-957.el7.x86_64   docker://18.9.7     

beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,key=compute,kubernetes.io/arch=amd64,

kubernetes.io/hostname=kubeworker2,kubernetes.io/os=linux
[root@kubemaster deployment]# kubectl get pods --show-labels -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES   LABELS
wildfly-app-deployment-57c6b57d6f-kprgl   1/1     Running   0          8h    10.233.104.48   kubeworker1                          app=wildfly-app,pod-template-hash=57c6b57d6f

apache-app-deployment-12ab8ce092-paghw   1/1     Running   0          8h    10.233.104.63   kubeworker2                          app=apache-app,pod-template-hash=12ab8ce092
[root@kubemaster deployment]# 

위와 같은 Label 정보를 기준으로 할 때 yaml 파일로 추가된 deployment가 배치 될 노드를 가정해 보자면, 다음과 같이 정리할 수 있습니다.

      affinity:

        podAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - wildfly-app

            topologyKey: kubernetes.io/hostname

labelSelector에 의해 pod의 라벨 정보가 app=wildfly-app인 pods가 기동 된 노드를 찾습니다. (pod label의 빨간색, 파란색 부분 - kubeworker1)

해당 노드의 라벨 정보가 topologyKey에 정의 된 key 값인 kubernetes.io/hostname의 value가 같은 모든 노드에 Pod를 배치(node label의 빨간색, 파란색 부분 - kubeworker1)합니다. 이때 hostname은 모든 서버가 서로 다르므로 wildfly-app이 배포된 kubeworker1에만 배포가 가능하다는 결론이 나옵니다.

 

한가지 예시를 더 들어보도록 하겠습니다.

예시 2) preferredDuringSchedulingIgnoredDuringExecution

      affinity:

        podAffinity:

          preferredDuringSchedulingIgnoredDuringExecution:

          - weight: 80

              preference:

                matchExpressions:

                - key: disktype

                  operator: In

                 values:

                 - ssd

            topologyKey: kubernetes.io/hostname

 

위와 같이 preferredDuringSchedulingIgnoredDuringExecution을 선택하고 weight로 80이 부여되어 있는것이 예시 1과 다른점입니다.

preferredDuringSchedulingIgnoredDuringExecution는 앞서 잠시 살펴보았지만, required와 달리 반드시 matchExpressions을 만족해야 하는 것은 아닙니다.

다만, preferredDuringSchedulingIgnoredDuringExecution 역시 배치해야 할 노드를 선정해야 할 기준이 있어야 하기에 다음과 같은 우선순위를 갖습니다.

(matchExpressions를 만족하는 노드들 중 weight가 높은 Node > matchExpressions를 만족하는 노드들 중 weight가 낮은 Node > matchExpressions를 만족하지 않는 노드)

이를 활용한다고 하면, Hard Disk로 HDD를 사용하는 서버보다는 SSD를 사용하는 서버에 우선순위를 높게 부여하여 먼저 기동되고 하고 싶을 경우 등에 적용한다면 유용하게 사용할 수 있을 듯 싶습니다.

 

이와같이 affinity를 응용하여 활용한다면 다음과 같은 케이스에서 사용할 수 있을 듯 합니다.

 - Active / Standby 구조로 동작하는 솔루션 이미지 등에 적용하여 Active 솔루션이 설치 된 노드에 중복되어 Standby가 배포되지 않도록 podAntiAffinity를 적용하여 사전에 방지하는 역할

- 이미 배포되어 있는 특정 Pod와 local 통신을 해야 하는 경우 해당 Pod가 배포되어 있는 개수 만큼 Replica를 지정하고 kubernetes.io/hostname을 topologyKey로 지정하여 배포하는 경우

- Kubernetes Cluster 내부에서 Pod를 기준으로 Cluster 단위를 다시한번 쪼개는 역할 등을 수행할 수 있습니다.

결론

이번 포스팅에서는 Label을 활용하여 내가 원하는 Node에 Pod를 배포하는 방법에 대해 살펴보았습니다.

간단히 정리하자면, Kubernetes Node와 Pods는 Label을 갖고 있으며, Label을 기준으로 Pod를 배치할 노드를 선택할 수 있습니다.

특히 nodeSelector를 통해 간단히 추가, 제거가 가능하며, 보다 유연한 기능을 적용하기 위해 affinity를 반영할 수 있습니다.

affinity는 node와 pod의 label을 기준으로 적용하여 node를 선택할 수 있습니다.

그 밖에도 직접 Go Lang을 이용하여 개발한 Pod 스케줄러를 적용할 수도 있습니다.

다양한 Label과 지정 방식을 적용하여 체계적이고 구조화된 Kubernetes를 설계하는 것은 구축을 넘어 운영단계에 접어들었을 때 더욱 빛을 발할 것입니다.

728x90
반응형