| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
Tags
- angular
- ES5
- 0.25px border
- 당근마켓
- 문서번호
- github
- TypeScript
- 1px border
- 10px
- Props
- Strict
- Websocket
- &연산
- 서버리스 #
- 클론코딩
- entity
- literal
- jwt
- 0.5px border
- es6
- TS
- 데이터베이스 #try #이중
- 으
- 0.75px border
- 전역변수
- npm
- font-size
- 컴포넌튼
- ZOOM
- 타입스크립트
Archives
- Today
- Total
복잡한뇌구조마냥
[AWS] 배포 정리(EC2 + ECR + RDS + Nginx) 본문
✅ 1. IAM 및 권한 구성

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

- 콘솔 → IAM → Identity providers → Add provider
- Provider URL: https://token.actions.githubusercontent.com
- Audience: sts.amazonaws.com
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 권한 최소화 (필요 버킷만 허용)
- .env는 600 유지, 가능하면 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 |
|---|