I got IT

EKS 기초 Hands-on 본문

카테고리 없음

EKS 기초 Hands-on

joshhoxy 2025. 2. 13. 19:11
※ 해당 글은 CloudNet@ gasida 님의 EKS 스터디 내용을 참고하여 작성하였습니다.

 

EKS에 대한 간단한 소개는 이전 글을 참조바랍니다. https://joshhoxy.tistory.com/33

EKS를 구성하는데에는 여러 방법이 있지만 가장 많이 사용하는 eksctl을 통해 구성해보겠습니다.

 

실습 아키텍처

컨트롤 플레인과 데이터 플레인을 퍼블릭 네트워크에 호스팅

실습 환경의 경우 비용과 편의성을 고려하여 퍼블릭 환경에 호스팅 하였습니다. 실제 운영환경에서는 프라이빗 환경에서 사용하는 것이 안정성을 위해서 권고 됩니다. 이외에 컨트롤플레인은 퍼블릭 + 데이터 플레인은 프라이빗 환경에 구성하는 하이브리드 아키텍처도 존재합니다. 이는 밑에서 더 자세히 다뤄 보겠습니다.

 

실습 환경 구성

쿠버네티스 클러스터를 배포하기 위해 필요한 기본 네트워크 및 Bastion 서버는 CloudFormation을 통해 구성합니다.

CloudFormation Template 링크  . 실습환경의 아키텍처는 다음과 같습니다.

실습환경 네트워크 아키텍처

위 링크를 클릭하면 CloudFormation 생성화면에 접근 됩니다. 다음 페이지에서 본인의 설정 값을 매개변수로 지정해줍니다. 

이 때 키 KeyName 파라미터에서 미리 생성한 혹은 기존에 가지고 있는 키페어를 지정하여 줍니다. 또한 SgIngressSshCidr은 본인의 IP를 지정하여 줍니다. 이외의 값들은 최대한 실습환경과 동일하게 구축하기 위해 default 값을 유지합니다. ❗

이후 전송을 요청하면 다음과 같이 CloudFormation 스택이 배포되는 것을 확인할 수 있습니다. 이때 테이블 보기 타임라인 보기 등으로 생성되는 리소스를 실시간으로 조회할 수 있습니다.

스택 상태가 CREATE_COMPLETE 로 변경되면 생성이 완료된 것입니다. 이 때 출력 탭리소스 탭 을 확인하면 내가 생성한 EC2의 퍼블릭 IP와 리소스가 조회 가능합니다.

 

AWS VPC 메뉴 Resource map 탭에서 생성된 리소스를 살펴봅니다.

VPC, 두 AZ에 퍼블릭 서브넷과 프라이빗 서브넷이 생성된 것을 확인할 수 있습니다. 이제 여기 퍼블릭 서브넷에 EKS 클러스터를 올릴 예정입니다.

그러기 위해서는 eksctl 명령어를 수행할 수 있는 환경이 필요합니다. 해당 실습에서는 클러스터를 구성하기 위한 패키지가 이미 CloudFormation Template의 AMI를 통해서 생성되어 있는 것을 확인할 수 있습니다.

 

EKS 생성 작업 환경 구성

우선 생성된 EC2에 여러 패키지들이 잘 설치되어 있는지 확인하기 위해 SSH로 서버에 접속을 합니다. 

ssh root@43.201.15.40 -i josh-key-1.pem

 

비밀번호는 'qwe123' 입니다. 현재는 개인 테스트 용도이기에 root로 접근을 하지만 보안상 반드시 root 접근을 제한하는 것을 권장합니다.

 

기본적으로 다음과 같은 패키지가 설치 되어있는지 확인합니다.

- eksctl

- kubectl

- awscli version 2

- docker veersion

- systemctl status docker (도커 실행중인지 확인)

 

클라우드포메이션으로 생성한 ec2는 이러한 기본설치 파일을 유저데이터를 통해 설치한 것을 확인할 수 있습니다.


※ 참고: 지금까지의 과정 cli로 구성하기

VPC, Subnet4개, EC2 1대 + 보안그룹(자기 IP만 허용)

myeks-1week.yaml
0.01MB

 

awscli 사용하여 cloudformation 실행

# yaml 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-1week.yaml

# 배포
# aws cloudformation deploy --template-file ~/Downloads/myeks-1week.yaml --stack-name mykops --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region <리전>
예시) aws cloudformation deploy --template-file ~/Downloads/myeks-1week.yaml \
     --stack-name myeks --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[*].OutputValue' --output text
예시) 3.35.137.31

# ec2 에 SSH 접속 : root / qwe123
예시) ssh root@3.35.137.31
ssh root@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
root@@X.Y.Z.A's password: qwe123

 

기본 패키지 설치_실습환경 구성을 위한 부트스트랩 코드 (EC2 유저 데이터)

#!/bin/bash
hostnamectl --static set-hostname "myeks-host"

# Config Root account
echo 'root:qwe123' | chpasswd
sed -i "s/^#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
rm -rf /root/.ssh/authorized_keys
systemctl restart sshd

# Config convenience
echo 'alias vi=vim' >> /etc/profile
echo "sudo su -" >> /home/ec2-user/.bashrc
sed -i "s/UTC/Asia\/Seoul/g" /etc/sysconfig/clock
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

# Install Packages
yum -y install tree jq git htop

# Install YAML Highlighter
wget https://github.com/andreazorzetto/yh/releases/download/v0.4.0/yh-linux-amd64.zip
unzip yh-linux-amd64.zip
mv yh /usr/local/bin/

# Install kubectl & helm
cd /root
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.31.2/2024-11-15/bin/linux/amd64/kubectl
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

# Install eksctl
curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_Linux_amd64.tar.gz" | tar xz -C /tmp
mv /tmp/eksctl /usr/local/bin

# Install aws cli v2
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip >/dev/null 2>&1
./aws/install
complete -C '/usr/local/bin/aws_completer' aws
echo 'export AWS_PAGER=""' >>/etc/profile
export AWS_DEFAULT_REGION=ap-northeast-2
echo "export AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" >> /etc/profile

# Install krew
curl -L https://github.com/kubernetes-sigs/krew/releases/download/v0.4.4/krew-linux_amd64.tar.gz -o /root/krew-linux_amd64.tar.gz
tar zxvf krew-linux_amd64.tar.gz
./krew-linux_amd64 install krew
export PATH="$PATH:/root/.krew/bin"
echo 'export PATH="$PATH:/root/.krew/bin"' >> /etc/profile

# Install kube-ps1
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'alias k=kubectl' >> /etc/profile
echo 'complete -F __start_kubectl k' >> /etc/profile

