IT/DevOps

jib와 Github Actions를 이용한 빌드 자동화

Aaron's papa 2021. 8. 25. 22:55
반응형

jib 시리즈의 마지막 글로 Github Actions를 이용한 빌드 자동화 과정에 대해서 살펴보겠습니다. jib를 이용해서 애플리케이션의 컨테이너 이미지를 생성하고, 생성된 이미지를 이미지 저장소에 저장하는 과정까지를 Github Actions를 이용해서 사람의 참여 없이 진행해 보려고 합니다. 우선, 본격적인 이야기를 하기에 앞서 Github Actions에 대해 살펴보겠습니다.


Github Actions

Github Actions는 Github에서 제공하는 CI/CD 도구입니다. 아래는 Github 페이지에서 소개하는 Actions입니다. Automate라는 단어와 workflows라는 단어가 눈에 띕니다.

https://docs.github.com/en/actions

Github Actions는 여러 개의 Action을 순서대로 조합해서 워크플로우라는 것을 만듭니다. 그리고 워크플로우를 Github Actions Runner라 불리는 시스템이 실행합니다.

Github Actions의 구성

워크플로우 안에는 언제 이 워크플로우를 실행할지에 대한 이벤트 기준을 설정할 수 있습니다. 예를 들어 develop 브랜치에 PR이 올라왔을 때 워크플로우를 실행하게 하거나, main 브랜치에 코드를 푸시했을 때 워크플로우를 실행하게 할 수 있습니다. 브랜치뿐만 아니라 특정 디렉터리 내의 코드들에 변화가 발생했을 때 실행하게도 할 수 있습니다.

그리고 워크플로우를 실제로 실행시키는 Runner들은 Github hosted runner와 Self hosted runner 두 개로 분류됩니다. 이름에서 알 수 있는 것처럼 Github hosted runner는 Github에서 직접 운영하고 관리하는 Runner를 의미하고 Self hosted runner는 사용자가 직접 운영하고 관리하는 Runner를 의미합니다. 당연히 Github hosted runner는 Runner를 실행할 때만 비용이 발생하고 직접 관리/운영하지 않기 때문에 운영 비용이 발생하지 않는다는 장점이 있습니다. 하지만 Github을 사용하는 라이선스에 따라 Github hosted runner를 사용할 수 있는 시간에 제한이 있습니다.

Github hosted runner의 제약 사항 (https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions)

반면에 Self hosted runner는 OS부터 모든 구성을 직접 할 수 있습니다. 그리고 Runner를 설치하는 과정도 매우 쉽기 때문에 큰 어려움 없이 설치해서 운영할 수 있습니다. 하지만 Runner를 구성하는 방식에 따라 (예를 들어 EC2 인스턴스에 올려서 운영한다면 인스턴스 비용과 EBS 비용 발생) 비용이 발생할 수 있으며, 운영 및 관리를 위한 인적 비용이 추가로 발생합니다. 이 둘은 장/단점이 명확하기 때문에 사용하려는 패턴에 따라 적합한 Runner를 사용하면 됩니다.

이렇게 다양한 옵션과 방식을 지원하며 무엇보다 Github에서 제공하기 때문에 코드 변화 등을 감지하기 위한 별도의 파이프라인을 구성할 필요가 없다는 게 큰 장점입니다.

그럼 Github Actions에 대해서 간단하게 살펴봤으니 본격적으로 워크플로우를 작성하면서 빌드 자동화를 구현해 보겠습니다.


워크플로우 정의

hello-jib 애플리케이션을 빌드하기 위한 워크플로우를 정의해 보겠습니다. 우리가 정의하려는 워크플로우는 main 브랜치에 코드가 푸시되면 컨테이너 이미지를 생성하고 생성된 이미지를 도커 허브에 업로드하도록 정의해 보겠습니다.

워크플로우 파일은 .github/workflows 디렉터리에 파일로 생성해서 저장해야 합니다. 이곳에 build.yaml 파일을 생성해서 아래와 같이 작성합니다.

