독학/[etc] 개발 관련 내용 정리

[배포🌎] AWS EC2, GitHub Action, Docker compose를 이용한 CI/CD 자동화 (2)

최연재 2025. 5. 18. 23:13

시작하기 앞서 해당 글에서 이어지는 내용입니다.

 

3. 필요한 프로그램을 ec2 환경 내에서 설치하기

3.1 인바운드/아웃바운드 규칙 설정

- 인바운드 규칙 편집 : 상황에 따라서 편집

- 아웃바운드 규칙 편집 : 

3.2. 필요한 프로그램 설치

- Git 설치하기

$ sudo apt update
$ sudo apt install git -y

- Docker 설치하기

$ sudo apt install docker.io -y
$ sudo systemctl start docker
$ sudo systemctl enable docker

- Docker-compose 설치하기

$ sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

- Java 설치하기

$ sudo apt install openjdk-17-jdk -y

- redis 설치하기

$ sudo apt update
$ sudo apt install redis-server -y
$ sudo systemctl enable redis
$ sudo systemctl start redis
$ sudo systemctl status redis

 

 

4. Docker Hub에서 레포지토리 생성하기

Create a repository 클릭해서 생성

 

5. GitHub Repository Secrets 에 필요한 값 설정하기

- 각각의 값에 대한 설명은 아래와 같습니다.

  • APPLICATION_PROPERTIES : Spring Boot 프로젝트 내 applicatioproperties 또는 application.yml 파일 내용
  • APPLICATION_TEST_PROPERTIES : 테스트용 applicatioproperties 또는 application.yml 파일 내용
  • DOCKERHUB_PASSWORD : 본인의 docker hub 비밀번호
  • DOCKERHUB_REPOSITORY : 위에서 생성한 리포지토리 이름
  • DOCKERHUB_USERNAME : 본인의 docker hub 아이디
  • HOST : 배포 서버 도메인
  • JWT_EXPIRATION_ACCESS : Access 토큰의 만료 시간
  • JWT_EXPIRATION_REFRESH : Refresh 토큰의 만료 시간
  • POSTGRES_DB : 사용할 PostgreSQL 데이터베이스 이름
  • JWT_SECRET_KEY : JWT 서명을 위한 비밀 키
  • SPRING_DATASOURCE_PASSWORD : DB 비밀번호
  • SPRING_DATASOURCE_URL  : DB 연결 URL
  • SPRING_DATASOURCE_USERNAME : DB 사용자 이름
  • SSH_PRIVATE_KEY : 배포용 SSH 프라이빗 키 (AWS 인스턴스 만들면서 다운받은 .pem키)
  • SSH_USERNAME : 배포 서버의 SSH 접속용 사용자 이름 (여기에는 ubuntu)

 

6. 프로젝트 루트 위치에 DockerFile 작성

FROM openjdk:17-jdk-slim-buster
COPY build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

 

7. 프로젝트 루트 위치에 docker-compose.yml 작성

services:
  app:
    container_name: spring-boot-app
    image: ${DOCKER_IMAGE}
    ports:
      - "8080:8080"
    restart: always
    environment:
      SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
      SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
      SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
      JWT_SECRET_KEY: ${JWT_SECRET_KEY}
      JWT_EXPIRATION_ACCESS: ${JWT_EXPIRATION_ACCESS}
      JWT_EXPIRATION_REFRESH: ${JWT_EXPIRATION_REFRESH}
      SPRING_REDIS_HOST: redis
      SPRING_REDIS_PORT: 6379
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:latest
    container_name: postgres-db
    environment:
      POSTGRES_USER: ${SPRING_DATASOURCE_USERNAME}
      POSTGRES_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    ports:
      - "5432:5432"
    volumes:
      - ./init_postgres.sql:/docker-entrypoint-initdb.d/init_postgres.sql  # SQL 파일 마운트
    restart: always

  redis:
    image: redis:latest
    container_name: redis
    ports:
      - "6379:6379"
    restart: always

 

8. GitHub Action 시작하기

8.1 리포지토리 옵션 중 Actions 클릭

해당하는 워크플로우 클릭

8.2 스크립트 작성

name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
    - uses: actions/checkout@v4
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'
        
    # Gradle wrapper에 실행 권한 부여    
    - name: Give Gradle wrapper execute permissions
      run: chmod +x ./gradlew
    - name: Setup Gradle
      uses: gradle/actions/setup-gradle@ # v4.0.0

    ###  application.properties (main) 생성
    - name: Generate application.properties (main)
      run: |
        mkdir -p src/main/resources
        echo "${{ secrets.APPLICATION_PROPERTIES }}" > src/main/resources/application.properties

    ### application.properties (test) 생성
    - name: Generate application.properties (test)
      run: |
        mkdir -p src/test/resources
        echo "${{ secrets.APPLICATION_TEST_PROPERTIES }}" > src/test/resources/application.properties

    ### Gradle Build (테스트 제외)
    - name: Build with Gradle Wrapper
      run: ./gradlew build -x test

    ### Docker 로그인
    - name: Docker login
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_PASSWORD }}

     ###  Docker 이미지 빌드 및 푸시
    - name: Build and push Docker image
      run: |
        docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.sha }} -f ./Dockerfile .
        docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.sha }}

     ###  EC2 배포
    - name: Deploy to EC2
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.SSH_USERNAME }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          echo "Starting deployment..."
          cd ~/howWeather-backend

          echo "Pulling latest changes from main branch..."
          git fetch --all
          git reset --hard origin/main

          echo "Pulling latest Docker image..."
          docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.sha }}

          echo "Setting environment variables..."
          cat > .env <<EOL
          SPRING_DATASOURCE_URL=${{ secrets.SPRING_DATASOURCE_URL }}
          SPRING_DATASOURCE_USERNAME=${{ secrets.SPRING_DATASOURCE_USERNAME }}
          SPRING_DATASOURCE_PASSWORD=${{ secrets.SPRING_DATASOURCE_PASSWORD }}
          DOCKERHUB_USERNAME=${{ secrets.DOCKERHUB_USERNAME }}
          DOCKERHUB_REPOSITORY=${{ secrets.DOCKERHUB_REPOSITORY }}
          GITHUB_SHA=${{ github.sha }}
          DOCKER_IMAGE=${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.sha }}
          JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }}
          JWT_EXPIRATION_ACCESS=${{ secrets.JWT_EXPIRATION_ACCESS }}
          JWT_EXPIRATION_REFRESH=${{ secrets.JWT_EXPIRATION_REFRESH }}
          DOCKER_IMAGE=${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.sha }}
          OPENWEATHER_API_KEY=${{ secrets.WEATHER_SERVICE_KEY }}
          EOL
  
          echo "Stopping spring-boot-app container..."
          docker-compose --env-file .env stop app || true

          echo "Removing spring-boot-app container..."
          docker-compose --env-file .env rm -f app || true

          echo "Recreating spring-boot-app service with docker-compose..."
          docker-compose --env-file .env up -d --no-deps --build app

          echo "Checking Docker containers..."
          docker-compose ps
          echo "Deployment completed successfully!"
      
  dependency-submission:

    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
    - uses: actions/checkout@v4
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'
    - name: Generate and submit dependency graph
      uses: gradle/actions/dependency-submission@ # v4.0.0

 

9. 성공 여부 확인

- 퍼블릭 IP 주소로 접근이 잘 되는지 확인

- docker-compose logs 명령어로 에러 발생하는지 확인

성공!