git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1
cat <<"EOT" >> /root/.bash_profile
source /root/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=false
function get_cluster_short() {
  echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT

# CLUSTER_NAME
export CLUSTER_NAME=myeks
echo "export CLUSTER_NAME=$CLUSTER_NAME" >> /etc/profile

# Create SSH Keypair
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa

# Install krew plugin
kubectl krew install ctx ns get-all neat # ktop df-pv mtail tree

# Install Docker
amazon-linux-extras install docker -y
systemctl start docker && systemctl enable docker

# Install Kubecolor
wget https://github.com/kubecolor/kubecolor/releases/download/v0.5.0/kubecolor_0.5.0_linux_amd64.tar.gz
tar -zxvf kubecolor_0.5.0_linux_amd64.tar.gz
mv kubecolor /usr/local/bin/

 


 

eksctl 사용

이제 이어서 본격적으로 eksctl을 사용하여 클러스터를 올려볼 것입니다.

eksctl은 실질적으로 내 AWS 어카운트에 리소스를 생성하는 것이므로 그와 관련된 사용권한 및 인증이 필요합니다. 이는 가장 기본적으로 사용하는 aws configure 을 통해 로컬 환경 호스트에 AWS 인증 정보를 설정합니다.

 - 이때 실습 편의를 위해서 Administrator 권한을 가진 User를 생성

 - AccessKey 발급 을 해줍니다.

aws configure 명령어로 자격증명 저장. 이제 이 자격증명을 사용하여 eksctl 명령어를 사용할 권한을 부여하는 작업이 완료됐습니다.

 

이제 다음과 같은 aws cli 명령어를 사용하여 권한이 제대로 부여됐는지 확인합니다.

aws ec2 describe-instances --filters "Name=tag:Name,Values=myeks-host" | head -n 10
aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" | head -n 10

 

이제 eksctl 을 사용하기 위한 환경변수 설정작업을 해줍니다.

# VPCID 추출하기
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" | jq -r .Vpcs[].VpcId)
echo "export VPCID=$VPCID" >> /etc/profile
echo $VPCID

# 서브넷 ID 추출하기
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
echo "export PubSubnet1=$PubSubnet1" >> /etc/profile
echo "export PubSubnet2=$PubSubnet2" >> /etc/profile
echo $PubSubnet1
echo $PubSubnet2

 


※ 참고

참고: 지원되는 EKS Add-on 확인하기

aws eks describe-addon-versions --kubernetes-version 1.32  --query 'addons[].{MarketplaceProductUrl: marketplaceInformation.productUrl, Name: addonName, Owner: owner Publisher: publisher, Type: type}' --output table

 

EKS Add-on 이란??

EKS Addon은 AWS에서 관리하는 Kubernetes 애드온으로, 클러스터의 핵심 구성 요소(예: VPC CNI, CoreDNS, Kube Proxy 등)를 간편하게 설치 및 업데이트할 수 있도록 지원합니다. 이를 통해 운영 부담을 줄이고, AWS에서 보안 및 안정성을 유지하는 최신 버전으로 관리할 수 있습니다. AWS EKS DOCS

EKS 버전별로 지원하는 Add-on이 다르므로 eks 구성 전에 필요한 애드온을 지원하는 지 확인이 필요합니다.

# eks 1.31버전 지원하는 애드온 확인
aws eks describe-addon-versions --kubernetes-version 1.31  --query 'addons[].{MarketplaceProductUrl: marketplaceInformation.productUrl, Name: addonName, Owner: owner Publisher: publisher, Type: type}' --output table


eksctl 실행하기 --dry-run

 

eksctl 관련 링크

 - Getting Started - https://eksctl.io/getting-started/
 - Config File Schema - https://eksctl.io/usage/schema/
 - Example - https://github.com/eksctl-io/eksctl/tree/main/examples

 

eksctl을 이용하여 클러스터 배포 연습을 해보겠습니다. 

eksctl에 --dry-run 옵션을 주면 실제로 실행하지는 않고 실행했을 경우의 인터랙티브 화면만 보여줍니다. (시뮬레이션)

따라서 eksctl 명령어를 연습하는 데 있어서 아주 유용한 옵션입니다. 👍 

다음은 eksctl 명령어 사용의 여러 예제 입니다.

# eks 클러스터 생성, 노드그룹없이
eksctl create cluster --name myeks --region=ap-northeast-2 --without-nodegroup --dry-run | yh

 

# eks 클러스터 생성, 노드그룹 없이, 서브넷 지정 가용영역 a,c zone
eksctl create cluster --name myeks --region=ap-northeast-2 --without-nodegroup --zones=ap-northeast-2a,ap-northeast-2c --dry-run | yh

위 예제와 달리 a,c 존에만 서브넷을 지정하는 것을 확인할 수 있다.

# eks 클러스터 생성, 관리형 노드그룹 생성, 인스턴스타입 지정, 볼륨 사이즈 지정, 서브넷 지정, VPC 지정
eksctl create cluster \
 --name myeks \
 --region=ap-northeast-2 \
 --nodegroup-name=mynodegroup \
 --node-type=t3.medium \
 --node-volume-size=30 \
 --zones=ap-northeast-2a,ap-northeast-2c \
 --vpc-cidr=172.20.0.0/16 \
 --dry-run | yh

 

더보기

[root@myeks-host ~]# clear
[root@myeks-host ~]# eksctl create cluster \
>  --name myeks \
>  --region=ap-northeast-2 \
>  --nodegroup-name=mynodegroup \
>  --node-type=t3.medium \
>  --node-volume-size=30 \
>  --zones=ap-northeast-2a,ap-northeast-2c \
>  --vpc-cidr=172.20.0.0/16 \
>  --dry-run | yh
accessConfig:
  authenticationMode: API_AND_CONFIG_MAP
addonsConfig: {}
apiVersion: eksctl.io/v1alpha5
availabilityZones:
- ap-northeast-2a
- ap-northeast-2c
cloudWatch:
  clusterLogging: {}
iam:
  vpcResourceControllerPolicy: true
  withOIDC: false
kind: ClusterConfig
kubernetesNetworkConfig:
  ipFamily: IPv4
managedNodeGroups:
- amiFamily: AmazonLinux2
  desiredCapacity: 2
  disableIMDSv1: true
  disablePodIMDS: false
  iam:
    withAddonPolicies:
      albIngress: false
      appMesh: false
      appMeshPreview: false
      autoScaler: false
      awsLoadBalancerController: false
      certManager: false
      cloudWatch: false
      ebs: false
      efs: false
      externalDNS: false
      fsx: false
      imageBuilder: false
      xRay: false
  instanceSelector: {}
  instanceType: t3.medium
  labels:
    alpha.eksctl.io/cluster-name: myeks
    alpha.eksctl.io/nodegroup-name: mynodegroup
  maxSize: 2
  minSize: 2
  name: mynodegroup
  privateNetworking: false
  releaseVersion: ""
  securityGroups:
    withLocal: null
    withShared: null
  ssh:
    allow: false
    publicKeyPath: ""
  tags:
    alpha.eksctl.io/nodegroup-name: mynodegroup
    alpha.eksctl.io/nodegroup-type: managed
  volumeIOPS: 3000
  volumeSize: 30
  volumeThroughput: 125
  volumeType: gp3
metadata:
  name: myeks
  region: ap-northeast-2
  version: "1.30"
privateCluster:
  enabled: false
  skipEndpointCreation: false
vpc:
  autoAllocateIPv6: false
  cidr: 172.20.0.0/16
  clusterEndpoints:
    privateAccess: false
    publicAccess: true
  manageSharedNodeSecurityGroupRules: true
  nat:
    gateway: Single

# eks 클러스터 생성 + 관리형노드그룹생성(AMI:Ubuntu 20.04, 이름, 인스턴스 타입, EBS볼륨사이즈, SSH접속허용) & 사용 가용영역(2a,2c) + VPC 대역 지정
eksctl create cluster \
 --name myeks \
 --region=ap-northeast-2 \
 --nodegroup-name=mynodegroup \
 --node-type=t3.medium \
 --node-volume-size=30 \
 --zones=ap-northeast-2a,ap-northeast-2c \
 --vpc-cidr=172.20.0.0/16 \
 --ssh-access \
 --node-ami-family Ubuntu2004 \
 --dry-run | yh

 

더보기

[root@myeks-host ~]# eksctl create cluster \
>  --name myeks \
>  --region=ap-northeast-2 \
>  --nodegroup-name=mynodegroup \
>  --node-type=t3.medium \
>  --node-volume-size=30 \
>  --zones=ap-northeast-2a,ap-northeast-2c \
>  --vpc-cidr=172.20.0.0/16 \
>  --ssh-access \
>  --node-ami-family Ubuntu2004 \
>  --dry-run | yh
accessConfig:
  authenticationMode: API_AND_CONFIG_MAP
addonsConfig: {}
apiVersion: eksctl.io/v1alpha5
availabilityZones:
- ap-northeast-2a
- ap-northeast-2c
cloudWatch:
  clusterLogging: {}
iam:
  vpcResourceControllerPolicy: true
  withOIDC: false
kind: ClusterConfig
kubernetesNetworkConfig:
  ipFamily: IPv4
managedNodeGroups:
- amiFamily: Ubuntu2004
  desiredCapacity: 2
  disableIMDSv1: true
  disablePodIMDS: false
  iam:
    withAddonPolicies:
      albIngress: false
      appMesh: false
      appMeshPreview: false
      autoScaler: false
      awsLoadBalancerController: false
      certManager: false
      cloudWatch: false
      ebs: false
      efs: false
      externalDNS: false
      fsx: false
      imageBuilder: false
      xRay: false
  instanceSelector: {}
  instanceType: t3.medium
  labels:
    alpha.eksctl.io/cluster-name: myeks
    alpha.eksctl.io/nodegroup-name: mynodegroup
  maxSize: 2
  minSize: 2
  name: mynodegroup
  privateNetworking: false
  releaseVersion: ""
  securityGroups:
    withLocal: null
    withShared: null
  ssh:
    allow: true
    publicKeyPath: ~/.ssh/id_rsa.pub
  tags:
    alpha.eksctl.io/nodegroup-name: mynodegroup
    alpha.eksctl.io/nodegroup-type: managed
  volumeIOPS: 3000
  volumeSize: 30
  volumeThroughput: 125
  volumeType: gp3
metadata:
  name: myeks
  region: ap-northeast-2
  version: "1.30"
privateCluster:
  enabled: false
  skipEndpointCreation: false
vpc:
  autoAllocateIPv6: false
  cidr: 172.20.0.0/16
  clusterEndpoints:
    privateAccess: false
    publicAccess: true
  manageSharedNodeSecurityGroupRules: true
  nat:
    gateway: Single

eksctl 실행하기

이제 dry-run 옵션 없이 찐으로 eks 클러스터 생성. 

명령어를 돌리기 전에 환경변수를 확인합니다.

echo $AWS_DEFAULT_REGION
echo $CLUSTER_NAME
echo $VPCID
echo $PubSubnet1,$PubSubnet2

 

eks클러스터를 생성 하기에 앞서 클러스터 생성 모니터링을 위해 새로운 쉘을 열어서 생성 과정 모니터링 하기

while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table

 

이제 실제로 생성을 해봅니다. eks클러스터와 관리형 노드그룹을 생성하는 데는 약 15분 정도 소요되므로 인내심을 가지고 기다려줍니다.

[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.31 \
 --ssh-access \
 --external-dns-access \
 --verbose 4

 

eksctl 출력 로그

더보기

2025-02-15 12:47:18 [▶]  Setting credentials expiry window to 30 minutes
2025-02-15 12:47:18 [▶]  role ARN for the current session is "arn:aws:iam::851725467479:user/josh-admin"
2025-02-15 12:47:18 [ℹ]  eksctl version 0.204.0
2025-02-15 12:47:18 [ℹ]  using region ap-northeast-2
2025-02-15 12:47:18 [✔]  using existing VPC (vpc-05d3dc182b7c09046) and subnets (private:map[] public:map[ap-northeast-2a:{subnet-041ea49bc7e9b9ae3 ap-northeast-2a 192.168.1.0/24 0 } ap-northeast-2c:{subnet-008f1188f2f0916c9 ap-northeast-2c 192.168.2.0/24 0 }])
2025-02-15 12:47:18 [!]  custom VPC/subnets will be used; if resulting cluster doesn't function as expected, make sure to review the configuration of VPC/subnets
2025-02-15 12:47:18 [ℹ]  nodegroup "myeks-nodegroup" will use "" [AmazonLinux2/1.31]
2025-02-15 12:47:18 [ℹ]  using SSH public key "/root/.ssh/id_rsa.pub" as "eksctl-myeks-nodegroup-myeks-nodegroup-62:ed:29:6a:65:d9:24:2f:ef:85:6e:45:a7:4a:f5:62"
2025-02-15 12:47:18 [▶]  importing SSH public key "eksctl-myeks-nodegroup-myeks-nodegroup-62:ed:29:6a:65:d9:24:2f:ef:85:6e:45:a7:4a:f5:62"
2025-02-15 12:47:19 [ℹ]  using Kubernetes version 1.31
2025-02-15 12:47:19 [ℹ]  creating EKS cluster "myeks" in "ap-northeast-2" region with managed nodes
2025-02-15 12:47:19 [▶]  cfg.json = \
{
    "kind": "ClusterConfig",
    "apiVersion": "eksctl.io/v1alpha5",
    "metadata": {
        "name": "myeks",
        "region": "ap-northeast-2",
        "version": "1.31"
    },
    "kubernetesNetworkConfig": {
        "ipFamily": "IPv4"
    },
    "iam": {
        "withOIDC": false,
        "vpcResourceControllerPolicy": true
    },
    "accessConfig": {
        "authenticationMode": "API_AND_CONFIG_MAP"
    },
    "vpc": {
        "id": "vpc-05d3dc182b7c09046",
        "cidr": "192.168.0.0/16",
        "subnets": {
            "public": {
                "ap-northeast-2a": {
                    "id": "subnet-041ea49bc7e9b9ae3",
                    "az": "ap-northeast-2a",
                    "cidr": "192.168.1.0/24"
                },
                "ap-northeast-2c": {
                    "id": "subnet-008f1188f2f0916c9",
                    "az": "ap-northeast-2c",
                    "cidr": "192.168.2.0/24"
                }
            }
        },
        "manageSharedNodeSecurityGroupRules": true,
        "autoAllocateIPv6": false,
        "nat": {
            "gateway": "Single"
        },
        "clusterEndpoints": {
            "privateAccess": false,
            "publicAccess": true
        }
    },
    "addonsConfig": {},
    "privateCluster": {
        "enabled": false,
        "skipEndpointCreation": false
    },
    "managedNodeGroups": [
        {
            "name": "myeks-nodegroup",
            "amiFamily": "AmazonLinux2",
            "instanceType": "t3.medium",
            "desiredCapacity": 2,
            "minSize": 2,
            "maxSize": 2,
            "volumeSize": 30,
            "ssh": {
                "allow": true,
                "publicKeyPath": "~/.ssh/id_rsa.pub",
                "publicKeyName": "eksctl-myeks-nodegroup-myeks-nodegroup-62:ed:29:6a:65:d9:24:2f:ef:85:6e:45:a7:4a:f5:62"
            },
            "labels": {
                "alpha.eksctl.io/cluster-name": "myeks",
                "alpha.eksctl.io/nodegroup-name": "myeks-nodegroup"
            },
            "privateNetworking": false,
            "tags": {
                "alpha.eksctl.io/nodegroup-name": "myeks-nodegroup",
                "alpha.eksctl.io/nodegroup-type": "managed"
            },
            "iam": {
                "withAddonPolicies": {
                    "imageBuilder": false,
                    "autoScaler": false,
                    "externalDNS": true,
                    "certManager": false,
                    "appMesh": false,
                    "appMeshPreview": false,
                    "ebs": false,
                    "fsx": false,
                    "efs": false,
                    "awsLoadBalancerController": false,
                    "albIngress": false,
                    "xRay": false,
                    "cloudWatch": false
                }
            },
            "securityGroups": {
                "withShared": null,
                "withLocal": null
            },
            "volumeType": "gp3",
            "volumeIOPS": 3000,
            "volumeThroughput": 125,
            "disableIMDSv1": true,
            "disablePodIMDS": false,
            "instanceSelector": {},
            "releaseVersion": ""
        }
    ],
    "availabilityZones": [
        "ap-northeast-2a",
        "ap-northeast-2c"
    ],
    "cloudWatch": {
        "clusterLogging": {}
    }
}

2025-02-15 12:47:19 [ℹ]  will create 2 separate CloudFormation stacks for cluster itself and the initial managed nodegroup
2025-02-15 12:47:19 [ℹ]  if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=ap-northeast-2 --cluster=myeks'
2025-02-15 12:47:19 [ℹ]  Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster "myeks" in "ap-northeast-2"
2025-02-15 12:47:19 [ℹ]  CloudWatch logging will not be enabled for cluster "myeks" in "ap-northeast-2"
2025-02-15 12:47:19 [ℹ]  you can enable it with 'eksctl utils update-cluster-logging --enable-types={SPECIFY-YOUR-LOG-TYPES-HERE (e.g. all)} --region=ap-northeast-2 --cluster=myeks'
2025-02-15 12:47:19 [ℹ]  default addons metrics-server, vpc-cni, kube-proxy, coredns were not specified, will install them as EKS addons
2025-02-15 12:47:19 [ℹ]
2 sequential tasks: { create cluster control plane "myeks",
    2 sequential sub-tasks: {
        2 sequential sub-tasks: {
            1 task: { create addons },
            wait for control plane to become ready,
        },
        create managed nodegroup "myeks-nodegroup",
    }
}
2025-02-15 12:47:19 [▶]  started task: create cluster control plane "myeks"
2025-02-15 12:47:19 [ℹ]  building cluster stack "eksctl-myeks-cluster"
2025-02-15 12:47:19 [▶]  CreateStackInput = &cloudformation.CreateStackInput{StackName:(*string)(0xc000483370), Capabilities:[]types.Capability{"CAPABILITY_IAM"}, ClientRequestToken:(*string)(nil), DisableRollback:(*bool)(0xc000709868), EnableTerminationProtection:(*bool)(nil), NotificationARNs:[]string(nil), OnFailure:"", Parameters:[]types.Parameter(nil), ResourceTypes:[]string(nil), RetainExceptOnCreate:(*bool)(nil), RoleARN:(*string)(nil), RollbackConfiguration:(*types.RollbackConfiguration)(nil), StackPolicyBody:(*string)(nil), StackPolicyURL:(*string)(nil), Tags:[]types.Tag{types.Tag{Key:(*string)(0xc000573bc0), Value:(*string)(0xc000573bd0), noSmithyDocumentSerde:document.NoSerde{}}, types.Tag{Key:(*string)(0xc000573be0), Value:(*string)(0xc000573bf0), noSmithyDocumentSerde:document.NoSerde{}}, types.Tag{Key:(*string)(0xc000573c00), Value:(*string)(0xc000573c10), noSmithyDocumentSerde:document.NoSerde{}}, types.Tag{Key:(*string)(0xc0006930f0), Value:(*string)(0xc000693100), noSmithyDocumentSerde:document.NoSerde{}}}, TemplateBody:(*string)(0xc000693110), TemplateURL:(*string)(nil), TimeoutInMinutes:(*int32)(nil), noSmithyDocumentSerde:document.NoSerde{}}
2025-02-15 12:47:19 [ℹ]  deploying stack "eksctl-myeks-cluster"
2025-02-15 12:47:49 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:48:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:49:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:50:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:51:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:52:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:53:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:54:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:55:19 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-cluster"
2025-02-15 12:55:19 [▶]  processing stack outputs
2025-02-15 12:55:20 [▶]  completed task: create cluster control plane "myeks"
2025-02-15 12:55:20 [▶]  started task:
    2 sequential sub-tasks: {
        2 sequential sub-tasks: {
            1 task: { create addons },
            wait for control plane to become ready,
        },
        create managed nodegroup "myeks-nodegroup",
    }

2025-02-15 12:55:20 [▶]  started task:
    2 sequential sub-tasks: {
        1 task: { create addons },
        wait for control plane to become ready,
    }

2025-02-15 12:55:20 [▶]  started task: 1 task: { create addons }
2025-02-15 12:55:20 [▶]  started task: create addons
2025-02-15 12:55:20 [▶]  resolve conflicts set to OVERWRITE
2025-02-15 12:55:20 [▶]  addon: &{metrics-server v0.7.2-eksbuild.2  [] map[]  {false false false false false false false} map[]  <nil> false  true [] [] []}
2025-02-15 12:55:20 [ℹ]  creating addon: metrics-server
2025-02-15 12:55:20 [▶]  EKS Create Addon output: {%!s(*string=0xc000dc24f0) %!s(*string=0xc000dc24c0) %!s(*string=0xc000dc24e0) %!s(*string=0xc000dc24d0) %!s(*string=<nil>) 2025-02-15 03:55:20.63 +0000 UTC %!s(*types.AddonHealth=&{[] {}}) %!s(*types.MarketplaceInformation=<nil>) 2025-02-15 03:55:20.642 +0000 UTC %!s(*string=<nil>) [] %!s(*string=<nil>) %!s(*string=<nil>) CREATING map[] {}}
2025-02-15 12:55:20 [ℹ]  successfully created addon: metrics-server
2025-02-15 12:55:20 [▶]  resolve conflicts set to OVERWRITE
2025-02-15 12:55:20 [▶]  addon: &{vpc-cni v1.19.0-eksbuild.1  [] map[]  {false false false false false false false} map[]  <nil> false  true [] [] []}
2025-02-15 12:55:21 [!]  recommended policies were found for "vpc-cni" addon, but since OIDC is disabled on the cluster, eksctl cannot configure the requested permissions; the recommended way to provide IAM permissions for "vpc-cni" addon is via pod identity associations; after addon creation is completed, add all recommended policies to the config file, under `addon.PodIdentityAssociations`, and run `eksctl update addon`
2025-02-15 12:55:21 [ℹ]  creating addon: vpc-cni
2025-02-15 12:55:21 [▶]  EKS Create Addon output: {%!s(*string=0xc000e043f0) %!s(*string=0xc000e043d0) %!s(*string=0xc000e043e0) %!s(*string=0xc000e043c0) %!s(*string=<nil>) 2025-02-15 03:55:21.226 +0000 UTC %!s(*types.AddonHealth=&{[] {}}) %!s(*types.MarketplaceInformation=<nil>) 2025-02-15 03:55:21.24 +0000 UTC %!s(*string=<nil>) [] %!s(*string=<nil>) %!s(*string=<nil>) CREATING map[] {}}
2025-02-15 12:55:21 [ℹ]  successfully created addon: vpc-cni
2025-02-15 12:55:21 [▶]  resolve conflicts set to OVERWRITE
2025-02-15 12:55:21 [▶]  addon: &{kube-proxy v1.31.2-eksbuild.3  [] map[]  {false false false false false false false} map[]  <nil> false  true [] [] []}
2025-02-15 12:55:21 [ℹ]  creating addon: kube-proxy
2025-02-15 12:55:21 [▶]  EKS Create Addon output: {%!s(*string=0xc000540a90) %!s(*string=0xc000540a80) %!s(*string=0xc000540a60) %!s(*string=0xc000540a50) %!s(*string=<nil>) 2025-02-15 03:55:21.862 +0000 UTC %!s(*types.AddonHealth=&{[] {}}) %!s(*types.MarketplaceInformation=<nil>) 2025-02-15 03:55:21.874 +0000 UTC %!s(*string=<nil>) [] %!s(*string=<nil>) %!s(*string=<nil>) CREATING map[] {}}
2025-02-15 12:55:21 [ℹ]  successfully created addon: kube-proxy
2025-02-15 12:55:22 [▶]  resolve conflicts set to OVERWRITE
2025-02-15 12:55:22 [▶]  addon: &{coredns v1.11.3-eksbuild.1  [] map[]  {false false false false false false false} map[]  <nil> false  true [] [] []}
2025-02-15 12:55:22 [ℹ]  creating addon: coredns
2025-02-15 12:55:22 [▶]  EKS Create Addon output: {%!s(*string=0xc000482900) %!s(*string=0xc0004828e0) %!s(*string=0xc000482910) %!s(*string=0xc0004828f0) %!s(*string=<nil>) 2025-02-15 03:55:22.371 +0000 UTC %!s(*types.AddonHealth=&{[] {}}) %!s(*types.MarketplaceInformation=<nil>) 2025-02-15 03:55:22.386 +0000 UTC %!s(*string=<nil>) [] %!s(*string=<nil>) %!s(*string=<nil>) CREATING map[] {}}
2025-02-15 12:55:22 [ℹ]  successfully created addon: coredns
2025-02-15 12:55:22 [▶]  completed task: create addons
2025-02-15 12:55:22 [▶]  completed task: 1 task: { create addons }
2025-02-15 12:55:22 [▶]  started task: wait for control plane to become ready
2025-02-15 12:57:22 [▶]  cluster = &types.Cluster{AccessConfig:(*types.AccessConfigResponse)(0xc00089ba60), Arn:(*string)(0xc000b90e70), CertificateAuthority:(*types.Certificate)(0xc000b90ec0), ClientRequestToken:(*string)(nil), ComputeConfig:(*types.ComputeConfigResponse)(nil), ConnectorConfig:(*types.ConnectorConfigResponse)(nil), CreatedAt:time.Date(2025, time.February, 15, 3, 47, 44, 465000000, time.UTC), EncryptionConfig:[]types.EncryptionConfig(nil), Endpoint:(*string)(0xc000b90df0), Health:(*types.ClusterHealth)(0xc00089ba00), Id:(*string)(nil), Identity:(*types.Identity)(0xc000b90e90), KubernetesNetworkConfig:(*types.KubernetesNetworkConfigResponse)(0xc000b7cbd0), Logging:(*types.Logging)(0xc00089ba20), Name:(*string)(0xc000b90de0), OutpostConfig:(*types.OutpostConfigResponse)(nil), PlatformVersion:(*string)(0xc000b90ee0), RemoteNetworkConfig:(*types.RemoteNetworkConfigResponse)(nil), ResourcesVpcConfig:(*types.VpcConfigResponse)(0xc0001c6fc0), RoleArn:(*string)(0xc000b90e80), Status:"ACTIVE", StorageConfig:(*types.StorageConfigResponse)(nil), Tags:map[string]string{"Name":"eksctl-myeks-cluster/ControlPlane", "alpha.eksctl.io/cluster-name":"myeks", "alpha.eksctl.io/cluster-oidc-enabled":"false", "alpha.eksctl.io/eksctl-version":"0.204.0", "aws:cloudformation:logical-id":"ControlPlane", "aws:cloudformation:stack-id":"arn:aws:cloudformation:ap-northeast-2:851725467479:stack/eksctl-myeks-cluster/8e2a5f20-eb4f-11ef-876d-06d9644ecb71", "aws:cloudformation:stack-name":"eksctl-myeks-cluster", "eksctl.cluster.k8s.io/v1alpha1/cluster-name":"myeks"}, UpgradePolicy:(*types.UpgradePolicyResponse)(0xc0008833c8), Version:(*string)(0xc000b90ef0), ZonalShiftConfig:(*types.ZonalShiftConfigResponse)(nil), noSmithyDocumentSerde:document.NoSerde{}}
2025-02-15 12:57:22 [▶]  completed task: wait for control plane to become ready
2025-02-15 12:57:22 [▶]  completed task:
    2 sequential sub-tasks: {
        1 task: { create addons },
        wait for control plane to become ready,
    }

2025-02-15 12:57:22 [▶]  started task: create managed nodegroup "myeks-nodegroup"
2025-02-15 12:57:22 [▶]  waiting for 1 parallel tasks to complete
2025-02-15 12:57:22 [▶]  started task: create managed nodegroup "myeks-nodegroup"
2025-02-15 12:57:22 [▶]  started task: create managed nodegroup "myeks-nodegroup"
2025-02-15 12:57:22 [▶]  started task: create managed nodegroup "myeks-nodegroup"
2025-02-15 12:57:22 [ℹ]  building managed nodegroup stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2025-02-15 12:57:22 [▶]  CreateStackInput = &cloudformation.CreateStackInput{StackName:(*string)(0xc000ba0a80), Capabilities:[]types.Capability{"CAPABILITY_IAM"}, ClientRequestToken:(*string)(nil), DisableRollback:(*bool)(0xc000da5478), EnableTerminationProtection:(*bool)(nil), NotificationARNs:[]string(nil), OnFailure:"", Parameters:[]types.Parameter(nil), ResourceTypes:[]string(nil), RetainExceptOnCreate:(*bool)(nil), RoleARN:(*string)(nil), RollbackConfiguration:(*types.RollbackConfiguration)(nil), StackPolicyBody:(*string)(nil), StackPolicyURL:(*string)(nil), Tags:[]types.Tag{types.Tag{Key:(*string)(0xc000573bc0), Value:(*string)(0xc000573bd0), noSmithyDocumentSerde:document.NoSerde{}}, types.Tag{Key:(*string)(0xc000573be0), Value:(*string)(0xc000573bf0), noSmithyDocumentSerde:document.NoSerde{}}, types.Tag{Key:(*string)(0xc000573c00), Value:(*string)(0xc000573c10), noSmithyDocumentSerde:document.NoSerde{}}, types.Tag{Key:(*string)(0xc000ba1970), Value:(*string)(0xc000ba1980), noSmithyDocumentSerde:document.NoSerde{}}, types.Tag{Key:(*string)(0xc000ba1990), Value:(*string)(0xc000ba19a0), noSmithyDocumentSerde:document.NoSerde{}}}, TemplateBody:(*string)(0xc000ba19b0), TemplateURL:(*string)(nil), TimeoutInMinutes:(*int32)(nil), noSmithyDocumentSerde:document.NoSerde{}}
2025-02-15 12:57:23 [ℹ]  deploying stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2025-02-15 12:57:23 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2025-02-15 12:57:53 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2025-02-15 12:58:23 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2025-02-15 12:59:25 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2025-02-15 13:00:25 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-nodegroup-myeks-nodegroup"
2025-02-15 13:00:25 [▶]  processing stack outputs
2025-02-15 13:00:25 [▶]  completed task: create managed nodegroup "myeks-nodegroup"
2025-02-15 13:00:25 [▶]  completed task: create managed nodegroup "myeks-nodegroup"
2025-02-15 13:00:25 [▶]  completed task: create managed nodegroup "myeks-nodegroup"
2025-02-15 13:00:25 [▶]  completed task: create managed nodegroup "myeks-nodegroup"
2025-02-15 13:00:25 [▶]  completed task:
    2 sequential sub-tasks: {
        2 sequential sub-tasks: {
            1 task: { create addons },
            wait for control plane to become ready,
        },
        create managed nodegroup "myeks-nodegroup",
    }

2025-02-15 13:00:25 [ℹ]  waiting for the control plane to become ready
2025-02-15 13:00:26 [▶]  merging kubeconfig files
2025-02-15 13:00:26 [▶]  setting current-context to josh-admin@myeks.ap-northeast-2.eksctl.io
2025-02-15 13:00:26 [✔]  saved kubeconfig as "/root/.kube/config"
2025-02-15 13:00:26 [ℹ]  no tasks
2025-02-15 13:00:26 [▶]  no actual tasks
2025-02-15 13:00:26 [✔]  all EKS cluster resources for "myeks" have been created
2025-02-15 13:00:26 [ℹ]  nodegroup "myeks-nodegroup" has 2 node(s)
2025-02-15 13:00:26 [ℹ]  node "ip-192-168-1-208.ap-northeast-2.compute.internal" is ready
2025-02-15 13:00:26 [ℹ]  node "ip-192-168-2-42.ap-northeast-2.compute.internal" is ready
2025-02-15 13:00:26 [ℹ]  waiting for at least 2 node(s) to become ready in "myeks-nodegroup"
2025-02-15 13:00:26 [▶]  event = watch.Event{Type:"ADDED", Object:(*v1.Node)(0xc000834c08)}
2025-02-15 13:00:26 [▶]  node "ip-192-168-1-208.ap-northeast-2.compute.internal" is ready in "myeks-nodegroup"
2025-02-15 13:00:26 [▶]  event = watch.Event{Type:"ADDED", Object:(*v1.Node)(0xc000b12308)}
2025-02-15 13:00:26 [▶]  node "ip-192-168-2-42.ap-northeast-2.compute.internal" is ready in "myeks-nodegroup"
2025-02-15 13:00:26 [ℹ]  nodegroup "myeks-nodegroup" has 2 node(s)
2025-02-15 13:00:26 [ℹ]  node "ip-192-168-1-208.ap-northeast-2.compute.internal" is ready
2025-02-15 13:00:26 [ℹ]  node "ip-192-168-2-42.ap-northeast-2.compute.internal" is ready
2025-02-15 13:00:26 [✔]  created 1 managed nodegroup(s) in cluster "myeks"
2025-02-15 13:00:26 [▶]  found authenticator: aws
2025-02-15 13:00:26 [▶]  kubectl: "/usr/local/bin/kubectl"
2025-02-15 13:00:26 [▶]  kubectl version: v1.31.2-eks-94953ac
2025-02-15 13:00:27 [ℹ]  kubectl command should work with "/root/.kube/config", try 'kubectl get nodes'
2025-02-15 13:00:27 [✔]  EKS cluster "myeks" in "ap-northeast-2" region is ready
2025-02-15 13:00:27 [▶]  cfg.json = \
{
    "kind": "ClusterConfig",
    "apiVersion": "eksctl.io/v1alpha5",
    "metadata": {
        "name": "myeks",
        "region": "ap-northeast-2",
        "version": "1.31"
    },
    "kubernetesNetworkConfig": {
        "ipFamily": "IPv4",
        "serviceIPv4CIDR": "10.100.0.0/16"
    },
    "iam": {
        "serviceRoleARN": "arn:aws:iam::851725467479:role/eksctl-myeks-cluster-ServiceRole-hS9RHVQY3gJt",
        "withOIDC": false,
        "vpcResourceControllerPolicy": true
    },
    "accessConfig": {
        "authenticationMode": "API_AND_CONFIG_MAP"
    },
    "vpc": {
        "id": "vpc-05d3dc182b7c09046",
        "cidr": "192.168.0.0/16",
        "securityGroup": "sg-0e757304802669ef3",
        "subnets": {
            "public": {
                "ap-northeast-2a": {
                    "id": "subnet-041ea49bc7e9b9ae3",
                    "az": "ap-northeast-2a",
                    "cidr": "192.168.1.0/24"
                },
                "ap-northeast-2c": {
                    "id": "subnet-008f1188f2f0916c9",
                    "az": "ap-northeast-2c",
                    "cidr": "192.168.2.0/24"
                }
            }
        },
        "sharedNodeSecurityGroup": "sg-0e69b11b17d63d31d",
        "manageSharedNodeSecurityGroupRules": true,
        "autoAllocateIPv6": false,
        "nat": {
            "gateway": "Single"
        },
        "clusterEndpoints": {
            "privateAccess": false,
            "publicAccess": true
        }
    },
    "addonsConfig": {},
    "privateCluster": {
        "enabled": false,
        "skipEndpointCreation": false
    },
    "managedNodeGroups": [
        {
            "name": "myeks-nodegroup",
            "amiFamily": "AmazonLinux2",
            "instanceType": "t3.medium",
            "desiredCapacity": 2,
            "minSize": 2,
            "maxSize": 2,
            "volumeSize": 30,
            "ssh": {
                "allow": true,
                "publicKeyPath": "~/.ssh/id_rsa.pub",
                "publicKeyName": "eksctl-myeks-nodegroup-myeks-nodegroup-62:ed:29:6a:65:d9:24:2f:ef:85:6e:45:a7:4a:f5:62"
            },
            "labels": {
                "alpha.eksctl.io/cluster-name": "myeks",
                "alpha.eksctl.io/nodegroup-name": "myeks-nodegroup"
            },
            "privateNetworking": false,
            "tags": {
                "alpha.eksctl.io/nodegroup-name": "myeks-nodegroup",
                "alpha.eksctl.io/nodegroup-type": "managed"
            },
            "iam": {
                "withAddonPolicies": {
                    "imageBuilder": false,
                    "autoScaler": false,
                    "externalDNS": true,
                    "certManager": false,
                    "appMesh": false,
                    "appMeshPreview": false,
                    "ebs": false,
                    "fsx": false,
                    "efs": false,
                    "awsLoadBalancerController": false,
                    "albIngress": false,
                    "xRay": false,
                    "cloudWatch": false
                }
            },
            "securityGroups": {
                "withShared": null,
                "withLocal": null
            },
            "volumeType": "gp3",
            "volumeIOPS": 3000,
            "volumeThroughput": 125,
            "disableIMDSv1": true,
            "disablePodIMDS": false,
            "instanceSelector": {},
            "releaseVersion": ""
        }
    ],
    "availabilityZones": [
        "ap-northeast-2a",
        "ap-northeast-2c"
    ],
    "cloudWatch": {
        "clusterLogging": {}
    }
}

실행로그를 보면 알 수 있지만 eksctl은 사실 cloudformation 활용하여 리소스를 프로비저닝합니다.

이는 웹콘솔에서도 확인 가능하다. 다음과 같이 eksctl- 접두사로 시작하는 cluster와 managed-nodegroup 즉 컨트롤플레인과 워커노드가 생성된 것을 확인할 수 있습니다.

 

eksctl-myeks-cluster 스택의 리소스는 다음과 같이 생겼다.

 

eksctl-myeks-nodegroup-myeks-nodegroup

 

생성된 리소스 중에 몇가지 눈여겨 볼만한 리소스를 살펴봅니다.

 

EKS 클러스터 리소스 살펴보기

▪️클러스터 역할

AmazonEKSClusterPolicy

더보기

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "autoscaling: DescribeAutoScalingGroups",
                "autoscaling:UpdateAutoScalingGroup",
                "ec2:AttachVolume",
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:CreateRoute",
                "ec2:CreateSecurityGroup",
                "ec2:CreateTags",
                "ec2:CreateVolume",
                "ec2:DeleteRoute",
                "ec2:DeleteSecurityGroup",
                "ec2:DeleteVolume",
                "ec2:DescribeInstances",
                "ec2:DescribeRouteTables",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVolumes",
                "ec2:DescribeVolumesModifications",
                "ec2:DescribeVpcs",
                "ec2:DescribeDhcpOptions",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribeAvailabilityZones",
                "ec2:DetachVolume",
                "ec2:ModifyInstanceAttribute",
                "ec2:ModifyVolume",
                "ec2:RevokeSecurityGroupIngress",
                "ec2:DescribeAccountAttributes",
                "ec2:DescribeAddresses",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeInstanceTopology",
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer",
                "elasticloadbalancing:AttachLoadBalancerToSubnets",
                "elasticloadbalancing:ConfigureHealthCheck",
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticloadbalancing:CreateLoadBalancerListeners",
                "elasticloadbalancing:CreateLoadBalancerPolicy",
                "elasticloadbalancing:CreateTargetGroup",
                "elasticloadbalancing:DeleteListener",
                "elasticloadbalancing:DeleteLoadBalancer",
                "elasticloadbalancing:DeleteLoadBalancerListeners",
                "elasticloadbalancing:DeleteTargetGroup",
                "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
                "elasticloadbalancing:DeregisterTargets",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeLoadBalancerAttributes",
                "elasticloadbalancing:DescribeLoadBalancerPolicies",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeTargetGroupAttributes",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:DetachLoadBalancerFromSubnets",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:ModifyLoadBalancerAttributes",
                "elasticloadbalancing:ModifyTargetGroup",
                "elasticloadbalancing:ModifyTargetGroupAttributes",
                "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer",
                "elasticloadbalancing:SetLoadBalancerPoliciesOfListener",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "iam:CreateServiceLinkedRole",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com"
                }
            }
        }
    ]
}

