Post

[CI_CD] 일련의 로직을 실행시키는 기능인 Github Actions을 이용한 개인 프로젝트 CI/CD 구축 방법

🪵 CI/CD란

🪵 테스트 (Test), 통합 (Merge), 배포(Deploy)의 과정을 자동화하는 것을 말한다

새 기능을 구현한 코드가 있을 때마다, 커밋(Commit) 하고 브랜치에 머지(Merge) 해서,
서버(AWS EC2)에서 직접 매번 업데이트된 코드를 다운받아, 빌드 (Build) 하고 테스트 (Test) 해서 실행시켜주는 과정이 너무 불편하다.
이러한 반복적인 배포 과정을 자동화시키기 위해 CI/CD (Continuous Integration, Continuous Deployment)를 배운다.

🎙️ CI/CD 구축 시, 사용하는 툴
Github Actions
Jenkins
※ 둘 중 하나마 사용할 줄 알아도 필요한 CI/CD 구성을 전부 할 수 있다.

Jenkins의 단점으로는 별도의 서버에 구축해야한다는 점이 있다.
Github Actions은 별도의 서버 구축 없이 Github에 내장된 Github Actions 기능을 사용할 수 있기 때문에,
비용적인 측면이나 세팅 측면에서 이점이 있다.

👹 Github Actions란

👹 Github Actions는 로직을 실행시킬 수 있는 일종의 컴퓨터라고 생각하면 된다

CI/CD 과정에서 Github Actions는 빌드, 테스트, 배포에 대한 로직을 실행시키는 역할을 한다.

1
2
3
4
5
6
7
8
9
🧶 동작원리
(1) 코드 작성 후 Commit
(2) Github에 Push
(3) Github이 Push(Event Trigger)라는 이벤트를 감지해서, Github Actions에 작성한 로직이 실행
    a. ssh를 통한 EC2로의 원격접속
    b. git pull
    c. 빌드(build) 및 테스트(Test)
    d. 코드 변경사항 반영 (deploy)
    e. 최신코드로 서버를 재실행

📂.github > 📂workflows > deploy.yml
※ 프로젝트 루트에 .github 디렉토리 아래 workflows 디렉토리 안에 yml 파일을 작성해야 한다.
※ yml: 들여쓰기에 따라 인식하는 파일형식

Github Actions에서 실행하는 일련의 로직을 Workflow라고 한다. (하나의 단위)
로직이 실행되는 시점 설정을 Event라 한다. (workflow_dispatch, push, pull_request)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Workflow 이름 설정
name: Practice Syntax on Github Actions

on:
  push:
    branches:
      - main

jobs:
  Deploy: # job을 식별하기 위한 id (내 마음대로 고유값 명명)
    runs-on: ubuntu-latest # 운영체제 선택(일종의 컴퓨터), ubuntu 환경 / 가장 최신버전 (latest)

    steps:
      - name: print Hello World
        run: echo "Hello World" # 실제 리눅스 코드

      - name: print Hello world x 2
        run: |
          echo "Hello World"
          echo "Hello World"

      - name: print variables built-in Github Actions
        run: |
          echo $GITHUB_SHA
          echo $GITHUB_REPOSITORY

      - name: print secrets value
        run: |
          echo ${{ secrets.MY_NAME }}
          echo ${{ secrets.MY_HOBBY }}

하나의 Workflow는 하나 이상의 Job으로 구성된다.
※ 여러 Job은 기본적으로 병렬적으로 수행된다.
또한, 하나의 Job은 하나 이상의 Step으로 구성되어 있다.
(Step은 특정 작업을 수행하는 가장 작은 단위이다.)

$GITHUB_SHA에는 현재 커밋의 id 값이 담겨있다. (각 커밋은 고유값을 가지고 있다.)
$GITHUB_REPOSITORY에는 현재 Github Repository이 담겨있다.

Github Actions에는 시크릿값을 저장할 수 있는 기능이 있다.
해당 시크릿값을 조회해도 ***로 뜬다.

