본문 바로가기

프로그래밍/kubernetes

CKA 시험 관련 잡다한 정리(1)

CKA 시험을 준비하면서 정리해둬야겠다고 생각해둔 내용들을 두서없이 써놓은 페이지임..

체계가 없기 때문에 무슨 의미인지 이해할수 없을수도 있음을 미리 밝혀둔다.

주된 내용은 udemy 강좌 영상을 보고 정리한 것이 주된 내용이 될듯


시험 후기에서 본 시험 비중과 현재의 시험 비중이 차이가 있어서 이게 맞는건가 싶었는데..

CKA 턱걸이 합격 후기를 보고 이해가 되었음(여기에서 언급하기로는 10개의 분야를 5개의 분야로 축소하고 Troubleshooting 비중이 30%로 올라간다고 되어 있어서 대조해보니 그러했음.)

 

시험 후기들을 보면 시험 푸는 시간을 3시간이라 되어 있는데 현재는 2시간이다. 착오없길..(Frequently Asked Questions: CKA and CKAD & CKS 의 How long will the exam take? 를 볼것)

 

시험 후기들을 보면 100점 기준에 74점 이상이면 된다고 되어 있는데 현재는 66점 이상이면 됨(What score is needed to pass the exam?)

 

시험제도가 kubernetes 1.19를 이용해서 보는 시점부터 변화가 생긴거 같다(시험 비중 재조정, 시험 시간이 2시간으로 축소, 합격점수가 66점 이상..) 가급적이면 kubernetes 1.19 버전으로 본 후기를 참조하는게 좋을듯하다..2021년 3월 7일 기준으로는 kubernetes 1.20 버전으로 시험을 본다.


Udemy에서 Certified Kubernetes Administrator (CKA) with Practice Tests 강좌를 할인할때 사서 보고 있다(할인을 의외로 자주 하기 때문에 강좌를 살 의향이 있는 사람이면 udemy를 1주일에 한번씩은 방문해서 체크해보길 바란다. 할인안할때 사면 20만원이 넘지만 할인할때 사면 2만원대에 살 수 있다) 사람들이 가장 많이 추천한 강좌라서..무엇보다 중간중간 내가 학습한 내용을 이용해 직접 실습 형태로 문제를 풀여 테스트 하는 그런 과정이 있어서 좋았다. 2020년 CKA 시험 기준으로 kubernetes 1.19로 업그레이드 된 환경을 제공해주고 있다(kubectl version 으로 확인했음) 다만 눈길을 끄는 것은 이 강좌에 있던 댓글이었다..

 

2021년 3월 7일 기준으로 3달전에 작성된 댓글인데 다음과 같이 올라와 있었다(오해가 있을수도 있어서 영문 그대로 올리도록 하겠다)

 

This week I passed CKA exam (thanks to this course). I noticed diffs between course tips, official guide and REAL exam.

There were 17 tasks , not 24 !
First time I gained 62% out of  66% . So 66% is enough, not 74%.

There was NO AUTOCOMPLETION and supervisor did not allow me to install it. Also TMUX was not allowed ( I asked).

As I passed exam from the second attempt, I noticed that tasks are same :) .

 

한글로 풀어보자면

24문항이 아니라 17문항으로 출제되며

첫시도때 66%(위에서 언급했던 커트라인인 66점을 의미)에서 62%을 맞았으며 74%가 아님

자동완성기능이 없고 감독관이 자동완성 기능 install을 허락하지 않았다. TMUX 또한 허용되지 않았다.

2번째 시도에서 합격했는데 문항수는 동일했다

 

이런 의미였다.

그래서 이 댓글에 누군가 alias 를 허용하는지 물어봤는데(alias k='kubectl')

동영상의 주인인 KodeKloud에서 그거는 허용해준다고 답글이 올라왔다

 