AmazonEKSVPCResourceController

더보기

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ec2:CreateNetworkInterfacePermission",
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "ec2:ResourceTag/eks:eni:owner": "eks-vpc-resource-controller"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateNetworkInterface",
                "ec2:DetachNetworkInterface",
                "ec2:ModifyNetworkInterfaceAttribute",
                "ec2:DeleteNetworkInterface",
                "ec2:AttachNetworkInterface",
                "ec2:UnassignPrivateIpAddresses",
                "ec2:AssignPrivateIpAddresses"
            ],
            "Resource": "*"
        }
    ]
}

각각의 정책을 살펴보면 EKS 클러스터가 내부적으로 어떤 동작을 수행하는 지 알 수 있습니다.

대표적으로

- 네트워크 인터페이스를 생성 및 관리

- EC2 생성 및 관리

- Elastic Load balancer 생성 및 관리 (이는 API 서버 접근을 위한 Ingress 등으로 사용됩니다)

 

▪️관리형 워커노드 역할

해당 역할은 관리형 워커노드의 인스턴스 Profile 에 적용됩니다.

 

▪️클러스터 보안 그룹

▪️워커노드 보안 그룹

SSH가 0.0.0.0/0 으로 열려있지만 생성된 키페어를 통해서만 접근이 가능합니다.

 

