Spring boot/프로젝트 회고

PLANIT 리팩토링 - CI/CD 구축하기

eunnnn 2024. 1. 3. 21:08

PLANIT, ' 관광정보 공공데이터를 사용한 여행 정보 제공 서비스'를 만들었다.

그 중 내가 대표적으로 담당한 기능은 hotplace 게시글 기능이었다.

 

다녀온 관광지에 대한 후기를 작성할 수 있고, 그 후기를 지역 별로 모아보는 기능이었는데, 전체 조회 페이지에서 후기글에 담겨져 있는 사진을 모두 보여주다보니 고화질 사진을 업로드 하지 못하고 업로드 할 수 있는 사진 크기를 제한해야만 했다.

그래서 파일 업로드 기능을 AWS S3 + Lambda를 사용해 개선하기로 마음 먹었고, 그 김에 미루어두었던 CI/CD 공부를 함께 하며 AWS를 사용한 배포 환경을 더 완벽하게 구축해두고, 이후에 리팩토링을 진행해보기로 했다!

 

S3 + Lambda를 제외하고 전체적으로 생각한 Build flow는 다음과 같다.

이를 구현하기 위해 Docker와 Github Action을 통한 CI/CD를 구현했다.

 

왜 Docker와 Github Action을 사용하는가?

Docker는 컨테이너 기반의 오픈소스 가상화 플랫폼을 말한다.

배에 싣는 화물 수송용 컨테이너처럼 서버에서도 다양한 OS환경, 여러 프로그램들을 화물과 비유하여 컨테이너에 싣고 여러 곳으로 운반하여 배포하는 것이다.

 

이러한 도커를 사용하는 이유는 다음과 같다.

  • 독립적인 개발 환경을 보장한다 ( 서버 설정에 대한 부분을 도커 컨테이너 위에서 진행한다면, 도커 컨테이너에 여러 소프트웨어를 설치하고, 설정 파일을 수정해도 호스트 OS에는 영향을 전혀 주지 않는다. )
  • 배포 서버, 개발 서버 모두 동일한 환경에서 실행이 가능하다 ( 개발 시에 컨테이너 내부에서 작업 후에 배포하려고 한다면, 이 내부 작업을 '도커 이미지' 라고 하는 일종의 패키지로 만들어 배포 서버에 전달 )

 

Github Action은 대표적인 CI/CD 툴이다.

CI/CD는 지속적 통합(Continuous Integration) 및 지속적 전달(Continuous Delivery) 또는 지속적 배포(Continuous Deployment)를 나타내는 용어로, 소프트웨어 개발 및 배포 프로세스를 자동화하고 지속적인 품질 향상을 도모하기 위한 방법이다.

CI/CD가 성공적으로 구축되면 간단한 코드 변경이 정기적으로 마스터에 커밋되고, 자동화된 빌드 및 테스트 프로세스를 거치며 다양한 사전 프로덕션 환경으로 승격되며, 문제가 발견되지 않으면 최종적으로 배포된다.

이러한 일련의 작업이 자동으로 수행되므로, 신속한 배포 및 빠른 피드백이 가능하다.

 

Docker 설정

0. 로컬 및 배포를 수행 할 EC2 Instance에 docker 설치

아래는 ec2에서 docker를 설치하는 코드다.

$ sudo apt-get update
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
$ sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io
$ docker -v

 

1. Docker Hub 가입/로그인 및 Repository 생성

Public Repository는 여러 개 생성 할 수 있지만, Private Repository는 계정 당 하나만 생성할 수 있기 때문에 repository는 public으로 생성했다.

 

 

2. local에서 jar 파일 정상적으로 생성되는 지, Build가 가능한 지 확인하기

maven 환경에서는 pom.xml 파일이 있는 디렉토리에서 mvn package 명령어를 통해 프로젝트를 build 할 수 있다. 

성공적으로 실행하면 build success 와 함께 프로젝트 내 package 폴더에 jar 파일 정상적으로 생성 된 것을 확인할 수 있다.

프로젝트를 java -jar target/enjoytrip-0.0.1-SNAPSHOT.jar 명령어를 통해 실행하고, 성공하면 다음 절차로 넘어간다

 

🛠️ Trouble Shooting - mvn not found 에러

Intellij와 같은 IDE에서는 기본적으로 Maven에 대한 지원을 해주기 때문에 따로 명령어를 사용하지 않아도 가능하지만 터미널에서 명령어를 직접 사용할 경우에는 사용자가 직접 PC에 설치를 해주어야 한다. (mac에서는 brew install mvn으로 설치 가능하다)

 

3. Dockerfile 작성 및 실행

1) Dockerfile 작성

