본문 바로가기
Kubernetes/PKOS

[보완] kubernetes 워커노드를 spot instance로 구성하기(Node Termination Handler)

by isn`t 2023. 4. 2.

*본 게시글의 내용은 개인 학습내용과 더불어 가시다님의 노션 페이지와 스터디 자료인 '24단계 실습으로 정복하는 쿠버네티스 도서' 를 기반으로 작성하였습니다.

"노드를 spot instance로 띄우면 더 저렴하지 않을까?"

지난 1주차에서 Kops를 처음 다루어 보면서 몇 가지 의문을 가지게 되었습니다. 이와 관련하여 이번에는 kOps 환경에서 노드를 스팟 인스턴스로 띄우는 방법에 대해 알아보겠습니다.

kOps를 spot instance로 구동할 수 있을까?


워커노드를 spot 인스턴스로 변경하기


Node Termination Handler는 kOPs 클러스터의 매니페스트를 수정하여 적용할 수 있다.

$ kops edit cluster --name ${KOPS_CLUSTER_NAME}

AWS 블로그에서는 위와 같이 소개하고 있으나, cluster 전체의 명세를 수정하면 master에도 적용되어버린다. 따라서 특정 노드, 또는 노드그룹에 대한 수정이 필요하다.

kops의 get 명령 메뉴얼을 보면 특정 인스턴스나 인스턴스그룹을 조회할 수 있다.

Usage:
  kops get [flags]
  kops get [command]

Available Commands:
  all            Display all resources for a cluster.
  assets         Display assets for cluster.
  clusters       Get one or many clusters.
  instancegroups Get one or many instance groups.
  instances      Display cluster instances.
  keypairs       Get one or many keypairs.
  secrets        Get one or many secrets.
  sshpublickeys  Get one or many secrets

또한 kops edit 명령은 cluster와 instancegroup 두 단위로 편집을 가능케 한다.

Usage:
  kops edit [command]

Available Commands:
  cluster       Edit cluster.
  instancegroup Edit instancegroup.

현 상황에서 필요한 것은 특정 instnacegroup에 대한 수정이므로, 아래와 같이 편집을 진행하였다.

(ilikebigmac:N/A) [root@kops-ec2 ~]#  kops get instancegroups
NAME                            ROLE            MACHINETYPE     MIN     MAX     ZONES
control-plane-ap-northeast-2a   ControlPlane    c5a.2xlarge     1       1       ap-northeast-2a
nodes-ap-northeast-2a           Node            c5a.2xlarge     1       1       ap-northeast-2a
nodes-ap-northeast-2c           Node            c5a.2xlarge     1       1       ap-northeast-2c

(ilikebigmac:N/A) [root@kops-ec2 ~]#  kops edit instancegroups nodes-ap-northeast-2a

nodes-ap-northeast-2a 인스턴스그룹의 편집 화면에서 spec 필드에 대하여 아래와 같이 적용한다.

spec:
...(생략)
  maxPrice: "0.20"
  maxSize: 1
  minSize: 1
...(생략)

추가한 부분은 maxPrice 필드이다. spot 인스턴스의 최대 가격을 지정하고 이 이하의 가격에만 입찰하여 사용하도록 설정한다.

kops update 명령은 cluster 단위로만 동작하며, 수정된 매니페스트를 클러스터가 인지하도록 한다. 일부 설정은 인지시킴과 동시에 적용되지는 않을 수 있으며, 재시작이나 업데이트를 별도로 수행해주어야 할 수 있다.

Usage:
  kops update [command]

Available Commands:
  cluster     Update a cluster.
(ilikebigmac:N/A) [root@kops-ec2 ~]# kops update cluster --name ${KOPS_CLUSTER_NAME} --yes
W0402 12:58:27.845974    4242 builder.go:230] failed to digest image "602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni:v1.12.2"
W0402 12:58:28.325512    4242 builder.go:230] failed to digest image "602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni-init:v1.12.2"
I0402 12:58:31.945893    4242 executor.go:111] Tasks: 0 done / 107 total; 52 can run
I0402 12:58:32.849849    4242 executor.go:111] Tasks: 52 done / 107 total; 21 can run
I0402 12:58:33.745034    4242 executor.go:111] Tasks: 73 done / 107 total; 28 can run
I0402 12:58:34.546219    4242 executor.go:111] Tasks: 101 done / 107 total; 3 can run
I0402 12:58:34.631015    4242 executor.go:111] Tasks: 104 done / 107 total; 3 can run
I0402 12:58:34.730021    4242 executor.go:111] Tasks: 107 done / 107 total; 0 can run
I0402 12:58:35.618845    4242 update_cluster.go:323] Exporting kubeconfig for cluster
kOps has set your kubectl context to ilikebigmac.link
W0402 12:58:35.636180    4242 update_cluster.go:347] Exported kubeconfig with no user authentication; use --admin, --user or --auth-plugin flags with `kops export kubeconfig`

Cluster changes have been applied to the cloud.


Changes may require instances to restart: kops rolling-update cluster

위에 서술한대로 변경된 설정은 클러스터가 인지하였으나, 바로 반영하지는 않는다. 출력된 결과의 맨 아래에서 kops rolling-update를 사용하여 편집한 대상 인스턴스를 재시작함으로써 롤링 업데이트가 가능하다고 가이드하고있다.

[root@kops-ec2 ~]# kops rolling-update cluster --name ${KOPS_CLUSTER_NAME} --yes
Detected single-control-plane cluster; won't detach before draining
NAME                            STATUS          NEEDUPDATE      READY   MIN     TARGET  MAX     NODES
control-plane-ap-northeast-2a   Ready           0               1       1       1       1       1
nodes-ap-northeast-2a           NeedsUpdate     1               0       1       1       1       1
nodes-ap-northeast-2c           Ready           0               1       1       1       1       1
I0402 13:01:23.272260    4415 instancegroups.go:501] Validating the cluster.
I0402 13:01:23.970569    4415 instancegroups.go:537] Cluster validated.
...(생략)

AWS Managed Console에서 spot 인스턴스로의 변경을 진행한 오토스케일링 그룹이 업데이트 중이며

먼저 새로운 인스턴스를 띄우고, 기존 인스턴스를 종료하는 방식으로 Rolling-Update가 수행되는 부분도 확인할 수 있었다.

인스턴스 역시 spot으로 변경되었으며, 인스턴스 그룹에도 정상적으로 잘 잡히고 있다.

(ilikebigmac:N/A) [root@kops-ec2 ~]#  kops get instancegroups
NAME                            ROLE            MACHINETYPE     MIN     MAX     ZONES
control-plane-ap-northeast-2a   ControlPlane    c5a.2xlarge     1       1       ap-northeast-2a
nodes-ap-northeast-2a           Node            c5a.2xlarge     1       1       ap-northeast-2a
nodes-ap-northeast-2c           Node            c5a.2xlarge     1       1       ap-northeast-2c

업데이트 로그를 보면, 기존 노드에 배치되어있던 파드를 eviction 하거나, 기존 인스턴스를 드레이닝 하는 시점 등 일련의 과정을 모두 확인할 수 있다.

I0402 13:19:12.451143    4670 instancegroups.go:537] Cluster validated.
I0402 13:19:12.451203    4670 instancegroups.go:431] Draining the node: "i-08bddba2b085acbb1".
WARNING: ignoring DaemonSet-managed Pods: kube-system/aws-node-cn88m, kube-system/ebs-csi-node-qnnqh, kube-system/node-local-dns-8bk6m
evicting pod kube-system/metrics-server-5f65d889cd-pnj2w
evicting pod kube-system/coredns-68cd66b8cc-6xlvq

...(생략)

I0402 13:20:22.076885    4670 rollingupdate.go:234] Rolling update completed for cluster "ilikebigmac.link"!

Trouble Shooting

롤링 업데이트 진행중 아래와 같은 로그를 뿜어내며 업데이트가 완료되지 않는 상황이 발생했다.

I0402 13:02:41.422613    4415 instancegroups.go:560] Cluster did not pass validation, will retry in "30s": InstanceGroup "nodes-ap-northeast-2a" did not have enough nodes 0 vs 1.

해당 오토스케일링그룹의 실패 원인을 확인해보면, 실습 환경 구성에 사용했던 c5a.2xlarge의 스팟인스턴스 최소 가격이 0.2274 이므로, 위에서 0.2로 maxPrice를 지정한 것이 원인이었다.

업데이트를 중단하고 maxPrice를 0.5로 다소 여유있게 늘려준 후, 다시 update cluster -> rolling-update cluster 수행한다.

다만, 이러한 상태로 한번 들어가게되면 앞서 maxPrice 0.2 상태에서 발생시켰던 Capacity Update가 정상적으로 종료되고 다음 업데이트를 발생시키기까지 다소 시간이 필요하다.

Node Termination Handler


Kops의 각 노드는 모두 EC2 Autoscaling Group으로 동작한다. 먼저 스팟 인스턴스의 인스턴스 종료 이벤트를 처리하기 위한 주체가 필요하다. 워커노드라 할지라도 노드가 갑작스럽게 종료되어버리는 사태는 안정적인 서비스 운영 환경이라고 보기는 어렵기 때문이다.

Node Termination Handler란?


AWS에서는 managed addon으로서 Node Termination Handler를 오픈소스로 공개하였다. 쿠버네티스 컨트롤 플레인이 워커노드로 사용되는 EC2의 maintenance, Spot 인스턴스의 종료, 인스턴스 Rebalancin과 같은 이벤트에 대응할 수 있도록 설계되었다. 만약 이러한 이벤트들이 핸들링되지 않았을 때, 애플리케이션이 graceful(처리중인 요청의 중단 없이 자연스러운 스케일링을 의미함)하게 동작하지 않을 수 있으며 미리 감지하지 못한만큼 정상화에 더 오랜 시간이 소요될 가능성이 높다.

kOps 뿐만 아니라 EKS에도 적용될 수 있는 adds-on으로, 워커노드를 spot으로 돌리고 싶은 경우에 유용하게 사용할 수 있을 것 같다.

이벤트가 감지되면 Pod spec의 terminationGracePeriodSeconds 를 따르므로, 파드 명세를 정의할 때 함께 설정해주어야 한다.

Event Bridge Rule에 명시된 모니터링 대상인 이벤트가 발생하면 SQS에 추가되고, Node Termination Handler 파드는 SQS를 보고있다가 큐에 이벤트가 추가됨을 감지한다. 이후에는 해당 노드에 추가 파드가 배치되지 않도록 하고, 새로운 노드로 교체 작업을 수행한다.

Which type should I use?

두 유형은 동작 방식과 지원 범위에서 차이를 가지고 있다.

  • IMDS 유형은 k8s 클러스터에 데몬셋, Queue 유형은 디플로이먼트로 올라가서 동작한다. 따라서 IMDS 유형은 노드 당 1대씩의 파드가, Queue 유형은 최소한 파드 1대만 있어도 동작하므로 클러스터 내 리소스 소모에서도 차이가 있다.
  • 개인적인 의견으로는 Queue 유형 사용시에 노드그룹을 가용영역별로 구분하고 Affinity를 설정해서 가용영역당 1~2개 정도로 운용 가능하지 않을까 하는 생각이다.
  • IMDS 유형은 간단한 구성으로 스팟 인스턴스에 대한 이벤트 트래킹을 잘 처리할 수 있다. 최소한 의 공수로 딱 원하는 만큼만 커버한다.
  • 인스턴스 레벨에서 동작하는 IMDS 유형은 EC2 메타데이터를 읽어서 처리하는 방식이기 떄문에, 인스턴스 단위 밖의 이벤트(AZ, ASG)는 처리하지 못 한다.
  • 반면에 Queue 유형은 좀 더 넓은 커버리지를 지원하는 대신, AWS SQS와 같은 추가 서비스를 필요로 하며 이에 따른 비용 지출이 있다.

실제 운영환경에 사용할 경우에는 AWS에서도 Queue 타입을 권장하고 있어 Queue 타입으로 설정해보기로 했다.

Node Termination Handler 적용

EKS나 다른 설치 툴로 쿠버네티스를 구성한 경우는 Node Termination Handler를 직접 배포해야 하지만, kops의 경우 Node Termnation Handler를 Queue 유형으로 구성하면 관련 AWS 리소스를 자동으로 프로비저닝해준다. 앞선 사용기에서는 느끼지 못했던 kOps만의 훌륭한 장점을 하나 발견한 느낌 :)

kops edit cluster 명령을 실행하고 spec 필드에 아래와 같이 추가한다.

  nodeTerminationHandler:
    cpuRequest: 200m
    enabled: true
    enableSQSTerminationDraining: true
    managedASGTag: "aws-node-termination-handler/managed"
    prometheusEnable: false
$ kops update cluster --name ${KOPS_CLUSTER_NAME} --yes
$ kops rolling-update cluster --name ${KOPS_CLUSTER_NAME} --yes

이후 cluster update와 rolling-update를 수행하여 적용.
태그의 추가나 추가 리소스 배포 - 연결등의 이유로 rolling-update까지 수행해야 하는 것으로 보인다.
만약 IDMS 타입으로 배포하고자 한다면 enableSQSTerminationDraining 필드 값을 반드시 false로 명시해야 한다. 명시하지 않는 경우 Queue 타입으로 판단하고 관련 리소스의 프로비저닝이 이루어진다.

배포 확인

SQS

Event Briedge

EC2 Tags

각 노드의 태그에는 aws-node-termination-handler/managed 태그의 key가 지정되어 있어야 Node Termination Handler의 관리 대상에 포함된다. 이 역시 앞선 kops 클러스터 업데이트 수행 과정에서 추가된다.

결과 확인

$ kubectl logs aws-node-termination-handler-569db89b97-wq5rl -n kube-system
...(생략)
{"level":"info","time":"2023-04-02T06:36:50Z","message":"Kubernetes AWS Node Termination Handler has started successfully!"}

{"level":"info","monitor_type":"SQS_MONITOR","time":"2023-04-02T06:36:50Z","message":"Started monitoring for events"}
...(생략)

kube-system 네임스페이스에 있는 aws-node-termination-haldner 파드의 로그를 확인해보면, SQS를 이용한 Queue 타입의 Node Termination Handler가 정상적으로 동작을 시작했다. spot 인스턴스가 언제 종료될지 모르는데 무작정 종료 알림을 기다리고 있을 수는 없어서, 정상적으로 이벤트가 감지되고 있는지 테스트하기 위해 워커노드 하나를 강제 종료시켜보았다.

{"level":"info","event":{"EventID":"ec2-state-change-event-38616537393536302d383739642d623865632d616230362d373630396662623437343561","Kind":"STATE_CHANGE","Monitor":"SQS_MONITOR","Description":"EC2 State Change event received. Instance i-0584e64fc8a044ded went into shutting-down at 2023-04-02 06:52:46 +0000 UTC 

...(생략)

{"level":"info","time":"2023-04-02T06:52:46Z","message":"Draining the node"}

{"time":"2023-04-02T06:52:46Z","message":"WARNING: ignoring DaemonSet-managed Pods: kube-system/aws-node-rw9nj, kube-system/ebs-csi-node-fx8jg, kube-system/node-local-dns-5khxs"}

{"time":"2023-04-02T06:52:46Z","message":"evicting pod kube-system/metrics-server-5f65d889cd-zv5dm"}

...

{"level":"warn","error":"node: 'i-0584e64fc8a044ded' in state 'terminated'","time":"2023-04-02T06:54:52Z","message":"dropping event"}

쏟아져 나오는 로그가 너무 많아 중략하였는데, 간단히 요약하자면

  • ec2의 state change event를 감지
  • SQS에 의해 종료 이벤트가 감지되어, node drain 절차에 들어감
  • 해당 노드에 배포되어 있던 파드가 eviction 되고
  • 마침내 노드가 terminated 됨

그리고 Autoscaling Group 설정에 의해 새로운 워커노드가 자동으로 생성되었다.

Additional

Rebalance Recommendation

Node Termination Handler를 설정할 때 enableRebalanceMonitoring: true 필드를 정의함으로써 Revalance Recommendation 시그널을 감지하게 선택할 수 있었는데, 이 옵션을 왜 true/false로 선택할 수 있도록 했는지 의문이 들었다.

Revalance Recommendation 시그널은 스팟 인스턴스의 종료 2분전 알림보다도 더 먼저 발생하기 떄문에 보다 여유 시간을 가지고 graceful하게 노드를 Drain 할 수 있도록 돕는다.

Revalance Recommendation은 '가능성'을 기준으로 발생하는 시그널이다. 구체적인 기준을 찾지는 못하였지만, 가용 영역의 용량 부족 여부나 인스턴스 배치 상황의 균형 여부, 스팟 인스턴스의 가격 변동 등의 요인을 종합적으로 판단하여 종료 가능성이 높은 스팟 인스턴스에 대하여 미리 시그널을 발생시키는 것으로 보인다.

그렇다면 운영 주체인 내가 직접 설정한 기준이 아닌, AWS에서 세운 기준으로 판단하기에 단지 종료 가능성이 높다는 이유만으로 Node Termination Handler를 동작시키는 것이 과연 합당한가? 에 대한 의문이 드는데, 때문에 이 옵션을 true/fasle로 선택할 수 있도록 해둔 것이 아닌가 하는 생각이 들었다.

이 부분은 개인적인 의견임..


워커노드의 spot 인스턴스 적용과, Node Termination Handler를 사용해보며 느꼈던 점은, 파드와 컨테이너가 언제든 종료될 수 있음을 감안하고 statelsess한 애플리케이션을 설계한다고는 하지만, 얼마나 graceful하게 동작할 수 있는지에 대한 부분도 고민해볼 점이라는 것이다.

또한 kops의 add on 배포가 생각보다 잘 갖춰져 있다는 점도 꽤나 신선했다. 오히려 EKS 에서 배포하게 되면 관련 AWS 리소스를 직접 배포하고 엮어주는 등의 작업이 필요했을텐데, 이러한 부분은 kops가 좀더 사용 편의성이 높아보였다.

위의 기능들을 테스트해보면서 Node Termination Handler를 이용해서 종료 알림에 대한 slack webhook을 걸 수 있다는 점도 알게 되었는데, 이번에 새로 구축할 환경에서 한번 테스트해보면 좋을 것 같다.

References

https://aws.amazon.com/ko/blogs/compute/proactively-manage-spot-instance-lifecycle-using-the-new-capacity-rebalancing-feature-for-ec2-auto-scaling/

https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/spot-interruptions.html

https://onica.com/blog/devops/aws-spot-instances-with-kubernetes-kops/

https://github.com/aws/aws-node-termination-handler

https://kops.sigs.k8s.io/addons/#queue-processor-mode