포스트

EC2 위의 K8s 배포 자동화 — Part 1: 아키텍처와 사전 준비

EC2 위의 K8s 배포 자동화 — Part 1: 아키텍처와 사전 준비

들어가며

CloudNativePG(CNPG)와 MySQL 오퍼레이터를 쿠버네티스 위에서 테스트하려면 클러스터가 먼저 필요합니다. EKS를 사용하면 편리하지만 클러스터 자체에 시간당 $0.10(월 약 $72)이 청구되고, 노드가 인터넷에 나가려면 NAT Gateway가 추가로 시간당 $0.045씩 더해집니다. 테스트 목적으로 띄웠다 내리는 환경이라면 부담입니다.

이 시리즈는 CloudFormation 명령 한 줄로 EC2 위에 kubeadm 기반 K8s 클러스터를 자동 구성하는 방법을 정리합니다. EKS 관리 비용이 없고, NAT Gateway도 사용하지 않습니다. 그 대신 EC2 인스턴스에 공인 IP를 직접 부여해 인터넷 접근을 처리합니다. 무엇보다 테스트가 끝난 후 스택 하나만 삭제하면 모든 리소스가 일괄 정리된다는 점이 개인적으로 가장 마음에 드는 부분입니다. 이 시리즈에서 소개하는 환경을 테스트 시 요긴하게 활용하고 있습니다.

이 환경은 테스트 / 검증 전용입니다. 프로덕션에서는 EKS, 멀티 AZ 배치, Private Subnet + NAT Gateway, 별도 보안 강화가 필요합니다.


결론부터

사전 조건이 모두 갖춰져 있다면 아래 순서로 진행합니다. 각 단계의 세부 사항은 이후 절에서 설명합니다.

사전 준비 — CloudFormation 템플릿 (로컬에만 저장)

bjh-k8s_modified_v2.yaml — CloudFormation 템플릿 bjh-k8s_modified_v2.yaml · 27 KB · sha256: 64259f65…

VPC, EC2, EFS, IAM 전체 스택 정의. 로컬에 저장 후 --template-file 옵션으로 직접 전달합니다.

bjh-k8s_modified_v2.yaml은 S3에 올리지 않습니다. CloudFormation 배포 시 로컬에서 직접 읽습니다.

1단계 — 스크립트 5개를 S3에 업로드

init_v2.sh — 공통 노드 초기화 init_v2.sh · 4.1 KB · sha256: 4e2dabd5…

containerd, kubeadm/kubelet/kubectl 설치 및 커널 모듈 설정 (모든 노드 공통)

master_v2.sh — 컨트롤플레인 구성 master_v2.sh · 2.0 KB · sha256: 8ac08275…

kubeadm init, admin.conf → S3, helm/kubectx/kube-ps1 설치, final_v2.sh 호출

worker_v2.sh — 워커 노드 조인 worker_v2.sh · 1.3 KB · sha256: 5bf37a36…

API 서버 대기 루프, kubeadm join, admin.conf ← S3 폴링

final_v2.sh — 클러스터 완성 final_v2.sh · 4.0 KB · sha256: bce73426…

Flannel CNI, Metrics Server, EBS CSI Driver, StorageClass, NFS Provisioner

kube-flannel.yml — Flannel CNI 매니페스트 kube-flannel.yml · 4.3 KB · sha256: eb2e7fe2…

v0.26.6, Pod CIDR 172.16.0.0/16, EnableNFTables: false (iptables 호환)

1
2
3
4
5
6
7
cd /로컬/스크립트/폴더

aws s3 cp init_v2.sh       s3://my-k8s-scripts/init_v2.sh
aws s3 cp master_v2.sh     s3://my-k8s-scripts/master_v2.sh
aws s3 cp worker_v2.sh     s3://my-k8s-scripts/worker_v2.sh
aws s3 cp final_v2.sh      s3://my-k8s-scripts/final_v2.sh
aws s3 cp kube-flannel.yml s3://my-k8s-scripts/kube-flannel.yml

2단계 — CloudFormation 배포 명령 한 줄이면 끝!