▪️SSH 키페어

 

▪️EKS 클러스터 정보

클러스터 구성 정보랑 워커노드에 대해서 볼 수 있습니다.

네트워킹 탭을 눌러보면 쿠버네티스 API 서버 접근이 퍼블릭으로 되어있다는 것을 확인 가능합니다.

해당 엔드포인트는 퍼블릭으로 열려있기 때문에 아무나 접근할 수 있습니다. 브라우저 창에 version 을 조회해 봅니다.

엔드포인트에 대한 접근을 제어하고싶다면 액세스 포인트 관리 버튼을 눌러서 아이피를 제한합니다.

▪️EKS Add-on 확인

 

 - Metrics Server: Kubernetes 클러스터 내에서 CPU 및 메모리 사용량을 수집하여 Horizontal Pod Autoscaler(HPA) 및 Kubernetes Metrics API를 지원하는 애드온.

 - Amazon VPC CNI: EKS에서 각 Pod가 VPC 네트워크의 ENI(Elastic Network Interface)를 직접 사용할 수 있도록 하는 네트워크 애드온.

 - kube-proxy: Kubernetes 서비스 간 트래픽을 관리하고 네트워크 라우팅을 처리하는 네트워크 프록시 애드온.

 - CoreDNS: Kubernetes 클러스터 내에서 DNS 서비스 및 서비스 검색 기능을 제공하는 애드온.

 

