๐Ÿ› ๏ธ Github actions โ€“ CI/CD pipeline ๊ตฌ์ถ•ํ•˜๊ธฐ

๊น€๊ณต์˜ยท2024๋…„ 5์›” 27์ผ

how-to

๋ชฉ๋ก ๋ณด๊ธฐ
2/12

๐Ÿค” Github Actions?

์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ lifecycle ๋‚ด์—์„œ Pull Request, Push ๋“ฑ์˜ ์ด๋ฒคํŠธ ๋ฐœ์ƒ์— ๋”ฐ๋ผ ์ž๋™ํ™”๋œ ์ž‘์—…์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ธฐ๋Šฅ

์ž๋™ํ™”๋œ ์ž‘์—…์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • โœจCI/CDโœจ
    • local -> remote๋กœ pushํ•˜๊ณ  ๋‚œ ํ›„ ์ด๋ฒคํŠธ ๋ฐœ์ƒ์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰
    • ์ž๋™ํ™”๋ฅผ ํ†ตํ•ด ๋ฐฐํฌ ์‹œ๊ฐ„์„ ๋‚ญ๋น„ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค!
  • Testing
    • Pull Request๋ฅผ ๋ณด๋ƒˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ๋„ ๊ตฌํ˜„ ๊ฐ€๋Šฅ
    • ํ…Œ์ŠคํŠธ ์„ฑ๊ณต ์—ฌ๋ถ€์— ๋”ฐ๋ผ PR์„ open/close ๊ฐ€๋Šฅ
  • Cron Job
    • ํŠน์ • ์‹œ๊ฐ„๋Œ€์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ฐ˜๋ณต ์‹คํ–‰ ๊ฐ€๋Šฅ
    • e.g. ๋งค์ผ ํŠน์ • ์‹œ๊ฐ„์— ํฌ๋กค๋ง ์ž‘์—… ์ง„ํ–‰

Github Actions์˜ ๊ตฌ์„ฑ ์š”์†Œ

Workflow

  • repository์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ๋ จ์˜ ์ž๋™ํ™”๋œ command set
  • ํ•˜๋‚˜ ์ด์ƒ์˜ job์œผ๋กœ ๊ตฌ์„ฑ
  • Push, Pull Request์™€ ๊ฐ™์€ ์ด๋ฒคํŠธ์— ์˜ํ•ด ์‹คํ–‰๋  ์ˆ˜๋„ ์žˆ๊ณ  ํŠน์ • ์‹œ๊ฐ„๋Œ€์— ์‹คํ–‰๋  ์ˆ˜๋„ ์žˆ๋‹ค.
  • build, test, deploy ๋“ฑ ํ•„์š”ํ•œ ์—ญํ• ์— ๋งž๋Š” workflow ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
  • .github/workflows directory์— .yaml ํ˜•ํƒœ๋กœ ์ €์žฅ

Event

  • workflow trigger(e.g. Push, Pull Request, Commit)
  • ํŠน์ • ํ–‰๋™์ด ์•„๋‹ˆ๋”๋ผ๋„ Repository Dispatch Webhook์„ ์ด์šฉํ•˜๋ฉด GitHub ์™ธ๋ถ€์—์„œ ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ์— ์˜ํ•ด์„œ๋„ workflow๋ฅผ ์‹คํ–‰ ๊ฐ€๋Šฅ
  • event ์ข…๋ฅ˜ : Events that trigger workflows

Job

  • ๋™์ผํ•œ runner์—์„œ ์‹คํ–‰๋˜๋Š” ์—ฌ๋Ÿฌ step์˜ ์ง‘ํ•ฉ
  • ํ•˜๋‚˜์˜ workflow ๋‚ด์— ์—ฌ๋Ÿฌ job์€ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰
  • ํ•„์š”์— ๋”ฐ๋ผ ์˜์กด ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•ด ์ˆœ์„œ ์ง€์ • ๊ฐ€๋Šฅ

Step

  • command๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ๊ฐ์˜ task
  • shell command๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ๊ณ , ํ•˜๋‚˜์˜ action์ด ๋  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ํ•˜๋‚˜์˜ job ๋‚ด์—์„œ ๊ฐ step์€ ๋‹ค์–‘ํ•œ task๋กœ ์ธํ•ด ์ƒ์„ฑ๋œ ๋ฐ์ดํ„ฐ ๊ณต์œ  ๊ฐ€๋Šฅ