1
2
3
4
5
6
7
8
aws cloudformation deploy \
  --template-file bjh-k8s_modified_v2.yaml \
  --stack-name bjh-test-k8s \
  --capabilities CAPABILITY_IAM \
  --parameter-overrides \
      # 작성자 예시: bjh-test-bastion
      KeyName=<YOUR-KEY-PAIR> \
      SgIngressCidr=$(curl -s https://checkip.amazonaws.com)/32

약 25분 후 CREATE_COMPLETE = K8s Ready.

이 스크립트에서 고정으로 사용하는 값
bjh-test-k8s — CloudFormation 스택 이름. 원하는 이름으로 바꿔도 됩니다.
my-k8s-scripts — S3 버킷명. IAM 정책에 하드코딩되어 있으므로 변경 시 bjh-k8s_modified_v2.yaml도 함께 수정해야 합니다.
bjh-k8s_modified_v2.yaml — 다운로드한 CFN 템플릿의 실제 파일명.


전체 아키텍처

리소스 구성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CloudFormation deploy (bjh-k8s_modified_v2.yaml)
  └─ VPC (192.168.0.0/16) + Internet Gateway
       ├─ PublicSubnet1  AZ1  192.168.10.0/24
       │    ├─ k8s-m   Master   .10.10   t3.large   [항상]
       │    ├─ k8s-w1  Worker1  .10.101  m5.xlarge  [항상]
       │    └─ k8s-w2  Worker2  .10.102  m5.xlarge  [WorkerCount ≥ 2]
       ├─ PublicSubnet2  AZ2  192.168.20.0/24
       │    └─ k8s-w3  Worker3  .20.103  m5.xlarge  [WorkerCount = 3]
       ├─ PrivateSubnet1/2  (미사용 — 향후 확장 예약)
       ├─ EFS + MountTarget × 2  (NFS 공유 스토리지)
       └─ IAM Role → S3 (my-k8s-scripts)  읽기/쓰기

S3 버킷 (my-k8s-scripts)  ← 배포 전 수동 업로드
  ├── init_v2.sh        공통 노드 초기화
  ├── master_v2.sh      컨트롤플레인 설정
  ├── worker_v2.sh      워커 조인
  ├── final_v2.sh       클러스터 완성 (CNI, CSI, StorageClass)
  └── kube-flannel.yml  Flannel CNI 매니페스트

EC2 부팅 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
EC2 기동 → UserData 실행
  ├─ apt: awscli, cfn-bootstrap 설치
  ├─ on_err 트랩 설치  (실패 시 S3에 로그 + cfn-signal fail 즉시 전송)
  ├─ [Master] EFS DNS → /root/efs.txt 기록 + NFS 마운트
  ├─ aws s3 cp 스크립트 다운로드
  ├─ ./init.sh     공통: containerd / kubeadm / kubelet / kubectl
  ├─ [Master]  ./master.sh
  │               ├─ kubeadm init
  │               ├─ admin.conf → S3 업로드  (워커에게 IAM으로 전달)
  │               └─ ./final.sh  Flannel / EBS CSI / StorageClass / NFS Provisioner
  ├─ [Worker]  ./worker.sh
  │               ├─ kubeadm join
  │               └─ admin.conf ← S3 다운로드
  └─ cfn-signal --success true → CloudFormation CREATE_COMPLETE

0. 빠른 체크리스트

배포 전 아래 항목을 모두 확인합니다. 하나라도 빠지면 Stack이 ROLLBACK됩니다.

#항목확인 명령 / 위치
1AWS CLI v2 설치 + 자격증명 등록aws sts get-caller-identity
2작업 Region이 us-east-1 (또는 파라미터로 변경)aws configure get region
3EC2 KeyPair <YOUR-KEY-PAIR> 존재 (us-east-1)aws ec2 describe-key-pairs --key-names <YOUR-KEY-PAIR>
4S3 버킷 my-k8s-scripts 존재 (us-east-1)aws s3 ls s3://my-k8s-scripts/
5S3에 스크립트 5개 업로드 완료aws s3 ls s3://my-k8s-scripts/ \| grep -E 'init_v2\|master_v2\|worker_v2\|final_v2\|kube-flannel'
6본인 공인 IP 확인 (SgIngressCidr용)curl -s https://checkip.amazonaws.com
7EC2 vCPU 서비스 한도 충분aws service-quotas get-service-quota --service-code ec2 --quota-code L-1216C47A
8IAM 권한: CFN/EC2/IAM/S3/EFS/VPC create아래 5. IAM 권한 참조
9비용 알람 (선택)AWS Budgets

1. AWS CLI v2 + 자격증명

설치 (Windows)

AWS 공식 MSI 인스톨러를 다운로드해 설치합니다.

설치 후 PowerShell 또는 Git Bash를 재시작한 뒤 확인합니다.

1
2
aws --version
# aws-cli/2.x.x Python/3.x ...

자격증명 등록

1
2
3
4
5
aws configure
# AWS Access Key ID:     [발급받은 키]
# AWS Secret Access Key: [발급받은 시크릿]
# Default region name:   us-east-1
# Default output format: json

정상 등록 확인

1
2
aws sts get-caller-identity
# Account, UserId, Arn이 출력되면 정상입니다

2. EC2 KeyPair

PEM 파일이 로컬에 있더라도 AWS 측에 KeyPair가 등록되어 있어야 SSH 접속이 가능합니다.

존재 확인

1
aws ec2 describe-key-pairs --key-names <YOUR-KEY-PAIR> --region us-east-1

결과가 정상이면 통과합니다. InvalidKeyPair.NotFound 에러가 나오면 아래 §2-2를 수행합니다.

KeyPair 생성 (없을 때만)

1
2
3
4
5
6
7
8
9
10
aws ec2 create-key-pair \
  # 작성자 예시: bjh-test-bastion
  --key-name <YOUR-KEY-PAIR> \
  --region us-east-1 \
  --query 'KeyMaterial' --output text \
  > "C:/경로/<YOUR-KEY-PAIR>.pem"

# Windows PowerShell에서 파일 권한 정리
icacls "<YOUR-KEY-PAIR>.pem" /inheritance:r
icacls "<YOUR-KEY-PAIR>.pem" /grant:r "$($env:USERNAME):(R)"

기존에 다른 PEM이 있으면 절대 덮어쓰지 마십시오. KeyPair가 이미 AWS에 등록된 경우 PEM을 재생성하면 SSH 접속이 깨집니다.


3. S3 버킷 + 스크립트 업로드

이 단계가 빠지면 CloudFormation 배포 자체는 성공해도 K8s가 절대 뜨지 않습니다. EC2 UserData가 부팅 시 S3에서 스크립트를 받아 실행하기 때문입니다. CREATE_COMPLETE가 뜨더라도 K8s는 미설치 상태로 남습니다.

버킷 존재 확인

1
aws s3 ls s3://my-k8s-scripts/ --region us-east-1

버킷 생성 (없을 때만)

1
2
3
aws s3api create-bucket \
  --bucket my-k8s-scripts \
  --region us-east-1

버킷명은 반드시 my-k8s-scripts이어야 합니다. 템플릿 IAM Role이 이 버킷명에 대해서만 권한을 허용합니다. 다른 이름을 사용하려면 YAML도 함께 수정해야 합니다. 다른 Region에 버킷을 만들 때는 --create-bucket-configuration LocationConstraint=<region>을 추가합니다.

업로드 대상 파일 (5개)

로컬 파일S3 경로
init_v2.shs3://my-k8s-scripts/init_v2.sh
master_v2.shs3://my-k8s-scripts/master_v2.sh
worker_v2.shs3://my-k8s-scripts/worker_v2.sh
final_v2.shs3://my-k8s-scripts/final_v2.sh
kube-flannel.ymls3://my-k8s-scripts/kube-flannel.yml

한 번에 업로드

1
2
3
4
5
6
7
cd "C:/경로/k8s"

aws s3 cp init_v2.sh       s3://my-k8s-scripts/init_v2.sh
aws s3 cp master_v2.sh     s3://my-k8s-scripts/master_v2.sh
aws s3 cp worker_v2.sh     s3://my-k8s-scripts/worker_v2.sh
aws s3 cp final_v2.sh      s3://my-k8s-scripts/final_v2.sh
aws s3 cp kube-flannel.yml s3://my-k8s-scripts/kube-flannel.yml

업로드 검증

1
2
aws s3 ls s3://my-k8s-scripts/
# 5개 파일이 모두 보여야 합니다

스크립트 내용을 수정했다면 반드시 S3에 다시 올린 후 스택을 재생성합니다. CloudFormation은 로컬 파일을 직접 참조하지 않습니다.
runtime/ 프리픽스(s3://my-k8s-scripts/runtime/<stack-name>/)는 Master가 admin.conf를 워커에 전달하기 위한 임시 영역입니다.
미리 만들 필요 없이 master_v2.sh가 자동으로 업로드합니다.


4. 본인 공인 IP 확인

배포 명령의 SgIngressCidr 파라미터에 본인 IP를 입력해야 SSH 접속이 허용됩니다.

1
2
curl -s https://checkip.amazonaws.com
# → 예: 165.225.228.251

위 결과를 <IP>/32 형태로 사용합니다. VPN 전환이나 공유기 재시작으로 IP가 바뀌면 SecurityGroup을 업데이트하거나 스택을 재배포해야 합니다.


5. IAM 권한

배포를 실행하는 IAM 주체(사용자/역할)가 다음 액션을 수행할 수 있어야 합니다.

서비스주요 필요 액션
CloudFormationCreateStack, UpdateStack, DeleteStack, DescribeStacks, DescribeStackEvents, DescribeStackResources
EC2CreateVpc, CreateSubnet, RunInstances, CreateSecurityGroup, AuthorizeSecurityGroupIngress, Describe*
EFSCreateFileSystem, CreateMountTarget, DescribeFileSystems, Delete*
IAMCreateRole, PutRolePolicy, CreateInstanceProfile, PassRole 등 (CAPABILITY_IAM 필요)
S3PutObject, GetObject, ListBucket
SSMGetParameters (Ubuntu AMI ID 자동 조회)

개인 계정이라면 AdministratorAccess 정책으로 모두 통과됩니다. 회사 계정에서 권한이 제한된 경우 위 액션 단위로 정책을 검토하셔야 합니다.


6. 비용 사전 인지

옵션구성일 예상 비용 (on-demand, us-east-1, worker 2대 기준)
A — 검소master + worker 2대 모두 t3.large약 $6~8/일
B — 권장master t3.large + worker 2대 m5.xlarge약 $16/일 (EC2 ~$11.2 + EBS ~$4.8)
C — 본격master m5.large + worker 2대 m5.2xlarge, EBS 12000 IOPS약 $33+/일

추가 비용 항목:

  • EFS: 사용량 기반 (Standard $0.30/GB-월). 테스트 데이터가 적으면 무시할 수준입니다.
  • NAT Gateway: 이 스택은 사용하지 않으므로 $0입니다.
  • EBS Throughput: CFN EC2::Instance에서 직접 지정이 안 되므로, 기본값(125 MB/s)에서 변경이 필요하면 인스턴스 기동 후 별도로 적용합니다.
1
2
3
4
5
6
7
8
9
10
# 인스턴스 기동 후 EBS Throughput 일괄 변경 예시
for ID in $(aws ec2 describe-instances \
  --filters "Name=tag:aws:cloudformation:stack-name,Values=bjh-test-k8s" \
            "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].InstanceId' --output text); do
  VOL=$(aws ec2 describe-instances --instance-ids $ID \
    --query 'Reservations[0].Instances[0].BlockDeviceMappings[?DeviceName==`/dev/sda1`].Ebs.VolumeId' \
    --output text)
  aws ec2 modify-volume --volume-id $VOL --throughput 500
done

테스트가 끝나면 aws cloudformation delete-stack --stack-name <스택이름>으로 정리하면 됩니다.

(선택) AWS Budgets 알람

일 $20 초과 시 이메일 알림을 설정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
aws budgets create-budget \
  --account-id $(aws sts get-caller-identity --query Account --output text) \
  --budget '{
    "BudgetName": "bjh-test-k8s-daily",
    "BudgetLimit": {"Amount": "20", "Unit": "USD"},
    "TimeUnit": "DAILY",
    "BudgetType": "COST"
  }' \
  --notifications-with-subscribers '[{
    "Notification": {
      "NotificationType": "ACTUAL",
      "ComparisonOperator": "GREATER_THAN",
      "Threshold": 100
    },
    "Subscribers": [{
      "SubscriptionType": "EMAIL",
      "Address": "[email protected]"
    }]
  }]'

7. 배포 직전 최종 점검 + 실행

(1) 사전 자원 한 번에 확인

1
2
3
4
5
6
7
aws sts get-caller-identity --query 'Arn' --output text
aws ec2 describe-key-pairs --key-names <YOUR-KEY-PAIR> --region us-east-1 \
  --query 'KeyPairs[0].KeyName' --output text
aws s3 ls s3://my-k8s-scripts/
aws service-quotas get-service-quota \
  --service-code ec2 --quota-code L-1216C47A \
  --region us-east-1 --query 'Quota.Value' --output text

(2) 공인 IP 확인

1
2
MY_IP=$(curl -s https://checkip.amazonaws.com)/32
echo "SgIngressCidr will be: $MY_IP"

(3) 템플릿 사전 검증

1
2
3
4
5
6
7
# CFN 서버 사이드 문법 검증
aws cloudformation validate-template \
  --template-body file://bjh-k8s_modified_v2.yaml \
  --query '{Params:Parameters[].ParameterKey,Caps:Capabilities}' --output table

# (선택) 로컬 lint — pip install cfn-lint 후
cfn-lint bjh-k8s_modified_v2.yaml

검증 실패 시 배포하지 마십시오. 즉시 ROLLBACK됩니다.

(4) 배포 실행

기본값(master 1 + worker 2, t3.large + m5.xlarge, EBS 200 GB / 6000 IOPS):

1
2
3
4
5
6
7
8
aws cloudformation deploy \
  --template-file bjh-k8s_modified_v2.yaml \
  --stack-name bjh-test-k8s \
  --capabilities CAPABILITY_IAM \
  --parameter-overrides \
      # 작성자 예시: bjh-test-bastion
      KeyName=<YOUR-KEY-PAIR> \
      SgIngressCidr=$MY_IP

HA 테스트용 worker 3대:

1
2
3
4
5
6
7
aws cloudformation deploy --template-file bjh-k8s_modified_v2.yaml \
  --stack-name bjh-test-k8s --capabilities CAPABILITY_IAM \
  --parameter-overrides \
      # 작성자 예시: bjh-test-bastion
      KeyName=<YOUR-KEY-PAIR> \
      SgIngressCidr=$MY_IP \
      WorkerCount=3

CREATE_COMPLETEcfn-signal까지 통과한 상태, 즉 K8s가 실제로 Ready된 것을 의미합니다. 최대 약 25분이 소요됩니다.

(5) 완료 확인 + Master 접속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 배포 완료 대기 (블로킹)
aws cloudformation wait stack-create-complete --stack-name bjh-test-k8s && echo "DONE"

# 모든 Output 조회
aws cloudformation describe-stacks --stack-name bjh-test-k8s \
  --query 'Stacks[0].Outputs' --output table

# Master 공인 IP 조회 + SSH 접속
MASTER=$(aws cloudformation describe-stacks --stack-name bjh-test-k8s \
  --query "Stacks[0].Outputs[?OutputKey=='MasterNodeIP'].OutputValue" --output text)
ssh -i <YOUR-KEY-PAIR>.pem ubuntu@$MASTER  # 작성자 예시: bjh-test-bastion.pem

# 노드 상태 확인
kubectl get nodes -o wide

노드 N+1대가 모두 Ready이면 다음 단계(CNPG Operator 설치)로 진행합니다.


8. 정리 (테스트 종료 시)

1
2
3
4
5
6
7
8
9
10
11
12
# 스택 삭제 시작 (EC2 / EBS / EFS / VPC 모두 정리)
aws cloudformation delete-stack --stack-name bjh-test-k8s

# 삭제 완료 대기 (최대 ~10분)
aws cloudformation wait stack-delete-complete --stack-name bjh-test-k8s && echo "DELETED"

# admin.conf S3 잔여물 수동 정리 (delete-stack은 S3 객체를 삭제하지 않음)
aws s3 rm s3://my-k8s-scripts/runtime/bjh-test-k8s/ --recursive

# 삭제 확인
aws cloudformation describe-stacks --stack-name bjh-test-k8s 2>&1 \
  | grep -q "does not exist" && echo "Stack fully deleted"

다음 편

Part 2에서는 CloudFormation 템플릿(bjh-k8s_modified_v2.yaml)의 각 리소스를 해설합니다. IAM Role 설계, NAT Gateway 없는 네트워크 구성, cfn-signal 동작 원리, WorkerCount 조건 분기, 그리고 전체 CFN 명령어 레퍼런스를 다룹니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.