1. EKS = AWS 리소스의 집합
Amazon EKS(Elastic Kubernetes Service)는 AWS의 완전관리형 Kubernetes 서비스다. 컨트롤 플레인을 AWS가 운영하므로 사용자는 워크로드에 집중하면 된다.
완전관리형이란? Kubernetes 컨트롤 플레인은 API 서버, etcd, 스케줄러, 컨트롤러 매니저로 구성된다. 직접 구축하면 etcd 클러스터링, API 서버 이중화, 인증서 로테이션 같은 운영 부담이 상당하다. EKS는 이를 대신 처리하고 99.95% SLA를 보장한다. 대신 시간당 $0.10의 클러스터 비용이 발생한다.
직접 구축(kubeadm 등)과 비교하면 EKS가 대신하는 범위가 명확하다.
| etcd 3대 클러스터링 + 백업/복구 | 3 AZ 분산, 자동 백업 |
| API 서버 이중화 + LB 구성 | 최소 2대 + NLB 자동 |
| 인증서 발급·갱신 (kubeadm은 1년 만료) | 자동 로테이션 |
| 노드 조인 토큰 관리 (kubeadm join) | UserData로 자동 조인 |
| CNI 별도 설치 (Calico, Flannel 등) | VPC CNI 기본 내장 |
| 버전 업그레이드 (순서대로 수동) | 콘솔/CLI에서 한 번에 |
그런데 "완전관리형"이라는 말 뒤에 가려진 게 있다. EKS 클러스터를 하나 띄우면 VPC, EC2, ENI, NLB, IAM, Security Group, Route 53 같은 AWS 리소스가 함께 생성된다. 결국 AWS 리소스를 잘 엮어놓은 것에 가깝다.
아키텍처 개요
EKS는 컨트롤 플레인과 데이터 플레인 두 영역으로 나뉜다.
영역 소유 구성 요소
| 컨트롤 플레인 | AWS Managed VPC | API 서버(최소 2개, 2+ AZ), etcd(3개, 3 AZ), NLB — 멀티 AZ 분산 |
| 데이터 플레인 | Customer VPC | 워커 노드(EC2), EKS Owned ENI, 파드 네트워크 |
컨트롤 플레인은 AWS 소유의 별도 VPC에서 돌아간다. 고객 콘솔에서는 보이지 않는다. API 서버 인스턴스(CPI)는 최소 2개가 서로 다른 AZ에, etcd는 3개 AZ에 분산 배치되어 있고 장애 시 자동 교체된다.
CPI(Control Plane Instance)란? API 서버가 돌아가는 EC2 인스턴스다. AWS 문서에서는 "at least 2 API server instances"라고만 표현하는데, 실제로는 3개 AZ에 걸쳐 배치된다. 고객이 직접 접근할 수 없고 AWS가 패치·교체·스케일링을 자동으로 처리한다.
두 영역을 잇는 것이 EKS Owned ENI다. 고객 VPC에 있지만 연결된 인스턴스는 AWS 소유다. 이 구조가 EKS 네트워킹의 핵심이다.
배포 방식
이번 실습에서는 Terraform을 사용했다. terraform-aws-modules/eks/aws 공식 모듈 기반이고 eksctl처럼 CloudFormation을 거치지 않아 배포가 더 빠르다.
2. 배포 — terraform apply의 결과
실습 환경
항목 값
| 리전 | ap-northeast-2 (서울) |
| EKS 버전 | v1.32 |
| 플랫폼 버전 | eks.x |
| 배포 도구 | Terraform 1.14.6 |
| EKS 모듈 | terraform-aws-modules/eks/aws v21.15.1 |
| VPC 모듈 | terraform-aws-modules/vpc/aws v6.6.0 |
| 노드 AMI | Amazon Linux 2023 (AL2023) |
| 노드 타입 | t3.medium × 2 (ON_DEMAND) |
| 컨테이너 런타임 | containerd 2.1.5 |
| 커널 | 6.12.x |
| cgroup | v2 (systemd) |
| 네트워크 | VPC CNI (aws-node) |
| 도메인 | hackjap.link |
실습 비용: EKS 클러스터 $0.10/hr + t3.medium × 2대 약 $0.084/hr = 시간당 약 $0.18. 12분 배포 + 2시간 실습 기준 약 $0.40. 실습 후 반드시 terraform destroy로 정리할 것.
배포 후 버전 확인:
# EKS 클러스터 버전
aws eks describe-cluster --name myeks --query 'cluster.{Version:version, PlatformVersion:platformVersion}' --output table
# kubectl 버전
kubectl version --short
# 노드 OS/커널 확인
kubectl get nodes -o wide
사전 준비
IAM 자격 증명과 SSH 키페어가 필요하다.
# IAM 자격 증명 확인
aws sts get-caller-identity
# SSH 키페어 생성 (이미 있으면 생략)
aws ec2 create-key-pair --key-name hackjap-keypair --query 'KeyMaterial' --output text > ~/.ssh/hackjap-keypair.pem
chmod 400 ~/.ssh/hackjap-keypair.pem
필수 도구 (macOS 기준):
brew install awscli kubernetes-cli helm
brew install tfenv && tfenv install 1.14.6 && tfenv use 1.14.6
# (권장) 편의 도구
brew install krew k9s kube-ps1 kubectx kubecolor
배포
# 실습 코드 다운로드
git clone https://github.com/gasida/aews.git && cd aews/1w
# 변수 지정
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
# 배포 (약 12분)
terraform init
terraform plan
terraform apply -auto-approve
# kubeconfig 설정
aws eks update-kubeconfig --region ap-northeast-2 --name myeks
kubectl config rename-context $(cat ~/.kube/config | grep current-context | awk '{print $2}') myeks
사용된 Terraform 모듈:
- terraform-aws-modules/vpc/aws (v6.6.0) — VPC
- terraform-aws-modules/eks/aws (v21.15.1) — EKS 클러스터 + 노드 그룹
배포 후 생긴 것
terraform apply 완료 후 AWS 콘솔에서 생성된 리소스를 확인한다. terraform state list로 Terraform이 관리하는 리소스 목록도 볼 수 있다.
# 클러스터 상태 확인
aws eks describe-cluster --name myeks --query 'cluster.{Status:status, Version:version, Endpoint:endpoint}' --output table
# 노드 상태 확인
kubectl get node -o wide
# NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE
# ip-192-168-2-xx.ap-northeast-2.compute.internal Ready <none> 5m v1.32.x 192.168.2.xx Amazon Linux 2023
# ip-192-168-3-xx.ap-northeast-2.compute.internal Ready <none> 5m v1.32.x 192.168.3.xx Amazon Linux 2023
# Terraform 리소스 목록 (일부)
terraform state list | head -10
# module.eks.aws_eks_cluster.this[0]
# module.eks.aws_eks_node_group.this["myeks-node-group"]
# module.vpc.aws_vpc.this[0]
# module.vpc.aws_subnet.public[0]
# ...
terraform apply 한 번에 아래 리소스가 만들어진다.
AWS 서비스 생성된 리소스 역할
| VPC | VPC, 서브넷, 라우팅 테이블, IGW | 네트워크 기반 |
| EC2 | 워커 노드 (t3.medium × 2) | 파드 실행 |
| Auto Scaling | ASG + 시작 템플릿 | 노드 그룹 스케일링 |
| ENI | EKS Owned ENI (2개) | 컨트롤 ↔ 데이터 플레인 통신 |
| ELB | NLB (API 서버용) | API 서버 부하분산 |
| IAM | 클러스터 역할, 노드 역할 | 권한·인증 |
| Security Group | 클러스터 SG, 노드 SG | 접근 제어 |
| EKS | 클러스터, 노드 그룹, Add-on 3종 | Kubernetes 오케스트레이션 |
| IAM | OIDC Provider | 파드 수준 AWS 권한 부여 (→ 이후 주차 심화) |
| CloudWatch | 로그 그룹 (선택) | 컨트롤 플레인 감사 로그 (→ 이후 주차 심화) |
3. AWS 리소스 탐색
terraform apply로 생긴 리소스를 AWS 콘솔과 CLI로 하나씩 열어본다. 각 리소스가 왜 필요하고 EKS 아키텍처에서 어떤 역할을 하는지 확인하는 게 이 섹션의 목표다.
3.1 VPC / Subnet
AWS 콘솔에서 VPC를 열면 myeks-VPC가 생성되어 있다.
VPC CIDR은 192.168.0.0/16이고 서브넷 3개가 각 AZ에 /24씩 할당됐다.
- 서브넷: ap-northeast-2a/2b/2c에 퍼블릭 서브넷 3개 (192.168.1.0/24, 192.168.2.0/24, 192.168.3.0/24)
- 라우팅 테이블: myeks-VPC-public이 서브넷 3개에 연결. IGW로 향하는 0.0.0.0/0 라우트 존재
- 네트워크 연결: myeks-IGW(Internet Gateway) 하나
- DNS 확인/호스트 이름: 둘 다 활성화됨 — Endpoint Access Private 모드의 필수 조건
이 VPC가 EKS 클러스터의 네트워크 기반이 된다.
고객 VPC (Customer VPC)
- Terraform VPC 모듈이 생성
- 워커 노드와 파드가 여기서 돌아간다
- 서브넷은 퍼블릭/프라이빗으로 나뉨
퍼블릭 서브넷 — 라우팅 테이블에 IGW(Internet Gateway)가 연결돼 있다. 노드에 공인 IP가 붙는다.
프라이빗 서브넷 — 인터넷 직접 접근 불가. Fully Private 클러스터에서 사용. NAT Gateway를 통한 아웃바운드만 가능하다.
왜 서브넷을 나누는가? EKS에서 퍼블릭 서브넷은 NLB나 인터넷 경유 트래픽이 필요한 리소스에 쓰이고, 프라이빗 서브넷은 워커 노드가 올라가는 곳이다. 보안 관점에서 노드를 프라이빗에 두면 외부에서 직접 접근할 수 없어 공격 표면이 줄어든다. 이번 실습에서는 편의상 퍼블릭에 노드를 뒀지만, 운영 환경에서는 프라이빗 서브넷이 표준이다.
보이지 않는 VPC: 컨트롤 플레인은 AWS Managed VPC에 있다. 고객 콘솔에선 보이지 않는다. API 서버와 etcd가 여기서 돌아가고 EKS Owned ENI로 고객 VPC에 연결된다.
EKS 서브넷 필수 조건
조건 설명
| 최소 2개 서브넷 | 서로 다른 AZ에 위치 (고가용성) |
| 사용 가능 IP 6개 이상 | ENI 프로비저닝에 필요. VPC CNI가 파드에 실제 IP를 할당하므로 넉넉하게 설계해야 한다 |
| VPC DNS 설정 | enableDnsSupport=true, enableDnsHostnames=true 필수 |
서브넷 태그
AWS 로드 밸런서 컨트롤러가 서브넷을 자동 인식하려면 태그가 필요하다. 없으면 LoadBalancer 타입 서비스 생성 시 서브넷을 못 찾아 실패한다.
태그 값 대상
| kubernetes.io/cluster/<cluster-name> | shared 또는 owned | 모든 EKS 서브넷 |
| kubernetes.io/role/elb | 1 | 퍼블릭 서브넷 — 외부향 LB 배치 |
| kubernetes.io/role/internal-elb | 1 | 프라이빗 서브넷 — 내부 LB 배치 |
환경별 서브넷 구성 패턴
환경 구성 서브넷 수 비고
| 학습/개발 | 퍼블릭 × 3 AZ | 3개 | 이번 실습 구성 |
| 일반 운영 | 퍼블릭 × 3 + 프라이빗 × 3 | 6개 | 가장 보편적 |
| 엔터프라이즈 | 퍼블릭 × 3 + 앱 × 3 + DB × 3 | 9개 | 3-tier 분리 |
이번 실습은 퍼블릭 서브넷 3개(/24)만 사용한다. 운영 환경에서는 노드를 프라이빗 서브넷에, NLB/ALB를 퍼블릭 서브넷에 배치하는 6개 구성이 표준이다.
생성 이후 변경 가능 여부
작업 가능 여부 비고
| 클러스터 서브넷 추가/제거 | 가능 | 최소 2개 AZ 유지 필요 |
| 노드 그룹 서브넷 변경 | 불가 | 노드 그룹 재생성 필요 (블루/그린) |
| VPC 자체 변경 | 불가 | 클러스터 재생성 필요 |
3.2 EC2 — 관리형 노드 그룹
EC2 콘솔에서 워커 노드 2개를 확인한다.
myeks-node-g... 이름으로 t3.medium 인스턴스 2개가 실행 중이다. 각각 ap-northeast-2b, 2c에 분산 배치되어 있고 퍼블릭 IP가 할당돼 있다. 상태 검사 3/3 통과.
ASG(Auto Scaling Group) 콘솔에서 스케일링 설정을 확인한다.
원하는 용량 2, 크기 조정 한도 1~4. 노드가 죽으면 ASG가 자동으로 새 인스턴스를 띄워서 desired 2를 유지한다.
시작 템플릿의 UserData가 핵심이다.
NodeConfig 리소스에 EKS 클러스터 정보가 전부 들어 있다:
- apiServerEndpoint — API 서버 주소
- certificateAuthority — 클러스터 CA 인증서 (base64)
- cidr: 10.100.0.0/16 — 서비스 CIDR
- maxPods: 17 — ENI/IP 기반 계산값
- clusterDNS: 10.100.0.10 — CoreDNS 서비스 IP
- --node-labels 플래그에 AMI ID, 노드그룹명, 시작 템플릿 ID 등 메타데이터
노드가 부팅되면 이 설정으로 kubelet이 자동으로 클러스터에 조인한다.
# 노드 그룹 정보
aws eks describe-nodegroup --cluster-name myeks --nodegroup-name myeks-node-group | jq
관리형 노드 그룹은 EC2 Auto Scaling Group을 EKS가 감싸서 관리하는 구조다.
관리형 vs 자체 관리형 vs Fargate: 워커 노드를 올리는 방법은 세 가지다. 관리형 노드 그룹은 ASG/시작 템플릿/AMI 업데이트를 AWS가 관리한다. 자체 관리형 노드 그룹은 ASG를 직접 만들어 EKS에 조인시키는 방식으로 커스터마이징 자유도가 높다. Fargate는 EC2 없이 파드 단위로 실행하는 서버리스 방식이다. 관리형이 운영 부담과 유연성의 균형이 가장 좋아서 대부분의 워크로드에서 쓰인다.
설정 값
| 인스턴스 타입 | t3.medium |
| AMI | AL2023 (Amazon Linux 2023) |
| Capacity Type | ON_DEMAND |
| Scaling | min 1 / max 4 / desired 2 |
| 컨테이너 런타임 | containerd 2.1.5 |
| cgroup | v2 (systemd) |
시작 템플릿(Launch Template):
- UserData에 EKS 노드 부트스트랩 설정이 들어 있다
- kubelet과 containerd가 자동 설정된다
- 클러스터 CA 인증서와 API 엔드포인트도 주입된다
maxPods가 왜 17인가
t3.medium은 ENI 3개를 쓸 수 있고 ENI당 IP는 6개다. 공식 계산식:
(ENI 3개 × (IP 6개 - 1)) + 2 = 17
- -1은 ENI 자체의 프라이머리 IP
- +2는 호스트 네트워크를 쓰는 파드(aws-node, kube-proxy)가 파드 IP를 소모하지 않는 것을 반영
AWS VPC CNI는 파드에 VPC의 실제 IP를 할당한다. overlay가 아니다. 그래서 인스턴스 타입에 따라 maxPods가 달라진다.
VPC CNI vs Overlay 네트워크: 일반적인 Kubernetes CNI(Calico, Flannel 등)는 overlay 네트워크를 만든다. 파드 IP는 가상이고 VXLAN이나 IP-in-IP 캡슐화를 거쳐야 외부와 통신한다. AWS VPC CNI는 다르다. 파드에 VPC 서브넷의 실제 IP를 직접 할당한다. 캡슐화 없이 VPC 네이티브로 통신하니 레이턴시가 낮고 보안 그룹을 파드 단위로 적용할 수 있다. 대신 서브넷 IP를 소모하기 때문에 CIDR 설계를 넉넉하게 해야 한다.
노드 내부 확인
# 노드 IP 확인
NODE1=<public-ip-1>
NODE2=<public-ip-2>
# SSH 접속
ssh -i ~/.ssh/id_rsa ec2-user@$NODE1
# OS 확인
hostnamectl
# → Amazon Linux 2023, kernel 6.12.x, t3.medium
# 컨테이너 런타임
nerdctl info
# → containerd 2.1.5, cgroup v2, systemd
# kubelet 설정 핵심
cat /etc/kubernetes/kubelet/config.json | jq '{maxPods, cgroupDriver, evictionHard, serializeImagePulls, protectKernelDefaults}'
kubelet 주요 설정:
설정 값 의미
| maxPods | 17 | ENI/IP 기반 |
| evictionHard | 메모리 100Mi↓, 디스크 10%↓ | 리소스 부족 시 파드 강제 축출 |
| serializeImagePulls | false | 이미지 병렬 다운로드 |
| protectKernelDefaults | true | kubelet이 커널 파라미터를 건드리지 않음 |
| serverTLSBootstrap | true | TLS 인증서 자동 발급 |
3.3 ENI — EKS Owned ENI의 정체
EKS에서 가장 독특한 리소스다.
ENI(Elastic Network Interface)란? VPC 안에서 가상 네트워크 카드 역할을 하는 AWS 리소스다. EC2 인스턴스에 붙이면 해당 서브넷의 프라이빗 IP를 받고 보안 그룹도 적용된다. 보통은 EC2를 만들면 자동으로 하나 생기는데, EKS에서는 AWS가 고객 VPC에 ENI를 직접 꽂아서 자기네 VPC의 API 서버와 연결하는 특별한 용도로 쓴다. Cross-account ENI attachment라고 볼 수 있다.
속성 설명
| 위치 | 고객 VPC 서브넷 안에 있다 |
| 소유 | ENI 자체는 고객 계정에 보인다 |
| 연결 대상 | AWS Managed VPC의 API 서버(CPI) |
| 개수 | 클러스터 서브넷에 각 1개씩 기본 2개 (스케일링/업그레이드 시 추가 가능) |
| 보안 그룹 | 클러스터 SG가 붙어 있다 |
콘솔에서 보면 소유자가 둘이다
ENI 상세 정보를 열어보면 "소유자"와 "인스턴스 소유자"가 다른 계정으로 나온다. 이것이 Cross-Account ENI의 핵심이다.
필드 값 의미
| 소유자 | 본인 계정 ID | ENI 리소스가 고객 VPC에 위치 |
| 요청자 ID / 인스턴스 소유자 | AWS EKS 서비스 계정 | 연결된 인스턴스(API 서버)가 AWS Managed VPC에 있음 |
| 인스턴스 ID | 비어있음 | API 서버 인스턴스는 고객 콘솔에서 안 보임 |
| Description | Amazon EKS <cluster-name> | EKS Owned ENI 식별자 |
내 VPC에 꽂혀있는 네트워크 카드인데, 케이블 반대쪽은 AWS Managed VPC의 API 서버로 연결"된 구조다.
왜 필요한가
컨트롤 플레인(AWS Managed VPC)과 데이터 플레인(고객 VPC)은 다른 VPC에 있다. 이 두 세계를 잇는 건 EKS Owned ENI뿐이다.
통신 경로:
- API 서버 → kubelet: kubectl logs나 kubectl exec를 실행하면 API 서버가 이 ENI를 통해 노드의 kubelet(10250 포트)에 접근한다
- 노드 → API 서버: Public+Private 또는 Private 모드에서 kubelet, kube-proxy, aws-node가 ENI의 프라이빗 IP로 API 서버에 접근한다
Endpoint Access 모드에 따라 노드→API 경로가 달라진다:
모드 노드 → API 서버 경로 ss -tnp Peer Address
| Public | 인터넷 → NLB 퍼블릭 IP | 43.x.x.x:443 |
| Public+Private | VPC 내부 → EKS Owned ENI | 192.168.x.x:443 |
| Private | VPC 내부 → EKS Owned ENI | 192.168.x.x:443 |
Public 모드에서는 EKS Owned ENI가 존재하지만, 노드→API 방향에서는 사용되지 않는다 (API→노드 방향의 kubectl exec, kubectl logs에서만 사용). 4절에서 모드별 차이를 실습으로 확인한다.
# 노드에서 확인 — Peer Address로 현재 모드 판별
ssh ec2-user@$NODE1 sudo ss -tnp | grep kubelet
# Public 모드: Peer Address = 43.x.x.x:443 (NLB 퍼블릭 IP)
# Public+Private 모드: Peer Address = 192.168.x.x:443 (EKS Owned ENI IP)
# kubectl exec 실행 시 API → 노드 방향 연결 확인
kubectl exec -it <pod> -- bash # 다른 터미널에서 실행
ssh ec2-user@$NODE1 sudo ss -tnp | grep kubelet
# → 10250 포트로 들어오는 연결 확인 (API → kubelet, 모든 모드에서 ENI 경유)
Cross-Account ENI는 EKS만의 패턴이 아니다. RDS, Lambda(VPC 연결), EFS, VPC Endpoint 등도 고객 VPC에 ENI를 생성한다. 차이점은 이들은 같은 VPC 안에서의 연결인 반면, EKS Owned ENI는 서로 다른 VPC(AWS Managed VPC ↔ 고객 VPC)를 잇는 Cross-VPC 연결이라는 점이다.
3.4 Security Group — 클러스터 SG, 노드 SG
EKS를 배포하면 보안 그룹이 자동으로 만들어진다.
왜 보안 그룹이 두 개인가? 클러스터 SG는 EKS가 만들고 관리하는 것으로, 컨트롤 플레인과 노드 사이의 통신을 보장한다. 건드리면 클러스터가 깨질 수 있으니 함부로 수정하면 안 된다. 노드 SG는 사용자가 추가로 붙이는 것으로, SSH 접근이나 외부 서비스 노출 같은 커스텀 정책을 여기에 건다. 관심사를 분리해서 EKS 핵심 통신과 사용자 정책이 서로 영향을 주지 않게 한 구조다.
클러스터 보안 그룹 (Cluster SG)
- EKS가 자동 생성
- EKS Owned ENI에 적용
- 컨트롤 플레인 ↔ 노드 간 통신을 전부 허용
- 노드도 이 SG에 포함되어 양방향 통신이 보장된다
노드 보안 그룹 (Node SG)
- Terraform에서 추가로 설정하는 보안 그룹
- SSH 접근이나 NodePort 서비스 접근 용도
- 이 실습에서는 자기 IP에서만 SSH/NodePort를 열었다
# 노드 보안 그룹 확인
aws ec2 describe-security-groups --filters "Name=tag:Name,Values=myeks-node-group-sg" --query 'SecurityGroups[*].IpPermissions' --output text
3.5 IAM — 역할과 인증 흐름
EKS에서 IAM은 인프라 권한과 Kubernetes 인증 두 가지를 맡는다.
왜 IAM으로 인증하는가? 바닐라 Kubernetes는 X.509 인증서나 서비스 어카운트 토큰으로 인증한다. AWS 환경에서는 IAM이 이미 모든 사용자와 서비스의 신원을 관리하고 있다. EKS는 이를 활용해서 "AWS에서 누구인지"와 "Kubernetes에서 무엇을 할 수 있는지"를 연결한다. 별도의 인증서 발급 인프라 없이 IAM의 MFA, 조건부 정책 같은 보안 기능을 그대로 쓸 수 있다.
클러스터 역할 (Cluster IAM Role)
- EKS 서비스 자체가 쓰는 역할
- ENI 생성, CloudWatch 로그 전송, 보안 그룹 관리 등
- Trust: eks.amazonaws.com
노드 역할 (Node IAM Role)
- 워커 노드(EC2)가 쓰는 역할
- ECR 이미지 풀, EKS API 통신, CloudWatch 지표 전송
- Trust: ec2.amazonaws.com
인증 흐름: kubectl → IAM → Kubernetes
EKS는 인증서가 아닌 IAM 기반 인증을 쓴다. kubeconfig를 열어보면:
users:
- name: myeks
user:
exec:
command: aws
args:
- eks
- get-token
- --cluster-name
- myeks
- --region
- ap-northeast-2
kubectl 요청 처리 과정:
- aws eks get-token이 실행되고 IAM 자격증명으로 STS 토큰을 발급받는다
- 이 토큰이 Bearer Token으로 API 서버에 전달된다
- API 서버는 AWS IAM Authenticator를 거쳐 IAM 엔티티 → Kubernetes 사용자 매핑을 한다
- 이후 Kubernetes RBAC으로 인가 처리
# 토큰 발급 확인
aws eks get-token --cluster-name myeks --region ap-northeast-2 | jq
# 인증 과정 로그 확인 (-v=6)
kubectl get node -v=6
# → Config loaded from file: /root/.kube/config
# → GET https://<api-endpoint>/api/v1/nodes 200 OK
kubelet도 같은 구조다. /var/lib/kubelet/kubeconfig를 보면 동일한 IAM 기반 인증을 쓴다.
파드 수준 IAM: OIDC Provider
EKS 클러스터를 만들면 OIDC Provider도 자동으로 생성된다. 이를 통해 파드에 최소 권한의 IAM 역할을 부여할 수 있다 (IRSA 또는 Pod Identity). 노드 역할에 모든 권한을 몰아주는 것보다 훨씬 안전하다. 파드 수준 IAM은 이후 주차에서 다룬다.
3.6 ELB — NLB와 Hyperplane
API 서버 앞에는 NLB(Network Load Balancer)가 붙어 있다.
# API 엔드포인트 확인
APIDNS=$(aws eks describe-cluster --name myeks | jq -r .cluster.endpoint | cut -d '/' -f 3)
dig +short $APIDNS
# → 퍼블릭 IP 2개 (NLB의 AZ별 IP)
# IP 소유자 확인
curl -s ipinfo.io/<ip>
# → "org": "AS16509 Amazon.com, Inc."
# API 서버 접근 테스트
curl -sk https://$APIDNS/version
왜 ALB가 아니라 NLB인가
비교 ALB NLB
| 동작 계층 | L7 (HTTP/HTTPS) | L4 (TCP/UDP) |
| 아키텍처 | VM 기반 | Hyperplane (메모리 기반) |
| 성능 | 상대적 낮음 | 초당 수억 연결 |
| 레이턴시 | 수십~수백 ms | 수십 ms 미만 |
| 헬스체크 최소 | ~10초 | ~20초 |
Kubernetes API는 HTTPS/gRPC 통신이라 L4에서 처리하는 게 맞고, NLB의 Hyperplane 아키텍처 덕분에 메모리 기반 처리로 지연이 극히 낮다. 워밍업 없이 즉시 확장되니 노드 100대가 동시에 부팅해도 문제없다.
Hyperplane이란? AWS 내부의 네트워크 가상화 플랫폼이다. NLB뿐 아니라 NAT Gateway, PrivateLink, VPC Endpoint도 Hyperplane 위에서 동작한다. 사용자가 직접 제어할 수 있는 리소스는 아니고, NLB의 고성능을 가능하게 하는 AWS 내부 기술이다.
4. Endpoint Access와 DNS
EKS 네트워크 설계에서 가장 중요한 결정 중 하나다. API 서버 엔드포인트를 어떻게 노출할 것인가?
3가지 모드가 있는데 단순히 "퍼블릭이냐 프라이빗이냐"가 아니다. Route 53 Private Hosted Zone이 생기느냐, VPC Endpoint가 필요하냐까지 달라지는 결정이다.
왜 이게 중요한가? Endpoint Access 모드는 보안과 비용 두 축에 영향을 준다. Public 모드는 설정이 간단하지만 노드의 API 트래픽이 인터넷을 거친다. Public+Private은 노드 트래픽을 VPC 내부로 돌리면서도 외부 접근을 유지한다. Private은 가장 안전하지만 VPC Endpoint(개당 월 $7~10)가 여러 개 필요하고 VPN/bastion 인프라도 갖춰야 한다. 모드를 바꾸면 DNS 응답부터 보안 그룹까지 여러 리소스가 연쇄적으로 바뀌니 초기에 잘 골라야 한다.
4.1 세 가지 모드 비교
모드 외부 → API 노드 → API API → 노드 추가 AWS 리소스
| Public | 퍼블릭 IP (인터넷) | 퍼블릭 IP (인터넷) | EKS Owned ENI | 없음 |
| Public + Private | 퍼블릭 IP (인터넷) | 프라이빗 IP (ENI) | EKS Owned ENI | Private Hosted Zone |
| Private | 불가 (VPC 내부만) | 프라이빗 IP (ENI) | EKS Owned ENI | Private Hosted Zone + VPC Endpoints |
4.2 Public 모드
가장 단순한 구성이다. 이 실습에서 기본으로 배포되는 모드다.
사용자 kubectl → (인터넷) → NLB → API 서버
워커 노드 → (인터넷) → NLB → API 서버
API 서버 → (EKS Owned ENI) → 워커 노드 kubelet
- 설정이 간편하고 어디서든 kubectl을 쓸 수 있다
- 대신 API 서버가 인터넷에 그대로 노출된다
- 학습이나 개발 환경용
# endpoint 설정 확인
aws eks describe-cluster --name myeks | jq '.cluster.resourcesVpcConfig | {endpointPublicAccess, endpointPrivateAccess}'
# → endpointPublicAccess: true, endpointPrivateAccess: false
# 노드의 kubelet이 퍼블릭 IP로 API 서버에 연결 중
ssh ec2-user@$NODE1 sudo ss -tnp | grep kubelet
# → Peer Address가 퍼블릭 IP (3.37.x.x 등)
4.3 Public + Private 모드
실무에서 가장 많이 쓰는 구성이다.
사용자 kubectl → (인터넷) → NLB → API 서버
워커 노드 → (프라이빗, EKS Owned ENI) → API 서버
API 서버 → (EKS Owned ENI) → 워커 노드 kubelet
핵심: 같은 도메인인데 DNS 응답이 다르다
이 모드로 전환하면 AWS가 Route 53 Private Hosted Zone을 자동으로 만든다. 같은 API 엔드포인트 도메인이라도:
질의 위치 DNS 응답 경유
| VPC 내부 (노드) | 프라이빗 IP (EKS Owned ENI) | VPC 내부 직접 |
| VPC 외부 (로컬 PC) | 퍼블릭 IP (NLB) | 인터넷 |
이 동작이 되려면 VPC에서 enableDnsSupport와 enableDnsHostnames가 켜져 있어야 한다.
eks.tf의 endpoint 설정을 변경한 뒤 terraform apply를 실행한다(약 7분 소요). 전환이 진행되는 동안 노드에서 dig를 반복 실행하면
DNS 응답이 퍼블릭 IP에서 프라이빗 IP로 바뀌는 순간을 포착할 수 있다.
전환 직후에는 기존 TCP 세션이 아직 퍼블릭 IP를 물고 있기 때문에, ss -tnp로 확인하면 Peer Address가 여전히 퍼블릭이다. kube-proxy와 aws-node를 rollout restart하고, kubelet도 재시작해야 비로소 프라이빗 IP로 전환되는 것을 확인할 수 있다.
Terraform 코드 변경:
endpoint_public_access = true
endpoint_private_access = true
endpoint_public_access_cidrs = [var.ssh_access_cidr] # 소스 IP 제한
전환 후 DNS 변화 확인:
# VPC 외부 (로컬)
dig +short $APIDNS
# → 3.37.98.54, 3.39.80.185 (퍼블릭)
# VPC 내부 (노드)
ssh ec2-user@$NODE1 dig +short $APIDNS
# → 192.168.2.185, 192.168.1.202 (프라이빗 = EKS Owned ENI IP)
모드를 전환해도 이미 연결된 kubelet, kube-proxy, aws-node의 TCP 세션은 퍼블릭 IP를 계속 쓴다. 프라이빗 IP로 바꾸려면 컴포넌트를 재시작해야 한다.
# kube-proxy
kubectl rollout restart ds/kube-proxy -n kube-system
# aws-node
kubectl rollout restart ds/aws-node -n kube-system
# kubelet — 노드에서 직접
for i in $NODE1 $NODE2; do
ssh ec2-user@$i sudo systemctl restart kubelet
done
# 확인 — Peer Address가 프라이빗 IP로 바뀜
ssh ec2-user@$NODE1 sudo ss -tnp | grep -v ssh
4.4 Private 모드
보안이 가장 강한 구성이다. 금융이나 의료처럼 규제가 엄격한 환경에서 쓴다.
사용자 kubectl → VPC 안(bastion/VPN)에서만 접근
워커 노드 → (프라이빗, EKS Owned ENI) → API 서버
API 서버 → (EKS Owned ENI) → 워커 노드 kubelet
- API 서버가 인터넷에 아예 보이지 않는다
- 외부에서 DNS 질의 자체가 되지 않는다
- VPN이나 bastion이 반드시 필요하다
- VPC Endpoint도 추가로 필요하다: ECR(dkr, api), S3, STS, CloudWatch Logs, EC2 등
Fully Private 클러스터를 배포하면(cd eks-private && terraform apply, 약 16분 소요) 로컬에서 kubectl을 실행했을 때 i/o timeout이 발생한다. API 서버가 인터넷에 노출되지 않기 때문이다. bastion에 SSH로 접속한 뒤 kubectl을 실행하면 정상적으로 노드가 보인다. 클러스터 SG에 bastion SG의 HTTPS(443) 인바운드를 수동으로 추가해야 한다. 이 설정이 빠지면 bastion에서도 접근이 안 된다.
# 로컬에서 시도 → 타임아웃
kubectl get node -v=7
# → dial tcp 10.0.14.147:443: i/o timeout
# bastion으로 접속
ssh ubuntu@$(terraform output -raw bastion_ec2-public_ip)
# bastion에서 kubeconfig 잡고
kubectl get node
# → Ready (프라이빗 IP로만 통신)
# DNS도 프라이빗 IP만 응답
dig +short $APIDNS
# → 10.0.14.147, 10.0.28.171
Fully Private에서 node-shell 트릭
SSH가 안 되는 프라이빗 노드에 들어가야 할 때, privileged 파드를 띄우고 chroot로 호스트에 진입한다.
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: node-shell
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
- name: debug
image: public.ecr.aws/docker/library/alpine:latest
command: ["sleep", "36000"]
securityContext:
privileged: true
volumeMounts:
- mountPath: /host
name: hostfs
volumes:
- name: hostfs
hostPath:
path: /
EOF
# 호스트 진입
kubectl exec -it node-shell -- chroot /host /bin/bash
4.5 어떤 모드를 골라야 하나
환경 추천 모드 이유
| 학습/개발 | Public | 빠르게 시작 |
| 일반 운영 | Public + Private | 노드 트래픽은 내부로, 외부 접근은 CIDR 제한 |
| 규제/보안 | Private | API 서버가 인터넷에 아예 노출 안 됨 |
세 가지 모드를 직접 전환해 보면, Public+Private 모드에서 같은 도메인인데 DNS 응답이 달라지는 동작이 가장 인상적이다. Route 53 Private Hosted Zone이 VPC 안팎을 자동으로 구분해주는 구조를 확인하고 나면, 실무에서도 Public+Private을 기본으로 가져가되 publicAccessCidrs로 소스 IP를 제한하는 방식이 가장 현실적이다.
5. Add-on과 시스템 파드
EKS를 배포하면 기본 Add-on 3종이 설치된다.
Add-on이 "관리형"이라는 의미: EKS Add-on은 AWS가 버전 호환성을 검증하고 자동 업데이트를 해준다. 직접 helm install로 설치한 것과 달리 EKS 콘솔에서 버전 관리가 되고 클러스터 업그레이드 시 호환 버전으로 같이 올라간다. 다만 커스텀 설정을 덮어쓸 수 있으니 configurationValues로 원하는 설정을 명시해두는 것이 좋다.
aws eks list-addons --cluster-name myeks | jq
# → ["coredns", "kube-proxy", "vpc-cni"]
Add-on 역할 배포 형태 특징
| coredns | 클러스터 내부 DNS | Deployment (2 replica) | PDB로 가용성 보장 |
| kube-proxy | 서비스 라우팅 | DaemonSet | iptables 모드 |
| vpc-cni (aws-node) | 파드 네트워킹 | DaemonSet | 컨테이너 2개: CNI + Network Policy Agent |
확인할 점:
- 컨테이너 이미지가 전부 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com에서 온다. 퍼블릭 레지스트리 없이 AWS 내부에서 완결된다.
- pause 컨테이너는 노드 로컬(localhost/kubernetes/pause)에 있다.
# 이미지 목록 확인
kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}" | tr -s '[[:space:]]' '\n' | sort | uniq -c
실습 리소스 정리
실습 후 반드시 리소스를 삭제해서 불필요한 과금을 방지한다.
# 리소스 삭제 (약 10분 소요)
terraform destroy -auto-approve
# 삭제 확인
aws eks list-clusters --query 'clusters' --output text
# → (빈 결과면 정상)
'AWS > EKS' 카테고리의 다른 글
| [ AEWS4 ] EKS 네트워킹 살펴보기 (0) | 2026.03.25 |
|---|---|
| Amazon VPC Lattice로 간소화하는 Amazon EKS 클러스터 통신 (0) | 2025.04.27 |
| ML Infra with EKS : AI 워크로드에 대한 컨테이너 사용 (1) | 2025.04.20 |
| AWS EKS 업그레이드, In-place부터 Blue/Green 마이그레이션까지 (1) | 2025.04.02 |
| EKS 서버리스 서비스 간략히 알아보기(Fargate & EKS AutoMode) (0) | 2025.03.23 |