클러스터 관리 (kubectl, eksctl aws eks 명령어 사용)

이제 본격적으로 쿠버네티스를 로컬 워크스페이스에서 다뤄 봅니다. kubectl 명령어 편의를 위해 alias를 적용하여 k=kubectl 로 명령어를 사용합니다.

# kubectl plugin 조회
k plugin list

 

 - ctx: 현재 Kubernetes 컨텍스트를 빠르게 전환할 수 있도록 도와주는 kubectl 플러그인.
 - get-all: kubectl get all보다 더 확장된 정보를 조회할 수 있도록 지원하는 플러그인.
 - krew: kubectl 플러그인을 검색, 설치 및 관리할 수 있는 패키지 관리자.
 - neat: kubectl 출력에서 불필요한 필드를 제거하여 가독성을 높여주는 플러그인.
 - ns: 현재 Kubernetes 네임스페이스를 빠르게 전환할 수 있도록 도와주는 플러그인. 

#클러스터의 모든 리소스를 조회
k get-all

약 380개의 리소스가 생성이 되어있다.

# 클러스터 정보 확인. API 서버 엔드포인트 확인
k cluster-info

 

eksctl로도 클러스터 정보를 확인할 수 있습니다.

eksctl get cluster

 

AWSCLI를 사용하여 클러스터 엔드포인트를 조회합니다.

