복잡한뇌구조마냥

[AWS] 배포 정리(EC2 + ECR + RDS + Nginx) 본문

BE/Infra

[AWS] 배포 정리(EC2 + ECR + RDS + Nginx)

지금해냥 2025. 10. 16. 16:37

✅ 1. IAM 및 권한 구성

1-1) GitHub Actions용 OIDC 공급자 등록

1-2) GitHub Actions용 IAM Role 생성

  • IAM → Roles → Create role
    • Trusted entity: Web identity
    • Provider: token.actions.githubusercontent.com
    • Audience: sts.amazonaws.com
    • 권한: AmazonEC2ContainerRegistryPowerUser
    • 이름: GitHubActionsECRAccessRole
신뢰 정책(Trust policy)**를 저장소/브랜치 단위로 최소화
🔒 기존에 repo:prgrms-aibe-devcourse/* 와일드카드로 열어둔 구성은 보안상 비권장.

✅ 2. ECR 레지스트리 생성

  • 콘솔 → ECR → Private → Create repository
    • Name: [레포지토리 이름]
    • Mutable tags: 허용
    • Scan on push: 활성화

✅ 3. GitHub Actions로 자동 푸시 설정

설정하면서 Repository가 대문자가 들어있으면 설정이 안되는 것 같아서 소문자 변환하는 로직을 추가했었습니다.
추가적으로 ECR 설정과 더불어 자동 EC2 배포까지 적용했었던 내용이라
ECR까지만 하실분은 remote-deploy 하단 부분은 생략해주세요.

.github/workflows/ecr-push.yml

name: Build & Push to ECR

on:
  push:
    branches: [ "main" ]        # dev에서도 실행

permissions:
  id-token: write
  contents: read

env:
  AWS_REGION: [AWS 지역설정]
  AWS_ACCOUNT_ID: [AWS Account ID]
  ECR_REPOSITORY: [레포지토리 이름]
  IMAGE_TAG: "${{ github.sha }}"

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    outputs:
      image_tag_final: ${{ steps.set-tag.outputs.image_tag_final }}
    steps:
      # 1️⃣ 소스 체크아웃
      - uses: actions/checkout@v4

      # 2️⃣ JDK 25 설정 (Gradle 빌드용)
      - name: Set up JDK 25
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '25'
          cache: gradle

      - name: Print expected OIDC sub
        run: |
          echo "EXPECTED_SUB=repo:${GITHUB_REPOSITORY,,}:ref:${GITHUB_REF}"

      # 3️⃣ Gradle 빌드 (Dockerfile이 단일 스테이지일 경우 필요)
      - name: Grant execute permission for Gradle
        run: chmod +x ./gradlew

      - name: Gradle Build Jar
        run: ./gradlew --no-daemon clean bootJar

      # 4️⃣ AWS OIDC 인증
      - name: Configure AWS creds (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::109936653790:role/GitHubActionsECRAccessRole
          aws-region: ${{ env.AWS_REGION }}
          audience: sts.amazonaws.com

      # 5️⃣ ECR 로그인
      - name: Login to ECR
        id: ecr
        uses: aws-actions/amazon-ecr-login@v2

      # 6️⃣ 레포지토리 이름 정규화 (그대로)
      - name: Normalize repository name
        run: |
          REPO="${ECR_REPOSITORY,,}"         # 소문자 변환
          REPO="${REPO//_/-}"                # _ → -
          echo "ECR_REPO_NORM=$REPO" >> $GITHUB_ENV

      # 7️⃣ (NEW) 태그 존재 여부 확인
      - name: Check if tag exists in ECR
        id: check-tag
        run: |
          set -euo pipefail
          if aws ecr describe-images --repository-name "$ECR_REPO_NORM" \
            --image-ids imageTag="${IMAGE_TAG}" >/dev/null 2>&1; then
            echo "exists=true" >> $GITHUB_OUTPUT
          else
            echo "exists=false" >> $GITHUB_OUTPUT
          fi

      - name: Set job output (IMAGE_TAG_FINAL)           # 👈 추가
        id: set-tag
        run: echo "image_tag_final=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"

      # 8️⃣ Build & Push — 태그가 없을 때만 수행
      - name: Build & Push (SHA tag only)
        if: steps.check-tag.outputs.exists == 'false'
        env:
          REGISTRY: ${{ steps.ecr.outputs.registry }}
        run: |
          echo "Building Docker image for commit ${{ env.IMAGE_TAG }}"
          docker build --pull --no-cache -t $REGISTRY/$ECR_REPO_NORM:${{ env.IMAGE_TAG }} .
          docker push $REGISTRY/$ECR_REPO_NORM:${{ env.IMAGE_TAG }}
  remote-deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS creds (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::109936653790:role/GitHubActionsECRAccessRole
          aws-region: ap-northeast-2
          audience: sts.amazonaws.com

      - name: Run deploy via SSM
        env:
          INSTANCE_ID: i-0a8f78a81cd34b4a8
          TAG: ${{ needs.build-and-push.outputs.image_tag_final }}
        run: |
          set -euo pipefail
          CMD_ID=$(aws ssm send-command \
            --instance-ids "$INSTANCE_ID" \
            --document-name "AWS-RunShellScript" \
            --comment "jobpick deploy $TAG" \
            --parameters 'commands=["/usr/local/bin/deploy_jobpick.sh '"$TAG"'"]' \
            --query "Command.CommandId" --output text)
          echo "SSM Command: $CMD_ID"

          # 진행상태 폴링
          for i in {1..40}; do
            STATUS=$(aws ssm list-command-invocations --command-id "$CMD_ID" --details \
              --query "CommandInvocations[0].Status" --output text || true)
            echo "Status: $STATUS"
            if [ "$STATUS" = "Success" ]; then break; fi
            if [ "$STATUS" = "Failed" ] || [ "$STATUS" = "Cancelled" ] || [ "$STATUS" = "TimedOut" ]; then
              # 실패 시 표준출력/에러를 바로 덤프해서 원인 확인
              aws ssm get-command-invocation --command-id "$CMD_ID" --instance-id "$INSTANCE_ID" \
                --query "StandardOutputContent" --output text || true
              echo "---- STDERR ----"
              aws ssm get-command-invocation --command-id "$CMD_ID" --instance-id "$INSTANCE_ID" \
                --query "StandardErrorContent" --output text || true
              exit 1
            fi
            sleep 5
          done

          # 성공시 마지막 출력도 한 번 보여주고 종료
          aws ssm get-command-invocation --command-id "$CMD_ID" --instance-id "$INSTANCE_ID" \
            --query "StandardOutputContent" --output text || true

✅ 4. EC2 인스턴스 생성 및 설정

4-1) 생성

  • Amazon Linux 2023 (x86_64), 인스턴스 유형: t3.small
  • 퍼블릭 서브넷 + 퍼블릭 IP 활성화
  • 보안그룹 (SG-EC2)
    • SSH(22): 내 IP
    • HTTP(80): 0.0.0.0/0
    • TCP(8080): 0.0.0.0/0 (테스트용, 나중에 삭제)

4-2) Docker/Nginx 설치

암호키로 인증해서 AWS 접속 후에 Docker와 Nginx를 설치했습니다.

sudo dnf update -y
sudo dnf install -y docker nginx
sudo systemctl enable --now docker
sudo usermod -aG docker ec2-user
exit
# 재접속 후
ssh -i jobpick-key.pem ec2-user@<EC2_PUBLIC_IP>

4-3) EC2 IAM Role 연결

  • Role: AmazonEC2ContainerRegistryReadOnly, AmazonSSMManagedInstanceCore
  • 연결 후 아래로 확인:
aws sts get-caller-identity
  •  

✅ 5. RDS (MySQL) 생성 및 연결

5-1) DB/계정 생성

설정할게 굉장히 많더군요..

5-2) DB 보안 규칙 EC2와 연결


 

✅ 6. S3 버킷 + EC2 권한 연결

6-1) 버킷 생성

6-2) EC2 역할에 인라인 정책 추가


✅ 7. 환경 변수 설정 (.env)

절대 깃 저장소에 커밋 금지. EC2에만 저장. 가능하면 SSM Parameter Store/Secrets Manager 사용 권장.
sudo mkdir -p /opt/jobpick
sudo nano /opt/jobpick/.env

예시

SPRING__PROFILES__ACTIVE=prod

# RDS
SPRING__DATASOURCE__URL=jdbc:mysql://<RDS_ENDPOINT>:3306/pidb?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
SPRING__DATASOURCE__USERNAME=jobpick
SPRING__DATASOURCE__PASSWORD=<STRONG_PASSWORD>

# JPA
SPRING__JPA__HIBERNATE__DDL_AUTO=update
SPRING__JPA__PROPERTIES__HIBERNATE__DIALECT=org.hibernate.dialect.MySQL8Dialect
SPRING__JPA__OPEN-IN-VIEW=false

# Mail (필요 시)
SPRING__MAIL__HOST=smtp.naver.com
SPRING__MAIL__PORT=587
SPRING__MAIL__USERNAME=<MAIL_ID>
SPRING__MAIL__PASSWORD=<MAIL_APP_PASSWORD>   # *** 회전/보안 저장 필수
SPRING__MAIL__PROPERTIES__MAIL__SMTP__AUTH=true
SPRING__MAIL__PROPERTIES__MAIL__SMTP__STARTTLS__ENABLE=true

# JWT
CUSTOM__JWT__SECRETKEY=<LONG_RANDOM_SECRET>  # *** 회전 필수

# S3
AWS__S3__BUCKET__NAME=<BUCKET_NAME>
AWS__REGION=ap-northeast-2

# Redis (나중에 사용 시)
# SPRING__REDIS__HOST=redis
# SPRING__REDIS__PORT=6379

✅ 8. Redis & Docker 네트워크 (선택)

docker network create jobpick-net || true
docker run -d --name jobpick-redis --network jobpick-net -p 127.0.0.1:6379:6379 redis:7-alpine

✅ 9. ECR Pull & 컨테이너 실행

aws ecr get-login-password --region ap-northeast-2 | \
docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.ap-northeast-2.amazonaws.com


docker rm -f jobpick || true


docker run -d --name jobpick \
--restart=always \
--env-file /opt/jobpick/.env \
-p 127.0.0.1:8080:8080 \
<ACCOUNT_ID>.dkr.ecr.ap-northeast-2.amazonaws.com/jobpick-backend:latest

10. Nginx 리버스 프록시 구성

sudo rm -f /etc/nginx/conf.d/default.conf
sudo tee /etc/nginx/conf.d/jobpick.conf <<'CONF'
server {
listen 80;
server_name _;


location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
CONF
sudo nginx -t && sudo systemctl enable --now nginx && sudo systemctl reload nginx

 

이후 8080 인바운드 포트는 삭제.

✅ 11. 테스트 및 배포 확인

  • curl http://<EC2_IP>/actuator/health → 200 OK
  • Nginx 포트 80 접속 → 앱 응답 확인
  • ECR → EC2 Pull 자동화까지 정상 확인

✅ 12. 보안 & 운영 권장사항

  • 비밀 정보 즉시 회전 (Mail, JWT, DB 등)
  • RDS Public Access 비활성화 유지
  • EC2 보안그룹 최소화 (22, 80만 유지)
  • S3 권한 최소화 (필요 버킷만 허용)
  • .env600 유지, 가능하면 AWS SSM Parameter Store로 이관
  • 필요 시 CloudWatch Logs, HTTPS(Let’s Encrypt) 적용

✅ 13. 도메인 연결

  • 도메인 구매후 EC2 퍼블릭 IP 연결하면 끝


📋 최종 순서 요약

1. IAM Role(OIDC + EC2) 설정
2. ECR 생성
3. GitHub Actions 자동 푸시 구성
4. EC2 생성 (Role 연결 + Docker/Nginx 설치)
5. RDS 생성 (EC2 SG 연결)
6. S3 생성 + EC2 권한 정책 추가
7. .env 작성 및 권한 설정
8. Redis(선택) + Docker 네트워크 구성
9. ECR Pull & 컨테이너 실행
10. Nginx Reverse Proxy 설정
11. 테스트 완료 후 8080 포트 폐쇄
12. (선택) CloudWatch / HTTPS / 자동배포 확장
13. 도메인 연결
LIST

'BE > Infra' 카테고리의 다른 글

Json-server, heroku, env (서버 배포)  (0) 2022.09.04