Skip to main content

EKS 배포 가이드: NestJS, NextJS, PostgreSQL

AWS EKS를 사용하여 NestJS 백엔드, NextJS 프론트엔드, PostgreSQL 데이터베이스를 배포하는 것은 일반적인 클라우드 네이티브 아키텍처입니다. 이는 여러 구성 요소와 단계를 포함하며, Kubernetes의 이점을 활용하여 확장성, 복원력 및 관리 용이성을 제공합니다.

다음은 배포를 위한 주요 구성 요소와 단계입니다.


0. 필수 도구 설치 및 AWS CLI 설정

EKS 클러스터 배포 및 관리를 위해 다음 도구들이 로컬 환경에 설치되어 있어야 합니다. 설치되어 있지 않다면 Homebrew(macOS 기준)를 사용하여 설치합니다.

  • aws-cli: AWS 리소스 관리 (ECR, IAM 등)
    brew install awscli
    aws configure # Access Key, Secret Key, Default region (예: ap-northeast-1), Default output format (json) 설정
  • kubectl: Kubernetes 클러스터 제어
    brew install kubectl
  • eksctl: EKS 클러스터 생성 및 관리
    brew install eksctl
  • docker: 애플리케이션 컨테이너화
    # Docker Desktop 설치 및 실행 확인
  • helm: Kubernetes 패키지 관리자 (Load Balancer Controller 배포에 사용)
    brew install helm

1. 아키텍처 개요

구성 요소기술/서비스역할
컨테이너 오케스트레이션AWS EKS (Kubernetes)NestJS, NextJS 애플리케이션 컨테이너를 관리, 배포, 확장
백엔드 애플리케이션NestJSREST API 또는 GraphQL 서비스 제공
프론트엔드 애플리케이션NextJS사용자 인터페이스 제공 (SSR/SSG/ISR 포함)
데이터베이스PostgreSQL애플리케이션 데이터 저장 및 관리
컨테이너 이미지 저장소AWS ECR (Elastic Container Registry)NestJS 및 NextJS Docker 이미지 저장
데이터베이스 서비스AWS RDS for PostgreSQL (권장) 또는 EKS 내부의 컨테이너화된 PostgreSQL영구적이고 관리되는 데이터베이스 인스턴스 제공
로드 밸런싱 및 접속AWS ALB/NLB (Application/Network Load Balancer), Kubernetes Service, Ingress외부 트래픽을 EKS의 서비스로 라우팅

2. 배포 단계별 상세 가이드

단계 1: 프로젝트 컨테이너화 및 이미지 저장 (ECR)