🍜 시크릿값 저장
[⚙️ Settings > 🗝️ Secrets and variables > Actions]
🕹️ 'New repository secret'

※ 한번 키와 값을 만들면 저장된 값을 조회하는 기능이 없다.

🗿 개인 프로젝트에서 많이 쓰는 CI/CD 구축 방법

🗿 개인 프로젝트에서 많이 쓰는 CI/CD 구축 방법

🌕 장점

  • git pull을 활용해서 프로젝트 코드의 변경된 부분만 업데이트하기 때문에, CI/CD 속도가 빠르다.
    (대부분의 CI/CD 방식은 전체 프로젝트를 통째로 갈아끼우는 방식을 사용한다. 압축해서 전달..)
  • CI/CD 툴로 Github Actions만 사용하기 때문에 인프라 구조가 단순하다.

🌑 단점

  • 🐖 빌드 작업을 EC2에서 직접 진행하기 때문에 운영하고 있는 서버의 성능에 영향을 미칠 수 있다.
    (빌드 작업이 생각보다 컴퓨터 자원을 많이 잡아먹는다..)
  • 🐖 Github 계정정보가 EC2에 저장되기 때문에 협업프로젝트에서는 협업 개발자에게 노출된다.

따라서, 개인프로젝트에서 CI/CD를 심플하고 빠르게 적용시키고 싶을 때 적용한다.

🍜 Spring Boot 애플리케이션 배포
[EC2]
✂️ EC2 인스턴스 생성..
(인스턴스 유형: t3a.small)
(※ Spring Boot가 생각보다 무겁기 때문에 이정도 스펙은 필요하다.)

[EC2 인스턴스 연결]
[Spring Boot 실행시키기 위한 JDK 설치 필요]
sudo apt update
sudo apt install openjdk-11-jdk -y
java -version

git clone [GITHUB_REPOSITORY_PATH]
cd [APPLICATION_ROOT_DIRECTORY]
chmod +x gradlew
./gradlew clean build
cd build/libs

nohup java -jar [NO_PLAIN_SNAPSHOT].jar &
enter ⌨️

sudo lsof -i:8080
(8080 포트에서 실행되고 있는 프로세스 확인)

sudo fuser -k -n tcp 8080
(8080 포트에서 실행되고 있는 프로세스 종료)

[private Github Repository Authentication 설정 변경]
git config --global credential.helper store
(한번 계정과 토큰값을 입력하면 그값을 저장해두고, 그 이후로는 물어보지 않는다.)

git pull origin main
Username for 'https://github.com':
Password for 'https://[USER_NAME]@github.com':
(설정 한 후 최초에만 입력)

cd ~
ls -a
cat .git-credentials
(여기에 입력했던 계정과 Github 토큰 정보가 노출된다.)
🍜 CI/CD 구축
[in Local Spring Boot Application]
프로젝트 루트에 📂.github > 📂 workflows > deploy.yml 파일 생성

ssh를 통한 원격접속을 할 때, Github Actions의 라이브러리를 이용한다 ( in MarketPlace )
이 때, 라이브러리에서 정한 규칙을 따르면 된다.
(Github Actions (일종의 컴퓨터) -> EC2(컴퓨터), 원격접속)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Connect to EC2 with ssh
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          script_stop: true
          script: |
            cd /home/ubuntu/[GITHUB_REPOSITORY_NAME]
            git pull origin main
            ./gradlew clean build
            sudo fuser -k -n tcp 8080 || true
            nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &
[Github Actions에 시크릿값 넣기]
[⚙️Settings > 🗝️ Secrets and variables > Actions]
🕹️ 'New repository secret'
 EC2_HOST
 [EC2 인스턴스 퍼블릭IP 주소]
 🕹️ 'Add secret'

🕹️ 'New repository secret'
 EC2_USERNAME
 ubuntu
 🕹️ 'Add secret'

🕹️ 'New repository secret'
 EC2_PRIVATE_KEY
 [keypair (.pem) 값]
 🕹️ 'Add secret'

keypair 값은 cmd로 .pem 파일이 있는 폴더로 가서
type [KEYPAIR_NAME].pem (in windows)
-----BEGIN RSA PRIVATE KEY----- 부터