aws eks describe-cluster --name $CLUSTER_NAME | jq -r .cluster.endpoint

 

해당 엔드포인트의 DNS 를 APIDNS 환경변수에 저장합니다.

APIDNS=$(aws eks describe-cluster --name $CLUSTER_NAME | jq -r .cluster.endpoint | cut -d '/' -f 3)

APIDNS를 dig 명령어로 IP 조회 해보면 두 공인 IP가 확인되는 것을 볼 수있습니다. 이는 컨트롤 플레인은 HA 구성으로 되어있고 앞단에 k8s Ingress로 NLB로 부터 프록시가 되어있기 때문입니다.

 

클러스터 관리 - 노드 정보 확인

# eksctl 명령어 사용
eksctl get nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup
# awscli 
aws eks describe-nodegroup --cluster-name $CLUSTER_NAME --nodegroup-name $CLUSTER_NAME-nodegroup | jq
# kubectl
k get nodes

 

클러스터 관리 - conif 정보 확인(context)

현재 kubectl로 클러스터에 대한 정보조회 및 Kube-API 서버에 접근 가능한 것은 인증과 인가를 config 정보를 통해 통과하였기 때문입니다. 다음 명령어를 통해 찾아볼 수 있습니다.

k config view

cat /root/.kube/config

kube-api 서버 인증과 관련된 인증서, 유저, 클러스터, 네임스페이스 등에 대한 정보가 저장되어 있는 것을 확인 가능합니다.

 

