이 전 프로젝트에서는 CI/CD를 구축하기 위해 Jenkins와 Github Action 중에 무엇을 사용할 지 많이 고민했었고 결국 Jenkins를 사용해서 구축했었다.
새로 시작한 프로젝트에서는 Github Action으로 구축을 해보았고 그 과정을 정리해보겠다. 먼저 간단하게 Jenkins와 Github Action를 정리해보겠습니다.
Jenkins : 별도의 서버가 필요, 고도의 커스터마이징 가능, 무한한 확장성, 강력한 커뮤니티와 다양한 온라인 리소스 등
Github Action : 별도의 서버가 필요 X, GitHub 환경 내에서 제한된 확장성, GitHub 공식 지원 및 포럼 제공 등
간단하게 아키텍처를 설명해보겠습니다.
현재 AWS에 EC2와 ECR을 생성한 상태이고 EC2에서는 Docker를 사용해서 Spring이 돌아가고 있습니다.
그래서 Workflow를 작성할 때 레포지토리에서 코드를 가져와서 빌드를 하고 빌드한 결과를 이미지로 생성하고 이미지를 ECR에 저장한 뒤에 EC2에서 ECR에 있는 이미지를 가져와서 컨테이너로 실행하는 것이 목표입니다.
다음으로는 각 단계별 코드와 내용을 설명해보겠습니다.
- name: Checkout repository
uses: actions/checkout@v4
이 단계에서는 현재 레포지토리의 코드를 체크아웃합니다. GitHub Actions에서 Workflow가 실행될 때, 이 단계가 있어야 이후 단계에서 코드베이스에 접근할 수 있습니다.
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
JDK 17을 설치합니다. 이 단계는 애플리케이션을 빌드하고 실행하는 데 필요한 Java 개발 키트를 설정합니다.
- name: Create application.yml
run: |
mkdir -p be/src/main/resources
echo "${{ secrets.APPLICATION_YML }}" > be/src/main/resources/application.yml
application.yml 파일을 생성합니다. 이 파일은 애플리케이션의 설정 파일로, GitHub Secrets에 저장된 값을 사용하여 생성합니다. 보안 문제로 인해 민감한 설정 정보는 GitHub Secrets에 저장하고 여기서 사용합니다.
- name: Grant execute permission for gradlew
run: chmod +x be/gradlew
gradlew 파일에 실행 권한을 부여합니다. 이를 실행 가능하게 만들어야 빌드를 진행할 수 있습니다.
- name: Build with Gradle
run: cd be && ./gradlew build
Gradle을 사용하여 애플리케이션을 빌드합니다. 이 단계에서는 소스 코드를 컴파일하고, 테스트를 실행하며, 최종적으로 패키징된 JAR 파일을 생성합니다.
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
AWS 자격 증명을 설정합니다. 이 단계는 AWS CLI를 사용하여 ECR에 로그인하고 이미지를 푸시하기 위해 필요한 AWS 자격 증명을 설정합니다. 자격 증명은 GitHub Secrets에 저장되어 있습니다.
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
Amazon ECR에 로그인합니다. ECR은 Docker 이미지를 저장하는 AWS 서비스입니다. 이 단계는 AWS CLI를 사용하여 ECR에 로그인합니다
- name: Build and push Docker image to ECR
env:
ECR_REGISTRY: ${{ secrets.AWS_ECR_REGISTRY }}
ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPOSITORY }}
IMAGE_TAG: ${{ github.sha }}
run: |
cd be && docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
Docker 이미지를 빌드하고 ECR에 푸시합니다. 이 단계에서는 애플리케이션을 Docker 이미지로 패키징하고, 이를 ECR에 푸시합니다. IMAGE_TAG는 현재 커밋의 SHA로 설정하여 고유하게 식별할 수 있습니다.
- name: Set up SSH key
uses: webfactory/ssh-agent@v0.5.3
with:
ssh-private-key: ${{ secrets.EC2_KEY }}
EC2 인스턴스에 SSH로 접속하기 위해 SSH 키를 설정합니다. GitHub Secrets에 저장된 개인 키를 사용합니다.
- name: Install SSH client
run: sudo apt-get install openssh-client -y
SSH 클라이언트를 설치합니다. SSH를 통해 EC2 인스턴스에 접속하기 위해 필요합니다.
- name: Add host key to known hosts
run: |
mkdir -p ~/.ssh
touch ~/.ssh/known_hosts
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
EC2 호스트 키를 known_hosts 파일에 추가하여 SSH 접속 시 호스트 키 확인을 자동으로 처리합니다.
- name: Deploy to EC2 via SSH
env:
EC2_HOST: ${{ secrets.EC2_HOST }}
ECR_REGISTRY: ${{ secrets.AWS_ECR_REGISTRY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
IMAGE_TAG: ${{ github.sha }}
run: |
ssh -o StrictHostKeyChecking=no ubuntu@${EC2_HOST} << EOF
echo "Logging into ECR..."
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY
echo "Stopping and removing existing container..."
docker stop sharefit-server || true
docker rm sharefit-server || true
echo "Removing Docker image..."
docker images -a | grep "sharefit" | awk '{print \$3}' | xargs docker rmi -f || true
echo "Pulling Docker image..."
docker pull $ECR_REGISTRY/sharefit:$IMAGE_TAG
echo "Running new container..."
docker run --restart always --name sharefit-server -d -p 8080:8080 $ECR_REGISTRY/sharefit:$IMAGE_TAG
EOF
ECR에 로그인하고, 기존 컨테이너를 중지 및 제거하고 이미지를 삭제한 후 최신 이미지를 가져와 새로운 컨테이너를 실행합니다.
APPLICATION_YML : Spring Boot 애플리케이션의 설정 파일입니다.
AWS_ACCESS_KEY_ID :AWS 계정의 액세스 키 ID입니다.
AWS_SECRET_ACCESS_KEY : AWS 계정의 비밀 액세스 키입니다.
AWS_REGION : AWS 서비스가 실행되는 리전입니다.
AWS_ECR_REGISTRY : AWS ECR 레지스트리 URI입니다.
AWS_ECR_REPOSITORY : AWS ECR 내의 저장소 이름입니다.
EC2_HOST : AWS EC2 인스턴스의 공인 도메인 이름 또는 IP 주소입니다.
EC2_KEY : EC2 인스턴스에 접속하기 위한 SSH 키입니다. 프라이빗 키 내용을 넣어야 합니다.
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Create application.yml
run: |
mkdir -p be/src/main/resources
echo "${{ secrets.APPLICATION_YML }}" > be/src/main/resources/application.yml
- name: Grant execute permission for gradlew
run: chmod +x be/gradlew
- name: Build with Gradle
run: cd be && ./gradlew build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push Docker image to ECR
env:
ECR_REGISTRY: ${{ secrets.AWS_ECR_REGISTRY }}
ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPOSITORY }}
IMAGE_TAG: ${{ github.sha }}
run: |
cd be && docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Set up SSH key
uses: webfactory/ssh-agent@v0.5.3
with:
ssh-private-key: ${{ secrets.EC2_KEY }}
- name: Install SSH client
run: sudo apt-get install openssh-client -y
- name: Add host key to known hosts
run: |
mkdir -p ~/.ssh
touch ~/.ssh/known_hosts
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
- name: Deploy to EC2 via SSH
env:
EC2_HOST: ${{ secrets.EC2_HOST }}
ECR_REGISTRY: ${{ secrets.AWS_ECR_REGISTRY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
IMAGE_TAG: ${{ github.sha }}
run: |
ssh -o StrictHostKeyChecking=no ubuntu@${EC2_HOST} << EOF
echo "Logging into ECR..."
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY
echo "Stopping and removing existing container..."
docker stop sharefit-server || true
docker rm sharefit-server || true
echo "Removing Docker image..."
docker images -a | grep "sharefit" | awk '{print \$3}' | xargs docker rmi -f || true
echo "Pulling Docker image..."
docker pull $ECR_REGISTRY/sharefit:$IMAGE_TAG
echo "Running new container..."
docker run --restart always --name sharefit-server -d -p 8080:8080 $ECR_REGISTRY/sharefit:$IMAGE_TAG
EOF