-----END RSA PRIVATE KEY----- 까지 복사 📑

시크릿값까지 Github Actions에서 설정이 되었다면

git add .
git commit -m "커밋메세지"
git push origin main

🍝 .gitignore에 추가된 application.yml

🍝 민감한 값이 작성된 application.yml 파일은 .gitignore에 추가되어 github에 올리지 않는다

AWS EC2에서 git pull을 당겨도 application.yml 파일이 당연히 없다.
결국 EC2에서 직접 파일을 생성해준다면, 자동 배포라 할 수 없다.

위에서 SSH 연결에 사용한 appleboy에서 환경변수에 대한 규칙을 제공한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
name: Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Connect to EC2 with ssh
        uses: appleboy/ssh-action@v1.0.3
        # 아래 script 안에서 환경변수를 사용하고 싶다면 먼저 선언
        env:
          APPLICATION_PROPERTIES: ${{ secrets.APPLICATION_PROPERTIES }}
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          # 위에서 선언한 변수 중 어떤 변수를 쓸지 선택
          envs: APPLICATION_PROPERTIES
          script_stop: true
          script: |
            cd /home/ubuntu/[GITHUB_REPOSITORY_NAME]
            rm -rf src/main/resources/application.yml
            git pull origin main
            echo "$APPLICATION_PROPERTIES" > src/main/resources/application.yml
            ./gradlew clean build
            sudo fuser -k -n tcp 8080 || true
            nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &

다음과 같이, Marketplace, SSH Remote Commands에서 사용법을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  - name: Pass environment
    uses: appleboy/ssh-action@v1
+   env:
+     FOO: "BAR"
+     BAR: "FOO"
+     SHA: ${{ github.sha }}
    with:
      host: ${{ secrets.HOST }}
      username: ${{ secrets.USERNAME }}
      key: ${{ secrets.KEY }}
      port: ${{ secrets.PORT }}
+     envs: FOO,BAR,SHA
      script: |
        echo "I am $FOO"
        echo "I am $BAR"
        echo "sha: $SHA"
🍜 Github Actions 에 시크릿값으로 application.yml 코드 설정
[⚙️Settings > 🗝️ Secrets and variables > Actions]
🕹️ 'New repository secret'
 APPLICATION_PROPERTIES
 [application.yml 코드 전체 복붙 📑]
 🕹️ 'Add secret'

시크릿값까지 설정 했다면
git add .
git commit -m "커밋메세지"
git push origin main

[In EC2 Instance]
[Application Project Root Directory]
cd src/main/resources
cat applicaiton.yml
제대로 반영되었나 확인 🔅

단, application.yml 파일의 변경은 수동으로 Github에서 시크릿값을 변경해주는 수 밖에 없다.

🔌 테스트 코드와 배포

🔌 ./gradlew clean build 명령에 테스트까지 포함되어 있다

🌱 start.spring.io에서 SpringBoot Application을 생성하면,
📂 src > 📂 test > 📂 java > 📂 [PACKAGE_NAME] 해당 경로에
☕ [APPLICATION_NAME]ApplicationTests.java 파일이 기본적으로 생성되는데,
이 파일 내에 있는 테스트 코드를 일부러 터뜨렸을 때, Github Actions에 작성한 로직 중 ./gradlew clean build 명령에서 빌드 오류를 뱉고 해당 명령어에서 배포를 중단한다.

1
2
3
4
5
6
7
8
@SpringBootTest
class TestServerApplicationTests {

  @Test
  void contextLoads() {
    throw new RuntimeException("Fail"); // 일부로 오류 뱉기
  }
}

./gradlew test 명령어를 입력해보면 테스트 코드가 실패하는 것을 알 수 있다.
해당 코드를 github에 push해보고 Github ActionsWorkflow가 중단된 것을 알 수 있다.

FAILURE: Build failed with an exception.

즉, 테스트 코드까지 통과해야 배포가 진행된다.

This post is licensed under CC BY 4.0 by the author.