DevOps

EC2와 GitHub Actions를 활용한 배포 파이프라인 구축: 효율적인 CI/CD 구현 가이드

devcomet 2025. 5. 24. 20:53
728x90
반응형

EC2와 GitHub Actions를 활용한 배포 파이프라인 구축: 효율적인 CI/CD 구현 가이드

 

현대 웹 개발에서 지속적 통합과 지속적 배포(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에 안전하게 저장해야 합니다.

  1. GitHub 리포지토리 → Settings → Secrets and variables → Actions
  2. New repository secret 클릭
  3. 다음 시크릿들을 추가:
    • 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 문화를 구축하는 첫걸음으로 이 가이드가 도움이 되었기를 희망합니다.

728x90
반응형