[CI/CD] Docker 와 Github Actions 를 활용한 CI/CD 환경 구축 - (4) Github Actions CD/CD 와 Docker 연결

HJ·2024년 3월 26일
0

CI/CD

목록 보기
4/4
post-thumbnail

Github Actions 살펴보기

드림코딩의 Github Actions를 참고해서 작성하였습니다.

Github Actions 는 특정한 이벤트가 발생했을 때 내가 원하는 자동으로 수행할 수 있도록 만들어주는 도구입니다.

주요 개념

[ Events ]

어떤 일이 발생했을 때 수행할 것인지를 지정합니다. 이때 이벤트에는 깃허브에서 발생하는 대부분의 이벤트를 지정할 수 있습니다. 예를 들어 특정 브랜치에 커밋을 하거나 머지를 하는 등의 이벤트를 지정할 수 있습니다.

[ Workflows ]

특정한 이벤트가 발생했을 때 어떤 일을 수행할 지를 지정합니다. Workflows 안에는 Job 이 있는데 하나의 workflow 안에는 하나 이상의 job 을 가질 수 있습니다.

.github/workflows 디렉토리에 yaml 파일을 만들고 여기에 작성합니다. 파일명은 아무거나 지정해도 됩니다.

[ Jobs ]

Workflows 의 Job 은 동시에 병렬적으로 수행됩니다. 반대로 순차적으로 진행하도록 지정하는 것도 가능합니다.

하나의 Job 안에는 어떤 순서로 Job 이 실행되어야 하는지 Step 을 지정할 수 있습니다. 쉘 스크립트를 사용해서 어떤 step 을 해야 하는지 명시해줄 수 있습니다. 또 actions 를 사용할 수 있습니다.

[ Actions ]

step 에는 직접만든 Action 을 사용할 수 있지만 Github Actions 에는 우리가 재사용할 수 있는, 공개적으로 오픈된 액션들이 존재하며, actions/ 으로 시작합니다.

[ Runners ]

지정한 Job 들을 실행하는 것이 바로 Runner( VM ) 입니다. 각각의 Job 들은 독립적인 각각의 Runner 라는 컨터이너에서 실행됩니다.


yml 파일 형태

name: [workflow 이름]
on: [이벤트 지정]
jobs: 
    [job 이름]:
        runs-on: [어떤 Runner(VM) 를 사용할 것인지]
        steps: [어떤 순서대로 job 을 실행해야 하는지 step 을 지정]  
            ## step 1
            - uses:
            ## step 2
            - name: [step 이름 지정]
              uses:
              with:
            ## step 3
            - name: [step 이름 지정]  
              run: 
            ...



Github Actions 사용하기

1. Github Secrets( 환경변수 ) 설정

Settings ➜ Secrets and Variables ➜ Actions 에 들어가서 Github Actions 에 쓰일 환경변수를 지정합니다.


2. Workflow 생성

Actions ➜ Java with Gradle 을 선택하면 어느 정도 작성되어 있는 yml 파일을 생성할 수 있습니다. 상단의 set up a workflow yourself 를 선택하면 처음부터 본인이 작성할 수 있습니다.


3. Workflow 작성

# Workflow 이름
name: CI/CD with Gradle

# Event 지정
on:
  push:
    branches: [ "main" ]

# Workflow 내 Job 을 정의
jobs:
  # Job 의 이름
  CI-CD-build:
    # Runner 환경 정의
    runs-on: ubuntu-latest

    # Step 정의
    steps:
    # 정의된 Actions 의 체크아웃 사용
    - uses: actions/checkout@v4

    # 1. jdk 세팅
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

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

    # 3. craete firebase key
    - name: create firebase key
      run: |
        cd ./src/main/resources
        ls -a .
        touch ./firebaseKey.json
        echo "${{ secrets.FIREBASE_KEY }}" > ./firebaseKey.json
      shell: bash

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

    # 5. docker build & push
    - name: docker build and push
      run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring ./
          docker push ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring

    # 6. Docker Compose Start
    - name: SSH into Ubuntu Server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USERNAME }}
        password: ${{ secrets.SERVER_PASSWORD }}
        script: |
          docker-compose stop
          docker rm -f $(docker ps -qa)
          docker pull ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring
          docker-compose up -d

workflow 이름

# Workflow 이름
name: CI/CD with Gradle

workflow 의 이름을 지정하면 Actions 에 들어갔을 때 해당 이름으로 workflow 가 생성된 것을 확인할 수 있습니다.


Jobs 이름

# Event 지정
on:
  push:
    branches: [ "main" ]

