Profile picture

[k8s] 노드 유지보수

JaehyoJJAng2023년 04월 07일


쿠버네티스는 여러 상황에서도 Pod를 안정적으로 작동하도록 관리하며 또한 노드의 관리또한 가능하다.
노드는 쿠버네티스 스케줄러에서 파드를 할당받고 처리하는 역할을 한다.

그렇다면 쿠버네티스를 운용하면서 최근 몇 차례 문제가 생긴 노드에 파드를 할당하면 어떻게 될까? 당연히 파드에 문제가 생길 가능성이 높다.
하지만, 어쩔 수 없이 해당 노드를 사용해야 한다면 비교적 인프라에 영향력이 적은 파드를 해당 노드에 할당해 일정 기간 사용하며 모니터링 해야할 것이다.

즉, 노드에 문제가 생기더라도 파드의 문제를 최소화해야 한다는 것인데 쿠버네티스는 모든 노드에 균등하게 Pod를 할당하려고 한다.

그렇다면 문제가 생길 가능성이 있는 노드라는 것을 어떻게 쿠버네티스에 알려줄 수 있을까?


Cordon

쿠버네티스는 모든 노드에 균등하게 Pod를 할당하려고 하기 때문에 cordon 기능을 사용하여 해당 노드에 더 이상 Pod가 할당 되지 않고 유지되도록 설정할 수가 있다.


먼저 아래 명세서를 기반으로 apply를 사용해 디플로이먼트를 생성해보자

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-hname
  labels:
    app: nginx
spec:
  replicas: 6
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: echo-hname
          image: sysnet4admin/echo-hname
$ kubectl apply -f ./echo-hname.yaml

Pod 정보를 출력해보자

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                        IP               STATUS    NODE
echo-hname-7894b67f-42vjl   172.16.103.129   Running   w2-k8s
echo-hname-7894b67f-5zpfm   172.16.221.167   Running   w1-k8s
echo-hname-7894b67f-72jd7   172.16.221.166   Running   w1-k8s
echo-hname-7894b67f-782pb   172.16.221.165   Running   w1-k8s
echo-hname-7894b67f-7gv9g   172.16.103.131   Running   w2-k8s
echo-hname-7894b67f-9bh79   172.16.103.130   Running   w2-k8s

scale 명령으로 배포한 파드의 수를 4개로 줄이자.

$ kubectl scale deployment/echo-hname --replicas=2

다시 한 번 Pod 정보를 출력해보자.

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                        IP               STATUS    NODE
echo-hname-7894b67f-57tkf   172.16.103.133   Running   w2-k8s
echo-hname-7894b67f-72jd7   172.16.221.166   Running   w1-k8s
echo-hname-7894b67f-782pb   172.16.221.165   Running   w1-k8s
echo-hname-7894b67f-86ft4   172.16.103.132   Running   w2-k8s

그런데 w2-k8s 노드에서 문제가 자주 발생해 현재 노드(w2-k8s)의 상태를 보존해야 한다. w2-k8s 노드에 cordon 명령을 실행해보자.

$ kubectl cordon w2-k8s
node/w2-k8s cordoned

cordon 명령이 제대로 실행되었는지 확인해보자

$ kubectl get nodes -o wide
NAME     STATUS                     ROLES    AGE     VERSION
m-k8s    Ready                      master   3d21h   v1.18.4
w1-k8s   Ready                      <none>   3d21h   v1.18.4
w2-k8s   Ready,SchedulingDisabled   <none>   3h40m   v1.18.4

w2-k8s가 더 이상 파드가 할당되지 않는 상태로 변경되었다. 이처럼 cordon 명령을 실행하면 해당 노드에 파드가 할당되지 않게 스케줄 되지 않는 상태(ScheduleingDisabled)라는 표시를 한다.


이 상태에서 파드 수를 증가시키고 확인하면 cordon 명령이 실행된 노드에는 파드가 더 이상 할당되지 않는 것을 확인할 수 있다.

$ kubectl scale deployment/echo-hname --replicas=9

image


