Published on

CI/CD

Authors
  • avatar
    Name
    ywj9811
    Twitter

CI/CD

CI/CD란? - 지속적 통합(Continuos Integration)/지속적 배포(Continous Deployment)

CI란?

CI는 간단히 말하자면 빌드/테스트 자동화 과정을 말하는 것이다.

CI를 성공적으로 구현하게 되면 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 레포지토리에 통합되어 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 충돌 위험성을 해결할 수 있다.

이를 통해 코드/버전 관리에 대한 변경 사항을 정기적으로 커밋하여 팀원들이 동일한 작업 기반으로 진행할 수 있도록 하는 것이다.

이러한 지속적 통합은 CI/CD 파이프라인 구현하기 위한 첫 단계이다.

CD란?

CD는 간단히 말하면 배포 자동화 과정을 말하는 것이다.

즉, 이는 지속적인 배포를 의미하는 것으로 빌드, 테스트 및 배포 단계를 자동화하는 DevOps 방식을 논리적 극한까지 끌어올린다.

코드 변경이 파이프라인의 이전 단계를 모두 성공적으로 통과하면 수동 개입 없이 해당 변경 사항이 프로덕션에 자동으로 배포될 수 있다.

CI/CD 종류

  • Jenkins
  • CircleCI
  • TravisCI
  • Github Actions
  • etc

이렇게 여러가지 종류가 존재하는데 이 중에서 Github Actions에 대해서 알아보도록 할 것이다.

Spring Boot + Docker + Github Action 자동 배포

📌 DockFile 작성

우선 DockFile을 작성하여 Docker 이미지를 빌드하기 위해 지시문을 넣어준다.

FROM openjdk:11-jdk
ARG JAR_FILE=./build/libs/dashboardback-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

📌 EC2 인스턴스에서 작업에 필요한 Docker를 설치하도록 한다.

- Ubuntu 버전 -

  1. 우선 패키지 인덱스를 업데이트하고 새 HTTPS 리포지토리를 추가하는데 필요한 종속성을 설치한다.
sudo apt update
sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
  1. curl 명령어를 사용하여 리포지토리의 GPG 키를 가져온다.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  1. Docker APT 리포지토리를 시스템에 추가한다.
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

이제 도커 저장소가 실행되었으므로 저장소에 있는 모든 도커 버전을 설치할 수 있다

  1. 최신 버전 도커 설치
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

⚠️ 본인은 여기서 오류가 발생하였는데, 해당 오류 경로를 찾아가 vi편집기로 확인하자

  1. 설치 확인
sudo systemctl status docker

# ● docker.service - Docker Application Container Engine
#      Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
#      Active: active (running) since Thu 2020-05-21 14:47:34 UTC; 42s ago
# ...

결과가 주석과 같이 나오면 성공한 것이다.

  1. 루트가 아닌 사용자로 도커 실행 권한 부여
sudo usermod -aG docker $USER

이는 현재 사용중인 사용자에게 권한을 부여하는 것으로 $USER 가 현 사용자를 의미한다.

이때 주의할 점은 한번 껏다가 다시 실행하도록 하자 (권한 부여가 그제서야 됨)


- Linux 버전 -

📌 Docker 설치

# yum으로 Docker 설치
sudo yum install docker -y

# Docker 실행
sudo service docker start

# Docker 그룹에 sudo 추가 (인스턴스 접속 후 도커 바로 제어할 수 있도록)
sudo usermod -aG docker ec2-user

# 인스턴스 재접속(putty 껐다 킴) 후 Docker 명령어 실행해보기
docker run hello-world

🖥️ GitHub-Actions 스크립트 파일 생성하기

배포할 때 사용할 GitHub-Actions 스크립트 파일을 생성해 보도록 하자

GitHub 레포지토리 → Actions → Continuous integration 의 Java With Gradle 의 Configure 클릭

Untitled

클릭하게 되면 아래와 같이 ~/.github/workflow/gradle.yml 파일이 생성된다.

이때, gradle.yml이라는 이름은 본인이 원하는 대로 변경해도 문제가 없으며 이는 현재 gradle build 전용 스크립트 파일이다.

Untitled

📌 gradle.yml (현재 파일 이름) 작성 → 상황에 맞게 변경해서 사용

name: Java CI with Gradle

on:
  push:
    branches: ['master']

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      working-directory: ./
      APPLICATION: ${{ secrets.APPLICATION }}

    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          java-version: '11'
          distribution: 'adopt'

      - name: Cache Gradle packages
        uses: actions/cache@v2
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - uses: actions/checkout@v2
      - run: |
          mkdir ./src/main/resources
          cd ./src/main/resources
          touch ./application.yml 
          echo "${{env.APPLICATION}}" > ./application.yml

      - uses: actions/upload-artifact@v2
        with:
          name: application.yml
          path: ./src/main/resources/application.yml

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
        working-directory: ${{ env.working-directory }}

      - name: Build with Gradle
        run: ./gradlew build
        working-directory: ${{ env.working-directory }}

      - name: Cleanup Gradle Cache
        if: ${{ always() }}
        run: |
          rm -f ~/.gradle/caches/modules-2/modules-2.lock
          rm -f ~/.gradle/caches/modules-2/gc.properties

      - name: Docker build
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t ${{ secrets.PROJECT_NAME }} .
          docker tag ${{ secrets.PROJECT_NAME }} ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
          docker push ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}

      - name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_SERVER_HOST }}
          username: ec2-user
          key: ${{ secrets.PRIVATE_KEY }}
          envs: GITHUB_SHA
          script: |
            docker rmi $(docker images -q)
            docker pull ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
            docker tag ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7} ${{ secrets.PROJECT_NAME }}
            docker stop ${{ secrets.PROJECT_NAME }}
            docker rm ${{ secrets.PROJECT_NAME }}
            docker run -d --name ${{ secrets.PROJECT_NAME }} -p 80:8080 ${{ secrets.PROJECT_NAME }}

