[EKS] EKS: Networking - 2주차 실습 환경 구성
서종호(가시다)님의 AWS EKS Workshop Study(AEWS) 2주차 학습 내용을 기반으로 합니다.
TL;DR
- 2주차 실습 환경은 1주차 대비 서브넷 CIDR을 /24에서 /22로 확대하고, VPC CNI addon에
WARM_ENI_TARGET설정을 추가했다 enable_irsa = true로 OIDC 기반 IRSA를 활성화하여, 파드(서비스 어카운트) 단위로 AWS IAM 권한을 세밀하게 부여할 수 있다terraform output을 활용하면 kubeconfig 설정 명령을 자동으로 얻을 수 있다- 배포 후 EKS 콘솔과 kubectl로 클러스터, 노드, 파드, addon, IAM 역할, 보안 그룹 등 기본 정보를 확인한다
실습 환경 변경점
이전 포스트에서 AWS VPC CNI의 구조와 동작 원리를 살펴봤다. 이제 직접 실습해 볼 차례다. 2주차 실습 환경은 1주차와 동일한 Terraform 기반이지만, VPC CNI 실습에 맞게 몇 가지 변경이 있다.
서브넷 CIDR 확대
1주차에는 퍼블릭/프라이빗 서브넷 모두 /24(IP 254개)를 사용했다. 2주차에서는 /22(IP 1,022개)로 확대했다.
variable "public_subnet_blocks" {
type = list(string)
default = ["192.168.0.0/22", "192.168.4.0/22", "192.168.8.0/22"] # 1주차: /24 → 2주차: /22
}
variable "private_subnet_blocks" {
type = list(string)
default = ["192.168.12.0/22", "192.168.16.0/22", "192.168.20.0/22"]
}
VPC CNI는 파드에게 VPC의 실제 IP를 부여하므로, ENI와 Secondary IP를 많이 소비한다. 서브넷이 좁으면 IP 부족으로 파드 스케줄링이 실패할 수 있다. /22로 확대하면 서브넷당 약 1,000개의 IP를 확보할 수 있어, 이후 실습에서 IP 부족 걱정 없이 다양한 시나리오를 테스트할 수 있다.
VPC CNI addon 설정
1주차에서는 VPC CNI를 기본 설정으로 배포했지만, 2주차에서는 configuration_values를 통해 환경 변수를 직접 지정한다.
addons = {
vpc-cni = {
most_recent = true
before_compute = true # 노드 그룹보다 먼저 배포
configuration_values = jsonencode({
env = {
WARM_ENI_TARGET = "1" # 현재 ENI 외에 여유 ENI 1개를 항상 확보
}
})
}
}
WARM_ENI_TARGET = "1"은 사실 기본값과 동일하다. 여기서는 addon 배포 시 환경 변수를 설정하는 방법 자체를 확인하기 위한 목적이 크다. 이후 실습에서 이 값을 변경하거나, WARM_IP_TARGET, MINIMUM_IP_TARGET, ENABLE_PREFIX_DELEGATION 등 다른 설정을 추가해 가며 동작을 비교할 예정이다.
before_compute = true를 설정하면, VPC CNI addon이 노드 그룹보다 먼저 배포된다. 노드가 생성될 때 이미 CNI 설정이 적용되어 있어야 IP 할당이 정상적으로 이루어지기 때문이다.
IRSA 활성화
module "eks" {
# ...
enable_irsa = true # 1주차에는 없던 설정
}
1주차 실습에서는 enable_irsa를 명시적으로 설정하지 않았다. terraform-aws-modules/eks 모듈은 v18.4.0부터 기본값이 true이므로 OIDC Provider 자체는 생성되었겠지만, IRSA를 활용하는 addon 설정은 없었다. 2주차에서는 이를 명시적으로 선언하여 의도를 드러내고, VPC CNI를 포함한 addon들이 파드 단위로 AWS IAM 권한을 받을 수 있는 기반을 마련한 것이다.
enable_irsa = true를 설정하면 EKS 클러스터에 OIDC Provider가 생성된다. 이 OIDC Provider가 쿠버네티스 서비스 어카운트와 AWS IAM Role을 연결하는 다리 역할을 한다. OIDC와 IRSA의 동작 원리는 아래에서 간략히 살펴보자.
OIDC와 IRSA
이 섹션은 OIDC/IRSA의 핵심 개념만 요약한다. 상세한 동작 원리와 실습은 이후 포스트에서 다룰 예정이다.
왜 필요한가
OIDC/IRSA가 없으면, 파드가 AWS API를 호출할 때 노드(EC2 인스턴스)의 IAM Role을 그대로 사용한다. 같은 노드의 모든 파드가 동일한 권한을 갖게 되므로, 최소 권한 원칙(Least Privilege)을 위반한다.
예를 들어, aws-node(VPC CNI) 파드는 AssignPrivateIpAddresses, AttachNetworkInterface 같은 EC2 네트워크 API 권한이 필요하다. 이 권한을 노드 IAM Role에 부여하면, 같은 노드의 일반 앱 파드도 이 권한에 접근할 수 있다.
IRSA가 하는 일
IRSA(IAM Roles for Service Accounts)는 서비스 어카운트 단위로 IAM Role을 매핑하는 메커니즘이다.
| 구성 요소 | 역할 |
|---|---|
| OIDC Provider | EKS 클러스터가 “이 서비스 어카운트는 진짜 이 클러스터의 것이다”라는 JWT 토큰을 발행 |
| AWS IAM Trust Policy | “이 OIDC Provider가 발행한 토큰 중, 특정 서비스 어카운트만 신뢰한다”고 설정 |
| AWS STS | JWT를 검증하고, 해당 서비스 어카운트 전용 임시 자격증명을 발급 |
핵심은 OIDC가 쿠버네티스 세계와 AWS IAM 세계를 연결하는 다리 역할을 한다는 것이다. 쿠버네티스 내부의 서비스 어카운트 토큰을 AWS IAM이 검증할 수 있는 표준(OIDC) 형식으로 변환해 준다.
이번 실습에서는 IRSA 덕분에 아래와 같은 addon들이 파드 단위로 세밀한 AWS 권한을 부여받을 수 있다.
- aws-node(VPC CNI): ENI 생성/삭제, IP 할당 등 EC2 네트워크 API 권한
- CoreDNS: 필요 시 Route53 접근 권한
- AWS Load Balancer Controller: ALB/NLB 생성 권한
- ExternalDNS: Route53 레코드 관리 권한
배포
Terraform 배포
실습 코드를 클론하고 변수를 설정한 뒤, Terraform으로 배포한다.
# 코드 다운로드, 작업 디렉터리 이동
git clone https://github.com/gasida/aews.git
cd aews/2w
# 변수 지정
export TF_VAR_KeyName=$(aws ec2 describe-key-pairs --query "KeyPairs[].KeyName" --output text)
export TF_VAR_ssh_access_cidr=$(curl -s ipinfo.io/ip)/32
echo $TF_VAR_KeyName $TF_VAR_ssh_access_cidr
# 실행 결과
my-eks-keypair 121.171.163.127/32
# 배포 (약 12분 소요)
terraform init
terraform plan
nohup sh -c "terraform apply -auto-approve" > create.log 2>&1 &
tail -f create.log
terraform init 출력
Initializing the backend...
Initializing modules...
Downloading registry.terraform.io/terraform-aws-modules/eks/aws 21.15.1 for eks...
- eks in .terraform/modules/eks
- eks.eks_managed_node_group in .terraform/modules/eks/modules/eks-managed-node-group
- eks.eks_managed_node_group.user_data in .terraform/modules/eks/modules/_user_data
- eks.fargate_profile in .terraform/modules/eks/modules/fargate-profile
Downloading registry.terraform.io/terraform-aws-modules/kms/aws 4.0.0 for eks.kms...
- eks.kms in .terraform/modules/eks.kms
- eks.self_managed_node_group in .terraform/modules/eks/modules/self-managed-node-group
- eks.self_managed_node_group.user_data in .terraform/modules/eks/modules/_user_data
Downloading registry.terraform.io/terraform-aws-modules/vpc/aws 6.6.0 for vpc...
- vpc in .terraform/modules/vpc
Initializing provider plugins...
- Finding hashicorp/time versions matching ">= 0.9.0"...
- Finding hashicorp/tls versions matching ">= 4.0.0"...
- Finding hashicorp/cloudinit versions matching ">= 2.0.0"...
- Finding hashicorp/null versions matching ">= 3.0.0"...
- Finding hashicorp/aws versions matching ">= 6.0.0, >= 6.28.0"...
- Installing hashicorp/tls v4.2.1...
- Installed hashicorp/tls v4.2.1 (signed by HashiCorp)
- Installing hashicorp/cloudinit v2.3.7...
- Installed hashicorp/cloudinit v2.3.7 (signed by HashiCorp)
- Installing hashicorp/null v3.2.4...
- Installed hashicorp/null v3.2.4 (signed by HashiCorp)
- Installing hashicorp/aws v6.37.0...
- Installed hashicorp/aws v6.37.0 (signed by HashiCorp)
- Installing hashicorp/time v0.13.1...
- Installed hashicorp/time v0.13.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Terraform 전체 코드 (var.tf, vpc.tf, eks.tf, output.tf)
var.tf
variable "KeyName" {
description = "Name of an existing EC2 KeyPair to enable SSH access to the instances."
type = string
}
variable "ssh_access_cidr" {
description = "Allowed CIDR for SSH access"
type = string
}
variable "ClusterBaseName" {
description = "Base name of the cluster."
type = string
default = "myeks"
}
variable "KubernetesVersion" {
description = "Kubernetes version for the EKS cluster."
type = string
default = "1.34"
}
variable "WorkerNodeInstanceType" {
description = "EC2 instance type for the worker nodes."
type = string
default = "t3.medium"
}
variable "WorkerNodeCount" {
description = "Number of worker nodes."
type = number
default = 3
}
variable "WorkerNodeVolumesize" {
description = "Volume size for worker nodes (in GiB)."
type = number
default = 30
}
variable "TargetRegion" {
description = "AWS region where the resources will be created."
type = string
default = "ap-northeast-2"
}
variable "availability_zones" {
description = "List of availability zones."
type = list(string)
default = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
}
variable "VpcBlock" {
description = "CIDR block for the VPC."
type = string
default = "192.168.0.0/16"
}
variable "public_subnet_blocks" {
description = "List of CIDR blocks for the public subnets."
type = list(string)
default = ["192.168.0.0/22", "192.168.4.0/22", "192.168.8.0/22"]
}
variable "private_subnet_blocks" {
description = "List of CIDR blocks for the private subnets."
type = list(string)
default = ["192.168.12.0/22", "192.168.16.0/22", "192.168.20.0/22"]
}
vpc.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~>6.5"
name = "${var.ClusterBaseName}-VPC"
cidr = var.VpcBlock
azs = var.availability_zones
enable_dns_support = true
enable_dns_hostnames = true
public_subnets = var.public_subnet_blocks
private_subnets = var.private_subnet_blocks
enable_nat_gateway = false
single_nat_gateway = true
one_nat_gateway_per_az = false
manage_default_network_acl = false
map_public_ip_on_launch = true
igw_tags = { "Name" = "${var.ClusterBaseName}-IGW" }
nat_gateway_tags = { "Name" = "${var.ClusterBaseName}-NAT" }
public_subnet_tags = {
"Name" = "${var.ClusterBaseName}-PublicSubnet"
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"Name" = "${var.ClusterBaseName}-PrivateSubnet"
"kubernetes.io/role/internal-elb" = "1"
}
tags = { "Environment" = "cloudneta-lab" }
}
eks.tf
provider "aws" {
region = var.TargetRegion
}
resource "aws_security_group" "node_group_sg" {
name = "${var.ClusterBaseName}-node-group-sg"
description = "Security group for EKS Node Group"
vpc_id = module.vpc.vpc_id
tags = { Name = "${var.ClusterBaseName}-node-group-sg" }
}
resource "aws_security_group_rule" "allow_ssh" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = [var.ssh_access_cidr, var.VpcBlock]
security_group_id = aws_security_group.node_group_sg.id
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 21.0"
name = var.ClusterBaseName
kubernetes_version = var.KubernetesVersion
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.public_subnets
enable_irsa = true
endpoint_public_access = true
endpoint_private_access = true
enabled_log_types = []
enable_cluster_creator_admin_permissions = true
eks_managed_node_groups = {
primary = {
name = "${var.ClusterBaseName}-1nd-node-group"
use_name_prefix = false
instance_types = ["${var.WorkerNodeInstanceType}"]
desired_size = var.WorkerNodeCount
max_size = var.WorkerNodeCount + 2
min_size = var.WorkerNodeCount - 1
disk_size = var.WorkerNodeVolumesize
subnets = module.vpc.public_subnets
key_name = "${var.KeyName}"
vpc_security_group_ids = [aws_security_group.node_group_sg.id]
labels = { tier = "primary" }
cloudinit_pre_nodeadm = [
{
content_type = "text/x-shellscript"
content = <<-EOT
#!/bin/bash
dnf update -y
dnf install -y tree bind-utils tcpdump nvme-cli links sysstat ipset htop
EOT
}
]
}
}
addons = {
coredns = { most_recent = true }
kube-proxy = { most_recent = true }
vpc-cni = {
most_recent = true
before_compute = true
configuration_values = jsonencode({
env = {
WARM_ENI_TARGET = "1"
}
})
}
}
tags = {
Environment = "cloudneta-lab"
Terraform = "true"
}
}
output.tf
output "configure_kubectl" {
description = "Configure kubectl: run this command to update your kubeconfig"
value = "aws eks --region ${var.TargetRegion} update-kubeconfig --name ${var.ClusterBaseName}"
}
배포가 완료되면 Outputs: 섹션에 configure_kubectl 값이 출력된다. 이것은 output.tf에서 정의한 출력 변수로, kubeconfig 업데이트에 필요한 AWS CLI 명령을 알려 준다.
Apply complete! Resources: 15 added, 0 changed, 0 destroyed.
Outputs:
configure_kubectl = "aws eks --region ap-northeast-2 update-kubeconfig --name myeks"
kubeconfig 설정
configure_kubectl은 Terraform 출력 변수명이지, 셸 명령이 아니다. 직접 치면 command not found 에러가 발생한다.
# 이렇게 하면 안 된다
configure_kubectl = "aws eks --region ap-northeast-2 update-kubeconfig --name myeks"
# 실행 결과
zsh: command not found: configure_kubectl
terraform output 명령으로 값을 추출해서 실행해야 한다.
# 출력 변수에서 명령 추출
terraform output -raw configure_kubectl
# 실행 결과
aws eks --region ap-northeast-2 update-kubeconfig --name myeks
# kubeconfig 업데이트
aws eks --region ap-northeast-2 update-kubeconfig --name myeks
kubeconfig 전체 내용
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
server: https://CFAB27981D87DF34CF9C43002AC10C64.gr7.ap-northeast-2.eks.amazonaws.com
name: arn:aws:eks:ap-northeast-2:988608581192:cluster/myeks
contexts:
- context:
cluster: arn:aws:eks:ap-northeast-2:988608581192:cluster/myeks
user: arn:aws:eks:ap-northeast-2:988608581192:cluster/myeks
name: arn:aws:eks:ap-northeast-2:988608581192:cluster/myeks
current-context: arn:aws:eks:ap-northeast-2:988608581192:cluster/myeks
kind: Config
preferences: {}
users:
- name: arn:aws:eks:ap-northeast-2:988608581192:cluster/myeks
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-2
- eks
- get-token
- --cluster-name
- myeks
- --output
- json
command: aws
context 이름이 ARN 형식이라 길고 불편하다. 짧은 이름으로 변경해 두면 편하다.
# context 이름 변경
kubectl config rename-context \
$(cat ~/.kube/config | grep current-context | awk '{print $2}') \
myeks
# 실행 결과
Context "arn:aws:eks:ap-northeast-2:988608581192:cluster/myeks" renamed to "myeks".
# 확인
kubectl config current-context
# 실행 결과
myeks
# 기본 네임스페이스 설정
kubens default
기본 정보 확인
EKS 관리 콘솔
Overview
API server endpoint와 OpenID Connect provider URL을 확인할 수 있다. OIDC Provider URL이 표시된다는 것은, 앞서 enable_irsa = true로 설정한 OIDC Provider가 정상적으로 생성되었다는 의미다. 이 URL이 있어야 IRSA를 통한 파드 단위 IAM 권한 부여가 가능하다.