name: hello-jib build

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup JDK 1.15
        uses: actions/setup-java@v1
        with:
          java-version: 1.15

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew jib

우리가 정의한 워크플로우의 이름은 hello-jib build 입니다. 그리고 main 브랜치에 푸시 이벤트가 발생하면 실행됩니다. 만약 PR이 생성되었을 때 실행하게 하려면 아래와 같이 작성합니다. 즉 on 지시자 밑에 이벤트를 기입하고 이벤트 밑에 branches를 통해 어떤 브랜치에서의 이벤트 발생 시 실행하게 할지 정의합니다.

on:
  pull_request:
    branches: [ main ]

다음으로 jobs 지시자를 통해서 작업을 순차적으로 정의합니다. 실행하는 작업의 이름은 build 입니다. 이 항목은 사용자가 직접 정의합니다. 워크플로우를 실행할 Runner는 runs-on 지시자를 통해 정의 합니다. 이 워크플로우는 Github hosted runner 중 ubuntu-latest 라는 이름을 가진 Runner에서 실행됩니다.

그리고 steps 지시자를 통해서 워크플로우가 순차적으로 실행시킬 Action을 정의합니다. 하나하나를 Action이라고 부릅니다. 정의된 순서에 따라 제일 먼저 actions/checkout@v2가 실행됩니다. 이 Action은 레포를 체크아웃합니다. 그 후 actions/setup-java@v1에 의해 JDK 1.15를 설치합니다. JDK가 있어야 gradle 빌드를 돌릴 수 있으니까 당연히 필요합니다. 그리고 gradlew 명령에 실행 권한을 주고, ./gradlew jib 명령을 통해 빌드를 시작합니다.

워크플로우 작성 완료 후 Github의 main 브랜치에 해당 코드를 푸시합니다.

❯ git push -u origin main
Enumerating objects: 32, done.
Counting objects: 100% (32/32), done.
Delta compression using up to 8 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (32/32), 17.61 MiB | 1.85 MiB/s, done.
Total 32 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:sepiro2000/hello-jib.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

실제 환경에서는 main 브랜치에 다이렉트 push 는 하지 않는 것이 좋습니다. 예시를 위해 main 브랜치에 다이렉트 push를 했습니다.

그리고 Github의 Actions 탭에 들어가면 아래와 같이 우리가 정의한 워크플로우가 실행되고 있는 것을 볼 수 있습니다.

워크플로우 실행 화면

그리고 first commit이라는 커밋명을 클릭해서 들어가면 어떤 Job이 실행 중인지 볼 수 있습니다.

실행 중인 Job 확인

이렇게 우리가 워크플로우에 정의한 이벤트가 발생하면 Github 이 워크플로우를 실행합니다. 별도의 복잡한 파이프라인을 구축하지 않아도 Github의 기능만으로도 구현이 가능한 파이프라인이 됩니다. 그럼.. 과연 저 작업은 성공했을까요?

워크플로우 Job 실패 화면

네, 확인해 보니 보시는 것처럼 작업은 실패했습니다. 왜 실패했을까요? build를 클릭해서 확인해 보겠습니다. 우리가 정의한 Action 중 Build with gradle에서 실패한 것을 볼 수 있습니다.

실패한 Action 확인

Build with Gradle 의 좌측에 있는 > 버튼을 클릭하면 상세한 로그를 볼 수 있습니다. 로그의 하단 부분에 보면 아래와 같은 에러가 보입니다.

로그를 통해 실패 원인 확인

네 맞습니다. 우리가 만든 jib 빌드는 도커 허브에 이미지를 푸시하도록 되어 있는데, 도커 허브에 push 하기 위한 인증 정보가 없기 때문에 에러가 발생했습니다. 이제 앞에서 만든 워크플로우를 조금 수정해서 도커 허브에 로그인하는 과정까지 포함시켜 보겠습니다.