이 과정은 GitHub 레포지토리 main 브랜치에 push가 될 때 AWS EC2 인스턴스에 배포가 되는 과정이다.

참고로 이때 노출되어서는 안되는 properties와 같은 파일은 깃허브에 올리면 안되기 때문에 GitHub-Actions의 스크립트에 직접 작성해야한다.

(공식 문서 필요시 참고)

이제, 위 코드를 하나씩 분석해보도록 하자.

🔗 on: push: branch:

이는 해당 브랜치에 push가 되었을 때 Workflow를 Trigger 실행한다는 뜻이다.

name: Java CI with Gradle

on:
  push:
    branches:
			- main

permissions:
  contents: read

🔗 jobs:

GitHub-Actions의 Workflow는 다양한 Job으로 구성되며 Job은 다시 Steps로 구성이 된다.

  • GitHub-Actions에서 사용될 JDK를 세팅
  • **Java-Version으로 11을 사용하고, distribution으로 ‘temurin’**을 사용
jobs:
  build:
    runs-on: ubuntu-latest
    # Ubuntu로 실행할 것이다.

    env:
      working-directory: ./
      APPLICATION: ${{ secrets.APPLICATION }}
    # 작업경로는 여기로 지정한다.

    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          java-version: '11'
          distribution: 'adopt'
  # 자바는 이것으로 사용할 것이다.

🔗 Gradle Caching

Gradle을 캐싱하는 코드이다.

- name: Cache Gradle packages
      uses: actions/cache@v2
      with:
        path: |
             ~/.gradle/caches
             ~/.gradle/wrapper
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
              ${{ runner.os }}-gradle-

⚠️ 필수 아님!! 사용하면 빌드 시간이 단축된다고 함!

🔗 application.yml 등록

파일을 생성하는 부분 (여러개의 환경이 있다면 여러개 작성하면 된다)

(민감한 정보가 있기 때문에 직접 GitHub-Actions에서 작업)

		- uses: actions/checkout@v2
    - run: |
          mkdir ./src/main/resources
          cd ./src/main/resources
          touch ./application.yml
          echo "${{env.APPLICATION}}" > ./application.yml

    - uses: actions/upload-artifact@v2
      with:
        name: application.yml
				path: ./src/main/resources/application.yml

이때 GitHub-Actions에서 설정한 값을 properties에 쓰기 위해서는 다음과 같은 과정이 필요하다.

레포지토리의 Settings → Secrets → Actions → New repository secret 버튼

(한번 만들면 확인이 불가능함, 변경만 가능)

Untitled

APPLICATIONapplication.yml 파일
DOCKER_HUB_REPO도커 허브 계정 id/project name
DOCKER_PASSWORD도커 허브 계정 비번
DOCKER_USERNAME도커 허브 계정 id
EC2_SERVER_HOSTAWS 인스턴스 (EC2) 퍼블릭 IP
PRIVATE_KEYAWS 인스턴스 Key(.ppm형태)
PROJECT_NAME프로젝트 이름(이건 별로 안중요)

➕등등과 같이 본인이 필요한 내용을 상황에 따라 추가하면 된다.

Untitled

Name에 변수 명을 Secret에 값을 복사하여 넣어주면 된다.

🔗 Gradle Build - Docker Build & Push

Gradle Build 및 Docker Build 및 Push 과정이다.

		- name: Grant execute permission for gradlew
      run: chmod +x gradlew
      working-directory: ${{ env.working-directory }}

    - name: Build with Gradle
      run: ./gradlew build
      working-directory: ${{ env.working-directory }}

    - name: Cleanup Gradle Cache
      if: ${{ always() }}
      run: |
          rm -f ~/.gradle/caches/modules-2/modules-2.lock
          rm -f ~/.gradle/caches/modules-2/gc.properties

    - name: Docker build
      run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t ${{ secrets.PROJECT_NAME }} .
          docker tag ${{ secrets.PROJECT_NAME }} ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
          docker push ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}

Gradle 빌드를 하며 동시에 도커 로그인 및 jar파일 빌드(이미지화) 하여 도커 허브에 푸시를 부탁하는 부분이다.

🔗 EC2 연결

		- name: Deploy
      uses: appleboy/ssh-action@master
      with:
          host: ${{ secrets.EC2_SERVER_HOST }}
          username: ec2-user
          key: ${{ secrets.PRIVATE_KEY }}
          envs: GITHUB_SHA
          script: |
            docker rmi $(docker images -q)
            docker pull ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
            docker tag ${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7} ${{ secrets.PROJECT_NAME }}
            docker stop ${{ secrets.PROJECT_NAME }}
            docker rm ${{ secrets.PROJECT_NAME }}
            docker run -d --name ${{ secrets.PROJECT_NAME }} -p 80:8080 ${{ secrets.PROJECT_NAME }}

순서대로

도커 허브에서 푸쉬된 내용을 pull, 기존에 실행하던 내용 stop 후 삭제하고 새로 받은 것을 실행한다.

참고 : https://minsu20.tistory.com/23 , https://jjeongil.tistory.com/1968