이전에 개발을 하면서 수동으로 배포를 해왔습니다. 항상 EC2에 접속을 해서 git으로 프로젝트를 다운로드를 받고 build를 실행을 시키는 반복적인 과정을 진행을 해왔습니다. 이런 과정는 시간이 항상 많이 소요가 되었고 배포에 대한 피로도 높았습니다.
이번에는 낭비와 피로를 줄이기 위해서 프로젝트를 진행을 하면서 효율적인 관리를 위해서 지속적인 통합, 지속적인 배포 CI/CD를 적용을 하고 싶었습니다. 그래서 github actions를 통해서 CI/CD를 구축을 하고자 합니다.
github actions를 선택한 이유는 github actions가 github 플랫폼에 내장이 되어 있기 때문에 git repository와 연동이 잘 되기 때문입니다. 또한 따로 설치를 할 필요가 없으며, 이미 다른 사람들이 만들어 놓른 Actions를 사용을 해서 편리하게 구현을 할 수 있어서 입니다.
이번 과정에서는 github actions를 이용해서 dev로 Pull Request를 할 때, CI가 일어나서 테스트 자동화가 되고 main으로 Pull Request가 되면 CD가 일어나도록 gitworkflow를 작성을 해보겠습니다.
branch 전략을 dev에서는 feature에서 딴 branch를 통합하는 과정을 거치기로 했습니다.
그래서 dev pull Request를 할 때, 자동을 CI가 작동을 할 수 있도록 구성을 해야 했습니다.
이 때, 고려를 해야하는 것이 docker를 이용하고 있다는 점이었습니다.
저는 공통적인 개발 환경을 만들기 위해서 docker를 사용하고 있었습니다. 그래서 CI를 구현을 할 때 docker를 사용하는 것을 고려 해야 했습니다.
먼저, 빌드와 테스트를 진행을 하기 위해서 소스코드를 받아야 하고 그 소스코드를 실행을 시키기 위한 환경을 제공을 해야 했습니다. 그래서 소스코드를 git repository에서 받고 Java 환경을 설치를 하고 그 안에서 테스트와 빌드가 진행이 되도록 구성을 했습니다. 또한 저는 지금 개발환경을 다른 개발자가 와도 맞출 수 있도록 하기 위해서 현재의 소스코드와 개발 환경을 docker 이미지로 빌드를 했습니다. 그리고 이미지를 다른 로컬 환경에 다운 받을 수 있도록 docker hub에 올리도록 구성을 했습니다.
이제 YMAL 코드를 보면서 하나씩 알아보겠습니다.
name: CI
on:
push:
branches:
- dev
pull_request:
branches:
- dev
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build and Test with Gradle
run: ./gradlew clean build --no-daemon
- name: Build Docker image
run: docker build -t tickerflow-my-server .
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Push Docker image to Docker Hub
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: dktmskf0813/tickerflow-java:latest
- name: Check out the repository
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
먼저, actions/checkout@v3로 현재의 repository에서 소스코드를 가지고 옵니다.
프로젝트가 Java17버전으로 개발이 되었기 때문에 Java 17버전을 설치를 해서 소스코드가 빌드와 테스트 될 수 있는 환경을 만들어 줍니다.
- name: Build and Test with Gradle
run: ./gradlew clean build --no-daemon
프로젝트에 테스트를 자동으로 진행을 하고 빌드를 하기 위한 명령어를 실행을 해줍니다.
clean으로 이전 버전의 빌드는 삭제를 하고 새로 가져온 소스 코드를 build라는 명령어를 통해서 테스트하고 빌드를 진행을 합니다.
- name: Build Docker image
run: docker build -t tickerflow-my-server .
위에서 언급을 했듯이 Docker를 사용하고 있기 때문에 이미지를 만들어 줘야 합니다.
그래서 docker build 명령어를 통해서 현재의 소스 코드와 라이브러리들을 이미지로 만들어 줍니다.
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Push Docker image to Docker Hub
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: dktmskf0813/tickerflow-java:latest
만든 이미지는 다른 개발자들도 사용을 할 수 있도록 Docker Hub에 올리도록 합니다.
프로젝트 github Repository에 Secrets and variables에서 미리 저장해 둔 DOCKER_HUB_USERNAME, DOCKER_HUB_ACCESS_TOKEN을 이용해서 Docker hub에 로그인을 합니다.
그리고 우리가 만들어 놓은 이미지를 최신 태그를 붙혀서 push를 합니다.
그러면 branch dev로 push를 하거나 pull request를 보내게 되면 우리가 작성한 workflow가 동작을 하게 되면서 자동으로 CI에 대한 검증이 되게 됩니다.

