SK쉴더스 루키즈 개발 5기 미니프로젝트3 실시간 경매 사이트 MACTA 서비스를 AWS 기반으로 배포하기 위한 인프라 레포입니다. Terraform으로 AWS 리소스를 구성하고, Kubernetes manifest와 Argo CD를 통해 EKS 위에 프론트엔드/백엔드 애플리케이션을 배포하는 구조입니다.
현재 구조의 핵심은 다음과 같습니다.
- Terraform: VPC, EKS, RDS, S3, ECR, WAF, IRSA, Helm 기반 컨트롤러 설치
- EKS: 프론트엔드, 백엔드, Ingress, External Secrets 리소스 실행
- SSM Parameter Store: DB, S3, IAM Role ARN, WAF ARN, ACM ARN 등 환경별 값을 저장
- External Secrets Operator: SSM 값을 Kubernetes Secret으로 동기화
- AWS Load Balancer Controller: Kubernetes Ingress를 보고 ALB 생성
- Route53 + ACM:
macta.store도메인과 HTTPS 인증서 연결 - 통신 구조: 정적 파일은 프론트 Nginx가 서빙하고, 동적 API 호출은 ALB가
/api/v1경로로 백엔드 서비스에 직접 전달
flowchart TB
user[User Browser]
r53[Route53<br/>macta.store A Alias]
acm[ACM Certificate<br/>macta.store]
waf[AWS WAFv2<br/>Regional Web ACL]
alb[Public ALB<br/>AWS Load Balancer Controller]
internet[Internet]
subgraph vpc[VPC]
igw[Internet Gateway]
subgraph public[Public Subnets]
alb
nat[NAT Gateway<br/>single AZ]
natEip[Elastic IP<br/>for NAT Gateway]
publicRt[Public Route Table<br/>0.0.0.0/0 -> IGW]
end
subgraph private[Private Subnets]
privateRt[Private Route Table<br/>0.0.0.0/0 -> NAT GW<br/>S3 prefix -> S3 Gateway Endpoint]
subgraph eks[EKS Cluster<br/>rookies5-macta-eks]
ing[Kubernetes Ingress<br/>macta.store]
subgraph frontend[Frontend Stack]
feSvc[frontend Service<br/>ClusterIP :80]
fePod[React + TypeScript Pods<br/>Vite Build + Nginx Static Serving]
feTech[Vite<br/>React<br/>TypeScript<br/>React Router Dom<br/>Tailwind CSS<br/>Shadcn/ui<br/>Lucide React<br/>Axios<br/>TanStack Query v5<br/>Zustand<br/>React Hook Form<br/>Zod<br/>date-fns]
end
subgraph backend[Backend Stack]
beSvc[backend Service<br/>ClusterIP :8080]
bePod[Spring Boot Pods<br/>REST API Server]
beTech[Java<br/>Spring Boot<br/>Spring Web<br/>Spring Security<br/>JWT<br/>JPA / Hibernate<br/>Spring Data JPA<br/>MariaDB Driver<br/>AWS SDK<br/>Gradle]
end
subgraph k8sops[Kubernetes Ops]
eso[External Secrets Operator]
patch[SSM Annotation Patch Job]
irsaPod[IRSA ServiceAccount<br/>backend-sa]
deploy[Deployment<br/>Rolling Update]
end
end
rds[RDS MariaDB 10.11<br/>mactadb]
end
s3ep[S3 Gateway Endpoint]
end
ssm[SSM Parameter Store<br/>DB / S3 / ARN / Image URI]
cw[CloudWatch<br/>logs and metrics]
ecr[ECR<br/>frontend/backend images]
s3[S3 Bucket<br/>rookies5-team4-macta-bucket]
irsa[IRSA IAM Roles]
gha[GitHub Actions<br/>Build / Test / Docker Push]
argocd[Argo CD<br/>GitOps Sync]
repo[GitHub Repositories<br/>backend / frontend / infra]
user -->|https://macta.store| r53
r53 --> internet
internet <--> igw
igw <--> publicRt
publicRt --> alb
acm -->|HTTPS listener cert| alb
waf -->|associated by Ingress annotation| alb
alb --> ing
ing -->|/| feSvc
ing -->|/api/v1| beSvc
feSvc --> fePod
fePod -.-> feTech
beSvc --> bePod
bePod -.-> beTech
bePod -->|JDBC 3306| rds
bePod -->|S3 API| privateRt --> s3ep --> s3
natEip --> nat
privateRt -->|default route| nat
nat --> publicRt
fePod -->|pull image| privateRt
bePod -->|pull image| privateRt
privateRt -->|outbound via NAT| internet
internet --> ecr
internet --> ssm
eso -->|read parameters| privateRt
eso -->|creates Kubernetes Secrets| bePod
patch -->|read synced Secret| privateRt
patch -->|patch SA, Ingress, image| ing
irsa --> eso
irsa --> bePod
irsaPod --> bePod
deploy --> fePod
deploy --> bePod
repo --> gha
gha -->|Docker image push| ecr
gha -->|update manifest image tag| repo
repo --> argocd
argocd -->|sync manifests| eks
eks -->|cluster and workload metrics/logs| cw
alb -->|access metrics| cw
rds -->|database metrics/logs| cw
이 인프라는 하나의 VPC 안에서 Public Subnet과 Private Subnet을 분리해 구성합니다. 외부 사용자가 직접 접근해야 하는 진입점만 Public Subnet에 두고, 실제 애플리케이션과 데이터베이스는 Private Subnet에 배치해 외부 노출 범위를 줄이는 구조입니다.
Public Subnet에는 Public ALB와 NAT Gateway가 배치됩니다. ALB는 인터넷에서 들어오는 HTTP/HTTPS 요청을 받는 유일한 외부 진입점이고, NAT Gateway는 Private Subnet의 리소스가 필요한 경우에만 외부로 나갈 수 있게 해주는 아웃바운드 통로입니다.
Private Subnet에는 EKS Worker Node, 프론트엔드 Pod, 백엔드 Pod, RDS MariaDB가 배치됩니다. 프론트엔드와 백엔드 Pod는 외부에서 직접 접근할 수 없고, ALB Ingress를 통해서만 서비스 트래픽을 받습니다. RDS도 Public 접근을 막고 Private Subnet 내부에서만 백엔드가 3306 포트로 접근하도록 구성합니다.
Public Subnet과 Private Subnet을 나눈 이유는 보안 경계를 명확히 하기 위해서입니다. 외부 인터넷과 직접 연결되는 리소스는 ALB로 제한하고, 애플리케이션 서버와 DB는 사설망에 두면 공격 표면을 줄일 수 있습니다. 또한 Private Subnet의 Pod가 ECR 이미지 pull, SSM Parameter 조회 등 외부 AWS API 접근이 필요할 때는 NAT Gateway 또는 VPC Endpoint를 통해 통제된 방향으로만 통신하게 됩니다.
사용자 요청 흐름은 다음과 같습니다.
User Browser
-> Route53(macta.store)
-> Public ALB
-> Kubernetes Ingress
/ -> frontend Service -> frontend Pod
/api/v1 -> backend Service -> backend Pod
프론트엔드 화면 요청은 / 경로로 들어와 React 정적 파일을 서빙하는 Nginx Pod로 전달됩니다. API 요청은 같은 도메인의 /api/v1 경로로 들어오고, ALB Ingress가 이 요청을 백엔드 Service로 라우팅합니다. 따라서 프론트엔드와 백엔드는 같은 EKS 클러스터 안에 있지만, 사용자-facing API 통신은 ALB Ingress의 경로 기반 라우팅을 통해 분리됩니다.
백엔드 내부 통신은 다음과 같습니다.
backend Pod
-> RDS MariaDB:3306
-> S3 Bucket(S3 Gateway VPC Endpoint 경유)
-> Redis
-> SSM/Secrets 값은 External Secrets Operator가 Kubernetes Secret으로 동기화
S3는 인터넷을 통해 우회하지 않고 Private Route Table에 연결된 S3 Gateway VPC Endpoint를 통해 접근합니다. Secret 값은 manifest에 직접 넣지 않고 SSM Parameter Store에 저장한 뒤, External Secrets Operator가 Kubernetes Secret으로 동기화해 백엔드 Pod 환경변수로 주입합니다.
배포 흐름은 GitOps 방식입니다. GitHub Actions가 프론트엔드/백엔드 이미지를 빌드해 ECR에 push하고 manifest의 image tag를 갱신하면, Argo CD가 변경 사항을 감지해 EKS에 자동으로 반영합니다. Kubernetes Deployment는 Rolling Update 전략을 사용해 새 Pod가 Ready 상태가 된 뒤 기존 Pod를 교체하므로 배포 중 서비스 중단 가능성을 줄입니다.
sequenceDiagram
participant Browser
participant ALB as ALB Ingress
participant FE as Frontend Nginx Pod
participant BE as Backend Pod
Browser->>ALB: GET https://macta.store/
ALB->>FE: route /
FE-->>Browser: React static files
Browser->>ALB: GET https://macta.store/api/v1/...
ALB->>BE: route /api/v1
BE-->>Browser: JSON API response
프론트엔드 Nginx는 React 정적 파일과 SPA fallback만 담당합니다.
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}프론트엔드의 API base URL은 같은 도메인 상대경로를 권장합니다.
VITE_API_BASE_URL=/api/v1
| 구분 | 연동 대상 | 역할 |
|---|---|---|
| 네트워크 | VPC | EKS / RDS / ALB 네트워크 분리 및 내부 통신 구성 |
| 네트워크 | Public Subnet | Public ALB 및 NAT Gateway 배치 |
| 네트워크 | Private Subnet | EKS Worker Node / Pod / RDS 내부망 구성 |
| 인터넷 연결 | Internet Gateway(IGW) | VPC 외부 인터넷 통신 제공 |
| 아웃바운드 | NAT Gateway | Private Subnet의 외부 인터넷 접근 제공 |
| DNS | Route53 | macta.store 도메인을 ALB로 연결 |
| 인증서 | ACM | HTTPS 인증서 제공 |
| 진입점 | ALB | 외부 HTTP/HTTPS 트래픽 수신 |
| Ingress 자동화 | AWS Load Balancer Controller | Kubernetes Ingress 기반 ALB 생성 및 관리 |
| 컨테이너 오케스트레이션 | EKS | Kubernetes 기반 애플리케이션 운영 |
| 노드 관리 | EKS Node Group | EKS Worker Node 자동 관리 |
| 보안 | WAFv2 | ALB 앞단 요청 필터링 및 Rate Limit 적용 |
| 컨테이너 이미지 | ECR | Frontend / Backend Docker Image 저장 |
| DB | RDS MariaDB 10.11 | 백엔드 영속 데이터 저장 |
| 캐시 | Redis | 캐시 및 실시간 데이터 처리 |
| 파일 저장소 | S3 | 이미지 및 파일 저장 |
| 내부 S3 통신 | S3 Gateway VPC Endpoint | Private Subnet에서 S3 접근 |
| Secret 저장 | SSM Parameter Store | DB/S3/JWT 설정 저장 |
| Secret 동기화 | External Secrets Operator | SSM 값을 Kubernetes Secret으로 변환 |
| 권한 관리 | IRSA | Pod 단위 IAM Role 사용 |
| 배포 자동화 | GitHub Actions | Build / Test / Image Push / Manifest 갱신 |
| GitOps | Argo CD | Kubernetes Manifest 자동 Sync |
| 모니터링 | CloudWatch | EKS / ALB / RDS / WAF 로그 및 메트릭 수집 |
infra/
argocd/
backend-application.yml
frontend-application.yml
k8s/
ingress.yaml
ssm-annotation-patch-job.yaml
backend/
namespace.yaml
backend.yaml
external-secret.yaml
frontend/
frontend.yaml
terraform/
main.tf
variables.tf
outputs.tf
vpc.tf
eks.tf
ecr.tf
rds.tf
s3.tf
waf.tf
external-secrets.tf
aws-load-balancer-controller.tf
policies/
aws-load-balancer-controller-iam-policy.json
| 항목 | 값 |
|---|---|
| AWS region | ap-northeast-2 |
| AWS profile | team4 |
| Project name | rookies5-macta |
| Environment | dev |
| EKS cluster | rookies5-macta-eks |
| Kubernetes namespace | rookies5-macta |
| Backend ServiceAccount | backend-sa |
| External Secrets namespace | external-secrets |
| External Secrets ServiceAccount | external-secrets |
| Domain | macta.store |
DB 계정 정보는 terraform/terraform.tfvars에서 관리합니다. 이 파일은 Git에 올리지 않습니다.
db_instance_class = "db.t3.micro"
db_name = "mactadb"
db_username = "admin"
db_password = "change-me"
cd C:\rookies\macta\infra\terraform
$env:AWS_PROFILE = "team4"
terraform init
terraform fmt
terraform validate
terraform plan
terraform applykubeconfig 설정:
aws eks update-kubeconfig --profile team4 --region ap-northeast-2 --name rookies5-macta-eks주요 output 확인:
terraform output -raw eks_cluster_name
terraform output -raw ecr_backend_repository_url
terraform output -raw ecr_frontend_repository_url
terraform output -raw backend_sa_role_arn
terraform output -raw rds_db_url
terraform output -raw s3_bucket_name
terraform output -raw waf_web_acl_arn
Kubernetes YAML에는 DB 비밀번호, DB URL, ARN, 인증서 ARN 같은 환경별 값을 직접 넣지 않습니다. SSM Parameter Store에 저장하고 External Secrets Operator가 Kubernetes Secret으로 동기화합니다.
현재 manifest가 참조하는 SSM 경로는 다음과 같습니다.
| SSM parameter | 용도 |
|---|---|
/rookies5-macta/dev/backend/DB_URL |
백엔드 DB JDBC URL |
/rookies5-macta/dev/backend/DB_USERNAME |
DB 사용자명 |
/rookies5-macta/dev/backend/DB_PASSWORD |
DB 비밀번호 |
/rookies5-macta/dev/backend/S3_BUCKET_NAME |
S3 버킷명 |
/rookies5-macta/dev/backend/AWS_REGION |
AWS region |
/rookies5-macta/dev/infra/BACKEND_ROLE_ARN |
백엔드 IRSA Role ARN |
/rookies5-macta/dev/infra/WAF_WEB_ACL_ARN |
WAF Web ACL ARN |
/rookies5-macta/dev/infra/ACM_CERTIFICATE_ARN |
ACM 인증서 ARN |
/rookies5-macta/dev/infra/BACKEND_IMAGE |
백엔드 이미지 URI |
/rookies5-macta/dev/infra/FRONTEND_IMAGE |
프론트엔드 이미지 URI |
SSM 값 생성 예시:
cd C:\rookies\macta\infra\terraform
$backendRoleArn = terraform output -raw backend_sa_role_arn
$wafWebAclArn = terraform output -raw waf_web_acl_arn
$backendImage = "$(terraform output -raw ecr_backend_repository_url):latest"
$frontendImage = "$(terraform output -raw ecr_frontend_repository_url):latest"
$dbUrl = terraform output -raw rds_db_url
$s3BucketName = terraform output -raw s3_bucket_name
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/backend/DB_URL" --type SecureString --value $dbUrl --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/backend/DB_USERNAME" --type SecureString --value "admin" --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/backend/DB_PASSWORD" --type SecureString --value "CHANGE_ME" --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/backend/S3_BUCKET_NAME" --type SecureString --value $s3BucketName --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/backend/AWS_REGION" --type String --value "ap-northeast-2" --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/infra/BACKEND_ROLE_ARN" --type SecureString --value $backendRoleArn --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/infra/WAF_WEB_ACL_ARN" --type SecureString --value $wafWebAclArn --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/infra/ACM_CERTIFICATE_ARN" --type SecureString --value "CHANGE_ME_ACM_CERTIFICATE_ARN" --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/infra/BACKEND_IMAGE" --type SecureString --value $backendImage --overwrite
aws ssm put-parameter --profile team4 --region ap-northeast-2 --name "/rookies5-macta/dev/infra/FRONTEND_IMAGE" --type SecureString --value $frontendImage --overwrite조회:
aws ssm get-parameters-by-path --profile team4 --region ap-northeast-2 --path "/rookies5-macta/dev" --recursive --with-decryption --query "Parameters[*].[Name,Type,Value]" --output table
Terraform은 External Secrets Operator를 Helm으로 설치합니다.
- Namespace:
external-secrets - ServiceAccount:
external-secrets - 인증 방식: IRSA
- SSM 권한:
ssm:GetParameter,ssm:GetParameters,ssm:GetParametersByPath,ssm:DescribeParameters
Kubernetes manifest:
k8s/backend/external-secret.yamlClusterSecretStore: AWS SSM Parameter Store 연결ExternalSecret rookies5-macta-backend-secret: 백엔드 런타임 환경변수 Secret 생성ExternalSecret rookies5-macta-infra-config: IAM Role, WAF, ACM, 이미지 URI Secret 생성
확인:
kubectl get deployment -n external-secrets external-secrets
kubectl get externalsecret -n rookies5-macta
kubectl get secret backend-secret -n rookies5-macta
kubectl get secret rookies5-macta-infra-config -n rookies5-macta
수동 적용:
cd C:\rookies\macta\infra
kubectl apply -f .\k8s\backend\namespace.yaml
kubectl apply -f .\k8s\backend\external-secret.yaml
kubectl apply -f .\k8s\backend\backend.yaml
kubectl apply -f .\k8s\frontend\frontend.yaml
kubectl apply -f .\k8s\ingress.yaml
kubectl apply -f .\k8s\ssm-annotation-patch-job.yaml확인:
kubectl get pods -n rookies5-macta
kubectl get svc -n rookies5-macta
kubectl get ingress -n rookies5-macta
ALB는 Terraform에서 직접 생성하지 않습니다. Terraform은 AWS Load Balancer Controller가 동작할 수 있도록 IAM Role, ServiceAccount, Helm release를 구성하고, 실제 ALB는 k8s/ingress.yaml의 Kubernetes Ingress 리소스를 AWS Load Balancer Controller가 감지해 생성합니다.
즉, 이 구조에서 Ingress는 클러스터 외부 트래픽을 프론트엔드와 백엔드 Service로 나누는 진입 라우터 역할을 합니다.
현재 라우팅:
https://macta.store/ -> rookies5-macta-frontend-service:80
https://macta.store/api/v1 -> rookies5-macta-backend-service:8080
요청 흐름:
User Browser
-> Route53 macta.store A Alias
-> Public ALB
-> Kubernetes Ingress
/ -> frontend Service -> frontend Pod
/api/v1 -> backend Service -> backend Pod
프론트엔드는 React 정적 파일을 Nginx로 서빙하고, API 호출은 같은 도메인의 /api/v1 상대 경로를 사용합니다. 브라우저가 https://macta.store/api/v1/...로 요청하면 ALB Ingress가 해당 요청을 백엔드 Service로 전달합니다. 따라서 프론트엔드 Pod가 백엔드 Pod를 직접 호출하는 구조가 아니라, 사용자 브라우저의 API 요청이 ALB와 Ingress의 경로 기반 라우팅을 통해 백엔드로 전달되는 구조입니다.
Ingress 주요 설정:
metadata:
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80},{"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
spec:
ingressClassName: alb
rules:
- host: macta.store
http:
paths:
- path: /api/v1
pathType: Prefix
backend:
service:
name: rookies5-macta-backend-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: rookies5-macta-frontend-service
port:
number: 80주요 annotation 의미:
| annotation | 의미 |
|---|---|
kubernetes.io/ingress.class: alb |
AWS Load Balancer Controller가 처리할 Ingress임을 표시 |
alb.ingress.kubernetes.io/scheme: internet-facing |
외부 인터넷에서 접근 가능한 public ALB 생성 |
alb.ingress.kubernetes.io/target-type: ip |
ALB Target Group이 Pod IP를 직접 대상으로 사용 |
alb.ingress.kubernetes.io/listen-ports |
ALB listener 포트 설정. 현재 HTTP 80, HTTPS 443 사용 |
alb.ingress.kubernetes.io/ssl-redirect: "443" |
HTTP 요청을 HTTPS로 리다이렉트 |
WAF/ACM처럼 환경마다 ARN이 달라지는 값은 manifest에 직접 고정하지 않고 SSM Parameter Store에 저장합니다. External Secrets Operator가 이 값을 rookies5-macta-infra-config Secret으로 동기화하고, ssm-annotation-patch-job이 Ingress annotation으로 주입합니다.
alb.ingress.kubernetes.io/wafv2-acl-arnalb.ingress.kubernetes.io/certificate-arnalb.ingress.kubernetes.io/listen-ports: [{"HTTP":80},{"HTTPS":443}]alb.ingress.kubernetes.io/ssl-redirect: "443"
이 방식으로 기본 Ingress 라우팅은 Git에 선언하고, 계정/환경에 따라 달라지는 WAF Web ACL ARN과 ACM 인증서 ARN은 SSM을 통해 런타임에 반영합니다.
확인:
kubectl get ingress rookies5-macta-frontend-ingress -n rookies5-macta
kubectl describe ingress rookies5-macta-frontend-ingress -n rookies5-macta확인할 항목:
Address: AWS Load Balancer Controller가 생성한 ALB DNS 이름Rules:macta.storehost와/,/api/v1path 라우팅Annotations: certificate ARN, WAF ACL ARN, HTTPS listener, SSL redirect 적용 여부Events: ALB, TargetGroup, Listener 생성 또는 오류 메시지
도메인:
macta.store
Route53 public hosted zone에 필요한 레코드:
| 이름 | 타입 | 대상 |
|---|---|---|
macta.store |
A Alias |
ALB dualstack DNS |
*.macta.store |
A Alias |
ALB dualstack DNS, 필요 시 |
_...macta.store |
CNAME |
ACM DNS validation |
주의:
- ACM DNS validation CNAME은 인증서 검증용입니다. 서비스 트래픽을 ALB로 보내지 않습니다.
*.macta.store는www.macta.store,api.macta.store같은 서브도메인에만 매칭됩니다.- 루트 도메인
macta.store를 쓰려면 별도macta.store A Alias -> ALB레코드가 필요합니다. - ALB 기본 DNS로 HTTPS 접속하면 인증서 이름이 맞지 않아 브라우저 경고가 납니다. 최종 접속은
https://macta.store로 확인합니다.
DNS 확인:
nslookup macta.store
nslookup macta.store 8.8.8.8
현재 RDS 엔진은 MySQL이 아니라 MariaDB입니다.
| 항목 | 값 |
|---|---|
| Engine | mariadb |
| Engine version | 10.11 |
| DB name | mactadb |
| Port | 3306 |
| Subnet | Private subnets |
| Public access | disabled |
JDBC URL은 MariaDB의 MySQL 호환 프로토콜을 사용해 다음 형태로 구성합니다.
jdbc:mysql://<rds-endpoint>:3306/mactadb?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
이 값은 SSM의 /rookies5-macta/dev/backend/DB_URL에 저장하고, External Secrets가 backend-secret으로 동기화합니다.
S3는 애플리케이션 파일 저장소로 사용합니다.
- Bucket:
rookies5-team4-macta-bucket - Public access block 적용
- EKS private subnet에서 S3 Gateway Endpoint로 접근
- 백엔드 Pod는 IRSA Role을 통해 S3 권한 사용
백엔드 ServiceAccount:
backend-sa
IRSA annotation은 SSM 값을 읽은 patch Job이 주입합니다.
eks.amazonaws.com/role-arn=<BACKEND_ROLE_ARN>
확인:
kubectl get serviceaccount backend-sa -n rookies5-macta -o yaml
Terraform이 ECR repository를 생성합니다.
terraform output -raw ecr_backend_repository_url
terraform output -raw ecr_frontend_repository_url이미지 예시:
105588835975.dkr.ecr.ap-northeast-2.amazonaws.com/rookies5-macta/backend:<tag>
105588835975.dkr.ecr.ap-northeast-2.amazonaws.com/rookies5-macta/frontend:<tag>
현재 Kubernetes manifest의 image는 placeholder로 둘 수 있습니다. 실제 이미지는 CI/CD 또는 SSM patch Job에서 반영합니다.
이미지 pull 오류 확인:
kubectl describe pod -n rookies5-macta -l app=rookies5-macta-frontend
kubectl describe pod -n rookies5-macta -l app=rookies5-macta-backend
Terraform은 Regional WAF Web ACL을 생성합니다.
적용 rule:
- Rate limit per IP
- AWS Managed Rules Common Rule Set
- AWS Managed Rules Known Bad Inputs Rule Set
- AWS Managed Rules SQLi Rule Set
WAF는 생성만으로 ALB에 자동 연결되지 않습니다. 현재 구조에서는 WAF ARN을 SSM에 저장하고, ssm-annotation-patch-job이 Ingress annotation으로 주입합니다.
alb.ingress.kubernetes.io/wafv2-acl-arn=<WAF_WEB_ACL_ARN>
WAF는 CloudWatch Metrics와 full request logging을 모두 사용합니다.
- CloudWatch Metrics: rule별
AllowedRequests,BlockedRequests지표 확인 - Sampled requests: WAF Console에서 일부 요청 샘플 확인
- CloudWatch Logs full logging: WAF를 통과하거나 차단된 요청 상세 로그 저장
Terraform은 WAF 로그 저장용 CloudWatch Log Group을 생성합니다.
aws-waf-logs-rookies5-macta-dev-web-acl
WAF logging 설정:
aws_wafv2_web_acl_logging_configuration
-> aws_cloudwatch_log_group.waf
로그에는 요청 IP, URI, HTTP method, User-Agent, WAF action, rule match 정보 등이 저장됩니다. authorization, cookie 헤더는 민감정보 노출을 줄이기 위해 redaction 처리합니다.
확인:
terraform output -raw waf_log_group_nameCloudWatch Logs Insights 예시:
fields @timestamp, action, terminatingRuleId, httpRequest.clientIp, httpRequest.uri, httpRequest.httpMethod
| sort @timestamp desc
| limit 50Rate Limit 차단 요청만 확인:
fields @timestamp, action, terminatingRuleId, httpRequest.clientIp, httpRequest.uri
| filter action = "BLOCK"
| filter terminatingRuleId = "RateLimitPerIp"
| sort @timestamp desc
| limit 50
Argo CD는 애플리케이션 배포 상태를 확인하고 GitOps 방식으로 manifest를 sync하기 위한 도구입니다.
프론트엔드/백엔드 레포에서 이미지 빌드 후 infra manifest를 갱신하더라도 Argo CD가 즉시 변경사항을 감지하지 못할 수 있습니다. 이를 줄이기 위해 Argo CD webhook을 함께 사용합니다. GitHub push 이벤트가 Argo CD webhook으로 전달되면 Application refresh가 트리거되어 기본 polling 주기를 기다리지 않고 빠르게 sync 대상 변경을 감지할 수 있습니다.
설치 예시:
kubectl create namespace argocd
kubectl apply --server-side --force-conflicts -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yamlUI를 임시로 볼 때:
kubectl port-forward svc/argocd-server -n argocd 8080:443브라우저:
https://localhost:8080
초기 비밀번호:
$encoded = kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}"
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded))LoadBalancer로 노출할 수도 있지만, 실습 후에는 ClusterIP로 되돌리는 것을 권장합니다.
kubectl patch svc argocd-server -n argocd --type merge -p '{"spec":{"type":"LoadBalancer"}}'
kubectl patch svc argocd-server -n argocd --type merge -p '{"spec":{"type":"ClusterIP"}}'GitHub webhook URL:
https://<argocd-server-domain-or-lb>/api/webhook
GitHub webhook 설정:
Payload URL: https://<argocd-server-domain-or-lb>/api/webhook
Content type: application/json
Event: Just the push event
Argo CD를 외부 LoadBalancer로 노출하지 않는 운영 환경에서는 port-forward 대신 Ingress, VPN, 사내망, 또는 별도 webhook relay 구성을 사용합니다.
권장 흐름:
Frontend or Backend repo push
-> GitHub Actions
-> Docker build
-> ECR push
-> infra manifest image tag update commit
-> GitHub webhook triggers Argo CD refresh
-> Argo CD sync
-> EKS rolling update
SSM에 유지할 값:
- DB URL, username, password
- S3 bucket name
- Backend IRSA Role ARN
- WAF Web ACL ARN
- ACM certificate ARN
- AWS region
이미지 URI는 일반적으로 민감정보가 아니므로, 완전한 GitOps를 원하면 manifest에 이미지 태그를 커밋하고 Argo CD가 sync하게 하는 구조가 더 단순합니다.
현재 SSM 기반 patch Job도 지원합니다.
SSM FRONTEND_IMAGE/BACKEND_IMAGE
-> ExternalSecret
-> rookies5-macta-infra-config Secret
-> ssm-annotation-patch-job
-> kubectl set image
프론트엔드와 백엔드는 Kubernetes Deployment의 Rolling Update 방식을 사용합니다. 이미지 태그가 변경되거나 Pod template이 변경되면 Kubernetes가 새 ReplicaSet을 만들고, 기존 Pod를 한 번에 모두 내리지 않고 순차적으로 새 Pod로 교체합니다.
현재 설정:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0적용 위치:
k8s/frontend/frontend.yaml
k8s/backend/backend.yaml
동작 방식:
1. 현재 frontend/backend Pod는 각각 2개 replica로 실행
2. 새 이미지 태그가 manifest에 반영됨
3. Argo CD sync 또는 kubectl apply가 Deployment 변경을 적용
4. Kubernetes가 새 ReplicaSet 생성
5. maxSurge: 1 설정에 따라 기존 2개 Pod 위에 새 Pod 1개를 추가로 생성
6. readinessProbe가 성공해 새 Pod가 Ready 상태가 되면 Service 트래픽 대상에 포함
7. maxUnavailable: 0 설정에 따라 Ready Pod 수를 유지하면서 기존 Pod 1개 종료
8. 같은 과정을 반복해 모든 Pod를 새 버전으로 교체
maxSurge: 1은 업데이트 중 원하는 replica 수보다 Pod를 최대 1개 더 만들 수 있다는 의미입니다. replicas: 2 기준으로 업데이트 중 일시적으로 최대 3개 Pod가 실행될 수 있습니다.
maxUnavailable: 0은 업데이트 중 사용 가능한 Pod 수를 줄이지 않겠다는 의미입니다. 새 Pod가 Ready 되기 전에는 기존 Pod를 먼저 종료하지 않으므로, 배포 중 서비스 중단 가능성을 줄입니다.
따라서 새 버전 Pod가 이미지 오류, 설정 오류, 애플리케이션 기동 실패 등으로 Ready 상태가 되지 못하면 기존 Pod가 계속 유지됩니다. 이 경우 Rolling Update가 중간에서 멈추고 Service는 기존 Ready Pod로 트래픽을 계속 전달하므로, 실패한 배포가 곧바로 서비스 중단으로 이어지지 않습니다.
readinessProbe는 새 Pod를 Service 트래픽에 넣어도 되는지 판단하는 기준입니다.
frontend: HTTP GET /, port 80, initialDelaySeconds 10, periodSeconds 5
backend: TCP socket 8080, initialDelaySeconds 30, periodSeconds 10
배포 상태 확인:
kubectl rollout status deployment/rookies5-macta-frontend -n rookies5-macta
kubectl rollout status deployment/rookies5-macta-backend -n rookies5-mactaReplicaSet과 Pod 교체 과정 확인:
kubectl get rs -n rookies5-macta
kubectl get pods -n rookies5-macta -w문제가 생겼을 때 이전 버전으로 롤백:
kubectl rollout undo deployment/rookies5-macta-frontend -n rookies5-macta
kubectl rollout undo deployment/rookies5-macta-backend -n rookies5-macta
kubectl get pods -n rookies5-macta
kubectl get svc -n rookies5-macta
kubectl get ingress -n rookies5-macta
kubectl describe ingress rookies5-macta-frontend-ingress -n rookies5-macta
kubectl get externalsecret -n rookies5-macta
kubectl get secret backend-secret -n rookies5-macta
kubectl get secret rookies5-macta-infra-config -n rookies5-mactaALB 접속:
http://<alb-dns>
https://macta.store
주의:
- ALB 기본 DNS로 HTTPS 접속하면 인증서 mismatch 경고가 날 수 있습니다.
- 최종 HTTPS 검증은
https://macta.store로 합니다. macta.store가 DNS 오류를 내면 Route53의macta.store A Alias -> ALB레코드와 도메인 네임서버 위임을 확인합니다.
다음 파일은 Git에 올리지 않습니다.
terraform/.terraform/terraform/terraform.tfstateterraform/terraform.tfstate.backupterraform/*.tfvars- Terraform plan output