이 댓글을 보고 자동완성기능을 쓰지 않게끔 한거는 맞는거 같다. 이 시험의 의도와 어긋나는 부분이 있기 때문에 그거는 감내할수는 있는데..여러 콘솔창을 열지 못하는 환경에서 그나마 작업을 편하게 할수 있는 TMUX가 허용이 되지 않는건 좀 충격이었다. 다른 후기들을 보면 TMUX를 사용했다는 후기들이 많은데 만약 지금부터 준비하는 사람이라면 TMUX는 사용하지 않고서 학습하는 습관을 들이는게 좋을듯 싶다(자동완성기능도 마찬가지..)


Kubernetes Resource를 생성해주는 yaml 파일은 이 4개의 요소가 1 Depth에 반드시 있게 된다

Pod을 만드는 yaml 파일이든, replicaset을 만드는 yaml 파일이든 우리가 자주 만드는 Kubernetes Resource와 관련된 yaml 파일에는 아래의 4가지 요소가 반드시 있다.

 

apiVersion, kind, metadata, spec

 

항상 이 4가지가 yaml 파일의 1 Depth에 반드시 있다..라고 생각하고 Kubernetes Resource 관련 yaml 파일을 작성하도록 하자.

 

Kubernetes Resource(ex : Pod, ReplicaSet 등)에 이름을 부여할려면 metadata.name 에 해당 Resource의 이름을 써주면 된다.


yaml 파일을 만들때 그냥 아무것도 없는 상태에서 만드는 것 보다는 다음과 같은 형태로 만드는 것이 바람직하다.

 

kubectl run nginx --image=nginx --dry-run=client -o yaml > nginx-pod.yaml

 

--dry-run=client 옵션을 적용하면 실제 Kubernetes에 Resource(위의 코드는 Pod이 된다)가 생성되지 않고 nginx-pod.yaml 파일을 생성해준다. 


udemy 동영상 강의에서 제공되는 연습문제들을 이제껏 잘 풀어오다가 풀지 못했던 것이 있어서 관련 내용을 써둔다

kubernetes cluster에 pod이 생성은 되었지만 동작되지 않는 replicaset을 수정하는 문제였다. 이 문제는 pod을 구성하는 컨테이너의 이미지가 busybox777 이어서 이를 busybox로 수정하는 것이었는데 kubectl edit를 이용해서 이미지 이름을 수정했는데도 불구하고 Check 버튼을 클릭하면 작업이 안되었다고 알려줬다. kubectl edit로 나온 yaml을 다른 이름으로 저장해서 kubectl replace -f 파일이름 을 써주어도 Check 버튼을 클릭하면 작업이 안되었다고 알려주었다. 아무리봐도 왜 그러는지를 몰라서 이 문제를 패스했는데 문제를 풀어주는 동영상을 보고 단번에 이해가 되었다.

 

이미지명만 수정하면 작업이 안되는 것이 당연했다. 왜냐면 해당 replicaset에 이미 고치기 이전의 이미지명을 사용한 pod이 replicas 항목에서 설정된 갯수만큼 이미 생성되어 있기 때문에 새로운 이미지(busybox)로 적용된 pod을 생성하기 위해 기존의 생성된 pod을 수동으로 지워줘야 한다. 그러면 replicaset 특성상 replicas에 정해진 갯수만큼 pod을 다시 만들기 때문에 그 만드는 시점에 수정된 이미지명을 사용한 pod을 생성하게 된다. replicaset의 동작과정을 응용한 문제이다.


replicaset은 kubectl create 명령으로 생성할 수 없다. kubectl create --help 옵션을 주어서 help 명령을 보면 create 명령을 이용해서 생성할 수 있는 resource들이 나오는데 여기에는 replicaset이 없다. 여기를 보면 kubectl create replicaset 명령을 이용해 생성하는 구문이 있으나 실제로 이대로 진행해보면 --image 옵션을 해석하지 못한다. 추측엔 예전 버전엔 지원했던거 같은데 지금은 지원하지 않는 것으로 추측된다.


yaml 문법을 잘 지키도록 하자. yaml 문법은 key: value 스타일인데 간혹 아무생각없이 타이핑 하다 보면 key:value 이런 식으로 value 앞에 공백을 띄지 않고 바로 :(콜론)뒤에 써버리는 상황이 종종 있다. 이렇게 yaml을 만들게 되면 그 다음줄을 인식못하기 때문에 엉뚱한 라인 번호로 에러 메시지를 발생시키게 된다.

