[DevOps] Docker + Github Actions + Spring Boot CI/CD 구축

Donghoon Jeong·2024년 2월 29일
0

DevOps

목록 보기
4/4
post-thumbnail

전 포스팅에서 EC2에 접속 후, Docker를 이용하여 편리하게 배포하는 방법을 알아봤는데, 프로젝트에 수정 사항이 생길 때마다 배포하는 과정을 거치면 아무리 간단한 작업이더라도 상당히 귀찮은 작업이 될 것입니다. 이를 해결하기 위해 CI/CD가 필요합니다.

❓ CI/CD란 무엇일까?


CI/CD

CI는 간단히 빌드/테스트 자동화 과정입니다. CI는 개발자를 위한 자동화 프로세스인 지속적인 통합(Continuous Integration) 을 의미합니다.

CD는 간단히 말하면 배포 자동화 과정입니다. CD는 지속적인 서비스 제공(Continuous Delivery) 또는 지속적인 배포(Continuous Deployment)을 의미합니다.

다시 말해, CI는 여러 개발자들이 작업한 코드를 정기적으로 통합하고, 자동화된 테스트를 통해 코드 품질을 확인하는 과정입니다. CD는 CI에서 나온 결과물을 자동으로 배포 환경에 반영하고, 필요한 경우 자동으로 실제 서비스에 배포하는 것을 의미합니다.

따라서 CI/CD를 도입하면 개발자는 코드를 변경할 때마다 수동으로 배포하는 수고를 덜 수 있으며, 빠르게 피드백을 받아 개발 및 배포 프로세스를 개선할 수 있습니다. 이로써 프로젝트의 유지 보수 및 배포 작업이 효율적으로 이루어집니다.

여러 CI/CD 도구들 중에서는 GitHub Actions, GitLab CI/CD, Jenkins 등이 널리 사용되고 있습니다. 이번 포스팅에서는 GitHub Actions를 사용하는 예시를 들어보겠습니다.


CICD 구축

Github Actions 와 Docker & Docker Compose를 이용하여 CI/CD 파이프라인을 구축하는 작업을 진행하겠습니다.

전체적인 프로세스는 다음과 같습니다.

  1. 변경된 코드를 Github으로 Push.

  2. Github에서 조건을 만족할 경우, Github Actions 실행.

  3. Github Actions는 미리 작성해 둔. yml 파일의 내용대로 처음에 해당 프로젝트를 build 하고, Docker Hub Repository에 이를 push, 그 후에 EC2 서버에서 도커 이미지를 pull 받아 실행합니다.


1. Docker Repository 생성

Docker Hub에 접속해 로그인하여 Docker Repository를 생성합니다.

저는 dev라는 이름의 Repository를 생성하였습니다.


2. EC2에 docker-compose.yml

EC2에 접속하여 docker-compose.yml 파일을 작성해 줍니다.

여기서 중요한 점은 Docker Repository에 올릴 image를 사용하는 것입니다.

version: "3"
services:
  mysql:
    container_name: mysql_db
    image: mysql
    environment:
      MYSQL_ROOT_HOST: '%'
      MYSQL_DATABASE: qna
      MYSQL_ROOT_PASSWORD: 1234
    ports:
      - "3306:3306"
    volumes:
      - ./mysql/conf.d:/etc/mysql/conf.d # MySQL 설정 파일 위치
    command:
      - "mysqld"
      - "--character-set-server=utf8mb4"
      - "--collation-server=utf8mb4_unicode_ci"
    networks:
      - test_network

  springbootapp:
    restart: on-failure
    image: dong/dev
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql_db:3306/qna?useSSL=false&allowPublicKeyRetrieval=true
      SPRING_DATASOURCE_USERNAME: "root"
      SPRING_DATASOURCE_PASSWORD: "1234"
    depends_on:
      - mysql
    networks:
      - test_network

networks:
  test_network:

3. Github Actions CI/CD 스크립트 작성

위와 같은 경로에 CICD.yml 파일을 생성합니다.

name: CI/CD with Gradle

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

permissions:
  contents: read

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: "adopt"

      - name: Build with Gradle
        run: |
          chmod +x ./gradlew
          ./gradlew clean build -x test

      - name: Docker build & push to docker repo
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/dev .
          docker push ${{ secrets.DOCKER_REPO }}/dev

      - name: Deploy to server
        uses: appleboy/ssh-action@master
        id: deploy
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          envs: GITHUB_SHA
          script: |
            cd compose
            sudo docker rm -f $(docker ps -qa)
            sudo docker pull ${{ secrets.DOCKER_REPO }}/dev
            docker-compose up --build -d
            docker image prune -f

위 코드를 자세하게 살펴보도록 하겠습니다.

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

on은 어떤 조건에서 이 스크립트를 실행할지 적어주는 것입니다. 저는 master 브랜치에 push 혹은 pull request가 될 때마다 배포하는 것으로 작성했습니다.


steps:
  - uses: actions/checkout@v3

  - name: Set up JDK 17
    uses: actions/setup-java@v3
    with:
      java-version: '17'
      distribution: "adopt"

checkout은 github actions와 연결된 레포지토리의 코드를 runner로 옮기는 것입니다. 즉 매번 새로 푸시 된 코드로 일을 처리하는 일과 jdk 17버전으로 작성된 프로젝트이기 때문에 이에 맞춰 JDK를 세팅해 줍니다.


- name: Build with Gradle
  run: |
    chmod +x ./gradlew
    ./gradlew clean build -x test

Gradle을 사용하여 프로젝트를 빌드하고, 불필요한 테스트를 건너뛰는 등의 작업을 수행할 수 있습니다.


- name: Docker build & push to docker repo
  run: |
    docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
    docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/dev .
    docker push ${{ secrets.DOCKER_REPO }}/dev

Docker에 접속하여 새로 빌드 한 jar 파일을 넣은 이미지를 빌드하고, 빌드 한 이미지를 docker hub에 push 해서 ec2 서버에서 쉽게 가져다가 쓸 수 있게 하였습니다.

도커 계정과 같은 민감한 정보들은 Secrets으로 저장해두었습니다.


- name: Deploy to server
  uses: appleboy/ssh-action@master
  id: deploy
  with:
    host: ${{ secrets.EC2_HOST }}
    username: ${{ secrets.EC2_USERNAME }}
    key: ${{ secrets.EC2_PRIVATE_KEY }}
    envs: GITHUB_SHA
    script: |
      cd compose
      sudo docker rm -f $(docker ps -qa)
      sudo docker pull ${{ secrets.DOCKER_REPO }}/dev
      docker-compose up --build -d
      docker image prune -f

appleboy/ssh-action@master를 통해서 ssh 접속이 가능합니다. 접속 시 필요한 정보 또한 모두 Secrets에 저장해두었습니다.

script 부분은 ec2에 접속해서 실행할 명령어입니다. 현재 실행 중인 모든 Docker 컨테이너를 강제로 중 삭제하고, docker hub에 push 한 이미지를 pull 받아 docker-compose up --build -d 명령어로 실행하게 됩니다.


Secrets 등록

해당 Repository의 settings에 Secrets and varialbes - Actions에서 등록해주면 됩니다.


결과

master branch에 새로운 코드를 push 하면, 위에서 작성한 코드가 하나씩 실행되어 배포가 정상적으로 이루어지는 것을 확인할 수 있습니다.

또한 변경된 코드가 배포 환경에 바로 반영된 것을 확인할 수 있습니다.

profile
정신 🍒 !

0개의 댓글