애플리케이션을 EKS에 배포하려면 먼저 Docker 이미지로 컨테이너화하고, AWS ECR(Elastic Container Registry)에 저장해야 합니다.

  1. Dockerfile 작성

    • 백엔드 (backend-vivid-ai) 멀티-스테이지 빌드를 사용하여 최종 이미지 크기를 최소화하고 효율성을 높입니다. backend-vivid-ai/Dockerfile 파일을 생성합니다.

      # ---- 1. Builder 스테이지: 앱 빌드 ----
      FROM node:22-alpine AS builder

      # 작업 디렉토리 설정
      WORKDIR /usr/src/app

      # package.json과 package-lock.json을 먼저 복사하여 의존성 캐싱 활용
      COPY package*.json ./

      # 의존성 설치
      RUN npm install

      # 소스 코드 전체 복사
      COPY . .

      # 애플리케이션 빌드
      RUN npm run build

      # 프로덕션용 의존성만 남기기 (개발용 의존성 제거)
      RUN npm prune --production


      # ---- 2. Runner 스테이지: 최종 이미지 생성 ----
      FROM node:22-alpine

      # 작업 디렉토리 설정
      WORKDIR /usr/src/app

      # Builder 스테이지에서 프로덕션용 의존성만 복사
      COPY --from=builder /usr/src/app/node_modules ./node_modules

      # Builder 스테이지에서 빌드된 결과물만 복사
      COPY --from=builder /usr/src/app/dist ./dist

      # 애플리케이션이 3000번 포트를 사용하므로 외부에 노출
      EXPOSE 3000

      # 컨테이너 시작 시 실행될 명령어
      CMD ["node", "dist/main"]
    • 프론트엔드 (frontend-vivid-ai) Next.js의 output: 'standalone' 기능을 활용하여 이미지 크기를 최적화합니다. 먼저 frontend-vivid-ai/next.config.ts 파일을 수정합니다.

      import type { NextConfig } from "next";

      const nextConfig: NextConfig = {
      /* config options here */
      output: 'standalone',
      };

      export default nextConfig;

      이후 frontend-vivid-ai/Dockerfile 파일을 생성합니다.

      # ---- 1. Builder 스테이지: 앱 빌드 ----
      FROM node:22-alpine AS builder

      # 작업 디렉토리 설정
      WORKDIR /app

      # 빌드 인수를 선언하고 환경 변수로 설정합니다.
      ARG NEXT_PUBLIC_API_URL
      ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

      # package.json과 package-lock.json을 먼저 복사
      COPY package*.json ./

      # 의존성 설치
      RUN npm install

      # 소스 코드 전체 복사
      COPY . .

      # 애플리케이션 빌드 (standalone output 생성)
      RUN npm run build


      # ---- 2. Runner 스테이지: 최종 이미지 생성 ----
      FROM node:22-alpine

      # 작업 디렉토리 설정
      WORKDIR /app

      # 프로덕션 환경 변수 설정
      ENV NODE_ENV=production

      # Builder 스테이지에서 생성된 standalone 폴더 내용 복사
      COPY --from=builder /app/.next/standalone ./

      # 정적 에셋을 위해 public 폴더 복사
      COPY --from=builder /app/public ./public

      # 최적화된 이미지, 폰트 등을 위해 .next/static 폴더 복사
      COPY --from=builder /app/.next/static ./.next/static

      # Next.js 기본 포트인 3000번 노출
      EXPOSE 3000

      # standalone 모드에서 서버를 실행하는 명령어
      CMD ["node", "server.js"]
  2. ECR 리포지토리 생성 및 Docker 이미지 푸시

    • ECR 리포지토리 생성 ap-northeast-1 리전에 백엔드와 프론트엔드용 ECR 리포지토리를 생성합니다.

      aws ecr create-repository --repository-name backend-vivid-ai --region ap-northeast-1
      aws ecr create-repository --repository-name frontend-vivid-ai --region ap-northeast-1
    • Docker 로그인 ECR에 이미지를 푸시하기 위해 Docker 클라이언트를 ECR에 로그인합니다.

      aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 194722419257.dkr.ecr.ap-northeast-1.amazonaws.com
    • Docker 이미지 빌드 및 푸시 트러블슈팅: no match for platform in manifest: not found (아키텍처 불일치) 로컬 개발 환경(예: Apple Silicon Mac)과 EKS 워커 노드(AMD64)의 아키텍처가 다를 경우 발생하는 오류입니다. docker build--platform linux/amd64 옵션을 명시하여 워커 노드 아키텍처에 맞는 이미지를 빌드해야 합니다.

      트러블슈팅: 403 Forbidden (ECR 로그인 만료) Docker 로그인 토큰이 만료되었을 경우 발생합니다. aws ecr get-login-password 명령어를 다시 실행하여 로그인 토큰을 갱신해야 합니다.

      백엔드 이미지 빌드 및 푸시:

      cd backend-vivid-ai
      docker build --platform linux/amd64 -t backend-vivid-ai .
      && docker tag backend-vivid-ai:latest 194722419257.dkr.ecr.ap-northeast-1.amazonaws.com/backend-vivid-ai:latest
      && docker push 194722419257.dkr.ecr.ap-northeast-1.amazonaws.com/backend-vivid-ai:latest
      cd ..

      프론트엔드 이미지 빌드 및 푸시:

      cd frontend-vivid-ai
      docker build --platform linux/amd64 \
      --build-arg NEXT_PUBLIC_API_URL=http://k8s-default-autoprom-179b96464f-1356167102.ap-northeast-1.elb.amazonaws.com/api \
      -t frontend-vivid-ai .
      && docker tag frontend-vivid-ai:latest 194722419257.dkr.ecr.ap-northeast-1.amazonaws.com/frontend-vivid-ai:latest
      && docker push 194722419257.dkr.ecr.ap-northeast-1.amazonaws.com/frontend-vivid-ai:latest
      cd ..

단계 2: PostgreSQL 데이터베이스 설정 (RDS 권장)

EKS 환경에서 영구적인 데이터베이스를 운영하는 것은 복잡하며, 백업, 복구, 확장성, 보안 관점에서 AWS RDS for PostgreSQL을 사용하는 것이 일반적이고 권장됩니다.

  1. AWS RDS 인스턴스 생성: (기존 내용 유지)

    • AWS RDS 콘솔에 접속합니다. (도쿄 리전으로 자동 선택)
    • "데이터베이스 생성" 버튼을 클릭합니다.
    • **"표준 생성"**과 엔진 유형으로 **"PostgreSQL"**을 선택합니다.
    • 템플릿 섹션에서 비용 절감을 위해 **"프리 티어"**를 선택합니다. (프리 티어 사용이 불가능한 경우, 가장 작은 사양으로 진행)
    • 설정 섹션에서 다음을 입력합니다.
      • DB 인스턴스 식별자: vivid-ai-db 와 같이 식별하기 쉬운 이름을 입력합니다.
      • 마스터 사용자 이름: postgres 또는 원하는 사용자 이름을 입력합니다.
      • 마스터 암호: 데이터베이스에 접속할 암호를 입력하고, 반드시 이 암호를 안전한 곳에 기록해 둡니다. Kubernetes Secret 설정 시 필요합니다.
    • 연결 섹션에서 다음을 설정합니다.
      • VPC: 나중에 EKS 클러스터를 생성할 VPC를 선택해야 합니다. 지금은 기본(default) VPC를 선택해도 괜찮습니다.
      • 퍼블릭 액세스: "아니요" 를 선택하여 데이터베이스가 외부 인터넷에 노출되지 않도록 합니다.
    • "데이터베이스 생성" 버튼을 눌러 생성을 시작합니다. (생성까지 몇 분 소요)
  2. 데이터베이스 엔드포인트 선택: (기존 내용 유지) RDS 인스턴스 생성 후, 두 가지 엔드포인트가 보일 수 있습니다. 백엔드 애플리케이션은 쓰기/읽기 작업을 모두 수행해야 하므로, Writer 엔드포인트를 사용해야 합니다.

    • Writer 엔드포인트 (쓰기/읽기 가능): vivid-ai-db-dev.cluster-c5ockyckitzu.ap-northeast-1.rds.amazonaws.com (예시) - 이 엔드포인트를 사용합니다.
    • Reader 엔드포인트 (읽기 전용): vivid-ai-db-dev.cluster-ro-c5ockyckitzu.ap-northeast-1.rds.amazonaws.com (예시) - 읽기 전용 복제본에 사용됩니다.
  3. 연결 정보 관리: 데이터베이스 연결 문자열 (호스트, 포트, 사용자, 비밀번호)은 Kubernetes의 Secret 객체를 사용하여 안전하게 관리합니다.

    • SSL 연결: 백엔드 애플리케이션은 RDS에 SSL을 사용하여 연결합니다. app.module.tsssl: { rejectUnauthorized: false }로 TypeORM을 구성하며, POSTGRES_SSL_ENABLED 환경 변수 (true로 설정)에 의해 제어됩니다.