EKS 에서는 자체 OIDC를 이용하여 토큰 기반의 인증을 사용합니다.

eks 토큰 조회
aws eks get-token --cluster-name $CLUSTER_NAME --region $AWS_DEFAULT_REGION

 

클러스터 관리 - Pod 정보 조회

# -A: 모든 네임스페이스 지정. -o 출력 형식 지정. wide는 자세히
k get pods -A
k get pods -A -owide  
k get pods -A -o wide
k get pods -A -o yaml >> pods.yaml

 

클러스터 관리 - 데이터플레인 노드 정보 파헤쳐보기

# 인스턴스 조회
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table

# 클러스터 워커노드 조회
kubectl get node --label-columns=topology.kubernetes.io/zone

# 워커노드 ip 저장
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile

 

eksctl-host에서 워커노드 통신해보기

노드 보안그룹에 eksctl-host 인바운드가 허용되어 있지 않으므로 통신이 되지 않습니다. 아래에서 eksctl-host에 대한 접근을 허용해 줍니다.

# 노드 보안그룹 ID 확인
aws ec2 describe-security-groups --filters Name=group-name,Values=*nodegroup* --query "SecurityGroups[*].[GroupId]" --output text

NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*nodegroup* --query "SecurityGroups[*].[GroupId]" --output text)
echo "export NGSGID=$NGSGID" >> /etc/profile