uncordon 명령으로 해제 가능하다.

$ kubectl uncordon w2-k8s

drain

쿠버네티스를 사용하다보면 비정기적인 유지보수를 위해 노드를 꺼야하는 상황이 발생하는데 이런 경우를 대비해 쿠버네티스에서는 drain 기능을 제공하고 있다. drain을 사용하면 지정된 노드의 파드를 전부 다른 곳으로 이동시켜 해당 노드의 유지보수를 용이하게 만들 수 있다.

$ kubectl drain <node>

아래 작업을 따라해보자.


1. kubectl drain 명령을 사용해 유지보수할 노드(w2-k8s)를 파드가 없는 상태로 만들자.

$ kubectl drain w2-k8s
node/w2-k8s cordoned
error: unable to drain node "w2-k8s", aborting command...

There are pending nodes to be drained:
 w2-k8s
error: cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/calico-node-r87l5, kube-system/kube-proxy-7rkb2

그런데 이 명령을 실행하면 w2-k8s에서 데몬셋을 지울 수 없어 명령을 수행할 수 없다고 나온다.
여기서 drain은 실제로 파드를 옮기는 것이 아닌 노드(w2-k8s)에서 파드를 삭제하고 다른 곳에 다시 생성한다. 파드는 언제든 삭제될 수 있기 때문에 쿠버네티스에서 대부분 이동은 파드를 지우고 다시 만드는 과정을 뜻한다.

그런데 DaemonSet은 각 노드에 1개만 존재하는 파드라서 drain으로는 삭제할 수 없다고 뜨는 것이다.


2. 이번에는 위에 명령에 ignore-daemonsets 옵션을 추가해 사용하도록 하자. 이 옵션을 사용하면 DaemonSet을 무시하고 진행한다. 경고가 뜨지만 모든 파드가 이동된다

$ kubectl drain w2-k8s --ignore-daemonsets
node/w2-k8s already cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-r87l5, kube-system/kube-proxy-7rkb2
evicting pod default/echo-hname-7894b67f-57tkf
evicting pod default/echo-hname-7894b67f-86ft4
pod/echo-hname-7894b67f-86ft4 evicted
pod/echo-hname-7894b67f-57tkf evicted
node/w2-k8s evicted

3. 노드 w2-k8s에 파드가 없는지 확인. 그리고 옮긴 노드에 파드가 새로 생성돼 파드 이름과 IP가 부여된 것도 확인해보자

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                        IP               STATUS    NODE
echo-hname-7894b67f-67q5z   172.16.221.169   Running   w1-k8s
echo-hname-7894b67f-72jd7   172.16.221.166   Running   w1-k8s
echo-hname-7894b67f-782pb   172.16.221.165   Running   w1-k8s
echo-hname-7894b67f-8gb84   172.16.221.173   Running   w1-k8s
echo-hname-7894b67f-cpxks   172.16.221.168   Running   w1-k8s
echo-hname-7894b67f-f4dp5   172.16.221.174   Running   w1-k8s

4. kubectl get nodes를 실행해 drain 명령이 수행된 w2-k8s 노드의 상태를 확인해보자.

$ kubectl get nodes
NAME     STATUS                     ROLES    AGE     VERSION
m-k8s    Ready                      master   3d21h   v1.18.4
w1-k8s   Ready                      <none>   3d21h   v1.18.4
w2-k8s   Ready,SchedulingDisabled   <none>   4h7m    v1.18.4

cordon을 실행했을 때처럼 w2-k8s는 SchedulingDisabled 상태이다.


5. 유지보수가 끝났다고 가정하고 w2-k8s에 uncordon 명령을 실행해 스케줄을 받을 수 있는 상태로 복귀시키자

$ kubectl uncordon w2-k8s

다시 노드 상태를 확인해보자.

$ kubectl get nodes
NAME     STATUS   ROLES    AGE     VERSION
m-k8s    Ready    master   3d21h   v1.18.4
w1-k8s   Ready    <none>   3d21h   v1.18.4
w2-k8s   Ready    <none>   4h8m    v1.18.4