단계 3: EKS 클러스터 설정

  1. VPC 및 서브넷 준비: EKS 클러스터에 필요한 VPC, 퍼블릭/프라이빗 서브넷, 인터넷 게이트웨이, NAT 게이트웨이를 준비합니다. (eksctl이 대부분 자동 처리)

  2. EKS 클러스터 생성: eksctl 명령어를 사용하여 EKS 클러스터를 생성합니다. vivid-ai-cluster-dev라는 이름으로 ap-northeast-1 리전에 t3.medium 인스턴스 타입의 워커 노드 2개를 생성합니다.

    eksctl create cluster \
    --name vivid-ai-cluster-dev \
    --region ap-northeast-1 \
    --node-type t3.medium \
    --nodes 2 \
    --nodes-min 1 \
    --nodes-max 3
    • 트러블슈팅: AccessDeniedException (IAM 권한 부족) eksctl을 실행하는 IAM 사용자에게 EKS 클러스터 생성에 필요한 권한이 없을 때 발생합니다. eksctl은 EKS뿐만 아니라 CloudFormation, VPC, EC2, IAM 등 다양한 AWS 리소스를 생성하므로 광범위한 권한이 필요합니다. 해결: eks-deploy-vivid-ai 사용자에게 다음 AWS 관리형 정책과 인라인 정책을 추가합니다.
      • 관리형 정책: AWSCloudFormationFullAccess, AmazonVPCFullAccess, AmazonEC2FullAccess, IAMFullAccess
      • 인라인 정책 (EKS 관련):
        {
        "Version": "2012-10-17",
        "Statement": [
        {
        "Effect": "Allow",
        "Action": "eks:*",
        "Resource": "*"
        }
        ]
        }
  3. 워커 노드 구성: 애플리케이션을 실행할 EC2 인스턴스 기반의 노드 그룹이 클러스터 생성 시 함께 구성됩니다.

    • 트러블슈팅: AWS 콘솔에서 EKS 클러스터 접근 불가 클러스터 생성 후 AWS 콘솔에서 EKS 클러스터에 접근하려 할 때 현재 IAM 보안 주체가 이 클러스터에 있는 Kubernetes 객체에 액세스할 수 없습니다. 메시지가 나타날 수 있습니다. 이는 클러스터를 생성한 IAM 사용자(eks-deploy-vivid-ai)만이 초기에 관리자 권한을 가지기 때문입니다. 해결: AWS 콘솔에 로그인한 사용자에게도 클러스터 관리자 권한을 부여해야 합니다. kubectl edit configmap aws-auth -n kube-system 명령어를 사용하여 mapUsers: 섹션에 콘솔 로그인 사용자의 ARN을 system:masters 그룹으로 추가합니다.
      # kubectl edit configmap aws-auth -n kube-system 실행 후
      data:
      mapRoles: |
      # ... 기존 내용 ...
      mapUsers: |
      - userarn: arn:aws:iam::YOUR_ACCOUNT_ID:user/YOUR_CONSOLE_LOGIN_USERNAME
      username: YOUR_CONSOLE_LOGIN_USERNAME
      groups:
      - system:masters

단계 4: Kubernetes Manifest 작성 및 배포

NestJS와 NextJS 애플리케이션을 EKS에 배포하기 위한 YAML 파일을 작성하고 클러스터에 적용합니다.

  1. Secret (secret.yaml): RDS 연결 정보, 환경 변수 등을 포함하는 **Secret**을 생성합니다. 이 파일은 민감한 정보를 포함하므로 .gitignore에 추가하여 Git에 추적되지 않도록 합니다.

    # secret.yaml (프로젝트 루트에 생성)
    apiVersion: v1
    kind: Secret
    metadata:
    name: vivid-ai-secret

type: Opaque data:

아래 값들은 Base64로 인코딩된 값이어야 합니다.

echo -n 'YOUR_VALUE' | base64

DB_HOST: ZGItYXV0b3Byb21ha2VyLWFpLXBvc3RncmVzLXJkcy5jNW9ja3lja2l0enUuYXAtbm9ydGhlYXN0LTEucmRzLmFtYXpvbmF3cy5jb20= DB_PORT: NTQzMg== # 5432 DB_USERNAME: cG9zdGdyZXM= DB_PASSWORD: YOUR_DATABASE_PASSWORD_BASE64 # 실제 DB 비밀번호를 Base64 인코딩하여 채워주세요。 DB_DATABASE: cG9zdGdyZXM= # postgres JWT_SECRET: c3VwZXItc2VjcmV0LWp3dC1rZXktdGhhdC1pcy1sb25nLWFuZC1jb21wbGV4LWZvci1kZXZlbG9wbWVudC1lbnZpcm9ubWVudA== # 업데이트된 JWT_SECRET