Actions

  • job์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด step์„ ๊ฒฐํ•ฉํ•œ ๋…๋ฆฝ์ ์ธ command
  • ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ workflow์˜ ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„ block
  • ์ง์ ‘ ๋งŒ๋“  action or GitHub community์— ์˜ํ•ด ์ƒ์„ฑ๋œ action์„ ๋ถˆ๋Ÿฌ์™€ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

Runner

  • workflow ๋‚ด์˜ job์„ ์‹คํ–‰์‹œํ‚ค๊ธฐ ์œ„ํ•œ application
  • Github์—์„œ ํ˜ธ์ŠคํŒ…ํ•˜๋Š” ๊ฐ€์ƒ ํ™˜๊ฒฝ ๋˜๋Š” ์ง์ ‘ ํ˜ธ์ŠคํŒ…ํ•˜๋Š” ๊ฐ€์ƒ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅ
  • Github์—์„œ ํ˜ธ์ŠคํŒ…ํ•˜๋Š” ๊ฐ€์ƒ ์ธ์Šคํ„ด์Šค์˜ ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ๋ฐ ์šฉ๋Ÿ‰ ์ œํ•œ ์กด์žฌ

๐Ÿš€ How to use

EC2 instance ์ƒ์„ฑ, docker ์„ค์น˜ ๋ฐ ํšŒ์› ๊ฐ€์ž…์ด ์ด๋ฏธ ๋˜์–ด์žˆ๋‹ค๊ณ  ๊ฐ€์ •

DockerFile ์ž‘์„ฑ

Docker๋ฅผ ํ™œ์šฉํ•ด ๋ฐฐํฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” DockerFile์„ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. ํ•ด๋‹น ํŒŒ์ผ์€ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.(README.md, gradlew ๋“ฑ๊ณผ ๊ฐ™์€ path)
๋‚˜๋Š” DockerFile์„ ์ด์šฉํ•œ ๋ฐฐํฌ ์‹œ deploy profile์„ active๋กœ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด์ฃผ์—ˆ๋‹ค.

FROM openjdk:17
LABEL authors="zero-zero" # ๊ฐœ๋ฐœ์ž ์ด๋ฆ„์œผ๋กœ ์„ค์ •

ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=deploy", "app.jar"]

Workflow ์ƒ์„ฑ

.github/workflows ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— .ymlํŒŒ์ผ์„ ์ƒ์„ฑํ•ด๋„ ๋˜์ง€๋งŒ, Repository > Actions ํƒญ์—์„œ ์ž๋™์œผ๋กœ template์„ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ํŽธ๋ฆฌํ•˜๋‹ค.

set up a workflow yourself ๋ฅผ ์„ ํƒํ•œ๋‹ค.

Secret ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋“ฑ๋ก

Repository > Settings > Secrets and variables ๋ฉ”๋‰ด์—์„œ ๋ณ€์ˆ˜๋ฅผ ๋“ฑ๋กํ•œ๋‹ค. ํ•ด๋‹น ๋ณ€์ˆ˜๋ฅผ workflow์— ์‚ฌ์šฉํ•˜๋ฉด workflow log์—์„œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋‚˜์˜ค๋ฉฐ ํ•ด๋‹น ๊ฐ’์ด ๋…ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด docker image name์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋“ฑ๋กํ•˜๋ฉด ๋กœ๊ทธ ์ƒ์—๋Š” ***๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ๋‚˜์˜จ๋‹ค.

Variables

  • DOCKER_USERNAME : ์‚ฌ์šฉํ•  docker hub user name
  • DOCKER_PASSWORD : ์‚ฌ์šฉํ•  docker hub password
  • DOCKER_IMAGE_NAME : docker image ์ด๋ฆ„(์—ฌ๊ธฐ์„œ๋Š” project name์„ ์‚ฌ์šฉํ•จ)
  • PORT : Docker๋ฅผ ์‹คํ–‰์‹œํ‚ฌ ํฌํŠธ ๋ฒˆํ˜ธ(์—ฌ๊ธฐ์„œ๋Š” 8080)
  • EC2_HOST : AWS EC2 instance์˜ public IPv4 DNS โ†’ IP ์ฃผ์†Œ๊ฐ€ ์•„๋‹ˆ๋‹ค
  • EC2_USERNAME : AWS EC2 instance์˜ username(์—ฌ๊ธฐ์„œ๋Š” ubuntu)
  • EC2_KEY : AWS EC2 instance๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์ €์žฅ๋œ pem key
    • terminal > cat <pem key path> > copy and paste
    • -----BEGIN RSA PRIVATE KEY----- ๋ถ€ํ„ฐ -----END RSA PRIVATE KEY-----๊นŒ์ง€
    • ๋งจ ๋’ค์— % ๋นผ๊ณ  ๋ณต์‚ฌํ•ด์„œ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