rollout

파드를 운영하다 보면 컨테이너에 새로운 기능을 추가하거나 치명적인 버그가 발생해 버전을 업데이트 하거나 롤백해야 하는데 이 때 쿠버네티스의 rollout 이라는 기능을 활용할 수 있다.

아래 작업을 따라해보자.


1. apply로 파드를 생성할 때 --record 옵션으로 생성하면 배포한 정보의 history를 rollout history 명령으로 확인이 가능하다.

# 파드 생성
$ kubectl apply -f ./echo-hname.yaml --record
deployment.apps/rollout-nginx created

2. record 옵션으로 기록된 히스토리는 rollout history 명령을 실행해 확인할 수 있다.

$ kubectl rollout history deployment/rollout-nginx
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=./rollout-nginx.yaml --record=true

3. 배포한 Pod의 정보를 출력하여 IP 주소를 확인하고 curl로 해당 Pod 컨테이너의 헤더 정보를 가져와보자.

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-64dd56c7b5-9kdzf   172.16.221.178   Running   w1-k8s
rollout-nginx-64dd56c7b5-dp8kx   172.16.103.138   Running   w2-k8s
rollout-nginx-64dd56c7b5-zpv2m   172.16.103.137   Running   w2-k8s
$ curl -Is 172.16.103.137 | grep -i 'server' # 헤더 정보 가져오기
Server: nginx/1.15.12

현재 nginx 버전이 1.23.4 인 것을 알 수가 있다.


4. kubectl set image 명령으로 파드의 nginx 컨테이너 버전을 1.16.0으로 업데이트해보자. 이번에도 --record를 명령에 포함해 실행해야 한다.

$ kubectl set image deployment/rollout-nginx nginx=nginx:1.16.0 --record
deployment.apps/rollout-nginx image updated

5. 업데이트 한 후에 파드의 상태 확인

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-64dd56c7b5-9kdzf   172.16.221.178   Running   w1-k8s
rollout-nginx-64dd56c7b5-zpv2m   172.16.103.137   Running   w2-k8s
rollout-nginx-8566d57f75-thgw5   172.16.221.180   Running   w1-k8s
rollout-nginx-8566d57f75-vpcwm   172.16.221.179   Running   w1-k8s
rollout-nginx-8566d57f75-zrf5k   172.16.103.139   Running   w2-k8s

6. nginx 컨테이너가 1.16.0으로 모두 업데이트 되면 Deployment의 상태를 확인하자

$ kubectl rollout status deployment/rollout-nginx
deployment "rollout-nginx" successfully rolled out

7. rollout history 명령을 실행해 rollout-nginx에 적용된 명령들을 확인해보자.

$ kubectl rollout history deployment/rollout-nginx
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=./rollout-nginx.yaml --record=true
2         kubectl set image deployment/rollout-nginx nginx=nginx:1.16.0 --record=true

<br.

8. curl -I 명령으로 업데이트(1.16.0)가 제대로 이루어졌는지 확인

$ curl -Is 172.16.103.139 | grep -i 'server'
Server: nginx/1.16.0

업데이트 실패 시 Pod 복구

그렇다면 업데이트가 할 때 실수로 잘못된 버전을 입력하면 어떻게 될까? 잘못된 컨테이너 버전을 입력하고 어떻게 되는지 테스트 해보자.


1. set image 명령으로 nginx 컨테이너 버전을 의도(1.17.2)와 다르게 1.17.23으로 입력하자.

$ kubectl set image deployment/rollout-nginx nginx=nginx:1.17.23 --record
deployment.apps/rollout-nginx image updated

2. 이번에는 한참을 기다려도 파드가 삭제되지 않고 pending(대기중) 상태에서 넘어가지를 않는다.

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-8566d57f75-thgw5   172.16.221.180   Running   w1-k8s
rollout-nginx-8566d57f75-vpcwm   172.16.221.179   Running   w1-k8s
rollout-nginx-8566d57f75-zrf5k   172.16.103.139   Running   w2-k8s
rollout-nginx-856f4c79c9-c28mj   172.16.103.140   Pending   w2-k8s