```bash
# secret.yaml 파일에 실제 값 채운 후 적용
kubectl apply -f secret.yaml
  1. Deployment (backend-deployment-dev.yaml, frontend-deployment-dev.yaml):

    • 백엔드 Deployment (backend-deployment-dev.yaml)

      # backend-deployment-dev.yaml (프로젝트 루트에 생성)
      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: backend-deployment-dev
      spec:
      replicas: 2
      selector:
      matchLabels:
      app: backend
      template:
      metadata:
      labels:
      app: backend
      spec:
      containers:
      - name: backend-container
      image: 194722419257.dkr.ecr.ap-northeast-1.amazonaws.com/backend-vivid-ai:latest
      ports:
      - containerPort: 3000
      env:
      - name: NODE_ENV # 환경 구분 (development, production)
      value: development
      - name: CORS_ORIGIN # 프론트엔드 ALB DNS
      value: http://k8s-default-autoprom-179b96464f-1356167102.ap-northeast-1.elb.amazonaws.com
      - name: SECURE_COOKIES # 쿠키 secure 속성 제어 (true/false)
      value: "false"
      - name: POSTGRES_SSL_ENABLED # PostgreSQL SSL 연결 제어 (true/false)
      value: "true"
      - name: POSTGRES_HOST
      valueFrom:
      secretKeyRef:
      name: vivid-ai-secret
      key: DB_HOST
      - name: POSTGRES_PORT
      valueFrom:
      secretKeyRef:
      name: vivid-ai-secret
      key: DB_PORT
      - name: POSTGRES_USER
      valueFrom:
      secretKeyRef:
      name: vivid-ai-secret
      key: DB_USERNAME
      - name: POSTGRES_PASSWORD
      valueFrom:
      secretKeyRef:
      name: vivid-ai-secret
      key: DB_PASSWORD
      - name: POSTGRES_DB
      valueFrom:
      secretKeyRef:
      name: vivid-ai-secret
      key: DB_DATABASE
      - name: JWT_SECRET
      valueFrom:
      secretKeyRef:
      name: vivid-ai-secret
      key: JWT_SECRET
    • 프론트엔드 Deployment (frontend-deployment-dev.yaml)

      # frontend-deployment-dev.yaml (프로젝트 루트에 생성)
      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: frontend-deployment-dev
      spec:
      replicas: 2
      selector:
      matchLabels:
      app: frontend
      template:
      metadata:
      labels:
      app: frontend
      spec:
      containers:
      - name: frontend-container
      image: 194722419257.dkr.ecr.ap-northeast-1.amazonaws.com/frontend-vivid-ai:latest
      ports:
      - containerPort: 3000
      env:
      - name: NEXT_PUBLIC_API_URL # 프론트엔드에서 백엔드 API 호출 시 사용
      value: http://k8s-default-autoprom-179b96464f-1356167102.ap-northeast-1.elb.amazonaws.com/api
    # Deployment 적용
    kubectl apply -f backend-deployment-dev.yaml
    kubectl apply -f frontend-deployment-dev.yaml
    • 트러블슈팅: ImagePullBackOff 또는 ErrImagePull (ECR 권한 부족 또는 아키텍처 불일치) 워커 노드가 ECR에서 이미지를 가져오지 못할 때 발생합니다. 주로 워커 노드의 IAM 역할에 ECR 읽기 권한(AmazonEC2ContainerRegistryReadOnly)이 없거나, 이미지 아키텍처가 워커 노드와 맞지 않을 때 발생합니다。 해결:
      1. 워커 노드의 IAM 역할 이름 확인 (예: eksctl-vivid-ai-cluster-dev-NodeInstanceRole-XXXXXX)
        eksctl get nodegroup --cluster vivid-ai-cluster-dev --region ap-northeast-1 -o json
      2. 정책 연결
        aws iam attach-role-policy --role-name YOUR_NODE_INSTANCE_ROLE_NAME --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
      3. --platform linux/amd64 옵션을 사용하여 이미지를 다시 빌드하고 ECR에 푸시합니다. (단계 1 참조)
      4. 기존 Pod들을 삭제하여 Kubernetes가 새 이미지로 Pod를 다시 생성하도록 합니다。
        kubectl delete pod -l app=backend
        kubectl delete pod -l app=frontend
  2. Service (backend-service-dev.yaml, frontend-service-dev.yaml):

    • 백엔드 Service (backend-service-dev.yaml)
      # backend-service-dev.yaml (프로젝트 루트에 생성)
      apiVersion: v1
      kind: Service
      metadata:
      name: backend-service

spec: selector: app: backend ports:

  • protocol: TCP port: 3000 targetPort: 3000 type: ClusterIP # 클러스터 내부에서만 접근 가능

* **프론트엔드 Service (`frontend-service-dev.yaml`)**
Ingress를 통해 외부 접근을 관리할 것이므로 `ClusterIP` 타입으로 변경합니다.
```yaml
# frontend-service-dev.yaml (프로젝트 루트에 생성)
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIP # 클러스터 내부에서만 접근 가능
# Service 적용
kubectl apply -f backend-service-dev.yaml
kubectl apply -f frontend-service-dev.yaml
  1. Ingress (ingress-dev.yaml): 외부 트래픽을 처리하고 라우팅하기 위해 **Ingress**를 생성합니다. AWS 환경에서는 AWS Load Balancer Controller를 설치하여 Ingress 객체가 자동으로 AWS ALB를 프로비저닝하도록 구성합니다.

    • AWS Load Balancer Controller 설치

      1. IAM 정책 생성: Load Balancer Controller가 AWS 리소스(ALB 등)를 관리할 수 있도록 IAM 정책을 생성합니다.
        curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.7.1/docs/install/iam_policy.json
        aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
      2. IAM OIDC 공급자 연결: eksctl을 사용하여 클러스터에 IAM OIDC 공급자를 연결합니다.
        eksctl utils associate-iam-oidc-provider --region=ap-northeast-1 --cluster=vivid-ai-cluster-dev --approve
      3. IAM 서비스 계정 생성: 위 정책을 사용할 Kubernetes 서비스 계정을 생성하고, 이 서비스 계정에 IAM 역할을 연결합니다.
        eksctl create iamserviceaccount \
        --cluster=vivid-ai-cluster-dev \
        --namespace=kube-system \
        --name=aws-load-balancer-controller \
        --attach-policy-arn=arn:aws:iam::YOUR_ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy \
        --override-existing-serviceaccounts \
        --approve \
        --region=ap-northeast-1
      4. Helm 설치: Helm이 설치되어 있지 않다면 설치합니다.
        brew install helm
      5. 컨트롤러 배포: Helm을 사용하여 Load Balancer Controller를 클러스터에 배포합니다.
        helm repo add eks https://aws.github.io/eks-charts
        helm repo update
        helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
        -n kube-system \
        --set clusterName=vivid-ai-cluster-dev \
        --set serviceAccount.create=false \
        --set serviceAccount.name=aws-load-balancer-controller \
        --set image.repository=public.ecr.aws/eks/aws-load-balancer-controller \
        --set region=ap-northeast-1
    • Ingress 리소스 (ingress-dev.yaml) 임시 도메인 사용을 위해 경로 기반 라우팅을 설정합니다.

      # ingress-dev.yaml (프로젝트 루트에 생성)
      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
      name: autopromomer-ai-ingress
      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}]'
      alb.ingress.kubernetes.io/healthcheck-path-backend-service-dev: /api/health # 백엔드 헬스 체크 경로
      alb.ingress.kubernetes.io/healthcheck-path-frontend-service-dev: /health # 프론트엔드 헬스 체크 경로