검증이 성공을 하게 된다면 위 사진처럼 초록불이 들어오면서 성공했다고 뜨게 됩니다.
이제 main으로 push나 pull Request를 했을 때, workflow를 어떻데 구성을 고민을 해보았습니다.
일단 클라우드 서비스로 AWS와 네이버 클라우드를 고민을 했습니다.
제 선택은 AWS였습니다. 그 이유는 AWS는 방대한 기능들을 제공을 합니다. AWS안에서 Load Banlancing, DB, Docker등을 모두 해결을 할 수 있습니다. 또한 거대 기업인만큼 안전성도 보장이 되었기 때문에 AWS를 선택을 했습니다.
다음으로 고려를 해야 할 상황으로 Docker를 사용하는 것과 대용량 트래픽이 들어왔을 때 어떻게 할 것인지에 대한 부분이었습니다. 또 EC2에 소스 파일을 넘겨줘서 실행을 할지를 고려를 해서 workflow를 구성을 하였습니다.

간단하게 설명을 하면 interillJ에서 github로 push 혹은 pull Request를 했을 때, github actions가 트리거가 되면서 workflow가 실행이 됩니다. 그리고 그 workflow 대로 AWS의 Docker hub의 역할을 하는 AWS ERC에 만들어 둔 이미지를 업로드하고 소스 파일 S3에 업로드를 합니다. 그리고 AWS CodeDeploy에 배포를 진행을 하라고 명령을 하면 AWS CodeDeploy가 EC2에게 S3에 배포에 필요한 파일들을 가지고 옵니다. 그리고 AWS ERC에서 이미지를 다운 받아서 컨테이너로 실행을 하면서 프로젝트를 실행을 합니다. 그리고 클라이언트가 접근을 할 때, 한 번에 많은 트래픽이 들어올 것을 고려를 해서 AWS LoadBalance를 사용을 해서 트래픽을 분산을 시킬 수 있도록 했습니다.
이제 전체 YMAL 코드를 보면서 하나씩 알아보겠습니다.
name: CD
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
Deploy:
runs-on: ubuntu-latest
steps:
- name: Github Repository에 올린 파일을 불러온다.
uses: actions/checkout@v4
- name: JDK 17버전 설치
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: 테스트 및 빌드하기
run: ./gradlew clean build
- name: AWS Resource에 접근할 수 있게 AWS credentials 설정
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ap-northeast-2
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: ECR에 로그인하기
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Docker 이미지 생성
run: docker build -t ticket-flow .
- name: Docker 이미지에 Tag 붙이기
run: docker tag ticket-flow ${{ steps.login-ecr.outputs.registry }}/ticket-flow:latest
- name: ECR에 Docker 이미지 Push하기
run: docker push ${{ steps.login-ecr.outputs.registry }}/ticket-flow:latest
- name: 압축하기
run: tar -czvf $GITHUB_SHA.tar.gz appspec.yml scripts
- name: S3에 프로젝트 폴더 업로드하기
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.tar.gz s3://ticket-flow/$GITHUB_SHA.tar.gz
- name: Code Deploy를 활용해 EC2에 프로젝트 코드 배포
run: aws deploy create-deployment
--application-name ticket-flow
--deployment-config-name CodeDeployDefault.AllAtOnce
--deployment-group-name Production
--s3-location bucket=ticket-flow,bundleType=tgz,key=$GITHUB_SHA.tar.gz
- name: Github Repository에 올린 파일을 불러온다.
uses: actions/checkout@v4
- name: JDK 17버전 설치
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: 테스트 및 빌드하기
run: ./gradlew clean build
github actions에서 먼저, repository의 소스 코드를 가지고 옵니다. 그리고 Java17을 설치를 해서 Java 환경을 만들어줍니다. 그리고 ./gradlew clean build 명령어를 통해서 테스트 및 빌드를 실행을 합니다.
- name: AWS Resource에 접근할 수 있게 AWS credentials 설정
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ap-northeast-2
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS의 서비스들을 사용을 하고 있기 때문에 AWS에 접근을 할 수 있는 권한이 필요하다. 그래서 AWS에서 발급을 받은 AWS에 키를 넣어서 준다. 여기서 중요한 부분은 이 두 키는 공개가 되면 안 되기 때문에 github repository의 secrets에 담아서 사용하거나 가려서 사용을 해야 한다.
- name: ECR에 로그인하기
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Docker 이미지 생성
run: docker build -t ticket-flow .
- name: Docker 이미지에 Tag 붙이기
run: docker tag ticket-flow ${{ steps.login-ecr.outputs.registry }}/ticket-flow:latest
- name: ECR에 Docker 이미지 Push하기
run: docker push ${{ steps.login-ecr.outputs.registry }}/ticket-flow:latest
지금의 프로젝트를 이미지로 빌드를 하고 AWS ECR에 올리기 위해서 로그인을 합니다. 그리고 이미지를 빌드를 하는 것을 AWS ECR에 push를 해줍니다.
- name: 압축하기
run: tar -czvf $GITHUB_SHA.tar.gz appspec.yml scripts
- name: S3에 프로젝트 폴더 업로드하기
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.tar.gz s3://ticket-flow/$GITHUB_SHA.tar.gz
먼저, 프로젝트 실행에 필요한 파일들을 모아서 압축을 한다. appspec.yml은 AWS CodeDeploy를 사용하기 위해서 필수적인 파일이다. appspec.yml은 S3에서 EC2로 옮길 대상 파일과 CodeDeploy가 실행을 할 때, 실행을 시킬 파일을 지정할 수 있다. 그리고 그 실행할 파일이 scripts이다. script는 실행 중인 컨테이너를 정지하고 새로운 이미지를 받아서 새롭게 컨테이너를 실행을 하는 명령어가 적혀있다. 이 압축한 파일을 S3에 업로드를 해준다.
- name: Code Deploy를 활용해 EC2에 프로젝트 코드 배포
run: aws deploy create-deployment
--application-name ticket-flow
--deployment-config-name CodeDeployDefault.AllAtOnce
--deployment-group-name Production
--s3-location bucket=ticket-flow,bundleType=tgz,key=$GITHUB_SHA.tar.gz
AWS CodeDeploy에게 EC2에 프로젝트를 가져와서 배포하라는 명령어를 내리면 배포를 자동화하는 yml이 끝이 난다.
이번 프로젝트를 진행을 하면서 CI/CD를 적용을 해보았습니다. 확실히 psuh나 pull Request를 통해서 자동 배포가 되니 따로 AWS에 들어갈 필요도 없고 수동으로 할 필요가 없어서 매우 편했고 효율성도 좋았습니다. 이번 포스트에서는 저는 어떻게 CI/CD를 적용을 했는지에 대해서 적어 보았습니다. github actions에서 빌드를 해서 이를 EC2에 넘겨서 실행하는 방법으로 시도를 해봤습니다. CI/CD를 구축할 때, 다양한 tool, 다양한 방법들이 있습니다. 각자가 좋다고 생각하는 방법으로 CI/CD를 구현을 해보셨으면 좋겠습니다.