예를 들어 yaml 파일의 12번째 라인에 - containerPort:80 이라 쓰고 13번째 라인에 protocol: TCP 라고 작성했다 가정하면 12번째 라인인 - containerPort:80 에서 80앞에 공백을 넣지 않아서 에러가 발생하게 되는데도 불구하고 에러 메시지는 error converting YAML to JSON: yaml: line 13: mapping values are not allowed in this context 이런 식으로 13번째 라인을 가르키게 된다. 반드시 : 와 value 앞에 공백을 넣어주는 습관을 들이자.


deployment를 사용하는 상황에서 컨테이너의 image를 변경하려 할 때는 다음과 같이 사용한다

kubectl set image deployment mynginx-deployment webnginx=nginx:1.11 --record

여기서 mynginx-deployment 는 이미지를 변경하고자 하는 container가 속한 pod을 관리해주는 deployment 이름이고, webnginx=nginx:1.11 에서 webnginx는 이미지를 변경하고자 하는 container의 이름이다. 즉 mynginx-deployment 란 이름을 가진 deployment가 생성한 pod이 지니고 있는 container 들 중에서 container 이름이 webnginx인 container가 사용하는 이미지를 nginx:1.11 로 변경하겠다는 의미이다.

 

--record 옵션을 사용하면 현재 실행한 명령을 기록해둔다는 의미이다(리비전 번호를 따서 기록해준다). 이렇게 기록을 해두면 kubectl rollout 명령을 이용해 특정 리비전 번호를 지정하여 그 당시의 상태로 되돌릴수 있다. 그러나 이것은 모든 상황에 적용되는 옵션은 아니다. kubectl 명령어를 실행하여 리소스를 생성하거나 생성된 리소스를 변경할 경우 --record 옵션을 사용할 수 있다.(ex : kubectl apply -f ... , kubectl scale --replicaset=5 ... 등) 그러나 리소스를 조회하는 상황에서는 --record 옵션을 사용할 수 없다(ex : kubectl get pods 등) 또 resource를 삭제할때도 --option을 사용할 수 없다(ex: kubectl delete ...) 내가 실행하고자 하는 명령어가 리소스를 생성하거나 변경하는 기능이 있고 리비전을 남기고 싶다면 먼저 --help 옵션을 줘서 도움말을 보고 거기에서 --record 옵션을 제공하는지 확인해보고 사용하도록 하자. 흔히 deployment 를 사용할때 --record 옵션을 쓰는 예제들이 많다보니 deployment 때만 사용 가능한 옵션이란 오해의 소지가 있을듯 하여 적어놓았다.


ClusterIP, NodePort, LoadBalancer 타입으로 구분되는 Service는 공통적으로 다음의 항목들이 있다. 이 부분이 은근 헷갈리는게 있어서 따로 정리해둔다

 

spec.selector : 어떤 pod들이 이 Service를 이용하는지 정의한다. 여기에 정의된 label이 Pod의 metadata.labels에 정의되어 있는 label 또는 replicaset이나 deployment를 통해 생성된 Pod의 경우 replicaset이나 deployment의 spec.template.metadata.labels에 정의되어 있는 label 에 있는 Pod들이 해당 Service를 이용할 수 있다.

 

spec.ports.port : 서비스를 생성하게 되면 해당 서비스에 대한 IP를 할당받게 되는데 이 IP로 접근할때 사용하게 될 port를 지정하게 된다. 다만 ClusterIP와 NodePort에서는 그 연결 관계가 약간 다른 점이 있다. 그건 항목 설명 끝난뒤 따로 정리

 

spec.ports.targetPort : 해당 서비스를 이용하게 되는 Pod이 내부적으로 사용하게 될 port를 지정한다. 우리가 Pod을 직접 생성하거나 또는 replicaset, deployment를 통해 Pod을 생성할때 containerPort(Pod을 직접 생성할때는 spec.containers.ports.containerPort, replicaset이나 deployment를 통해 Pod을 생성할 경우는 spec.template.spec.containers.ports.containerPort) 값을 적어주었는데 이 값을 여기에 적어주면 된다(Pod의 container가 실제 이용하는 port이므로..)

