EKS 기초
EKS란?
Amazon Elastic Kubernetes Service(Amazon EKS)는
자체 Kubernetes 컨트롤 플레인 또는 노드를 설치, 운영 및 유지 관리할 필요 없이 AWS의 Kubernetes 실행에 사용할 수 있는 관리형 서비스
일반적인 Kubernetes의 마스터노드에 구성되는 컨트롤 플레인 영역을 AWS에서 관리해주며, 해당 컨트롤 플레인을 사용할 수 있게 해주는 서비스이다.
EKS 구축 방법
EKS를 생성하는 방법은 크게 3가지가 있는데, 어떤 방법을 사용해도 결과물은 큰 차이가 없기에 구축에 편하거나 유지보수가 용이한 쪽으로 진행하면 된다.
- AWS CLI를 사용해 구축하는 방법
# EKS 생성
aws eks create-cluster \
--region <region-code> \
--name <my-cluster> \
--kubernetes-version <1.25> \
--role-arn <arn:aws:iam::111122223333:role/myAmazonEKSClusterRole> \
--resources-vpc-config subnetIds=<subnet-ExampleID1>,<subnet-ExampleID2>,securityGroupIds=<sg-ExampleID1>
# NodeGroup 생성
aws eks create-nodegroup \
--cluster-name <my-cluster> \
--nodegroup-name <my-nodegroup> \
--subnets <subnet-ExampleID1>,<subnet-ExampleID2> \
--node-role <arn:aws:iam::111122223333:role/myAmazonEKSNodeRole> \
--region <region-code>
# kubectl 사용을 위한 kubeconfig 적용
aws eks update-kubeconfig \
--region <region-code> \
--name <my-cluster>
EKS 생성에 필요한 IAM 정책과 보안그룹 등이 사전에 생성되어있어야 한다.
(참고 : https://docs.aws.amazon.com/cli/latest/reference/eks/)
- eksctl 명령어를 사용해 구축하는 방법
# eks 클러스터 생성 + 노드그룹없이
eksctl create cluster \
--name <my-cluster> \
--region <region-code> \
--version <1.25> \
--without-nodegroup
# eks 클러스터 생성 +
# 관리형노드그룹생성(이름, 인스턴스 타입, EBS볼륨사이즈, SSH접속허용) &
# 사용 가용영역(2a,2c) +
# VPC 대역 지정
eksctl create cluster \
--name <my-cluster> \
--region=<region-code> \
--nodegroup-name=<my-nodegroup> \
--node-type=<t3.medium> \
--node-volume-size=<30> \
--zones=<ap-northeast-2a>,<ap-northeast-2c> \
--vpc-cidr=<172.20.0.0/16> \
--ssh-access
eksctl을 사용할 경우 AWS CLI에 비해 kubeconfig 과정 등을 자동으로 진행해주므로 편리하다.
- AWS Console을 통해 생성하는 방법
웹 Console을 통해서 생성하므로 좀 더 직관적일 수 있지만 다수의 클러스터를 생성할 때는 번거롭고 과정이 복잡할 수 있다.
하지만 Amazon EKS add-ons(Amazon EKS 추가 기능)와 AWS Marketplace add-ons( 추가 기능) 사용 시 웹 Console이 좀 더 보기 편하고 금액적인 요소도 고려할 수 있는 장점이 있다.
eksctl을 사용한 EKS 구축 실습
EKS 배포 실습의 구성도
EKS 배포 진행
# EKS 배포 명령어
# 해당 명령어를 수행하기 전 bastion 서버에 권한이 있는 IAM 자격증명(aws configure)을 수행함
eksctl create cluster \
--name $CLUSTER_NAME \
--region=$AWS_DEFAULT_REGION \
--nodegroup-name=$CLUSTER_NAME-nodegroup \
--node-type=t3.medium \
--node-volume-size=30 \
--vpc-public-subnets "$PubSubnet1,$PubSubnet2" \
--version 1.24 \
--ssh-access \ # 해당 ssh key는 사전에 생성 후 기본경로(~/.ssh/id_rsa.pub)에 넣어줌
--external-dns-access
eksctl로 명령을 수행하기 전에 dry-run 옵션을 통해 어떤 yaml로 배포할지를 미리 확인해볼 수 있는데 이 부분이 다른 방법에 비해 굉장히 매력적이었다.
[root@myeks-host ~]# eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium \
> --node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.24 --ssh-access --external-dns-access \
--dry-run | yh # 해당 옵션을 통해 사전 점검을 수행할 수 있음
apiVersion: eksctl.io/v1alpha5
cloudWatch:
clusterLogging: {}
iam:
vpcResourceControllerPolicy: true
withOIDC: false
kind: ClusterConfig
kubernetesNetworkConfig:
ipFamily: IPv4
managedNodeGroups:
- amiFamily: AmazonLinux2
desiredCapacity: 2
disableIMDSv1: false
disablePodIMDS: false
iam:
withAddonPolicies:
albIngress: false
appMesh: false
appMeshPreview: false
autoScaler: false
awsLoadBalancerController: false
certManager: false
cloudWatch: false
ebs: false
efs: false
externalDNS: true
fsx: false
imageBuilder: false
xRay: false
instanceSelector: {}
instanceType: t3.medium
labels:
alpha.eksctl.io/cluster-name: myeks
alpha.eksctl.io/nodegroup-name: myeks-nodegroup
maxSize: 2
minSize: 2
name: myeks-nodegroup
privateNetworking: false
releaseVersion: ""
securityGroups:
withLocal: null
withShared: null
ssh:
allow: true
publicKeyPath: ~/.ssh/id_rsa.pub
tags:
alpha.eksctl.io/nodegroup-name: myeks-nodegroup
alpha.eksctl.io/nodegroup-type: managed
volumeIOPS: 3000
volumeSize: 30
volumeThroughput: 125
volumeType: gp3
metadata:
name: myeks
region: ap-northeast-2
version: "1.24"
privateCluster:
enabled: false
skipEndpointCreation: false
vpc:
autoAllocateIPv6: false
cidr: 192.168.0.0/16
clusterEndpoints:
privateAccess: false
publicAccess: true
id: vpc-0ccd949f402353015
manageSharedNodeSecurityGroupRules: true
nat:
gateway: Disable
subnets:
public:
ap-northeast-2a:
az: ap-northeast-2a
cidr: 192.168.1.0/24
id: subnet-0ee4110119bf59a71
ap-northeast-2c:
az: ap-northeast-2c
cidr: 192.168.2.0/24
id: subnet-07313af79d9071dca
yaml 점검 후 명령어를 수행하면 약 15~20분 가량 지난 후 EKS 배포가 완료된다.
[root@myeks-host ~]# eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium --node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.24 --ssh-access --external-dns-access
2023-04-28 16:06:23 [ℹ] eksctl version 0.138.0
2023-04-28 16:06:23 [ℹ] using region ap-northeast-2
2023-04-28 16:06:23 [✔] using existing VPC (vpc-0ccd949f402353015) and subnets (private:map[] public:map[ap-northeast-2a:{subnet-0ee4110119bf59a71 ap-northeast-2a 192.168.1.0/24 0 } ap-northeast-2c:{subnet-07313af79d9071dca ap-northeast-2c 192.168.2.0/24 0 }])
...
...
}
2023-04-28 16:06:24 [ℹ] building cluster stack "eksctl-myeks-cluster"
2023-04-28 16:06:24 [ℹ] deploying stack "eksctl-myeks-cluster"
2023-04-28 16:06:54 [ℹ] waiting for CloudFormation stack "eksctl-myeks-cluster"
2023-04-28 16:07:24 [ℹ] waiting for CloudFormation stack "eksctl-myeks-cluster"
...
...
2023-04-28 16:16:24 [ℹ] waiting for CloudFormation stack "eksctl-myeks-cluster"
2023-04-28 16:18:25 [ℹ] building managed nodegroup stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2023-04-28 16:18:26 [ℹ] deploying stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2023-04-28 16:18:26 [ℹ] waiting for CloudFormation stack "eksctl-myeks-nodegroup-myeks-nodegroup"
...
...
EKS와 KOPS로 Kubetenetes를 설치했을 때의 POD 조회 차이점
EKS는 컨트롤 플레인 영역을 AWS에서 관리하기 때문에
KOPS를 통해서 직접 마스터 노드를 구축했을 경우와 EKS로 구축했을 때를 비교하면
POD 전체 조회 결과에서 컨트롤 플레인 영역의 POD가 빠져있는 것을 확인할 수 있다.
# KOPS를 사용해서 구축한 Kubernetes 환경의 POD조회
# cert-manager와 같은 잡다한 것들을 설치해서 복잡해보이지만
# kube-scheduler, kube-apiserver, kube-controller, etcd 관련 POD를 보면된다
(ersia:N/A) [root@kops-ec2 ~]# kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system aws-cloud-controller-manager-57hhq 1/1 Running 0 8m5s
kube-system aws-load-balancer-controller-869c699d87-6b7wq 1/1 Running 0 8m31s
kube-system aws-node-8shvc 1/1 Running 0 6m31s
kube-system aws-node-gjgnc 1/1 Running 0 8m31s
kube-system aws-node-jtwkg 1/1 Running 0 6m36s
kube-system cert-manager-9d894d6f-jxfxp 1/1 Running 0 8m12s
kube-system cert-manager-cainjector-8659b5599b-bjz69 1/1 Running 0 8m12s
kube-system cert-manager-webhook-854dfb7d9-hp2qj 1/1 Running 0 8m11s
kube-system coredns-68cd66b8cc-mn92t 1/1 Running 0 5m54s
kube-system coredns-68cd66b8cc-rn2g2 1/1 Running 0 8m31s
kube-system coredns-autoscaler-66fbc7dd48-bmfnz 1/1 Running 0 8m31s
kube-system ebs-csi-controller-f597c6697-9ckx8 5/5 Running 0 8m31s
kube-system ebs-csi-node-2fpww 3/3 Running 0 6m31s
kube-system ebs-csi-node-6gv4c 3/3 Running 0 8m31s
kube-system ebs-csi-node-9s4fd 3/3 Running 0 6m36s
kube-system etcd-manager-events-i-08d0fe4437011ba62 1/1 Running 0 8m25s
kube-system etcd-manager-main-i-08d0fe4437011ba62 1/1 Running 0 8m13s
kube-system external-dns-5fcdc9b4b8-cf62n 1/1 Running 0 8m31s
kube-system kops-controller-k9hvw 1/1 Running 0 8m5s
kube-system kube-apiserver-i-08d0fe4437011ba62 2/2 Running 1 (9m13s ago) 7m51s
kube-system kube-controller-manager-i-08d0fe4437011ba62 1/1 Running 2 (9m4s ago) 8m59s
kube-system kube-proxy-i-03098fe71258e525b 1/1 Running 0 6m36s
kube-system kube-proxy-i-08d0fe4437011ba62 1/1 Running 0 8m46s
kube-system kube-proxy-i-0b0d25b2ca039f660 1/1 Running 0 6m30s
kube-system kube-scheduler-i-08d0fe4437011ba62 1/1 Running 0 8m25s
kube-system metrics-server-5f65d889cd-c8qlk 1/1 Running 0 8m31s
kube-system metrics-server-5f65d889cd-j7c7x 1/1 Running 0 8m31s
kube-system node-local-dns-59ldw 1/1 Running 0 6m36s
kube-system node-local-dns-j8t6p 1/1 Running 0 8m31s
kube-system node-local-dns-x5sww 1/1 Running 0 6m31s
# EKS로 구축한 Kubernetes 환경의 POD조회
# kube-scheduler, kube-apiserver, kube-controller, etcd 관련 POD가 확인되지 않음
(test_admin@myeks:N/A) [root@myeks-host ~]# kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system aws-node-d8z86 1/1 Running 0 2m39s
kube-system aws-node-mswth 1/1 Running 0 2m40s
kube-system coredns-dc4979556-4f8kj 1/1 Running 0 9m46s
kube-system coredns-dc4979556-65fmt 1/1 Running 0 9m46s
kube-system kube-proxy-ls7qk 1/1 Running 0 2m40s
kube-system kube-proxy-qk5c5 1/1 Running 0 2m39s
eksctl로 설치 시 Cloudformation이 추가로 생성되는 이유?
eksctl로 설치할 경우 Cloudformation에 새로운 스택이 생성되는 것을 확인할 수 있다.
2023-04-28 16:06:24 [ℹ] building cluster stack "eksctl-myeks-cluster"
2023-04-28 16:06:24 [ℹ] deploying stack "eksctl-myeks-cluster"
2023-04-28 16:06:54 [ℹ] waiting for CloudFormation stack "eksctl-myeks-cluster"
...
2023-04-28 16:18:25 [ℹ] building managed nodegroup stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2023-04-28 16:18:26 [ℹ] deploying stack "eksctl-myeks-nodegroup-myeks-nodegroup"
eksctl 설치 로그에서도 관련 내용이 확인되는데, 공식홈페이지에서 확인해보면 Go언어로 만들어졌으며 Cloudformation을 사용한다고 한다.
eksctl is a simple CLI tool for creating clusters on EKS - Amazon's new managed Kubernetes service for EC2. It is written in Go, and uses CloudFormation.
(참고 : https://github.com/weaveworks/eksctl)
코드를 간단하게 확인해보니 내부에서 Cloudformation을 생성하기 위한 함수를 확인할 수 있었다.
개인적인 생각으로 eksctl 명령어는 일종의 Cloudformation Wrapping 명령어라고 봐도 무방할 것 같다.
위의 내용과 별개로 예전에 EKS를 사용할 때는 인식하지 못하고 있다가
스터디 경험발표에서 EKS가 생성되는 내부 과정에 대한 언급을 들을 수 있었는데,
EKS도 내부적으로 AWS 서비스를 사용해서 구축된다고 한다.
결과적으로 AWS 서비스 구축을 위해 AWS 서비스를 사용한다는 점이 상당히 인상깊었다.
EKS를 Spot 인스턴스로 배포하기
Spot 인스턴스는 On-Demand 가격보다 저렴한 비용으로 제공되는 예비 EC2 용량을 사용하는 인스턴스이다.
여유 자원을 가지고 사용하기 때문에 큰 폭으로 할인된 가격에 EC2를 사용할 수 있는데,
다른 곳에서 사용 요청이 발생해 가용 자원이 없을 경우 Spot 인스턴스는 종료되는 단점이 있다.
이 때문에 언제든 인스턴스가 종료될 수 있다는 단점이 있지만
AutoScaleGroup에서는 Node가 주기적으로 생성되고 종료되므로, Spot 인스턴스 종료 2분 전에 알려주는 알림을 활용해 2분안에 새 Spot을 할당받고 서비스 POD가 배치된다면 비용적인 면에서 큰 이점을 얻을 수 있다.
(다만 최근에는 오픈소스인 Karpenter를 통해 효율적인 AutoScale 사용과 큰 비용절감이 가능하다고 한다.)
- AWS Spot Instance 이해하기 : https://blog.leedoing.com/178
- 오픈 소스 Karpenter를 활용한 Amazon EKS 확장 운영 전략(신재현, 무신사) : https://youtu.be/FPlCVVrCD64
Spot Nodegroup 생성하기
현재 배포된 Node의 용량타입 OnDemand 확인
(test_admin@myeks:N/A) [root@myeks-host ~]# kubectl get nodes \
> --label-columns=eks.amazonaws.com/capacityType \
> --selector=eks.amazonaws.com/capacityType=ON_DEMAND
NAME STATUS ROLES AGE VERSION CAPACITYTYPE
ip-192-168-1-104.ap-northeast-2.compute.internal Ready <none> 86m v1.24.11-eks-a59e1f0 ON_DEMAND
ip-192-168-2-141.ap-northeast-2.compute.internal Ready <none> 86m v1.24.11-eks-a59e1f0 ON_DEMAND
Spot 용량타입을 가진 Nodegroup 생성 및 Cluster에 포함되었는지 확인
# Spot Nodegroup 생성
(test_admin@myeks:N/A) [root@myeks-host ~]# eksctl create nodegroup \
> --cluster=$CLUSTER_NAME --region=$AWS_DEFAULT_REGION \
> --managed --spot --name=$CLUSTER_NAME-spotnodegroup \
> --instance-types=t3.small,t3.medium
2023-04-28 17:50:20 [ℹ] will use version 1.24 for new nodegroup(s) based on control plane version
2023-04-28 17:50:21 [ℹ] nodegroup "myeks-spotnodegroup" will use "" [AmazonLinux2/1.24]
...
...
2023-04-28 17:54:36 [✔] created 1 managed nodegroup(s) in cluster "myeks"
2023-04-28 17:54:37 [ℹ] checking security group configuration for all nodegroups
2023-04-28 17:54:37 [ℹ] all nodegroups have up-to-date cloudformation templates
# Nodegroup 생성 후 Cluster join 확인
(test_admin@myeks:N/A) [root@myeks-host ~]# kubectl get nodes --sort-by=.metadata.creationTimestamp
NAME STATUS ROLES AGE VERSION
ip-192-168-1-104.ap-northeast-2.compute.internal Ready <none> 96m v1.24.11-eks-a59e1f0
ip-192-168-2-141.ap-northeast-2.compute.internal Ready <none> 96m v1.24.11-eks-a59e1f0
ip-192-168-2-118.ap-northeast-2.compute.internal Ready <none> 4m47s v1.24.11-eks-a59e1f0
ip-192-168-1-106.ap-northeast-2.compute.internal Ready <none> 4m44s v1.24.11-eks-a59e1f0
# capacityType이 Spot으로 되어있는지 확인
(test_admin@myeks:N/A) [root@myeks-host ~]# kubectl get nodes \
> --label-columns=eks.amazonaws.com/capacityType \
> --selector=eks.amazonaws.com/capacityType=SPOT
NAME STATUS ROLES AGE VERSION CAPACITYTYPE
ip-192-168-1-106.ap-northeast-2.compute.internal Ready <none> 4m53s v1.24.11-eks-a59e1f0 SPOT
ip-192-168-2-118.ap-northeast-2.compute.internal Ready <none> 4m56s v1.24.11-eks-a59e1f0 SPOT
임의의 서비스를 Spot 인스턴스에 배포하기
기존 스터디에서 사용하던 테스트용 mario 서비스 yaml에 아래의 내용을 추가해 Spot 인스턴스에 POD가 배포되도록 설정한다.
(test_admin@myeks:N/A) [root@myeks-host ~]# vim mario.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mario
labels:
app: mario
spec:
replicas: 1
selector:
matchLabels:
app: mario
template:
metadata:
labels:
app: mario
spec:
containers:
- name: mario
image: pengbai/docker-supermario
########### 아래의 내용을 추가 ###########
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: eks.amazonaws.com/capacityType
operator: In
values:
- SPOT
##########################################
---
apiVersion: v1
kind: Service
metadata:
name: mario
spec:
selector:
app: mario
ports:
- port: 80
protocol: TCP
targetPort: 8080
type: LoadBalancer
Pod Affinity and Anti-Affinity 참고 : https://www.eksworkshop.com/docs/fundamentals/managed-node-groups/affinity/
아래의 명령어를 통해서 Spot인스턴스에 배포된 POD를 확인할 수 있다.
# Spot 인스턴스에서 기동 중인 모든 POD 조회
(test_admin@myeks:N/A) [root@myeks-host ~]# for n in $(kubectl get nodes -l eks.amazonaws.com/capacityType=SPOT --no-headers | cut -d " " -f1); do echo "Pods on instance ${n}:";kubectl get pods --all-namespaces --no-headers --field-selector spec.nodeName=${n} ; echo ; done
Pods on instance ip-192-168-1-106.ap-northeast-2.compute.internal:
kube-system aws-node-prt6r 1/1 Running 0 14m
kube-system kube-proxy-tv2p7 1/1 Running 0 14m
Pods on instance ip-192-168-2-118.ap-northeast-2.compute.internal:
default mario-6fb766fdd9-xljbs 1/1 Running 0 78s
kube-system aws-node-hlwjk 1/1 Running 0 14m
kube-system kube-proxy-x5h8g 1/1 Running 0 14m
# Spot 인스턴스로 기동 중인 Node 조회
(test_admin@myeks:N/A) [root@myeks-host ~]# kubectl get nodes -owide --label-columns=eks.amazonaws.com/capacityType --selector=eks.amazonaws.com/capacityType=SPOT
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME CAPACITYTYPE
ip-192-168-1-106.ap-northeast-2.compute.internal Ready <none> 33m v1.24.11-eks-a59e1f0 192.168.1.106 3.38.214.187 Amazon Linux 2 5.10.176-157.645.amzn2.x86_64 containerd://1.6.19 SPOT
ip-192-168-2-118.ap-northeast-2.compute.internal Ready <none> 33m v1.24.11-eks-a59e1f0 192.168.2.118 13.209.75.206 Amazon Linux 2 5.10.176-157.645.amzn2.x86_64 containerd://1.6.19 S
# ip-192-168-2-118.ap-northeast-2.compute.internal Node에서 기동 중인 모든 POD 조회
(test_admin@myeks:N/A) [root@myeks-host ~]# kubectl get pods --all-namespaces --field-selector spec.nodeName=ip-192-168-2-118.ap-northeast-2.compute.internal
NAMESPACE NAME READY STATUS RESTARTS AGE
default mario-6fb766fdd9-xljbs 1/1 Running 0 22m
kube-system aws-node-hlwjk 1/1 Running 0 35m
kube-system kube-proxy-x5h8g 1/1 Running 0 35m
실제 서비스 중인 EKS에 Spot 인스턴스를 적용할 때 고려할 사항
- 실습을 위해 별도의 조치없이 서비스를 삭제하고 재배포하거나 새로 생성하였는데,
실제 서비스에 적용할 경우 별도의 label 설정과 taint를 통해 POD를 안전하게 이전하고 필요한 서비스만 Spot 인스턴스에 할당되도록하는 작업이 필요하다. - 또한 갑작스런 Spot 인스턴스 종료에 대비해 Spot 인스턴스 유형과 Pool을 다양하게 갖추고, Spot 인스턴스를 쓰기 적합한 서비스인지 검토 등이 필요하다.
- 만약 자체 관리형 Nodegroup일 경우 aws-node-termination-handler를 구축해 Spot 종료에 대한 별도 대비를 해야한다.
(참고 : https://repost.aws/knowledge-center/eks-spot-instance-best-practices)
참고자료
- AWS re:Invent 2019: [REPEAT 1] Amazon EKS under the hood (CON421-R1) : https://youtu.be/7vxDWDD2YnM
- eksctl - The official CLI for Amazon EKS : https://github.com/weaveworks/eksctl
- Spot 관리형 노드그룹추가 : https://archive.eksworkshop.com/beginner/150_spotnodegroups/spotnodegroups/
- Spot Instance로 EKS 구성 : https://clarkshim.tistory.com/140
- AWS Spot Instance 이해하기 : https://blog.leedoing.com/178
- 오픈 소스 Karpenter를 활용한 Amazon EKS 확장 운영 전략(신재현, 무신사) : https://youtu.be/FPlCVVrCD64