도커 허브 로그인 액션 추가

도커 허브에 로그인하기 위해서는 우리가 만든 워크플로우에 Action을 하나 추가해야 합니다. 바로 https://github.com/marketplace/actions/docker-login Action 입니다. 이 Action을 포함해서 아래와 같이 워크플로우를 수정합니다.

name: hello-jib build

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup JDK 1.15
        uses: actions/setup-java@v1
        with:
          java-version: 1.15

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew jib

Action을 추가할 때는 순서도 중요한데, jib 작업을 통해 컨테이너 이미지가 만들어지고 푸시되기 때문에 jib 작업을 진행하기 전에 도커 허브의 인증 정보를 획득해야 합니다. 그래서 JDK 설치가 완료된 후 도커 허브에 로그인하는 Action을 넣어 줍니다. 그리고 도커 허브에 로그인 하는 Action을 보면 secrets로 시작하는 두 개의 변수들이 보이는데요, 인증과 관련된 민감 정보이기 때문에 일반 텍스트로 넣지 않고 Github에서 지원하는 시크릿을 이용해서 넣어 주는 변수들입니다. 이 변수들은 레포의 Settings 탭에 들어가면 보이는 Secrets 메뉴에서 설정할 수 있습니다.

Settings - Secrets 메뉴

Secrets 메뉴에서 설정한 시크릿 값은 워크플로우를 정의할 때 secret.XXX 로 접근할 수 있습니다. 그래서 아래와 같이 시크릿 값들을 만듭니다.

도커 허브 로그인 Action에서 사용할 시크릿

이제 준비가 다 되었으니 수정한 워크플로우 파일을 git 명령을 이용해 main 브랜치에 푸시해보겠습니다.

❯ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   .github/workflows/build.yaml

no changes added to commit (use "git add" and/or "git commit -a")

~/Desktop/Project/hello-jib main*
❯ git add .

~/Desktop/Project/hello-jib main*
❯ git commit -m "modify_workflow"
[main 503a530] modify_workflow
 Committer: alden <alden@aldenui-MacBookAir.local>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly. Run the
following command and follow the instructions in your editor to edit
your configuration file:

    git config --global --edit

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 1 file changed, 6 insertions(+)

~/Desktop/Project/hello-jib main ⇡
❯ git push origin main
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 511 bytes | 511.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:sepiro2000/hello-jib.git
   0f2b5fe..503a530  main -> main

main 브랜치에 푸시 이벤트가 발생했으니 우리가 설정한 워크플로우가 실행됩니다. 이번엔 어떻게 되었을까요? 에러 없이 잘 실행되었을까요?

워크플로우 성공 화면

네, 이번엔 에러 없이 잘 실행되었습니다. 워크플로우를 통해 생성된 이미지도 도커 허브에 잘 푸시되었는지 확인해 보겠습니다.

도커 허브에 이미지 푸시 완료

이렇게 소스 코드를 작성하고 main 브랜치에 푸시하는 것만으로 컨테이너 이미지가 생성되어 레포지터리에 푸시까지 되었습니다. 정말 편하지 않나요?


마치며

이번 글까지 해서 총 3개의 글을 통해 jib에 대해 살펴봤습니다. jib는 자바 기반 애플리케이션을 손쉽게 컨테이너 이미지로 만들어 주는 라이브러리입니다. 그리고 지난번 글에서 살펴본 것처럼 APM 에이전트 및 다른 라이브러리들을 포함시킬 수 있는 옵션을 제공해 주고 있으며, 이번 글에서 살펴본 것처럼 Github Actions와 같은 파이프라인을 활용하면 빌드 자동화까지 손쉽게 구현할 수 있습니다. 여기서 더 발전시킨다면 배포까지 연결하는 배포 자동화까지도 손쉽게 구현할 수 있습니다. 지금까지 긴 글 읽어 주셔서 감사합니다.

반응형