spec.ports.port와 같은 값을 사용한다면 spec.ports.targetPort는 생략 가능하다(spec.ports.targetPort 를 생략하면 spec.ports.port 에서 사용하는 포트번호를 Pod의 Container가 사용하는 Port 번호로 사용하게 된다)

 

spec.type : 해당 서비스가 어떤 타입인지를 나타낸다(ClusterIP, NodePort, LoadBalancer)

 

서비스를 생성하면 해당 서비스를 이용할 수 있는 IP가 할당되는데 이 IP를 사용할 수 있고 또는 서비스 이름 자체를 도메인으로 삼아 이용할 수도 있다.

(ex: spec.ports.port를 8080 으로 설정한 hostname-svc-clusterip 서비스를 생생해서 이 서비스가 10.106.4.178 IP를 할당받았다고 가정할 경우 http://10.106.4.178:8080 으로 호출할수도 있고 http://hostname-svc-clusterip:8080 으로도 호출할 수 있다. 그러나 도메인 형식의 접근은 Kubernetes Cluster에서 실행중인 Pod 에서 해당 서비스를 통해 다른 Pod을 접근하고자 할때만 가능하다. IP로 접근하는것은 Pod 밖에서도 접근이 가능하다)

 

Pod 간에 통신할때는 위에서 언급한대로 Service 이름을 도메인 삼아 접근하는 것이 일반적이라 한다. IP라는게 서비스를 재생성한다든가 하면 IP가 바뀔수도 있기 때문에..

 

NodePort의 경우는 Master Node나 Worker Node의 IP에 NodePort를 만들면서 연결하게 되는 랜덤포트를 사용해서 접속이 가능하다(Master Node의 IP가 192.168.101.11 이고 NodePort가 만들어지면서 연결에 사용되는 Port가 32647이라 가정하면 192.168.101.11:32647 로 연결하면 된다. ClusterIP의 경우는 spec.ports.port 항목에 설정한 port를 spec.ports.targetPort 항목에 설정한 port와 연결되어져 통신이 이루어지지만 NodePort의 경우는 NodePort를 만들면서 생성된 Port와 spec.ports.port와 spec.ports.targetPort가 같이 연결되는 구조이다. 대신 ClusterIP의 경우 Kubernetes Cluster 안에서만 통신이 가능한 반면 NodePort는 외부에서도 통신이 된다는 차이가 있다.

 

NodePort는 spec.ports.nodePort 항목에서 NodePort 서비스가 외부와 통신을 열게 될 Port 번호를 지정할 수 있다. 만약 이를 지정하지 않으면 30000~32767 번 범위 안에서 Port가 할당된다 

 

NodePort 타입의 서비스는 ClusterIP 서비스의 기능을 포함하고 있다.


kubernetes cluster에 특정 deployment가 이미 존재하고 있는 상황에서 해당 deployment를 외부에 노출시키고자 하는 Service를 만들려고 할 경우 다음과 같이 한다

deployment의 이름이 simple-webapp-deployment 라고 한다면

 

kubectl expose deployment simple-webapp-deployment --name=webapp-service --target-port=8080 --type=NodePort --port=8080 --dry-run=client -o yaml > svc.yaml

 

Service 이름이 webapp-service 이고, targetPort와 port가 각각 8080 이며 Service Type이 NodePort인 그런 Service를 만들 svc.yaml 파일을 만들게 된다


다른 namespace에 있는 Pod을 접근하고 싶을 경우엔 다른 namespace에 연결하고자 하는 Pod과 연결되어 있는 Service를 통해 접근이 가능하다. 이럴 경우 Service 이름.namespace 이름.svc.cluster.local 로 도메인을 사용하면 된다.(도메인을 사용하는 것이기 때문에 Pod 안에서만 이런 방식으로의 호출이 가능하다는 점 잊지 말도록)

예를 들어 A namespace에 있는 B란 Pod이 C namespace에 있는 D Service와 연결된 E Pod을 접근하고자 한다면

 

D.C.svc.cluster.local 로 연결이 가능하다. D Service의 8080 port로 접속해야 한다면 D.C.svc.cluster.local:8080 이렇게 하면 된다.


Udemy 강의 동영상에서 Imperative와 Declarative 에 대한 것을 알게 되면서 Service를 현재 운영중인 Pod을 기반으로 생성하는 방법을 알게 되어서 적어둔다

먼저 Pod을 다음과 같이 생성했다고 가정하자

kubectl run redis --image=redis:alpine --labels=tier=db

이렇게 pod을 생성하면 redis:alpine 이미지를 사용하고 tiers=db 라는 label을 가지게 되는 redis란 이름을 가진 Pod을 만들게 된다.

 

그리고 이 Pod과 연결되는 Service를 다음과 같이 만든다

kubectl expose pod redis --name=redis-service --port=6379 --target-port=6379

이렇게 하면 redis-service 란 이름으로 pod의 6379번 port(--target-port=6379)와 service의 6379번 port(--port=6379)번의 port가 연결된 ClusterIP Service(--type 옵션을 주어서 Service의 타입(ClusterIP, NodePort, LoadBalancer,ExternalName)을 줄 수 있는데 이 옵션의 기본값이 ClusterIP여서 이 옵션을 안주면 자동으로 ClusterIP로 설정된다)가 만들어진다. 이렇게 service를 만드는 방법의 장점은 pod이 가지고 있는 metadata.label 의 내용을 Service의 spec.selector 에 자동으로 설정된다는 것이다. 

 

또 다른 방법은 다음과 같다

kubectl run httpd --image=httpd:alpine --port=80 --expose

이렇게 하면 httpd:alpine 이미지를 사용하고 container port를 80을 사용하는 httpd 란 이름의 pod을 만들면서 이 pod과 연결되는 ClusterIP Service를 만들게 된다. 이때 만들어지는 Service의 이름은 Pod의 이름과 같은 httpd 이며 위에서 언급했던 Service Type을 지정하는 Option이 아무것도 주어지지 않았기 때문에 default로 ClusterIP 타입으로 만들게 된다. 그리고 만들어지는 Service의 port와 targetPort가 80으로 설정이 된다. label의 경우 Pod에는 metadata.labels 에 run: http로 설정이 되어지는데 이 run: httpd 가 만들어지는 Service의 spec.selector 에 그대로 설정되어지기 때문에 httpd Pod 이 httpd Service를 이용할 수 있게 된다.

 

이렇게 kubectl 의 run, create 등의 명령으로 Resource를 생성하게 되면 CKA 시험을 볼때 시간단축에 도움이 된다. 그러나 이러한 명령만으로는 할 수 없는 작업들이 몇몇 존재하는데 그럴 경우엔 --dry-run=client -o yaml > yaml파일명.yaml 형태로 명령문을 마무리를 지어서 resource를 만들지 말고 yaml 파일만 생성한뒤 yaml 파일을 편집해서 이를 구현하면 된다(예를 들어 kubectl run을 이용해서 단일 컨테이너를 이용하는 pod을 만들수 있으나 멀티 컨테이너를 이용하는 pod은 만들수 없기 때문에 방금 얘기한 방법대로 yaml 파일을 만든뒤 해당 yaml 파일을 편집해서 멀티 컨테이너가 구동되는 pod을 만들수 있다)


scheduler가 없을 경우 pod을 생성하면 pod이 Pending(대기) 상태가 되어버린다. 왜냐면 scheduler가 없기 때문에 어느 node에 배포되어야 하는지 알 수 없기 때문이다. 내가 개발자출신이다보니 scheduler라는 단어를 보면 정해진 시간에 어떤 작업을 진행한다는 의미로 받아들였는데, kubernetes에서는 그런 의미가 아니라 pod의 관리를 의미하는듯 하다. pod의 관리라고 말한 이유는 schedluer가 단순히 현재 만든 pod을 어느 node에 배포하는지에 대한 작업만 할것 같지 않아서이다. 내가 아직 scheduler에 대한 공부를 처음하다보니 scheduler에 대해 공부하면 좀더 내용이 늘어날수도 있다. 

아무튼 Pod 이 Pending 상태가 되는 여러가지 상황중 scheduler가 없어서 어느 node에 배포되는지 정해지지 않는 상황도 있다는 것을 알았다.

scheduler는 kube-system namespace 에서 Pod으로 있기 때문에 다음의 명령어를 통해 나오는 결과를 보고 확인할 수 있다.

kubectl get pod -n kube-system

위와 같은 명령어를 내려서 kube-system namespace 에 대한 pod을 조회하여 scheduler란 단어가 들어가 있는 Pod이 없다면 scheduler가 없다고 보면 된다. 나의 경우는 udemy 테스트 환경에서는 확인하지 못했고(당연한게 scheduler가 없게끔 테스트 환경이 설정되어 있으니 확인할수가 없지...) 내가 별도로 가지고 있는 kubernetes cluster 환경에서 위와 같은 명령어를 실행하여 kube-scheduler-kubernetes-master 란 Pod이 있는 것을 확인했다.


위의 상황과 같이 scheduler가 없어서 Pod을 수동으로 특정 Node에 배포할려면 먼저 기존의 Pending 상태로 있던 Pod을 삭제한뒤 아래와 같이 spec.nodeName 항목에 배포하고자 하는 node 이름(node01) 을 지정해주면 된다

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

labels 과 selector와 annotation의 차이

labels은 만들어지는 resource 자체에 부여되는 resource를 구분하는 key=value 형태의 값들의 집합이다.

예로 Pod을 만들경우 metadata.labels에 key: value 형태로 설정해줌으로써 해당 key: value의 값을 가지고 있는 Pod 임을 알릴수 있다.

 

selector는 Resource의 적용 대상이 되는 Resource를 지정하는데 사용된다

일례로 replicaset을 보면 replicaset의 관리 대상이 되는 Pod의 metadata.labels에 있는 key: value 값을 spec.selector.matchLabels에 설정함으로써 해당 Pod이 replicaset의 관리 대상이 되는 것으로 설정하게 된다

 

annotation 은 부가정보를 적어주는 것이다. 부가정보를 적어주는것 자체는 labels와 비슷하지만 이 부가정보는 kubernetes resource가 사용하지 않기 때문에 resource가 사용하지 않는 부가적인 정보를 적어둔다. 자바 개발자다보니  자바의 어노테이션이 떠올랐는데 labels가 오히려 자바의 어노테이션에 더 가깝다. 자바의 어노테이션은 여러 클래스에서 어노테이션을 설정해서 동작하는 개념이 있기 때문에 kubernetes에서는 labels에 가깝다고 보는게 좋을듯.. 


pod에 있는 label이 어떤것인지를 보고자 할때는 다음과 같이 한다

kubectl get pod --show-labels

udemy 강좌 테스트를 풀때 나는 이 옵션을 몰라서 describe 를 이용해서 각각 pod 의 상세내용을 보고 label을 알았다


특정 label이 있는것만 조회하려 할때는 -l 또는 --selector 옵션을 사용한다

에를 들어 다음과 같이 실행하면

kubectl get pod -l env=dev

env=dev (또는 env: dev)가 label에 있는 pod을 조회한다. 내 경우는 -l 이 아니라 --selector 옵션을 사용했는데 이걸 사용했을때는 다음과 같이 썼다

kubectl get pod -selector=env=dev

여러개의 label이 있는 것을 찾을때는 ,로 연결해서 찾는다(-l env=dev,bu=finance) 다만 여러개를 사용할 경우 이것은 and의 의미이지 or의 의미가 아니다. 즉 해당 label을 모두 가지고 있는 pod을 찾는거지 이것이 있거나 저것이 있거나 하는 식이 아니란 뜻이다.

 

Pod으로 한정 짓는게 아니라 모든 resource를 대상으로 특정 label을 가지고 있는 것을 찾고자 할때는 다음과 같이 한다 

kubectl get all -l env=dev

Taints and Tolerations : 지정된 node(Taints)엔 지정된 Pod(Tolerations) 만이 들어갈 수 있다.

 

이렇게만 써놓으면 오해가 있어서 보강 설명을 하자면 Tolerations가 설정되어 있는 Pod은 Tolerations와 매치되는 Taints가 있는 Node에만 설정된다는 뜻이 아니다.

예를 들어 A, B, C Node가 있고 A에 Taints로 app=apple이 있다고 가정하자. 또 A1, B1, C1, D1이란 Pod이 있고 그중 A1에 Tolerations로 app=apple이 있다고 가정하자.

이때 B1, C1, D1 Pod은 Tolerations로 app=apple이 없기 때문에 A Node에는 해당 Pod들이 할당되지는 않는다. A Node에는 app=apple Tolerations가 있는 A1 Pod만이 할당 가능하다. 그러나 그렇다고 해서 A1 Pod이 B, C Node에 할당이 안되는 것은 아니다. 이것은 Pod 이 할당되는 Node의 범위로 봐야 하는 것이다.

정리하자면 B1, C1, D1 Pod은 B, C Node에 할당이 가능하고, A1 Pod은 app=apple taint가 있는 A Node 뿐만 아니라 B, C Node에도 할당이 가능하다

 

Taints는 Node에 설정하고 Tolerations는 Pod에 설정한다

 

Master Node에 사용자가 만든 Pod이 올라가지 않는 이유가 Master Node에 Taint가 설정되어 있기 때문이며 Master Node에서 운영되는 Pod(예를 들면 kube-system namespace에 있는 Pod들 중 calico 계열 pod이나 kube-proxy pod들 중 Master node에 운영되는 Pod)에는 이러한 Taint와 매핑되는 Toleration가 있다.

 

Taint는 기본적으로 key=value(key:value) 형태이지만 value가 생략된 형태로도 운영되는데 value가 생략되었을 경우 value는 빈 문자열("")값을 가지는 것으로 간주한다

 

Node에 Taint를 설정하는 방법은 다음과 같이 한다

kubectl taints nodes kubernetes-worker1 app=blue:NoSchedule

Node에 있는 Taint를 제거하는 방법은 다음과 같이 한다(설정하는 명령과 동일한데 대신 끝에 -(하이픈)을 붙인다

kubectl taints nodes kubernetes-worker1 app=blue:NoSchedule-

NoSchedule이 붙은 부분은 Taint를 설정했을 경우의 정책(policy, 이해하기 쉽게는 Taint에 대한 효과)를 설정하는 부분으로 NoSchedule, PreferNoSchedule, NoExecute 이렇게 3가지가 있다.

NoSchedule은 방금까지 얘기했던 내용인 Pod이 해당 Node에 할당되지 않는 것을 의미한다.

PreferNoSchedule은 가능한한 Pod이 해당 Node에 할당되지 않는 것을 의미한다. 그러나 이 설정값은 반드시 할당이 안되게 한다는 뜻은 아니다(할당되지 않는 것을 보장하지 않는다. 정리하자면 어떤 Pod이 여러 노드들 중에서 할당되고자 할 경우 PreferNoSchedule은 여분의 할당 가능한 Node가 없고 Taint 설정된 Node만이  할당 가능한 상황이면 설사 해당 Taint에 대한 Toleration이 없는 Pod이더라도 Taint가 있는 Node에 할당하겠다는 것이지만 NoSchedule은 그러한 상황이 오더라도 할당을 하지 않겠다(이럴경우 Pod은 Pending 상태가 될 것이다)는 의미로 해석됨)

NoExecute는 해당 Node에서 Pod을 실행시키지 않는 설정인데 이 의미를 이해할려면 NoSchedule과 NoExecuted에 대한 비교를 해야 이해하기 쉽다. NoSchedule은 설정된 Node에 기존의 실행중인 Pod은 해당 Taint에 대한 Toleration이 없더라도 Pod을 종료시키지 않는다(즉 Taint 설정 이전의 운영중인 Pod에 대한 실행은 보장이 된다) 그러나 NoExecute는 Taint 설정 이전의 Pod 중 해당 Taint에 대응되는 Toleration이 없는 Pod이면 실행을 종료시켜버린다. 하지만 만약 종료 대상이 되는 Pod이 일반적인 Pod 생성 방법이 아닌 replicaset이나 deployment로 인해 생성된 Pod 이면 Taint가 걸려버린 Node에서 Pod을 종료시켜버린뒤 Taint가 아예 없는 Node나 또는 Toleration을 가지고 있을 경우 Toleration을 만족시키는 Taint Node에 Pod을 재생성하여 실행시킨다. 왜냐면 replicaset이나 deployment는 특정 갯수의 pod 실행을 보장하는 옵션인 replicas가 있기 때문에 replicas 옵션에 설정된 수 만큼의 Pod이 실행되어야 하기 때문이다. 

 

Toleration은 명령어로 설정할 수 있는 부분이 아니어서 yaml 파일에서 설정해야 한다

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  containers:
  - image: nginx
    name: nginx-container
  tolerations:
  - key: "app"
    operator: "Equal"
    value: "blue"
    effect: "NoSchedule"

spec.tolerations에 관련 항목들을 설정하고 값들은 쌍따옴표로 감싼다. operator 항목에는 "Equal"과 "Exists" 이렇게 2개중 1개가 들어오게 되는데 "Exists"는 해당 key가 Node에 설정된 Taint에 존재할 경우 Node에 Pod이 할당 가능함을 의미하게 되고(이럴 경우 값이 의미가 없기 때문에 value 항목이 생략된다) "Equals"는 해당 Key에 대한 value를 가지고 있는 Taint의 Node에 Pod이 할당 가능함을 의미한다. 이 과정에서 두가지 특별한 경우의 수가 있는데 operator가 "Exists" 인데 key에 아무 값이 없을 경우 모든 key, value, effect에 적용되는 것을 의미한다(즉 Taint가 설정되어 있는 모든 Node에 할당 가능함을 의미) effect가 없을 경우 해당 key가 있는 모든 Taint에 적용된 effect를 적용시킨다는 의미이다.


NodeSelector를 이용해서 Pod이 어떤 Node에 배포되어야 하는지를 지정할 수 있다.

예를 들어 Worker Node가 3대가 있는데 이 Node들의 하드웨어 사양이 서로 다를 경우 Pod의 성격에 따라 많은 양의 자원이 필요한 Pod이라면 하드웨어 사양이 좋은 Node에, 자원을 많이 필요로 하지 않는 Pod 일 경우 하드웨어 사양이 다소 떨어지는 Node에 배포를 하게끔 해야 하는 상황이 발생할 수 있다. 그럴때 NodeSelector를 이용한 Pod Scheduling이 하나의 해결책이 될 수 있다.

 

Node에 label을 설정한뒤 Pod의 spec.nodeSelector 항목에 해당 Pod이 배포되는 Node의 label을 설정하여 특정 Node에 Pod이 설정되도록 할 수 있다.

Node에 label을 설정하는 방법은 다음과 같다

kubectl label nodes kubernetes-worker1 size=Large

size=Large 란 label을 지닌 Node에 Pod이 할당되게끔 할려면 다음과 같이 한다

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  containers:
  - image: nginx
    name: nginx-container
  nodeSelector:
    size: Large

그러나 이 NodeSelector를 이용한 배포는 단순히 label의 key=value에 대한 비교만 하기 때문에 다양한 조건을 이용한 배포에는 적합하지 않다. 예를 들어 label의 key에 대한 value값을 OR 관계로 해서 선택의 여지를 준다거나(size가 Large이거나 Medium일 경우에 배포) 또는 not 조건을 설정하는(not Large)식의 방법을 사용할 수 없다. 그래서 이러한 상황일 경우 NodeAffinity 를 이용해서 배포한다