spec: rules:

  • http: paths:
    • path: /api pathType: Prefix backend: service: name: backend-service-dev port: number: 3000
    • path: / pathType: Prefix backend: service: name: frontend-service port: number: 80

```bash
# Ingress 적용
kubectl apply -f ingress-dev.yaml
  • 트러블슈팅: 403 Forbidden (ALB Controller 권한 부족) Load Balancer Controller가 elasticloadbalancing:DescribeListenerAttributes와 같은 AWS API를 호출할 권한이 없을 때 발생합니다。 해결: AWSLoadBalancerControllerIAMPolicy 정책에 누락된 권한을 추가하고, 정책 버전을 업데이트합니다。

    1. 정책 문서에 elasticloadbalancing:DescribeListenerAttributes 액션을 추가한 iam_policy_updated.json 파일을 생성합니다.
    2. aws iam create-policy-version --policy-arn arn:aws:iam::YOUR_ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy_updated.json --set-as-default 명령어로 새 정책 버전을 기본으로 설정합니다.
    3. 기존 Ingress를 삭제하고 재적용하여 컨트롤러가 업데이트된 정책을 사용하도록 합니다.
  • 트러블슈팅: Ingress 리소스가 삭제되지 않고 멈춤 (Finalizer 문제) kubectl delete ingress 명령 후 Ingress 리소스가 Terminating 상태로 멈춰있을 때 발생합니다. 이는 Ingress 리소스에 설정된 Finalizer 때문에 컨트롤러가 관련 AWS 리소스 정리를 기다리다가 멈춘 경우입니다。 해결: kubectl patch ingress autopromomer-ai-ingress -p '{"metadata":{"finalizers":null}}' --type=merge 명령어로 Ingress 리소스의 finalizers를 강제로 제거합니다. (ALB를 수동으로 삭제한 후 진행하는 것이 안전합니다.)


단계 5: 배포 및 확인

  1. Manifest 적용: kubectl apply -f [manifest 파일] 명령어로 Secret, Deployment, Service, Ingress 순서대로 배포합니다.

  2. 배포 상태 확인: kubectl get pods, kubectl get deployments, kubectl get svc, kubectl get ing 명령어로 모든 리소스가 정상적으로 실행 중인지 확인합니다.

  3. 접속 테스트: Ingress가 제공하는 엔드포인트(ALB DNS)를 통해 애플리케이션에 접속합니다.

    # Ingress의 ADDRESS 확인
    kubectl get ingress autopromomer-ai-ingress

    출력된 ADDRESS (예: k8s-default-autoprom-179b96464f-1356167102.ap-northeast-1.elb.amazonaws.com)를 사용하여 접속합니다.

    • 프론트엔드 접근 URL: http://<INGRESS_ALB_DNS_NAME>/
    • 백엔드 API 접근 URL: http://<INGRESS_ALB_DNS_NAME>/api

3. 핵심 고려 사항

  • 보안 (Security): RDS와 EKS 간의 통신은 프라이빗 서브넷을 통해 이루어져야 하며, 보안 그룹을 통해 최소한의 접근만 허용해야 합니다. DB 비밀번호는 반드시 Kubernetes Secret으로 관리해야 합니다.
  • 지속적 배포 (CD): 배포 과정을 자동화하기 위해 AWS CodePipeline/CodeBuild 또는 GitLab CI/GitHub Actions/ArgoCD 같은 CI/CD 도구를 구축하는 것이 좋습니다. 새로운 이미지 빌드 시 EKS의 Deployment를 자동으로 업데이트할 수 있습니다.
  • 비용 (Cost): EKS는 관리형 서비스 비용과 워커 노드의 EC2 비용이 발생합니다. NextJS와 NestJS의 트래픽 패턴과 리소스 요구 사항을 고려하여 노드 크기와 오토 스케일링을 설정해야 합니다.
  • 영구 저장소 (Persistent Storage): PostgreSQL을 EKS 내부에서 실행하는 경우 **PersistentVolume (PV)**과 **PersistentVolumeClaim (PVC)**이 필요하며, AWS에서는 EBS 기반의 StorageClass를 사용해야 합니다. 하지만 RDS 사용을 강력히 권장합니다.

4. 환경 변수 관리 및 인증

  • 환경 구분: NODE_ENV 환경 변수를 사용하여 production, development, local 환경을 명확하게 구분합니다.
    • production: 실제 운영 서버
    • development: EKS 개발 서버 (현재 배포된 서버)
    • local: 로컬 개발 환경
  • 백엔드 환경 변수 (backend-deployment-dev.yaml):
    • NODE_ENV: development (EKS 개발 서버용).
    • CORS_ORIGIN: ALB DNS 이름으로 설정됩니다 (예: http://<ALB_DNS>).
    • SECURE_COOKIES: false (현재 HTTP 접속용; HTTPS 시 true로 설정).
    • POSTGRES_SSL_ENABLED: true (RDS SSL 연결용).
    • JWT_SECRET: 토큰 서명/검증을 위한 길고 복잡한 문자열.
  • 프론트엔드 환경 변수 (.env.production / Docker build-arg):
    • NEXT_PUBLIC_API_URL: /api 접두사를 포함한 ALB DNS 이름으로 설정됩니다 (예: http://<ALB_DNS>/api).
  • 인증 (JWT & 쿠키):
    • JWT 시크릿: JWT_SECRET 환경 변수에 의해 관리됩니다.
    • 쿠키 구성 (auth.controller.ts):
      • httpOnly: true
      • secure: SECURE_COOKIES 환경 변수에 따라 조건부로 설정됩니다.
      • sameSite: secure 속성에 따라 조건부로 설정됩니다 (secure 시 none, 비secure 시 lax) - 크로스-사이트 컨텍스트에서 브라우저의 쿠키 거부를 방지합니다.

7. 프라이빗 RDS 데이터베이스 접속 (SSH 터널링)

보안을 위해 RDS 데이터베이스는 프라이빗 서브넷에 배포되어 인터넷에서 직접 접근할 수 없습니다. 데이터베이스를 GUI 도구(예: PgAdmin)로 관리해야 할 경우, VPC 내부에 있는 **배스천 호스트(Bastion Host)**를 통해 우회하여 접속해야 합니다.

단계 1: 배스천 호스트 EC2 인스턴스 생성

  1. AWS EC2 콘솔에서 **"인스턴스 시작"**을 클릭합니다.
  2. 이름: vivid-ai-bastion-dev
  3. AMI: Amazon Linux 2023 AMI
  4. 인스턴스 유형: t2.micro (프리 티어)
  5. 키 페어: **"새 키 페어 생성"**을 통해 .pem 형식의 키 페어(vivid-ai-bastion-key.pem)를 생성하고, 다운로드하여 안전하게 보관합니다.
  6. 네트워크 설정:
    • VPC: EKS 클러스터와 동일한 VPC(vpc-05d26f998a53b5067)를 선택합니다.
    • 서브넷: 이름에 **Public**이 포함된 서브넷을 선택합니다.
    • 퍼블릭 IP 자동 할당: "활성화(Enable)"
    • 방화벽(보안 그룹): "보안 그룹 생성"을 선택하고, 인바운드 규칙으로 유형: SSH, 소스: 내 IP를 설정합니다. 보안 그룹 이름은 vivid-ai-bastion-sg로 지정합니다.
  7. **"인스턴스 시작"**을 클릭합니다.

단계 2: 보안 그룹 및 RDS 설정 변경

  1. RDS 보안 그룹 수정 (vivid-ai-development-rds-sg):
    • **"인바운드 규칙 편집"**으로 이동합니다.
    • 기존의 PostgreSQL 규칙(소스: "내 IP" 또는 0.0.0.0/0)을 삭제합니다.
    • **"규칙 추가"**를 클릭하고 유형: PostgreSQL, 소스: 위에서 생성한 배스천의 보안 그룹(vivid-ai-bastion-sg)을 선택합니다.
  2. RDS 퍼블릭 액세스 비활성화:
    • RDS 인스턴스 "수정" 페이지로 이동합니다.
    • "연결" 섹션에서 **"퍼블릭 액세스"**를 **"아니요(No)"**로 변경하고 즉시 적용합니다.

단계 3: GUI 도구(PgAdmin)에 SSH 터널 설정

  1. Connection 탭:
    • Hostname/address: RDS 엔드포인트 주소
    • Port: 5432
    • Maintenance database: postgres
    • Username/Password: RDS 마스터 사용자 정보
  2. SSH Tunnel 탭:
    • Use SSH tunneling?: "Yes"
    • Tunnel host: 배스천 호스트의 퍼블릭 IPv4 주소
    • Tunnel port: 22
    • Username: ec2-user
    • Authentication: "Identity file" 선택 후, 다운로드한 .pem 키 파일 지정
  3. Parameters 탭:
    • SSL mode: Require
  4. 설정을 저장하고 접속합니다.

8. 문제 해결 노트

  • Ingress rewrite-target 미작동: rewrite-target이 적용되지 않을 경우, 백엔드를 글로벌 접두사로 구성하고 Ingress에서 어노테이션을 제거합니다.

  • Ingress 삭제 지연: Ingress가 Terminating 상태로 멈출 경우, AWS 콘솔에서 연결된 ALB를 수동으로 삭제한 후 kubectl patch ingress autopromomer-ai-ingress -p '{"metadata":{"finalizers":null}}' --type=merge 명령으로 Kubernetes 리소스의 finalizers를 강제로 제거합니다.

  • no pg_hba.conf entry... no encryption: POSTGRES_SSL_ENABLED=trueapp.module.tsssl: { rejectUnauthorized: false }가 활성화되어 있는지 확인합니다.

  • 401 Unauthorized (쿠키 미전송): auth.controller.tsSECURE_COOKIESsameSite 설정을 HTTP/HTTPS 접속 환경에 맞게 확인합니다.

  • 403 Forbidden (ECR 로그인 만료): Docker 로그인 토큰이 만료되었을 경우 발생합니다. aws ecr get-login-password 명령어를 다시 실행하여 로그인 토큰을 갱신해야 합니다.

  • no match for platform in manifest: not found (아키텍처 불일치): docker build--platform linux/amd64 옵션을 명시하여 워커 노드 아키텍처에 맞는 이미지를 빌드해야 합니다.

  • ImagePullBackOff 또는 ErrImagePull (ECR 권한 부족): 워커 노드의 IAM 역할에 AmazonEC2ContainerRegistryReadOnly 정책이 연결되어 있는지 확인합니다.

  • VPC CIDR 충돌로 인한 EKS 클러스터 재생성:

    • 원인: 기존 EKS 클러스터의 VPC CIDR(192.168.0.0/16)이 로컬 네트워크 대역과 충돌하여 네트워크 문제가 발생했습니다.

    • 해결: eksctl delete cluster로 기존 클러스터를 삭제하고, 10.12.0.0/16 CIDR을 사용하는 새로운 VPC 내에 eksctl create clustervivid-ai-cluster-development 클러스터를 재생성했습니다.

    • 주의: 클러스터 삭제 시 Ingress Controller가 프로비저닝한 ALB (finalizer 문제)로 인해 삭제가 지연될 수 있습니다. 이때는 AWS 콘솔에서 ALB를 수동으로 삭제한 후 클러스터 삭제를 재시도해야 합니다.

  • GitHub Actions ResourceNotFoundException (EKS 클러스터 이름 변경):

    • 원인: EKS 클러스터 이름이 vivid-ai-cluster-dev에서 vivid-ai-cluster-development로 변경되면서 GitHub Actions 워크플로우 내의 클러스터 이름 설정이 불일치했습니다.

    • 해결: backend-vivid-aifrontend-vivid-ai 각각의 .github/workflows/deploy.yml 파일에서 EKS_CLUSTER_NAME 환경 변수를 새로운 클러스터 이름으로 업데이트했습니다.

  • GitHub Actions AccessDeniedException (EKS IAM 정책 누락):

    • 원인: 배포 IAM 역할(GitHubActions-vivid-ai-dev-role)에 새로운 EKS 클러스터에 대한 eks:DescribeCluster 등 필수 권한이 없었습니다. aws-iam-authenticator 버전 업데이트 등으로 인해 eks:DescribeCluster 외 추가 권한이 필요해졌습니다.

    • 해결: EKS-Deploy-User-Policy IAM 정책에 eks:DescribeNodegroup, eks:ListClusters, eks:ListNodegroups 액션을 vivid-ai-cluster-development 클러스터 ARN에 대한 리소스로 추가했습니다.

  • GitHub Actions You must be logged in to the server (aws-auth ConfigMap):

    • 원인: EKS 클러스터의 aws-auth ConfigMap에 GitHub Actions에서 사용하는 IAM 역할(GitHubActions-vivid-ai-dev-role)이 system:masters 그룹으로 매핑되어 있지 않았습니다.

    • 해결: kubectl patch configmap aws-auth -n kube-system 명령을 사용하여 aws-auth ConfigMap에 GitHubActions-vivid-ai-dev-role 역할을 system:masters 그룹으로 매핑하는 userarnrolearn 항목을 추가했습니다. (동시에 AWS 콘솔 로그인 사용자의 ARN도 system:masters로 추가하여 콘솔 접근 권한 확보)

  • 백엔드 502 Bad Gateway (RDS/Redis/SQS/S3 연결 문제 종합):

    • 원인: 새로 생성된 VPC 및 EKS 클러스터 환경에서 RDS, ElastiCache Redis, SQS, S3 연결 정보 및 보안 그룹 설정이 최신 상태로 업데이트되지 않아 발생하는 총체적 문제였습니다.

    • 해결:

      • RDS: 새로운 EKS 워커 노드 보안 그룹(sg-084a140a753d6a7d0)을 RDS 보안 그룹(vivid-ai-development-rds-sg)의 인바운드 규칙에 추가하여 5432 포트 접근을 허용했습니다.

      • ElastiCache Redis: 새로운 ElastiCache Redis 클러스터(vivid-ai-redis-dev)를 생성하고, EKS 워커 노드 보안 그룹을 Redis 보안 그룹으로 설정했습니다. Redis 클라이언트의 connectTimeout을 10초로 늘리고, "전송 중 암호화"가 활성화되어 있는 점을 감안하여 Redis 연결 URL을 rediss:// 스키마 기반으로 변경했습니다 (app.module.ts 수정).

      • SQS: 테스트용 SQS 큐(vivid-ai-computation-queue-dev)를 생성하고, backend-deployment-dev.yaml에 해당 큐의 URL과 이름을 설정했습니다. generation.module.tsSQS_COMPUTATION_QUEUE_URL 의존성 활성화 코드를 다시 적용했습니다.

      • S3: vivid-ai-generated-dev S3 버킷을 생성하고, backend-deployment-dev.yaml에 버킷 이름을 설정했습니다.

      • 환경 변수: backend-deployment-dev.yaml이 .env 파일과 동기화되도록 필수 환경 변수를 추가/수정했습니다.

      • API 버전 관리: 백엔드 API에 /v1 접두사를 도입했으며, Ingress 헬스 체크 경로도 /api/v1로 변경했습니다.

  • 백엔드 Pod 0/1 Ready (Startup Probe 실패):

    • 원인: 애플리케이션 시작 시간이 startupProbe 설정보다 길거나, 시작 과정 중 특정 모듈(초기에는 Swagger 설정, 이후 Redis 연결 문제)에서 블로킹되어 헬스 체크에 응답하지 못했습니다.

    • 해결:

      • main.ts에서 app.listen() 호출 전후에 상세 로그를 추가하여 블로킹 지점을 파악했습니다.

      • Swagger 설정이 시작을 지연시키는 것을 확인하고, main.ts에서 Swagger를 임시 주석 처리했습니다. (나중에 Redis 연결 문제가 해결된 후 Swagger는 다시 활성화)

      • Redis 연결 문제가 해결된 후, startupProbefailureThreshold를 60으로 늘려 애플리케이션이 완전히 시작될 시간을 충분히 제공하여 이 문제를 최종적으로 해결했습니다. (프로브는 다시 활성화)

9. 배포 자동화 (CI/CD with GitHub Actions)

development 환경에 대한 배포는 GitHub Actions를 통해 자동화됩니다. 각 서브모듈(backend-vivid-ai, frontend-vivid-ai) 리포지토리의 development 브랜치에 코드가 푸시되면, 해당 애플리케이션의 빌드, ECR 푸시, EKS 배포가 자동으로 실행됩니다.

9.1. 워크플로우 파일 위치

  • 백엔드: backend-vivid-ai/.github/workflows/deploy.yml
  • 프론트엔드: frontend-vivid-ai/.github/workflows/deploy.yml

9.2. 인증 방식 (OIDC)

  • GitHub Actions는 AWS와 안전하게 통신하기 위해 **OIDC(OpenID Connect)**를 사용합니다. 이를 통해 장기 자격 증명(Access Key) 없이 임시 역할을 수임하여 보안을 강화합니다.
  • GitHubActionsOidcRole이라는 IAM 역할이 사용되며, 이 역할의 신뢰 정책은 각 서브모듈 리포지토리의 development 브랜치에서 오는 요청만 허용하도록 제한되어 있습니다.

9.3. GitHub Secrets

  • 각 서브모듈 리포지토리의 Settings > Secrets and variables > Actions에 다음 Secret들이 설정되어 있어야 합니다.
    • AWS_IAM_ROLE_ARN_DEV: 워크플로우가 수임할 IAM 역할의 ARN.
    • NEXT_PUBLIC_API_URL_DEV (프론트엔드 전용): 개발 서버 ALB의 API URL.

9.4. 배포 프로세스

  1. 트리거: development 브랜치에 코드가 푸시됩니다.
  2. AWS 인증: aws-actions/configure-aws-credentials 액션이 OIDC를 통해 IAM 역할을 수임하여 임시 자격 증명을 얻습니다.
  3. ECR 로그인: aws-actions/amazon-ecr-login 액션이 ECR에 로그인합니다.
  4. Docker 이미지 빌드 & 푸시:
    • 애플리케이션을 Docker 이미지로 빌드합니다. (--platform linux/amd64 사용)
    • 이미지에 Git 커밋 해시(github.sha)와 latest 태그를 붙여 ECR에 푸시합니다.
  5. EKS 클러스터 연결: aws-actions/amazon-eks-cluster 액션이 kubeconfig를 설정하여 kubectl이 클러스터와 통신할 수 있도록 합니다.
  6. 배포:
    • kubectl set image 명령을 사용하여 EKS의 Deployment가 새로운 이미지 태그를 사용하도록 업데이트합니다.
    • 이 명령은 Kubernetes의 롤링 업데이트를 트리거하여, 중단 없이 새로운 버전의 애플리케이션으로 점진적으로 교체합니다.