현대 웹 개발에서 지속적 통합과 지속적 배포(CI/CD)는 필수적인 개발 프로세스가 되었습니다.
특히 AWS EC2와 GitHub Actions를 결합한 자동 배포 파이프라인은 개발 생산성을 크게 향상시킬 수 있습니다.
이 글에서는 실제 프로젝트에 적용할 수 있는 완전한 배포 자동화 시스템을 단계별로 구축하는 방법을 자세히 알아보겠습니다.
GitHub Actions를 선택하는 이유
GitHub Actions는 GitHub과 완벽하게 통합된 CI/CD 플랫폼으로, 다음과 같은 장점을 제공합니다.
코드 저장소와의 완벽한 통합
별도의 외부 서비스 연동 없이 GitHub 리포지토리에서 직접 워크플로우를 관리할 수 있습니다.
푸시, 풀 리퀘스트, 이슈 생성 등 다양한 GitHub 이벤트를 트리거로 활용할 수 있어 유연한 자동화가 가능합니다.
무료 사용량 제공
퍼블릭 리포지토리의 경우 무제한 무료 사용이 가능하며, 프라이빗 리포지토리도 월 2,000분의 무료 실행 시간을 제공합니다.
중소 규모 프로젝트에서는 별도 비용 없이 충분한 배포 자동화를 구현할 수 있습니다.
다양한 사전 정의된 액션
GitHub Marketplace에서 제공하는 수천 개의 사전 제작된 액션을 활용하여 복잡한 배포 과정을 간단하게 구성할 수 있습니다.
AWS EC2 인스턴스 초기 설정
효과적인 배포 파이프라인 구축을 위해서는 먼저 EC2 인스턴스를 적절히 설정해야 합니다.
EC2 인스턴스 생성 및 기본 설정
# EC2 인스턴스 연결 후 시스템 업데이트
sudo apt update && sudo apt upgrade -y
# Node.js 설치 (Node.js 애플리케이션 배포 시)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# PM2 설치 (프로세스 매니저)
sudo npm install -g pm2
# Nginx 설치 (리버스 프록시)
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
보안 그룹 및 네트워크 설정
EC2 인스턴스의 보안 그룹에서 다음 포트를 개방해야 합니다.
- 포트 22: SSH 접속용
- 포트 80: HTTP 트래픽
- 포트 443: HTTPS 트래픽
- 포트 3000: 애플리케이션 포트 (필요시)
# 방화벽 설정 확인
sudo ufw status
# 필요한 포트 개방
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
GitHub Actions 워크플로우 기본 구조
GitHub Actions의 핵심은 .github/workflows
디렉토리에 위치한 YAML 파일입니다.
기본적인 배포 워크플로우의 구조를 살펴보겠습니다.
기본 워크플로우 파일 생성
name: Deploy to EC2
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build application
run: npm run build
이 기본 구조는 코드 체크아웃, 환경 설정, 의존성 설치, 테스트 실행, 빌드 과정을 포함합니다.
SSH 키 기반 보안 연결 설정
GitHub Actions에서 EC2 인스턴스에 안전하게 접근하기 위해서는 SSH 키 페어를 사용해야 합니다.
SSH 키 페어 생성 및 설정
# 로컬 환경에서 SSH 키 페어 생성
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"
# 공개 키를 EC2 인스턴스에 추가
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh
GitHub Secrets 설정
생성된 SSH 개인 키를 GitHub 리포지토리의 Secrets에 안전하게 저장해야 합니다.
- GitHub 리포지토리 → Settings → Secrets and variables → Actions
- New repository secret 클릭
- 다음 시크릿들을 추가:
SSH_PRIVATE_KEY
: SSH 개인 키 내용HOST
: EC2 인스턴스 퍼블릭 IP 주소USERNAME
: EC2 사용자명 (보통 ubuntu 또는 ec2-user)
완전한 CI/CD 파이프라인 구현
이제 테스트, 빌드, 배포까지 포함하는 완전한 파이프라인을 구현해보겠습니다.
전체 배포 워크플로우
name: Complete CI/CD Pipeline
on:
push:
branches: [ main ]
env:
NODE_VERSION: '18'
APP_NAME: 'my-app'
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run unit tests
run: npm test -- --coverage
- name: Upload coverage reports
uses: codecov/codecov-action@v3
build:
name: Build Application
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Create deployment archive
run: |
tar -czf deployment.tar.gz \
--exclude=node_modules \
--exclude=.git \
--exclude=tests \
.
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: deployment-files
path: deployment.tar.gz
deploy:
name: Deploy to EC2
runs-on: ubuntu-latest
needs: [test, build]
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: deployment-files
- name: Deploy to EC2
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# 기존 애플리케이션 중지
pm2 stop ${{ env.APP_NAME }} || true
# 백업 생성
sudo mkdir -p /var/backups/app
sudo cp -r /var/www/${{ env.APP_NAME }} /var/backups/app/$(date +%Y%m%d_%H%M%S) || true
# 새 버전 배포 디렉토리 생성
sudo mkdir -p /var/www/${{ env.APP_NAME }}
cd /var/www/${{ env.APP_NAME }}
- name: Copy files to EC2
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "deployment.tar.gz"
target: "/tmp/"
- name: Extract and start application
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/${{ env.APP_NAME }}
# 압축 해제
sudo tar -xzf /tmp/deployment.tar.gz -C .
sudo chown -R $USER:$USER .
# 의존성 설치 (프로덕션 모드)
npm ci --only=production
# PM2로 애플리케이션 시작
pm2 start ecosystem.config.js --env production
pm2 save
# Nginx 설정 재로드
sudo nginx -t && sudo systemctl reload nginx
# 정리
rm -f /tmp/deployment.tar.gz
고급 배포 전략 및 최적화
무중단 배포 (Blue-Green Deployment) 구현
- name: Blue-Green Deployment
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# 현재 활성 포트 확인
CURRENT_PORT=$(pm2 jlist | jq -r '.[] | select(.name=="'${{ env.APP_NAME }}'") | .pm2_env.PORT // "3000"')
if [ "$CURRENT_PORT" = "3000" ]; then
NEW_PORT=3001
else
NEW_PORT=3000
fi
echo "Deploying to port $NEW_PORT"
# 새 포트에서 애플리케이션 시작
PORT=$NEW_PORT pm2 start ecosystem.config.js --name ${{ env.APP_NAME }}-new
# 헬스 체크
sleep 10
curl -f http://localhost:$NEW_PORT/health || exit 1
# Nginx 설정 업데이트
sudo sed -i "s/proxy_pass http:\/\/localhost:[0-9]*/proxy_pass http:\/\/localhost:$NEW_PORT/" /etc/nginx/sites-available/default
sudo nginx -t && sudo systemctl reload nginx
# 기존 애플리케이션 중지
pm2 delete ${{ env.APP_NAME }} || true
pm2 start ${{ env.APP_NAME }}-new --name ${{ env.APP_NAME }}
pm2 delete ${{ env.APP_NAME }}-new
환경별 설정 관리
- name: Set environment variables
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "ENVIRONMENT=production" >> $GITHUB_ENV
echo "PORT=3000" >> $GITHUB_ENV
elif [ "${{ github.ref }}" = "refs/heads/develop" ]; then
echo "ENVIRONMENT=staging" >> $GITHUB_ENV
echo "PORT=3001" >> $GITHUB_ENV
fi
모니터링 및 로깅 통합
배포 상태 알림 설정
- name: Notify deployment success
if: success()
uses: 8398a7/action-slack@v3
with:
status: success
text: '🚀 Deployment to EC2 completed successfully!'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- name: Notify deployment failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: '❌ Deployment to EC2 failed!'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
로그 수집 및 분석
# PM2 로그 설정
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30
# CloudWatch 에이전트 설치 (선택사항)
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i amazon-cloudwatch-agent.deb
보안 강화 및 베스트 프랙티스
GitHub Actions 보안 설정
Secrets 최소 권한 원칙
permissions:
contents: read
actions: read
security-events: write
의존성 취약점 검사
- name: Run security audit
run: |
npm audit --audit-level high
npm audit fix --dry-run
EC2 보안 강화
# SSH 설정 강화
sudo sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart ssh
# 자동 보안 업데이트 설정
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
트러블슈팅 가이드
일반적인 배포 실패 원인과 해결책
SSH 연결 실패
- SSH 키 형식 확인 (BEGIN/END 라인 포함)
- EC2 보안 그룹에서 22번 포트 개방 확인
- 인스턴스 상태 및 네트워크 ACL 점검
권한 오류
# EC2에서 사용자 권한 확인
groups $USER
sudo usermod -a -G www-data $USER
# 디렉토리 권한 설정
sudo chown -R $USER:www-data /var/www/
sudo chmod -R 755 /var/www/
포트 충돌 문제
# 포트 사용 현황 확인
sudo netstat -tulpn | grep :3000
sudo lsof -i :3000
# PM2 프로세스 정리
pm2 kill
pm2 resurrect
성능 최적화 전략
빌드 시간 단축
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache build output
uses: actions/cache@v3
with:
path: |
.next/cache
dist/
key: ${{ runner.os }}-build-${{ github.sha }}
병렬 처리 활용
strategy:
matrix:
node-version: [16, 18, 20]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
test-group: [unit, integration, e2e]
마무리
EC2와 GitHub Actions를 활용한 배포 파이프라인 구축은 초기 설정에 시간이 소요되지만, 일단 구축되면 개발 팀의 생산성을 크게 향상시킵니다.
핵심 성공 요소
- 보안을 최우선으로 고려한 SSH 키 관리
- 단계별 테스트와 검증을 통한 안정적인 배포
- 모니터링과 로깅을 통한 운영 가시성 확보
- 롤백 전략과 무중단 배포로 서비스 안정성 보장
지속적인 개선과 모니터링을 통해 더욱 효율적이고 안정적인 배포 파이프라인을 만들어 나가시기 바랍니다.
현대적인 DevOps 문화를 구축하는 첫걸음으로 이 가이드가 도움이 되었기를 희망합니다.
'DevOps' 카테고리의 다른 글
GitOps로 CI/CD 파이프라인 자동화하기: 현대적 DevOps 워크플로우 구축 가이드 (0) | 2025.05.25 |
---|---|
Docker를 활용한 Spring Boot + Nginx 리버스 프록시 설정 완벽 가이드 (0) | 2025.05.24 |
AWS 비용 최적화 전략: EC2, S3, RDS 중심으로 (0) | 2025.05.24 |
AWS EC2 + RDS 배포 실전 가이드 - 비용 최적화까지 완벽 정리 (1) | 2025.05.05 |
Docker로 Spring Boot 애플리케이션 배포하기 - 실전용 Dockerfile 작성법 (0) | 2025.05.05 |