Compute
Node groups를 클릭하면 상세 정보를 확인할 수 있다. Terraform에서 설정한 tier = primary 레이블이 노드 그룹에 적용되어 있다. 이후 2번째 관리형 노드 그룹을 추가 배포할 때 라벨로 구분하기 위한 준비다.

Networking
서비스 IPv4 범위(10.100.0.0/16), 서브넷 목록, 클러스터 엔드포인트 접근 설정(public and private)을 확인할 수 있다.

Add-ons
VPC CNI addon을 클릭하면 설정된 configuration values를 확인할 수 있다. Terraform에서 지정한 WARM_ENI_TARGET 설정이 반영되어 있다.

kubectl 확인
# 클러스터 정보
kubectl cluster-info
# 실행 결과
Kubernetes control plane is running at https://CFAB27981D87DF34CF9C43002AC10C64.gr7.ap-northeast-2.eks.amazonaws.com
CoreDNS is running at https://CFAB27981D87DF34CF9C43002AC10C64.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
# 노드 정보 (인스턴스 타입, 용량 유형, 가용 영역 확인)
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
# 실행 결과
NAME STATUS ROLES AGE VERSION INSTANCE-TYPE CAPACITYTYPE ZONE
ip-192-168-0-216.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f t3.medium ON_DEMAND ap-northeast-2a
ip-192-168-7-38.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f t3.medium ON_DEMAND ap-northeast-2b
ip-192-168-9-174.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f t3.medium ON_DEMAND ap-northeast-2c
3개 노드가 각각 다른 가용 영역(AZ)에 하나씩 배포되어 있다.
# 노드 라벨 확인 (tier 라벨 필터)
kubectl get nodes -L=tier
# 실행 결과
NAME STATUS ROLES AGE VERSION TIER
ip-192-168-0-216.ap-northeast-2.compute.internal Ready <none> 22h v1.34.4-eks-f69f56f primary
ip-192-168-7-38.ap-northeast-2.compute.internal Ready <none> 22h v1.34.4-eks-f69f56f primary
ip-192-168-9-174.ap-northeast-2.compute.internal Ready <none> 22h v1.34.4-eks-f69f56f primary
모든 노드에 tier=primary 라벨이 붙어 있다. Terraform의 노드 그룹 설정에서 지정한 라벨이 정상 적용된 것이다.
kubectl get node -v=6 출력
kubectl get node -v=6
# 실행 결과
I0325 23:17:26.462115 38921 loader.go:405] Config loaded from file: /Users/eraser/.kube/config
I0325 23:17:26.462345 38921 envvar.go:172] "Feature gate default state" feature="ClientsPreferCBOR" enabled=false
I0325 23:17:26.462352 38921 envvar.go:172] "Feature gate default state" feature="InOrderInformers" enabled=true
I0325 23:17:26.462355 38921 envvar.go:172] "Feature gate default state" feature="InOrderInformersBatchProcess" enabled=true
I0325 23:17:26.462357 38921 envvar.go:172] "Feature gate default state" feature="InformerResourceVersion" enabled=true
I0325 23:17:26.462358 38921 envvar.go:172] "Feature gate default state" feature="WatchListClient" enabled=true
I0325 23:17:26.462360 38921 envvar.go:172] "Feature gate default state" feature="ClientsAllowCBOR" enabled=false
I0325 23:17:27.024329 38921 round_trippers.go:632] "Response" verb="GET" url="https://CFAB27981D87DF34CF9C43002AC10C64.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500" status="200 OK" milliseconds=558
NAME STATUS ROLES AGE VERSION
ip-192-168-0-216.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f
ip-192-168-7-38.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f
ip-192-168-9-174.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f
kubectl get node --show-labels 전체 출력
kubectl get node --show-labels
# 실행 결과
NAME STATUS ROLES AGE VERSION LABELS
ip-192-168-0-216.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t3.medium,beta.kubernetes.io/os=linux,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup-image=ami-0041be04b53631868,eks.amazonaws.com/nodegroup=myeks-1nd-node-group,eks.amazonaws.com/sourceLaunchTemplateId=lt-020ba32d69dc74a74,eks.amazonaws.com/sourceLaunchTemplateVersion=1,failure-domain.beta.kubernetes.io/region=ap-northeast-2,failure-domain.beta.kubernetes.io/zone=ap-northeast-2a,k8s.io/cloud-provider-aws=5553ae84a0d29114870f67bbabd07d44,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-192-168-0-216.ap-northeast-2.compute.internal,kubernetes.io/os=linux,node.kubernetes.io/instance-type=t3.medium,tier=primary,topology.k8s.aws/zone-id=apne2-az1,topology.kubernetes.io/region=ap-northeast-2,topology.kubernetes.io/zone=ap-northeast-2a
ip-192-168-7-38.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t3.medium,beta.kubernetes.io/os=linux,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup-image=ami-0041be04b53631868,eks.amazonaws.com/nodegroup=myeks-1nd-node-group,eks.amazonaws.com/sourceLaunchTemplateId=lt-020ba32d69dc74a74,eks.amazonaws.com/sourceLaunchTemplateVersion=1,failure-domain.beta.kubernetes.io/region=ap-northeast-2,failure-domain.beta.kubernetes.io/zone=ap-northeast-2b,k8s.io/cloud-provider-aws=5553ae84a0d29114870f67bbabd07d44,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-192-168-7-38.ap-northeast-2.compute.internal,kubernetes.io/os=linux,node.kubernetes.io/instance-type=t3.medium,tier=primary,topology.k8s.aws/zone-id=apne2-az2,topology.kubernetes.io/region=ap-northeast-2,topology.kubernetes.io/zone=ap-northeast-2b
ip-192-168-9-174.ap-northeast-2.compute.internal Ready <none> 23h v1.34.4-eks-f69f56f beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t3.medium,beta.kubernetes.io/os=linux,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup-image=ami-0041be04b53631868,eks.amazonaws.com/nodegroup=myeks-1nd-node-group,eks.amazonaws.com/sourceLaunchTemplateId=lt-020ba32d69dc74a74,eks.amazonaws.com/sourceLaunchTemplateVersion=1,failure-domain.beta.kubernetes.io/region=ap-northeast-2,failure-domain.beta.kubernetes.io/zone=ap-northeast-2c,k8s.io/cloud-provider-aws=5553ae84a0d29114870f67bbabd07d44,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-192-168-9-174.ap-northeast-2.compute.internal,kubernetes.io/os=linux,node.kubernetes.io/instance-type=t3.medium,tier=primary,topology.k8s.aws/zone-id=apne2-az3,topology.kubernetes.io/region=ap-northeast-2,topology.kubernetes.io/zone=ap-northeast-2c
# 파드 정보
kubectl get pod -A
# 실행 결과
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system aws-node-mgwdh 2/2 Running 0 23h
kube-system aws-node-nczbm 2/2 Running 0 23h
kube-system aws-node-xtphq 2/2 Running 0 23h
kube-system coredns-d487b6fcb-hz52x 1/1 Running 0 23h
kube-system coredns-d487b6fcb-kmj4x 1/1 Running 0 23h
kube-system kube-proxy-8pqgv 1/1 Running 0 23h
kube-system kube-proxy-f2c4w 1/1 Running 0 23h
kube-system kube-proxy-p4gqv 1/1 Running 0 23h
aws-node(VPC CNI), coredns, kube-proxy가 각각 데몬셋/디플로이먼트로 동작하고 있다. aws-node은 2/2인데, VPC CNI 컨테이너와 aws-network-policy-agent 컨테이너가 함께 실행되기 때문이다.
# PodDisruptionBudget 확인
kubectl get pdb -n kube-system
# 실행 결과
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
coredns N/A 1 1 23h
# addon 확인
eksctl get addon --cluster myeks
# 실행 결과
NAME VERSION STATUS ISSUES IAMROLE UPDATE AVAILABLE CONFIGURATION VALUES NAMESPACE
coredns v1.13.2-eksbuild.3 ACTIVE 0 kube-system
kube-proxy v1.34.5-eksbuild.2 ACTIVE 0 kube-system
vpc-cni v1.21.1-eksbuild.5 ACTIVE 0 {"env":{"WARM_ENI_TARGET":"1"}} kube-system
vpc-cni addon의 CONFIGURATION VALUES 컬럼에서 WARM_ENI_TARGET 설정이 반영된 것을 확인할 수 있다.
aws eks describe-nodegroup 전체 출력
{
"nodegroup": {
"nodegroupName": "myeks-1nd-node-group",
"nodegroupArn": "arn:aws:eks:ap-northeast-2:988608581192:nodegroup/myeks/myeks-1nd-node-group/24ce902a-549c-7e49-dfdd-3b6128f30175",
"clusterName": "myeks",
"version": "1.34",
"releaseVersion": "1.34.4-20260317",
"createdAt": "2026-03-24T23:51:55.574000+09:00",
"modifiedAt": "2026-03-25T23:12:13.917000+09:00",
"status": "ACTIVE",
"capacityType": "ON_DEMAND",
"scalingConfig": {
"minSize": 2,
"maxSize": 5,
"desiredSize": 3
},
"instanceTypes": [
"t3.medium"
],
"subnets": [
"subnet-0264e2a4b63b5ee46",
"subnet-03a7ebc904d58cb97",
"subnet-0a4dc4d5a526507fd"
],
"amiType": "AL2023_x86_64_STANDARD",
"nodeRole": "arn:aws:iam::988608581192:role/myeks-1nd-node-group-eks-node-group-20260324142907655200000006",
"labels": {
"tier": "primary"
},
"resources": {
"autoScalingGroups": [
{
"name": "eks-myeks-1nd-node-group-24ce902a-549c-7e49-dfdd-3b6128f30175"
}
]
},
"health": {
"issues": []
},
"updateConfig": {
"maxUnavailablePercentage": 33
},
"launchTemplate": {
"name": "primary-20260324145146816000000002",
"version": "1",
"id": "lt-020ba32d69dc74a74"
},
"tags": {
"Terraform": "true",
"Environment": "cloudneta-lab",
"Name": "myeks-1nd-node-group"
}
}
}
워커 노드 IAM 역할
EC2 콘솔에서 워커 노드를 확인하면, 3개 노드가 각각 다른 AZ에 t3.medium 타입으로 동작하고 있다.

