클라우드 버스팅
이전 배포를 기반으로, 이제 "클라우드 버스팅" 사용 사례를 시뮬레이션하는 시나리오를 살펴보겠습니다. 이는 EKS Hybrid Nodes에서 실행되는 워크로드가 수요가 최고조일 때 탄력적인 클라우드 용량을 사용하여 EC2 노드로 "버스팅"할 수 있는 방법을 보여줍니다.
이전 예제와 마찬가지로 하이브리드 노드를 선호하도록 nodeAffinity를 사용하는 새로운 워크로드를 배포하겠습니다. preferredDuringSchedulingIgnoredDuringExecution 전략은 Kubernetes에게 스케줄링할 때 Hybrid Node를 _선호_하지만 실행 중에는 _무시_하도록 지시합니다.
이는 단일 하이브리드 노드에 더 이상 공간이 없을 때, 이러한 Pod들이
클러스터의 다른 곳, 즉 EC2 인스턴스에 자유롭게 스케줄링될 수 있음을 의미합니다. 이는
훌륭합니다! 원하는 클라우드 버스팅을 제공합니다. 그러나
IgnoredDuringExecution 부분은 스케일을 다시 줄일 때 Kubernetes가
실행 중에는 _무시_되기 때문에 Pod가 실행되는 위치를 신경 쓰지 않고 무작위로 Pod를 제거한다는 것을 의미합니다. 일반적으로 Kubernetes는
먼저 오래된 Pod를 제거하는데, 이는 Hybrid Nodes에서 실행 중인 Pod가 될 것입니다. 우리는
그것을 원하지 않습니다!
Kubernetes를 위한 정책 엔진인 Kyverno를 배포할 것입니다. Kyverno는
하이브리드 노드로 스케줄링된 Pod(eks.amazonaws.com/compute-type: hybrid 레이블이 지정됨)를 감시하고, 실행 중인
Pod에 Annotation을 추가하는 정책으로 설정됩니다.
controller.kubernetes.io/pod-deletion-cost
어노테이션은 Kubernetes에게 덜 비싼 Pod를 먼저 삭제하도록 효과적으로 지시합니다.
작업을 시작해봅시다. Helm을 사용하여 Kyverno를 설치한 다음 아래에 포함된 정책을 배포하겠습니다:
아래 ClusterPolicy 매니페스트는 Kyverno에게 EKS Hybrid Nodes 인스턴스에
도착하는 Pod를 감시하고 pod-deletion-cost
어노테이션을 추가하도록 지시합니다.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: set-pod-deletion-cost
annotations:
policies.kyverno.io/title: Set Pod Deletion Cost
policies.kyverno.io/category: Pod Management
policies.kyverno.io/severity: medium
policies.kyverno.io/description: >-
Sets pod-deletion-cost label on nginx pods scheduled to hybrid compute nodes.
spec:
rules:
- name: set-deletion-cost-for-nginx-on-hybrid
match:
any:
- resources:
kinds:
- Pod/binding
context:
- name: node
variable:
jmesPath: request.object.target.name
default: ""
- name: computeType
apiCall:
urlPath: "/api/v1/nodes/{{node}}"
jmesPath: metadata.labels."eks.amazonaws.com/compute-type" || 'empty'
preconditions:
all:
- key: "{{ computeType }}"
operator: Equals
value: hybrid
mutate:
targets:
- apiVersion: v1
kind: Pod
name: "{{ request.object.metadata.name }}"
namespace: "{{ request.object.metadata.namespace }}"
patchStrategicMerge:
metadata:
annotations:
controller.kubernetes.io/pod-deletion-cost: "1"
Pod/binding 리소스를 감시하며, 이 시점에서 Pod가 노드로 스케줄링되었습니다
admission review 요청의 해당 값으로 node 변수를 설정합니다
Pod가 스케줄링된 노드에 대한 정보를 Kubernetes API에 쿼리하여 computeType 변수를 설정합니다
'hybrid' 노드로 스케줄링된 Pod만 선택합니다
Pod를 수정하여 pod-deletion-cost 어노테이션을 추가합니다
Kyverno가 실행 중인지 확인하고 정책을 적용해봅시다:
이제 샘플 워크로드를 배포하겠습니다. 이는 앞서 논의한 nodeAffinity 규칙을 사용하여 하이브리드 노드에 3개의 nginx Pod를 배치합니다:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: eks.amazonaws.com/compute-type
operator: In
values:
- hybrid
containers:
- name: nginx
image: public.ecr.aws/nginx/nginx:1.26
resources:
requests:
cpu: 200m
limits:
cpu: 200m
ports:
- containerPort: 80
배포가 롤아웃된 후 3개의 nginx-deployment Pod를 볼 수 있으며, 모두
하이브리드 노드에 배포되었습니다. 노드와 어노테이션을 한 번에 모두 볼 수 있도록 kubectl의 사용자 정의 출력을 사용하고 있습니다. Kyverno가 pod-deletion-cost 어노테이션을 적용한 것을 볼 수 있습니다!
NAME NODE ANNOTATIONS
nginx-deployment-7474978d4f-9wbgw mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-fjswp mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-k2sjd mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
스케일을 확장하고 클라우드로 버스팅해봅시다! 여기서 nginx 배포는 시연 목적으로
비합리적인 양의 CPU(200m)를 요청하고 있습니다. 이는 하이브리드 노드에
약 8개의 레플리카를 배치할 수 있음을 의미합니다. Pod의 레플리카를 15개로 스케일업하면
스케줄링할 공간이 없습니다. preferredDuringSchedulingIgnoredDuringExecution 어피니티 정책을 사용하고 있으므로, 이는
하이브리드 노드로 시작함을 의미합니다. 스케줄링할 수 없는 것은
다른 곳(클라우드 인스턴스)에 스케줄링될 수 있습니다.
일반적으로 스케일링은 CPU, 메모리, GPU 가용성 또는 큐 깊이와 같은 외부 요인에 따라 자동으로 이루어집니다. 여기서는 스케일업을 강제로 수행하겠습니다:
이제 사용자 정의 컬럼으로 kubectl get pods를 실행하면, 추가 Pod들이
워크샵 EKS 클러스터에 연결된 EC2 인스턴스에 배포된 것을 볼 수 있습니다. Kyverno는
하이브리드 노드에 배치된 모든 Pod에 pod-deletion-cost 어노테이션을 적용했고,
EC2에 배치된 모든 Pod에는 이를 적용하지 않았습니다. 스케일을 다시 줄이면 Kubernetes는 먼저 모든 저렴한 Pod, 즉 비용이 없는 Pod를 삭제할 것입니다. 그러면 Kubernetes는 나머지를 모두 동등하게 보고 일반적인 삭제 로직이 작동합니다. 이제 실제로 확인해봅시다:
NAME NODE ANNOTATIONS
nginx-deployment-7474978d4f-8269p ip-10-42-108-174.us-west-2.compute.internal <none>
nginx-deployment-7474978d4f-8f6cg ip-10-42-163-36.us-west-2.compute.internal <none>
nginx-deployment-7474978d4f-9wbgw mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-bjbvx ip-10-42-154-155.us-west-2.compute.internal <none>
nginx-deployment-7474978d4f-f55rj ip-10-42-108-174.us-west-2.compute.internal <none>
nginx-deployment-7474978d4f-fjswp mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-jrcsl mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-k2sjd mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-mstwv ip-10-42-154-155.us-west-2.compute.internal <none>
nginx-deployment-7474978d4f-q8nkj mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-smc9f ip-10-42-163-36.us-west-2.compute.internal <none>
nginx-deployment-7474978d4f-ss76l mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-tbzf2 mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-txxlw mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-wqbsd ip-10-42-154-155.us-west-2.compute.internal <none>
샘플 배포를 다시 3개로 스케일 다운해봅시다. 하이브리드 노드에서 실행 중인 3개의 Pod만 남게 되어 원래 상태로 돌아갑니다:
마지막으로, 확실히 하기 위해 하이브리드 노드에서 실행 중인 레플리카가 3개로 줄어들었는지 확인해봅시다:
NAME NODE ANNOTATIONS
nginx-deployment-7474978d4f-9wbgw mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-fjswp mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]
nginx-deployment-7474978d4f-k2sjd mi-0ebe45e33a53e04f2 map[controller.kubernetes.io/pod-deletion-cost:1]