3. 문제를 확인하기 위해 rollout status를 실행해보자.

$ kubectl rollout status deployment/rollout-nginx
Waiting for deployment "rollout-nginx" rollout to finish: 1 out of 3 new replicas have been updated..

새로운 replicas는 생성 했으나 디플로이먼트를 배포하는 단계에서 대기 중(Waiting)으로 더 이상 진행되지 않고 있는 것을 확인할 수 있다.


4. describe 명령으로 문제점을 조금 더 자세하게 살펴보자

$ kubectl describe deployment/rollout-nginx

image
describe 명령으로 확인해보니 replicas가 새로 생성되는 과정에서 멈춰있다. 그 이유는 1.17.23 버전의 nginx 컨테이너가 존재하지 않기 때문이다. 따라서 replicas가 생성을 시도했으나 컨테이너 이미지를 찾을 수 없어 디플로이먼트가 배포되지 않았던 것이다.
이를 방지하고자 업데이트할 때 rollout을 사용하고 --record로 기록하는 것이다.


5. 문제를 확인했으니 정상적인 상태로 복구해보자. 업데이트할 때 사용했었던 명령들을 rollout history로 확인하자

$ kubectl rollout history deployment/rollout-nginx
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=./rollout-nginx.yaml --record=true
2         kubectl set image deployment/rollout-nginx nginx=nginx:1.16.0 --record=true
3         kubectl set image deployment/rollout-nginx nginx=nginx:1.17.23 --record=true

6. rollout undo로 명령 실행을 취소해 마지막 단계(revision 3)에서 전 단계(revision 2)로 상태를 되돌려보자.

$ kubectl rollout undo deployment/rollout-nginx
deployment.apps/rollout-nginx rolled back

7. 파드 상태를 다시 확인

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-8566d57f75-thgw5   172.16.221.180   Running   w1-k8s
rollout-nginx-8566d57f75-vpcwm   172.16.221.179   Running   w1-k8s
rollout-nginx-8566d57f75-zrf5k   172.16.103.139   Running   w2-k8s

8. rollout history로 실행된 명령을 다시 확인.

$ kubectl rollout history deployment/rollout-nginx
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=./rollout-nginx.yaml --record=true
3         kubectl set image deployment/rollout-nginx nginx=nginx:1.17.23 --record=true
4         kubectl set image deployment/rollout-nginx nginx=nginx:1.16.0 --record=true

revision 4가 추가되고 revision 2가 삭제되었다. 현재 상태를 revision 2로 되돌렸기 때문에 revision 2는 삭제되고 가장 최근 상태는 revision 4가 되었다.


9. 배포된 컨테이너의 nginx 버전을 curl로 재확인해보자.

$ curl -Is 172.16.103.139 | grep -i 'server'
Server: nginx/1.16.0

상태가 정상적으로 되돌려졌음을 알 수가 있다.


특정 시점으로 Pod 복구

그렇다면 바로 전 상태가 아니라 특정 시점으로 돌아가고 싶다면 어떻게 할까? 이럴 때는 --to-revision 옵션을 사용한다.


1. 처음 상태인 revision 1으로 돌아가보자.

$ kubectl rollout undo deployment/rollout-nginx --to-revision=1
deployment.apps/rollout-nginx rolled back

2. 새로 생성된 Pod들의 정보 확인

$ kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase,NODE:spec.nodeName
NAME                             IP               STATUS    NODE
rollout-nginx-64dd56c7b5-9nkz7   172.16.221.181   Running   w1-k8s
rollout-nginx-64dd56c7b5-h7pm7   172.16.103.141   Running   w2-k8s
rollout-nginx-64dd56c7b5-n4n54   172.16.103.142   Running   w2-k8s

3. curl로 nginx 컨테이너 버전 재확인

$ curl -Is 172.16.103.142 | grep -i 'server'
Server: nginx/1.15.12

1.15.2 버전이므로 처음 상태로 잘 복구되었다.


Loading script...