# Workflow 내 Job 을 정의
jobs:
  # Job 의 이름
  CI-CD-build:

수행된 workflow 를 들어가면 Jobs 의 이름이 지정된 것을 볼 수 있고, 우측에 보면 on push 라고 되어 있습니다.

이벤트를 main 브랜치에 push 한 경우에 실행되도록 했기 때문에 정상적으로 실행된 것을 확인할 수 있습니다.


Runner 환경 정의

# Workflow 내 Job 을 정의
jobs:
  # Job 의 이름
  CI-CD-build:
    # Runner 환경 정의
    runs-on: ubuntu-latest

runs-on 으로 runner 가 수행될 환경을 지정합니다. 저는 우분투 서버를 사용하기 때문에 ubuntu 로 지정하였습니다.


step 정의

steps:
    # 정의된 Actions 의 체크아웃 사용
    - uses: actions/checkout@v4

    # 1. jdk 세팅
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

steps 를 이용해 job 안에서 수행할 작업들을 지정합니다. 먼저 미리 정의된 action 을 활용하기 위해 actions 를 이용해 체크아웃을 합니다.

그 후 runner 는 각각의 독립적인 환경이기 때문에 JDK 를 setup 합니다.


Gradle 캐싱

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

gradle 캐싱을 수행합니다. 해당 step을 작성하면 빌드가 조금 빠르다고 합니다.


firebaseKey 생성

# 3. craete firebase key
    - name: create firebase key
      run: |
        cd ./src/main/resources
        ls -a .
        touch ./firebaseKey.json
        echo "${{ secrets.FIREBASE_KEY }}" > ./firebaseKey.json
      shell: bash

기존에는 로컬에서 테스트를 했기 때문에 main/resources 에 firebaseKey 파일이 있었는데 github 리포지토리에는 존재하지 않기 때문에 해당 파일을 생성해줍니다.

이때 Github Secret 에 지정한 환경변수인 FIREBASE_KEY 를 참조하도록 하였습니다. FIREBASE_KEY 는 파일의 내용을 붙여넣었으며, " 를 인식하기 위해서는 \" 를 사용해야 합니다.

// 기존 파일 내용
{
  "type": "service_account",
  "project_id": "...",
}

// secret 에 작성한 내용
{
  \"type\": \"service_account\",
  \"project_id\": \"...\",
}

COPY src/main/resources/firebaseKey.json firebaseKey.json

이 과정을 거치면 /main/resources 에 파일이 생성되며, 기존에 dockerfile 에 작성한 위의 명령어가 수행되어 도커 이미지 생성 시에 정상적으로 파일이 복사됩니다.


gradle build

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

gradle 빌드를 수행합니다. 이때 chmod 를 통해 실행 권한을 부여해야 합니다.


도커 이미지 build & push

# 5. docker build & push
    - name: docker build and push
      run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring ./
          docker push ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring

docker image 를 build 하고 push 하는 작업입니다. 해당 명령어에 필요한 USERNAME 과 PASSWORD 는 github secret 에 정의되어야 합니다.


docker-compose 실행

 # 6. Docker Compose Start
    - name: SSH into Ubuntu Server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USERNAME }}
        password: ${{ secrets.SERVER_PASSWORD }}
        script: |
          docker-compose stop
          docker rm -f $(docker ps -qa)
          docker pull ${{ secrets.DOCKER_USERNAME }}/vitalroutes-spring
          docker-compose up -d

그 후 서버에 접속하여 새로운 이미지를 pull 받고, docker-compose 를 실행하는 과정입니다. 이때 다른 사람의 정의한 action 을 사용하도록 uses 에 작성하였습니다.

먼저 서버의 정보를 with 에 작성하고, 서버에서 실행할 명령어들을 script 에 작성합니다.

docker-compose logs -f -t

그 후 서버에 접속해서 위 명령어를 입력하면 로그를 확인할 수 있습니다.


참고> docker 권한 오류

err: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1": dial unix /var/run/docker.sock:

위처럼 권한 오류가 뜨는 경우에는 아래 명령어를 통해 docker.sock 의 권한을 수정하면 됩니다.

sudo chmod 666 /var/run/docker.sock



최종 구조

지금까지 총 4단계에 걸쳐 서버 구축, Docker-Compose, NGINX 및 HTTPS 설정, Github Actions 를 진행하였고, 자동화된 CI/CD 구축을 완료하였습니다. 최종적인 구조는 위와 같습니다.

profile
공부한 내용을 정리해서 기록하고 다시 보기 위한 공간

0개의 댓글