# 노드 보안그룹에 eksctl-host 에서 노드(파드)에 접속 가능하게 룰(Rule) 추가 설정
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32

# eksctl-host 에서 노드의IP나 coredns 파드IP로 ping 테스트
ping -c 2 $N1
ping -c 2 $N2

 

모든 프로토콜에 대해서 eksctl의 접근을 허용해줬기 때문에 이제 워커노드에 SSH 연결이 가능합니다.

# eksctl이 생성한 키페어로 워커노드 접근
ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no ec2-user@$N1 hostname
ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no ec2-user@$N2 hostname

 

클러스터 관리 - 네트워크 정보 확인

AWS 에서는 CNI로서 VPC-CNI를 사용합니다. 보통 자체구현 k8s는 calico, cilium, flannel 등을 사용합니다. CNI는 데몬셋으로 올라가있습니다.

"ds= daemonset, aws-node는 VPC CNI를 위한 데몬셋 이름"

# AWS VPC CNI 사용 확인
kubectl -n kube-system get ds aws-node

# aws-node POD가 사용하는 이미지 확인
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2

# aws-node POD 에는 2개의 컨테이너가 실행되고 있습니다.
kubecolor describe pod -n kube-system -l k8s-app=aws-node

이렇듯 eks는 amazon-k8s-cni라는 VPC CNI를 사용한다는 것을 알 수 있습니다.

 

aws-node 데몬셋은 두 개의 컨테이너로 이루어져 있는데 각 역할은 다음과 같습니다.

 - aws-node: VCP-CNI 역할의 컨테이너

 - aws-eks-nodeagent: eBPF 기반의 Network Policy 역할의 컨테이너 

 

 

 

 

# aws-node 파드 조회
k get pod -n kube-system -o wide | grep -i aws-node

# 워커노드에서 ip route 테이블 조회
for i in $N1 $N2; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done

각 워커노드의 컨테이너는 aws-node의 IP를 통해 라우팅을 한다는 것을 알 수 있습니다.

지금부터는 컨테이너 기술이라는 좀 더 범용적인 관점에서 리소스를 살펴보겠습니다.


cgroup

cgroup(Control Groups)은 리눅스 커널 기능으로, 프로세스 그룹에 대해 CPU, 메모리, 디스크 I/O, 네트워크 등의 리소스를 제한하거나 할당할 수 있도록 관리하는 기능입니다.
컨테이너에서는 cgroup을 활용하여 각 컨테이너가 지정된 리소스 내에서만 동작하도록 격리하고 제어하며, 이를 통해 과도한 리소스 사용을 방지하고 안정적인 환경을 유지할 수 있습니다.

즉 한마디로 루트 파일시스템 및 호스트의 리소스를 필요에 따라 조각조각 잘라서 격리하여 사용하는 기술입니다. 이는 컨테이너 기술의 핵심이라 볼 수 있습니다.

# 워커노드의 cgroup 확인
for i in $N1 $N2; do echo ">> node $i <<"; ssh ec2-user@$i stat -fc %T /sys/fs/cgroup/; echo; done

OS 에따라 cgroup이 조금씩 다릅니다.

 - Amazon Linux 2는 cgroup v1 사용 --> tmpfs
 - Amazon Linux 2023은 cgroup v2 사용 --> cgroup2fs
https://kubernetes.io/docs/concepts/architecture/cgroups/

 

kubelet - 워커노드의 프로세스를 관리

kubelet은 쿠버네티스 노드에서 실행되는 핵심 에이전트로, 노드의 생명줄 같은 역할을 합니다.

kubelet은 마스터 노드로부터 파드(Pod) 스펙을 받아 컨테이너를 실행 및 관리하며, 컨테이너 상태를 모니터링하고 자동 복구하는 중요한 기능을 수행합니다. kubelet은 모든 워커노드에 데몬으로 기동됩니다.

# 워커노드에서 kubelet 데몬 정상 동작 확인
for i in $N1 $N2; do echo ">> node $i <<"; ssh ec2-user@$i sudo systemctl status kubelet; echo; done

 

쿠버네티스에서 컨테이너를 실행할 때, kubelet → containerd → containerd-shim → runc → 컨테이너 순으로 동작하며, 각 구성 요소는 서로 유기적으로 연결되어 있습니다.

kubelet
- 쿠버네티스의 핵심 에이전트로, 노드에서 실행되며 Pod을 생성하고 컨테이너를 관리함.
- 컨테이너 런타임(containerd)와 통신하여 컨테이너를 실행하도록 요청함.

containerd
- 컨테이너 런타임으로, 컨테이너의 생성, 실행, 중지, 삭제 등 라이프사이클을 관리함.
- OCI(오픈 컨테이너 이니셔티브) 표준을 따르며, runc를 사용하여 컨테이너를 실행함.

containerd-shim
- containerd에서 실행된 컨테이너가 containerd 프로세스와 독립적으로 동작할 수 있도록 관리하는 역할.
- containerd가 종료되더라도 컨테이너가 계속 실행될 수 있도록 함.

runc
- 컨테이너를 실행하는 가장 로우레벨(Low-Level) 런타임으로, containerd가 요청하면 Linux cgroups 및 네임스페이스를 활용해 컨테이너를 생성함.

컨테이너 (Container)
- 실행 중인 애플리케이션으로, 위의 모든 계층을 거쳐 최종적으로 사용자 코드가 동작하는 환경이 됨.

 

컨테이너 프로세스 살펴보기

그렇다면 실제로 워커노드에서 컨테이너 관련 프로세스는 어떻게 실행되고 있을까? 이를 직접 pstree로 살펴봅니다.

for i in $N1 $N2; do echo ">> node $i <<"; ssh ec2-user@$i sudo pstree; echo; done

 

실습 정리

지금까지 AWS EKS 에 대한 간단한 소개와 직접 구성해보며 컴포넌트를 살펴보았고 어떻게 동작하는 지 살펴봤습니다. 

여기까지 직접 따라오셨다면 마무리로 생성한 실습 자원을 삭제 하도록 합니다. 이는 비용문제와 직결되기 때문에 꼭 자원을 삭제하셔야 합니다.❗️❗️❗️❗️❗️❗️

 

eksctl로 생성한 클러스터를 삭제합니다. 약 10분 정도 소요됩니다.

# 클러스터 삭제
eksctl delete cluster --name $CLUSTER_NAME

터미널을 보면 노드그룹을 삭제한 후 컨트롤 플레인을 삭제하는 것을 확인할 수 있습니다. 웹 콘솔에서 삭제작업을 진행할때도 노드그룹을 먼저 삭제해야 클러스터 삭제가 가능합니다.

 

실습 초반에 CloudFormation 으로 생성한 기본 네트워크 환경도 삭제해 줍니다.