워커 노드의 IAM 역할을 확인하면, AmazonEKS_CNI_Policy가 연결되어 있다.

이 정책에는 AssignPrivateIpAddresses, AttachNetworkInterface, CreateNetworkInterface, DeleteNetworkInterface 등 EC2 네트워크 API 권한이 포함되어 있다.

이것이 바로 이번 주차의 핵심 주제인 AWS VPC CNI가 동작하기 위해 필요한 권한이다. aws-node(VPC CNI) 데몬셋이 이 권한으로 ENI를 생성/삭제하고, Secondary IP를 할당/해제한다.
현재는 이 권한이 노드 IAM Role에 직접 붙어 있다. 앞서 IRSA를 설명할 때 언급한 것처럼, 이 구조에서는 같은 노드의 모든 파드가 동일한 권한에 접근할 수 있다. IRSA를 적용하면 aws-node 서비스 어카운트에만 이 권한을 부여하고 노드 Role에서는 제거할 수 있지만, 이번 실습에서는 기본 구성 그대로 진행한다.
보안 그룹
EC2 콘솔에서 워커 노드의 보안 그룹을 확인하면, 192.168.0.0/16(VPC CIDR 전체)에서 모든 트래픽을 허용하는 인바운드 규칙이 있다. Terraform의 aws_security_group_rule 리소스에서 설정한 것으로, VPC 내부 통신이 제한 없이 가능하도록 한 것이다.

정리
| 항목 | 1주차 | 2주차 |
|---|---|---|
| 서브넷 CIDR | /24 (약 254 IP) | /22 (약 1,022 IP) |
| VPC CNI 설정 | 기본값 | WARM_ENI_TARGET=1 명시 |
| IRSA | 기본값(암묵적 true) |
enable_irsa = true 명시 |
| Terraform output | 없음 | configure_kubectl 추가 |
VPC CNI 실습에서는 많은 파드를 생성하고, 다양한 IP 할당 모드를 테스트해야 한다. 서브넷 확대와 CNI 설정 옵션 추가는 이를 위한 준비다. IRSA 활성화는 보안 모범 사례를 따르는 첫 단계로, 이후 AWS Load Balancer Controller 등을 배포할 때 직접 활용하게 된다.
댓글남기기