DevOps

[DevOps] Github Actions, Docker로 CICD 구축하기

dltjdn 2023. 8. 7. 19:57

CI 구축

1. 환경 변수 설정

Settings -> Security ->Secretes and variables -> Actions 탭에 들어가 New repository secret 클릭

Application.yml(Properties) 등 gitignore 한 파일들을 작성해 줍니다.

우리 프로그램은 env.properties 에 환경변수 정보를 적은 후 gitignore 해주었기 때문에

ENV라는 이름으로 env.properties 내용을 복붙 해주었습니다.

Name은 컨벤션에 맞게 대문자, 숫자와 언더스코어만 쓰자

2. workflow 구축

Github에 올라와있는 자바 프로젝트를 jar파일로 빌드하는 작업을 자동화할 것입니다.

프로젝트에서 /. github/workflows 폴더 안에 Deploy.yml을 생성한 후 아래 코드를 작성하였습니다.

# github repository actions 페이지에 나타날 이름
name: Deploy

# event trigger
# develp 브랜치에 push가 되었을 때 실행
on:
  push:
    branches: [ "develop" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # 기본 체크아웃 - 레포지토리의 최신 코드를 가져와 현재 워크플로우에서 사용할 수 있도록 로컬에 복제
      - name: Checkout
        uses: actions/checkout@v3

      # Gradlew 실행 허용
      - name: Run chmod to make gradlew executable
        run: chmod +x ./gradlew

      # JDK 17 세팅 - github actions에서 사용할 JDK 설정 (프로젝트나 AWS의 java 버전과 달라도 무방)
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      # 환경 변수 파일 생성
      - name: Set environment values
        run: |
          cd ./src/main/resources # resources 폴더로 이동
          touch ./env.properties # env.properties 파일 생성
          echo "${{ secrets.ENV }}" > ./env.properties # github actions secret에서 가져온 값을 application.yml 파일에 씁니다
        shell: bash

      # Gradle build (Test 제외)
      - name: Build with Gradle
        uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
        with:
          arguments: clean build -x test

 

3. CI 테스트

Deploy.yml에 pull_request를 이벤트 트리거로 추가하여

develop 브랜치에 pr을 날렸을 때 CI가 동작하도록 만든 후 테스트를 진행합니다.

저는 cicd 용 branch를 만들어 cicd branch -> develp branch로 pr을 생성하여 테스트를 진행하였습니다.

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

actions 탭으로 들어가 빌드가  잘 되는 것을 확인할 수 있다.

 

Docker & Docker-compose 설정

1. MAC에 도커 설치 ( Homebrew 사용 ) 

brew cask는 Docker Desktop on Mac 도커를 설치해 주며, 

docker-compose, docker-machine 등을 같이 설치해 줍니다.

맥 OS에서 띄우기 때문에 가상 머신에서 포트 포워딩을 할 필요가 없습니다.

터미널에서 아래 명령어를 입력합니다.

brew install --cask docker

다음과 같이 잘 설치된 것을 확인할 수 있습니다.

2. Dockerfile 생성

프로젝트 최상단 경로에 Dockerfile을 생성하고 아래 코드를 작성합니다.

FROM openjdk:17
COPY build/libs/sumnail-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

3. Docker Hub 리포지토리 생성

Docker Hub에 회원가입 후, 리포지토리를 생성해 줍니다.

저는 저희 프로젝트의 이름인 sumnail으로 리포지토리를 생성했습니다

도커 허브 리포지토리는 꼭 Private로 생성해 주세요!
public으로 설정할 시 github secret이 외부에 노출될 수 있습니다.

4. 인스턴스에 Docker 설치

https://docs.docker.com/engine/install/ubuntu/

 

Install Docker Engine on Ubuntu

Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install Docker Engine on Ubuntu.

docs.docker.com

위의 공식문서에 나온 대로 Docker를 설치해 주면 됩니다.

마지막에 버전명이 나오면 설치 성공입니다.

$ sudo apt-get update
$ sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

$ sudo mkdir -m 0755 -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
$ sudo docker --version

5. 인스턴스에 docker-compose 설치

아래 명령어로 docker-compose를 설치해주면 됩니다.

마지막에 버전명이 나오면 설치 성공입니다.

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
$ docker-compose -v

6. Docker-compose.yml 생성

인스턴스 최상단 경로(/)에 docker-compose.yml 파일을 생성해 줍니다.

/home/ubuntu 경로가 아니라 최상단 경로에 생성해야 합니다.
sudo vim ~ 로 sudo 권한을 주어야 파일이 저장됩니다.
ubuntu@ip-172-31-35-223:~$ cd ../../
ubuntu@ip-172-31-35-223:/$ ls
bin   docker-compose.yml  lib    libx32      mnt   root  snap  tmp
boot  etc                 lib32  lost+found  opt   run   srv   usr
dev   home                lib64  media       proc  sbin  sys   var
ubuntu@ip-172-31-35-223:/$ sudo vim docker-compose.yml

docker-compose.yml에 아래 내용을 작생해준 뒤 저장합니다.

version : "3"

services:
  application:
    image: sumnail # 사용할 Docker 이미지를 지정
    environment:
      SPRING_DATASOURCE_URL: <RDS url>
      SPRING_DATASOURCE_USERNAME: <RDS username>
      SPRING_DATASOURCE_PASSWORD: <RDS password>
    restart: always # 서비스가 종료되면 항상 자동으로 다시 시작하도록 설정
    container_name: sumnail # 컨테이너의 이름 지정
    ports:
            - "80:8080" # 호스트 머신과 컨테이너 간의 포트 매핑

호스트에서 웹 브라우저인 80번 포트로 접속하면,

해당 요청은 Docker 컨테이너 내부의 8080 포트로 전달되어 Spring boot 서비스에 도달하게 됩니다.

이렇게 설정함으로써 호스트 머신의 80번 포트를 통해 도커 컨테이너 내의 Spring boot 서비스에 접근할 수 있습니다.

CD 구축

빌드한 jar 파일을 바탕으로 docker image를 생성하고, docker hub에 푸시했다가

ec2에서 해당 이미지를 pull 받아와 배포하는 작업을 자동화할 것입니다.

1. docker image를 생성하고 docker hub에 푸시하는 작업 자동화

(1) docker username, password 환경변수에 추가

위에서 ENV 파일을 만들어 준 것처럼

DOCKER_USERNAME, DOCKE_PASSWORD을 작성해 줍니다.

(2) Deploy.yml 에 Docker build & push 추가

우리 프로젝트의 경우 docker hub repository 이름이 sumnail 이였다.

      # Docker build & push
      - name: Docker build
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t sumnail .
          docker tag sumnail ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
          docker push ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}

