-
GitHub Actions 사용하여 CI/CD 적용해보기ETC 2023. 2. 18. 00:51test
용어
Github Action 의 개념 용어로는 Workflow, Event, Job, Step, Action, Runner 가 있다.
- Workflow
- Github Action 의 최상위 개념
- Repository의 .github/workflows 폴더 아래에 저장
- 하나 이상의 Job으로 구성되고, Push나 PR과 같은 Event에 또는 특정 시간대에 실행 될 수 있는 자동화된 프로세스
- Event
- Workflow를 Trigger(실행)하는 특정 활동이나 규칙
- 특정 활동이란 Push, Pull Request, Commit 특정시간(Cron) 등을 의미
- 특정 행동이 아닌,webhook (Repository Dispatch Webhook)을 사용하면 Github 외부에서 발생한 이벤트에 의해서도 Workflow를 실행 가능
- Runner
- Runner란 Workflow 안의 Job을 실행시키기 위한 애플리케이션 머신
- Github에서 호스팅해주는 Github-hosted runner와 직접 호스팅하는 Self-hosted runner로 구분
- Github-hosted runner는 Azure의 Standard_DS2_v2로 vCPU 2, 메모리 7GB, 임시 스토리지 14GB
- Job
- Job은 동일한 Runner에서 실행 되는 여러 Step으로 구성되고, 가상 환경의 인스턴스에서 실행
- 다른 Job에 의존 관계를 가질 수 있고, 독립적으로 병렬 실행도 가능
- Job 의 관계를 주어 Test Job 은 항상 Build Job이 성공해야만 동작하는등 의 행위를 할수있다.
- Step
- Task들의 집합으로, 커맨드를 날리거나 action을 실행할 수 있음
- 하나의 Job 내에서 각각의 Step은 다양한 Task로 인해 생성된 데이터를 공유할 수 있다.
- Action
- Workflow의 가장 작은 블럭(smallest portable building block)
- Job을 만들기 위해 Step들을 연결할 수 있음
- 재사용이 가능한 컴포넌트
- 개인적으로 만든 Action을 사용할 수도 있고, Marketplace에 있는 공용 Action을 사용할 수도 있음
제한
- Pree
- Storage 한도 500MB, 월에 실행 시간 2,000분
- Pro
- Storage 한도 1GB, 월에 실행 시간 3,000분
적용하기
.github/workflows 디렉토리를 생성후 .yml 파일을 생성해도되지만
github가 다양한 템플릿을 제공하기에 이것 사용하자
이중 set up a workflow yourself 를 사용할것이다
YML 작성방법
yml 을 특정 키워드를 이용하여 특정 행위를 할수있게한다.
다양한 예약어들이 존재하지만 필수적인 예약어만 알아본다면
먼저 최상단 부분에는 action에 대한 정보를 작성하며
- name : action 의 이름을 명시
- on : 특정 브랜치에 어떤 이벤트로 동작할것인지에 대한 명시
- 예) master 브랜치에 push
- 예) develop 브랜치에 PR
- jobs : action에서 동작할 하나 이상의 job 목록
- job 이름과 Runner의 실행 환경을 지정한다.
실제 동작의 핵심이 되는 steps 의 키워드로는
- name: step의 이름으로 workflow 에 표시됨
- run: 명령을 수행
- uses: 다른 action을 불러와서 수행
등이 존재한다.
uses 를 통해 다양한 action을 불러와서 수행할수있는데
github에는 action 의 marketplace 가 있으며 여기서 필요한 action들을 사용할수있다.
GitHub Marketplace: actions to improve your workflow
이제 점진적으로 기능을 추가해가면서 어떻게 사용하는지 알아보자.
master push 혹은 PR 시 echo 수행
name: CI/CD on: push: branches: [master] pull_request: branches: [master] jobs: build: runs-on: ubuntu-latest steps: - name: Run Master Branch CI run: echo Hello, CI!
만약 브랜치 구분없이 동작하고싶다면
on: [push, pull_request]
위와같이 작성할 수 있다.
master push 시 빌드 후 echo 수행
//생략 ---- - name: checkout action uses: actions/checkout@v3 - name: setup JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'zulu' - name: Grant execute permission run: chmod +x gradlew - name: gradlew build run: ./gradlew clean build --exclude-task test - name: Run Master Branch CI run: echo Hello, CI!
이곳에서는 빌드를 할 master로 이동하기위해, 그리고 빌드를 할 환경을 구성하기위한 java 를
github action marketplace에서 가지고와서 사용하였다.
master push 시 빌드 후 slack 메시지 보내기
일단 slack 에 메시지를 보내기위해 github action marketplace 에서 찾아보니
slack api web hooks 를 사용하여 메시지를 보내는 action이 있어 사용하려한다.
물론 slack bot으로도 진행가능.
일단 슬랙에서 webHooks 를 설치하고
webHooks URL 을 획득한다.
그리고 해당 URL 을 GitHubAction 에서 사용하려고 yml 파일에 작성하게되면
중요한 URL 이 노출되는것이기에 이를 감출 필요가있다.
repository - settion - secrets - new repository secret
에서 webHooks URL 을 추가한다.
이렇게 숨긴 secret 값은
${{ secrets.SECRET-NAME }} 로 사용할수있다.
//생략 .... - name: slack send message uses: slackapi/slack-github-action@v1.23.0 with: payload: | { "text": "GitHub Action build result: ${{ job.status }}\\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "GitHub Action build result: ${{ job.status }}\\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" } } ] } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACKTESTHOOK }}
이제 master에 push 를 하면
위와같은 메시지를 받을수있다.
Docker hub 업로드
먼저 Dockerfile를 작성해준다
FROM adoptopenjdk/openjdk11 COPY ./build/libs/<project-name>-<version>-SNAPSHOT.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"]
action yml에 docker 이미지를 빌드하여 업로드 하는 과정을 추가한다.
- name: Docker Image Build run: docker build -t <docker hub nickname>/<product-name> . - name: Docker Hub Login uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_HUB_ID }} password: ${{ secrets.DOCKER_HUB_TOKEN }} - name: Docker Hub push run: docker push <docker hub nickname>/<product-name>
Build Job, Deploy Job 분리
하나의 job에서 모든 기능을 수행하는것이 아닌
build job가 완료되면 deploy job이 실행되도록 작성하려한다.
먼저 job의 순서가 중요하기에
needs 예약어를 통해 build job이 완료된후에 deploy job이 실행되도록한다.
jobs: build: steps: //생략... deploy: needs: build steps: //생략...
이제 위에서 부터 진행해온 코드를 분리만 하면될것같지만
각 job은 다른 환경이기에
build job에서 만들어진 jar를 deploy에서 그대로 사용할수없는 문제가 발생하니
build job의 jar를 업로드 하고 deploy는 해당 jar를 다운로드 하여 사용해야한다.
build job 는 upload-artifact 을 통해 필요한 jar, dockerfile 를 업로드하고
deploy job 는 download-artifact 을 통해 필요한 jar, dockerfile 를 다운로드 할것이다.
jobs: build: steps: - name: upload jar uses: actions/upload-artifact@v1 with: name: build path: ./build - name: upload dockerfile uses: actions/upload-artifact@v1 with: name: dockerfile path: ./Dockerfile deploy: needs: build steps: - name: download jar uses: actions/download-artifact@v1 with: name: build - name: download dockerfile uses: actions/download-artifact@v1 with: name: dockerfile
동작 제어
1. 특정 커밋메시지면 동작 안하게 하기
jobs: build: ... deploy: needs: build ...
위와같이 동작하던 job에서 build 를 if 절 을 사용하여 특정 커밋메시지가 존재하면 동작하지 않게 할것이다
jobs: build: if: ${{ !contains(github.event.head_commit.message, '[master action skip]') }} ... deploy: needs: build ...
커밋 메시지 안에 "[master action skip]" 문구가 들어가면 동작하지 않게 build job에 if 절을 추가한다.
2. 상위 step이 실패해도 동작하기
조건의 failure 와 always 를 사용하여 위의 step 가 실패했을때 동작하거나
무조건 실행되도록 할수있다.
steps: - name: fail step run: fail... - name: always step if: ${{ always() }} run: echo always step! - name: failure step if: ${{ failure() }} run: echo failure step!
3. 상위 job 성공시, 실패시 동작하도록 하기
needs를 통해 순서가 정해진 job에서
자신 앞에 호출된 job의 결과를 얻을수있다.
jobs: deploy: needs: docker runs-on: ubuntu-latest steps: - name: deploy start run: echo deploy start report-deploy-success: needs: deploy runs-on: ubuntu-latest if: ${{ needs.deploy.result == 'success' }} steps: - name: slack send message run: echo ${{ needs.deploy.result }} report-deploy-failure: needs: deploy runs-on: ubuntu-latest if: ${{ needs.deploy.result == 'failure' }} steps: - name: slack send message run: echo ${{ needs.deploy.result }}
전체 코드
name: CI/CD on: push: branches: [master] pull_request: branches: [master] jobs: report-workflow: runs-on: ubuntu-latest steps: - name: event slack send message run: echo report-job-state! build: if: ${{ !contains(github.event.head_commit.message, '[skip]') }} runs-on: ubuntu-latest steps: - name: Run Master Branch CI run: echo Hello, CI! - name: checkout action uses: actions/checkout@v3 - name: setup JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'zulu' - name: Grant execute permission run: chmod +x gradlew - name: gradlew build run: ./gradlew clean build -Pprofile=prod --exclude-task test - name: upload jar uses: actions/upload-artifact@v1 with: name: build path: ./build - name: upload dockerfile uses: actions/upload-artifact@v1 with: name: dockerfile path: ./Dockerfile report-build-success: needs: build runs-on: ubuntu-latest if: ${{ needs.build.result == 'success' }} steps: - name: slack send message run: echo ${{ needs.build.result }} report-build-failure: needs: build runs-on: ubuntu-latest if: ${{ needs.build.result == 'failure' }} steps: - name: slack send message uses: slackapi/slack-github-action@v1.23.0 with: channel-id: ${{ secrets.SLACK_BOT_CHANNEL }} slack-message: "[Master Branch] CI/CD build job failure" env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} docker: needs: build runs-on: ubuntu-latest steps: - name: download jar uses: actions/download-artifact@v1 with: name: build - name: download dockerfile uses: actions/download-artifact@v1 with: name: dockerfile - name: echo build/libs dir run: ls -al ./build/libs - name: move dockerfile run: mv dockerfile/Dockerfile . - name: Docker Image Build run: docker build -t k4keye/vkestrel . - name: Docker Hub Login uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_HUB_ID }} password: ${{ secrets.DOCKER_HUB_TOKEN }} - name: Docker Hub push run: docker push k4keye/vkestrel - name: slack send message if: ${{ failure() }} uses: slackapi/slack-github-action@v1.23.0 with: channel-id: ${{ secrets.SLACK_BOT_CHANNEL }} slack-message: "[Master Branch] CI/CD docker job ${{ job.status }}" env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} report-docker-success: needs: docker runs-on: ubuntu-latest if: ${{ needs.docker.result == 'success' }} steps: - name: slack send message run: echo ${{ needs.docker.result }} report-docker-failure: needs: docker runs-on: ubuntu-latest if: ${{ needs.docker.result == 'failure' }} steps: - name: slack send message uses: slackapi/slack-github-action@v1.23.0 with: channel-id: ${{ secrets.SLACK_BOT_CHANNEL }} slack-message: "[Master Branch] CI/CD docker job failure" env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} deploy: needs: docker runs-on: ubuntu-latest steps: - name: deploy start run: echo deploy start - name: deploy server accessq uses: appleboy/ssh-action@v0.1.6 with: host: ${{ secrets.WAS_HOST }} username: ${{ secrets.WAS_USERNAME }} password: ${{ secrets.WAS_PASSWORD }} port: ${{ secrets.WAS_SSH_PORT }} script: | docker stop vkestrel docker rm vkestrel docker pull k4keye/vkestrel docker run -d -p 8791:8791 --name vkestrel k4keye/vkestrel report-deploy-success: needs: deploy runs-on: ubuntu-latest if: ${{ needs.deploy.result == 'success' }} steps: - name: slack send message run: echo ${{ needs.deploy.result }} report-deploy-failure: needs: deploy runs-on: ubuntu-latest if: ${{ needs.deploy.result == 'failure' }} steps: - name: slack send message uses: slackapi/slack-github-action@v1.23.0 with: channel-id: ${{ secrets.SLACK_BOT_CHANNEL }} slack-message: "[Master Branch] CI/CD deploy job failure" env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
'ETC' 카테고리의 다른 글
Custom GitHub Action 으로 Chat-GPT 코드리뷰 제작하기 (0) 2023.06.22 Value Object 가 최고다. (1) 2023.06.14 ?? : DB 의 인덱스 가 뭔가요? (0) 2022.11.16 그놈의 REST 한 API (0) 2022.09.27 HashMap 내용물 이해하기 (0) 2022.09.10 - Workflow