FROM openjdk:17-alpine
COPY target/enjoytrip-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
  • FROM openjdk:17-alpine : Docker를 올릴 때 jdk17 버전을 사용하겠다는 선언
  • COPY target/enjoytrip-0.0.1-SNAPSHOT.jar app.jar : 프로젝트 빌드 후 생성 된 jar 파일을 컨테이너의 루트 디렉토리에 app.jar의 이름으로 복사
  • ENTRYPOINT ["java","-jar","/app.jar"] :  도커파일이 도커엔진을 통해서 컨테이너로 올라갈 때, 도커 컨테이너의 시스템 진입점이 어디인지를 선언하는 커맨드. 이 커맨드는 java -jar 명령어를 이용해서 컨테이너의 루트에 위치한 app.jar을 실행하라는 뜻이다.

2) Docker image 생성

 

그리고 docker는 다음과 같은 명령어를 사용해서 build 하고, docker image가 생성된다.

docker build -t enjoytrip . --platform linux/x86_64

M1칩의 경우 linux/arm64/v8 베이스로 이미지 빌드를 하는데, 아직 arm64의 경우 과도기라 해당 하는 이미지가 없는 경우가 있어서 --platform linux/x86_64 을 덧붙여서 플랫폼을 명시해주어야 한다고 한다.

 

3) Dockerhub push

그리고 이렇게 생성된 이미지 파일을 docker push {dockerhub 경로명} (이 프로젝트의 경우 docker push nueahx7674/planit) 명령어를 통해 Dockerhub에 push 할 수 있다.

아래 tags 부분을 확인하여, docker image가 dockerhub에 정상적으로 push 되는 것을 확인할 수 있다.

 

 

Github Action을 통한 CD 구축

이렇게 docker에 이미지가 잘 올라가는 것을 확인 했으면, 이제는  master에 변동사항이 push 될 때마다 build 재실행 > docker push > ec2 배포가 적용 될 수 있도록 CI/CD를 구축하는 작업이 필요하다.

이를 위한 절차는 다음과 같다.

 

1. secrets 등록하기

프로젝트를 build 할 때 필요하지만 외부에 공개되면 안 되는 정보는 github repository의 secrets 에 등록할 수 있다.

레포지토리 Settings > Secrets and variables > Actions 에서 등록할 수 있다.

이 때 등록이 필요한 정보는 아래와 같다.

  • DOCKER_ID : Docker-hub 이메일
  • DOCKER_PASSWORD : Docker-hub 비밀번호
  • DOCKER-REPO : Docke-hub Repository 이름
  • EC2_HOST : EC2 public IP address
  • EC2_PRIVATE_KEY : EC2 key pair pem 키 값을 복사한 내용
  • EC2_USERNAME : EC2 인스턴스가 ubuntu라면 ubuntu, linux라면 ec2-user
  • PROPERTIES_xxx : properties 파일 내용

그리고 이렇게 등록한 정보는 Github action을 생성한 후 작성할 yml 파일에서 ${{ 변수명 }} 의 형태로 사용 가능하다.

 

2. Github action 생성

프로젝트 Repository에서 Actions > Java with Maven을 선택하고 Github Action 을 생성한다.

그리고 CI/CD 수행을 위한 yml 파일을 다음과 같이 작성한다.

# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: CD

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

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        cache: maven
        
    - name: Make application.properties
      run: echo "${{ secrets.APPLICATION }}" > ./src/main/resources/application.properties
      shell: bash
        
    - name: Build with Maven
      run: mvn -B package --file pom.xml

    - name: Login to DockerHub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Build and push Docker image
      uses: docker/build-push-action@v2
      with:
        context: .
        push: true
        tags: ${{ secrets.DOCKER_REPO }}:latest

    - name: Setup SSH
      uses: webfactory/ssh-agent@v0.5.3
      with:
        ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

    - name: Deploy to AWS EC2
      run: |
        ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }} <<EOF
          docker pull ${{ secrets.DOCKER_REPO }}:latest
          docker stop my-app || true
          docker rm my-app || true
          docker run -d --name my-app -p 80:8080 ${{ secrets.DOCKER_REPO }}:latest
        EOF

 

코드의 흐름을 설명하면 다음과 같다.

  1. Master branch에 push, pull request 이벤트가 생성되면 다음의 작업을 수행한다.
  2. actions/setup-java 액션을 사용하여 JDK 17을 설정한다.
  3. GitHub Secrets으로부터 정보를 가져와 Docker 이미지에 포함될 application.properties 파일을 생성한다.
  4. Maven을 사용하여 프로젝트를 빌드한다.
  5. DockerHub에 로그인한다.
  6. Docker 이미지를 빌드하고 DockerHub에 푸시합니다.
  7. AWS EC2 인스턴스에 접속하기 위해 SSH를 설정한다.
  8. AWS EC2 인스턴스로 SSH 연결을 설정하고, Docker 이미지를 가져오고 실행하여 애플리케이션을 배포한다.

이렇게 코드를 작성하면 다음과 같이 master branch가 변경될 때 마다 성공적으로 작업을 수행하는 것을 확인할 수 있다.

 

 

참고자료

더보기