위의 코드를 한 줄씩 설명하겠습니다.

docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}

Docker Hub에 로그인합니다. Github secrets에 저장된 Docker Hub 사용자 이름과 비밀번호를 사용합니다.

# docker build -t <이미지 이름>:<태그> <Dockerfile 경로>  
docker build -t sumnail .

'.'는 현재 디렉터리에 있는 Dockerfile을 사용하여 Docker 이미지를 빌드합니다. 
저희의 경우 빌드한 이미지에 sumnail이라는 이름을 부여하였습니다.

# docker tag <이전 이미지 이름>:<이전 태그> <새 이미지 이름>:<새 태그>
docker tag sumnail ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}

이전에 빌드한 이미지에 태그를 지정합니다. Github 커밋의 일부인 SHA 값의 앞 7자리를 사용하여 이미지를 고유하게 식별합니다.

새 이미지 이름은 유저네임/리포지토리이름으로 만들었습니다.

docker push ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}

Docker이미지를 Docker Hub에 push 합니다

2. EC2에서 해당 이미지를 pull 받아와 배포하는 작업 자동화

(1) github에 환경변수 등록

PEM_KEY라는 이름으로 EC2에 접속하는데 필요한 pem 키의 내용을 복붙 해 저장해 줍니다.

SSH_USERNAME과 ELASTIC_IP라는 이름으로 각각 현재 인스턴스의 username과 eip를 저장해 줍니다.

(2) Deploy.yml에  Docker pull & deploy 코드 추가

이미지 이름은 Docker-compose.yml 파일의 application.image에 지정해 둔 것입니다

      # Deploy (docker compose 사용)
      - name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.ELASTIC_IP }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.PEM_KEY }}
          envs: GITHUB_SHA
          script: |
            sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
            sudo docker pull ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
            sudo docker tag ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7} sumnail
            sudo docker-compose -p sumnail up -d

위의 코드를 한 줄씩 설명하겠습니다

 uses: appleboy/ssh-action@master

ssh를 통해 원격 서버에 접속하는 데 사용되는 ssh-action 액션을 사용합니다

      with:
          host: ${{ secrets.ELASTIC_IP }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.PEM_KEY }}
          envs: GITHUB_SHA
  • host : 원격 서버의 ip 주소를 GitHub Secrets에서 가져옵니다
  • username: 원격 서버에 접속할 사용자 이름을 지정합니다 ( 기본 설정은 ubuntu )
  • key: 원격 서버 접속에 사용될 개인 키를 Github Secrets에서 가져옵니다.
  • envs : GITHUB_SHA 환경 변수로 현재 Github 커밋의 SHA 값을 전달합니다.
 sudo docker pull ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}

Docker hub에서 해당 이미지를 pull 합니다.

# docker tag <이전 이미지 이름>:<이전 태그> <새 이미지 이름>:<새 태그>
sudo docker tag ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7} sumnail

새로 pull 한 이미지에 다시 태그를 지정합니다

# docker-compose -p <docker compose 프로젝트 이름> up -d
sudo docker-compose -p sumnail up -d
  • -p 옵션 : Docker Compose 프로젝트의 이름을 설정
  • -d 옵션 : 컨테이너를 백그라운드에서 실행

Docker compose를 사용하여 컨테이너를 배포합니다.

(3) CD 테스트

CI 테스트와 같은 방식으로 cicd branch -> develop branch로 PR을 날려 테스트합니다.

 

CICD 가 될 되는 것을 확인한 후 추가했던 코드를 제거해 줍니다.

필요에 따라 pull_request가 생성될 때도 CICD가 적용되길 바라시는 분들은 그대로 나 두시면 됩니다.

 

 

관련 PR

https://github.com/Sum-nail/sum-nail-server/pull/31