main.yml ์ž‘์„ฑ

์ด ๊ธ€์—์„œ๋Š” CI/CD pipeline ๊ตฌ์ถ•์„ ๋ชฉ์ ์œผ๋กœ ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Docker image build โ†’ push โ†’ EC2 deploy ๊ณผ์ •์„ workflow๋กœ ์ž‘์„ฑํ–ˆ๋‹ค. ์ž‘์„ฑํ•œ workflow๋Š” ๋‹ค์Œ ์ฝ”๋“œ์™€ ๊ฐ™๋‹ค.
์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” dev branch์— push or PR ๋ฐœ์ƒ ์‹œ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •ํ•ด๋‘์—ˆ๋‹ค.

# Workflow name
name: Gradle Build and EC2 Deploy - dev server

# ์–ด๋–ค event๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด workflow๋ฅผ ์‹คํ–‰ํ•  ์‹œ ๋ช…์‹œ
on:
  # dev branch์— push or pull request ๋ฐœ์ƒ ์‹œ
  push:
    branches: ["dev"]
  pull_request:
    branches: ["dev"]

# ์œ„ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ์‹คํ–‰๋  ์ž‘์—…๋“ค
jobs:
  build:
    # VM์˜ ์‹คํ–‰ ํ™˜๊ฒฝ ์ง€์ • -> ubuntu์˜ ์ตœ์‹  ๋ฒ„์ „
    runs-on: ubuntu-latest

    # ์‹คํ–‰๋  jobs ์ˆœ์„œ๋Œ€๋กœ ๋ช…์‹œ
    steps:
    - name: Checkout
      # uses keyword๋ฅผ ํ†ตํ•ด action ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
      # ์—ฌ๊ธฐ์„œ๋Š” ํ•ด๋‹น repository๋กœ checkoutํ•ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” action์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. v3๋„ ์žˆ์œผ๋‹ˆ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ์ ์šฉํ•  ๊ฒƒ.
      uses: actions/checkout@v2

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'

	# yml ํŒŒ์ผ์— ์žˆ๋Š” ๋ณ€์ˆ˜๋“ค์„ secrets๋กœ๋ถ€ํ„ฐ ๊ฐ€์ ธ์™€์„œ yml ํŒŒ์ผ์— ์ ์šฉ
    - name: Set yml file
      uses: microsoft/variable-substitution@v1
      with:
        files: 'src/main/resources/application.yml'
      env:
        cloud.aws.credentials.access-key: ${{ secrets.AWS_ACCESS_KEY }}
        cloud.aws.credentials.secret-key: ${{ secrets.AWS_SECRET_KEY }}

    # Gradle Build๋ฅผ ์œ„ํ•œ ๊ถŒํ•œ ๋ถ€์—ฌ
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    # Gradle Build (test ์ œ์™ธ)
    - name: Build with Gradle
      run: ./gradlew clean build --exclude-task test

    - name: DockerHub Login
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Docker Image Build
      run: docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }}:latest .

    - name: Docker Push
      run: docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }}:latest

    - name: EC2 instance ์ ‘์† ๋ฐ app ์‹คํ–‰
      uses: appleboy/ssh-action@v0.1.6
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        key: ${{ secrets.EC2_KEY }}

        script: |
          sudo docker kill ${{ secrets.DOCKER_IMAGE_NAME }}
          sudo docker rm -f ${{ secrets.DOCKER_IMAGE_NAME }}
          sudo docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }}
          sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }}

          sudo docker run -p ${{ secrets.PORT }}:${{ secrets.PORT }} \
          --name ${{ secrets.DOCKER_IMAGE_NAME }} \
          -d ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE_NAME }}

์ž‘์„ฑ ์™„๋ฃŒ ํ›„ ์ปค๋ฐ‹ํ•ด์ฃผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ฒดํฌ ํ‘œ์‹œ๊ฐ€ ๋œจ๋ฉฐ ์ •์ƒ์ ์œผ๋กœ ์ ์šฉ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


Reference : Github actions๋ฅผ ํ™œ์šฉํ•œ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•

profile
๋‚˜๋Š”์•ผ ๋งํ•˜๋Š” ๊ฐœ๋ฐœ(๊ฐ)์ž

0๊ฐœ์˜ ๋Œ“๊ธ€