Home Github actions 으로 구현하는 Spring -> AWS CI/CD 자동화
Post
Cancel

Github actions 으로 구현하는 Spring -> AWS CI/CD 자동화

Github actions 기초

Github actions 의 기본 구성

  • Events
  • Workflows
  • Jobs
  • Actions
  • Runners

Events
특정 event 가 발생하면 일련의 동작을 수행한다.

특정 이벤트 예시

  1. main 브랜치로 merge
  2. commit 을 push
  3. issue 를 누군가 열먼..

Workflow 이벤트를 정했으면 해당 이벤트가 발생했을 때, 수행할 동작을 정한다.
이를 Workflow 라고 한다.
수행할 동작을 Step 이라고 하며 해당 동작을 모아 Job 이라고 한다.
예를 들어 Job 을 두 개 만들었다.

  • Job : run unit tests
    • step1 : action check out
    • step2 : npm test
  • Job : run E2E tests
    • step1 : action check out
    • step2 : action setup node
    • step3 : npm install
    • step4 : npm run e2e-test

Job : run unit tests 는 step1, step2 를 차례대로 수행한다.
Job : run unit tests 와 Job : run E2E tests 은 병렬로 수행할 수도 있고 순차적으로 수행할 수도 있다. 이건 직접 설정하는 것이다.
이러한 Job 을 모아 하나의 Workflow 에 등록을 하면된다.

Actions
특정 일련의 작업을 모아 미리 정의해둔 것을 action 이라고 한다.
예를 들어 github 에서 미리 만들어둔 checkout 이라는 action이 있다. 내가 checkout 을 새로 구현할 필요 없이 가져다 사용하면 된다.

Runners
Job 이 수행되는 머신(VM 또는 Docker 의 컨테이너와 비슷함)을 Runner 라고 한다.
Job 은 각각의 Runner 에서 수행된다. Job 이 2개면 2개의 Runner 에서 각각의 Job이 수행된다.

설정방법

github actions 를 설정할 프로젝트로 간다.
다음 경로에 yml 파일을 만들어준다.
.github/workflow/원하는파일명.yml
그리고 파일 내용을 작성한다.

1
2
3
4
5
6
7
8
9
10
name: learn-gihub-actions ## 워크플로우 이름을 지정해준다.
on: [push] ## push 이벤트가 발생하면 수행한다.
jobs: ## Job 들을 작성해주는 영역
    check-bats-version: ## 내가 원하는 Job 이름을 지정해준다.
        runs-on: ubuntu-latest ## Job을 수행할 VM 지정
        steps: ## step 들을 작성해주는 영역
            - uses: actions/checkout@v3 ## step: 깃허브에서 제공하는 checkout 이라는 액션을 사용
    job2: ## Job 이름
        runs-on: ubuntu-latest ## Job을 수행할 VM 지정
        ## 이하 생략

실제로 적용하기

  1. Github actions 를 작업할 프로젝트를 들어가 action 탭으로 간다.

사진1

  1. 그 다음 set a workflow your self 를 클릭한다.
    아래 추천해주는 것 중 선택해도 되지만, 처음하는거라 그런거 잘 모르고 내가 필요한 것만 직접 설정할 것이므로 set a workflow your self 버튼을 클릭한다.

  2. yml 을 직접 작성한다.

AWS 세팅을 먼저 하고 와도 되고, yml 코드를 먼저 작성해도 된다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
## 워크플로우 이름 작성
name: deploy-to-ec2

## on 은 어떤 이벤트가 발생했을 때 워크플로우를 수행할지 결정한다.
# main 브런치에 push 하면 실행
on:
  push:
    branches:
      - main

## 환경변수 설정
# 환경변수 모르면 그냥 변수라고 생각하자..
# key : value 구조이다.
env: 
  # AWS S3 버킷을 생성하고 해당 버킷이름을 변수에 저장한다.
  S3_BUCKET_NAME: unichat-deploy-bucket
  # 애플리케이션 이름과 배포그룹 이름을 변수에 저장한다.
  CODE_DEPLOY_APPLICATION_NAME: Unichat-CodeDeploy
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: Unichat-Deploy-Group

## 실행 할 job 목록
jobs:
  # 딱히 생각나는 job 이름이 없다...
  deployment-job:
    
    # ubuntu 최신 버전에서 실행
    runs-on: ubuntu-latest
    
    # 이 Job 에서 수행할 동작들 작성
    steps:
      ## 체크아웃이 뭔지 모르면 깃허브 repository 에서 코드를 내려받는 것...이라고만 알아두자.
      # 각각의 step 에도 이름을 지정할 수 있다. 생략해도 무방함.
      - name: Checkout
        uses: actions/checkout@v3
        
      ## Java 를 set up 한다. 프로젝트의 java 버전이랑 맞춰준다.
      - name: Set up Java 17
        uses: actions/setup-java@v1
        with:
          java-version: 17

      ## gradlew 에 권한부여.
      # gradlew 는 gradle 을 빌드하는 tool 이라고 한다.
      # 권한부여를 안해주면 빌드하다가 오류가 날 수도 있다고 한다.
      # shell 에서 수행하는건 run 으로 하는것 같다..
      - name: Grant execute permission to gradlew
        run: chmod +x ./gradlew
        shell: bash

      ## Gradle 빌드 (test 코드 제외)
      # 테스트 코드가 제대로 안만들어져있다.
      # 테스트 코드에서 오류나면 안되니까 제외시켰다.
      - name: build with gradle
        run: ./gradlew build -x test
        shell: bash
        
      ## 프로젝트를 zip 으로 압축
      # GITHUB_SHA 는 Github actions 에서 자동으로 제공되는 환경변수 중 하나이다.
      # SHA는 설명 안해도 알것이고.. 몰라도 상관없다. SHA 값은 현재 commit 의 SHA 해시 값이다.
      - name: Make zip file
        run: zip -r ./$GITHUB_SHA.zip
        shell: bash

      ## AWS 자격 증명
      # Github action secret 에서 값을 가져온다.
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: $ # IAM User 엑세스 키
          aws-secret-access-key: $ # IAM User 시크릿 엑세스 키
          aws-region: ap-northeast-2

      ## 압축한 zip 파일 S3 로 업로드
      # aws s3 cp 는 약간 scp 명령어랑 비슷한 역할을 하는 듯?? 근데 이제 aws s3 에 맞게 커스텀이 된 명령어 같다..
      # --region 은 S3 가 위치한 지역을 명시한다. ap-northeast-2 는 서울을 의미한다.
      - name: Upload to S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip

      ## S3 에 있는 zip 파일을 EC2 로 배포
      # aws deploy create-deployment 는 awscli 명령어다. deployment를 생성하는 명령어인듯
      # deployment config name 옵션 : 여러 EC2 인스턴스에 한번에 배포할지 순차적으로 배포할지 작성한다. 나는 인스턴스가 하나이므로 뭘 적든 의미가 없다.
      # file exists behavior 옵션 : 배포할 파일의 이름이 중복될 경우 오류가 발생한다. 이 때 덮어씌워서 오류없이 지나갈 것인지 정할 수 있다. 
      # 나는 파일명이 SHA(해시함수) 이므로 매번 달라서 필요 없는 옵션이기는 하다.
      - name: Code Deploy
        run: |
          aws deploy create-deployment \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --file-exists-behavior OVERWRITE \
          --application-name $ \
          --deployment-group-name $ \
          --s3-location bucket=$, bundleType=zip, key=$GITHUB_SHA.zip \
          --region ap-northeast-2

위에서 공부한 문법을 그대로 쓰면 된다. 나는 spring boot 를 ec2 에 배포하는 방법을 검색하여 참고하여 작성했고 모르는 동작은 검색해서 주석으로 넣었다.
채워 넣을 수 없는 부분들은 AWS 를 직접 세팅하면서 채워넣으면 된다.

AWS 세팅

IAM 설정

우선 회권가입을 해준다.

사진2

콘솔에 로그인 클릭한다.

사진3

IAM 으로 이동한다. 검색해서 이동하면 간편하다.

사진4

사진5

사진6
검색창에 AmazonEC2FullAccess, AmazonS3FullAccess, AWSCodeDeployFullAccess 를 각각 검색하여 체크하고 다음으로 넘어간다.

사진7

사진8

사진9

사진10

사진11
선택사항이라 작성하지 않아도 된다.

사진12
완료 버튼 누르기 전에 다른 곳에 복사해놓기.

Github actions 의 secret 에 액세스 키 저장

액세스 키는 타인에게 노출되면 안된다.
하지만 aws 에 액세스 하려면 Runner 가 액세스 키를 알아야한다.
workflow.yml 에 액세스 키를 작성하지 않고 Runner 에 넘겨주기 위해서는 Action secret 이라는 방법을 사용한다.

사진13
프로젝트로 돌아와서 사진의 화살표를 따라간다.

사진14

사진15
액세스 키, 비밀 액세스 키 총 두 개를 만들어준다.

S3

사진16
프로젝트로 돌아와서 사진의 화살표를 따라간다.

사진17

사진18

1
2
3
4
5
## 환경변수 설정
# key : value 구조이다.
env: 
  # AWS S3 버킷을 생성하고 해당 버킷이름을 변수에 저장한다.
  S3_BUCKET_NAME: 방금-생성한-버킷이름

내가 정의한 버킷 이름을 워크플로의 환경변수에 저장한다.

IAM 역할(Role) 생성

S3 에서 zip 을 다운받을 수 있도록 하는 역할 생성

사진19

사진20
EC2 를 선택합니다. 나중에 EC2 에 이 역할을 부여할 수 있다.

사진21

권한 추가 하는 부분에 다음 두 개를 체크하고 넘어간다.

  • AWSCodeDeployFullAccess
  • AmazonS3FullAccess

S3 와 CodeDeploy 에 접근을 할 수 있게 한다.
S3 에서 zip 파일을 다운로드하고, CodeDeploy 가 배포를 수행할 수 있어야 하기 때문이다.

사진22
이름을 붙여주고 역할 생성 버튼을 눌러 마무리한다.
EC2 에 부여할 역할이 생성되었다. 뒤에서 EC2에 이 역할을 부여한다.

CodeDeploy 가 배포를 할 수 있도록 하는 역할 생성

사진23

사진24

CodeDeploy 가 배포를 할 수 있게 하는 권한은 기본적으로 선택되어 있다. 이름 작성하고 바로 역할을 생성해준다.

EC2 생성 및 세팅

사진25

화살표를 따라가 지역을 서울로 변경한다.

사진26

사진27

사진28

키 페어 생성 완료 후 인스턴스 생성까지 완료한다.

사진29

사진30

사진31

역할 드롭박스를 클릭해 위에서 만든 ‘S3 에서 zip 을 다운받을 수 있도록 하는 역할’ 을 선택하고 역할 업데이트 버튼을 클릭한다.

사진32
이제 EC2 에 접속하여 다음 명령어를 입력해 세팅을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo apt update
sudo apt install ruby-full
sudo apt install wget
sudo apt install openjdk-17-jdk
java -version

cd /home/ubuntu
ls -l

wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
ls -l

chmod +x ./install
sudo ./install auto

CodeDeploy agent 설치 확인

1
systemctl status codedeploy-agent

install 실행이 끝나면 위 명령어를 입력해 EC2에 CodeDeploy agent 가 정상적으로 설치되었는지 확인한다.

CodeDeploy 생성

이제 CodeDeploy 를 생성하고 세팅할 차례이다.

사진33

사진34

사진35

사진36

역할에는 아까 위에서 만든 ‘CodeDeploy 가 배포를 할 수 있도록 하는 역할’ 로 설정한다.

사진37

사진대로 셋팅을 마치면 아래로 내려가 배포 그룹 생성 버튼을 눌러 완료한다.

1
2
3
4
5
6
7
8
## 환경변수 설정
# key : value 구조이다.
env: 
  # AWS S3 버킷을 생성하고 해당 버킷이름을 변수에 저장한다.
  S3_BUCKET_NAME: 버킷이름
  # 애플리케이션 이름과 배포그룹 이름을 변수에 저장한다.
  CODE_DEPLOY_APPLICATION_NAME: 애플리케이션 이름
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: 배포 그룹 이름

EC2 인바운드 규칙 설정

내 프로젝트는 웹 사이트이므로 외부에서 HTTP(80) 포트와 8080 포트로 접속이 가능해야한다.

사진38

사진39

사진40

외부 모든 IP 주소에서 80 및 8080 포트를 통해 접속을 허용했다.

appspec.yml

AWS CodeDeploy 는 appspec.yml 에 적힌대로 수행한다.
세부적으로 수행할 동작은 스크립트를 만들어 hook 에 따로 명시한다.

프로젝트의 루트 경로에 appspec.yml 파일을 만들어준다.

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
version: 0.0
os: linux

# source 에 있는 파일들을 destination 으로 복사한다.
# source 는 appspec.yml 파일을 기준으로 상대경로가 된다.
# source: / 이렇게 작성하면 os 기준으로 root 경로가 아니라 appspec.yml 파일이 있는 위치이다.
# 즉 다음 내용은 appspec.yml 파일이 위치한 경로에 있는 모든 파일을 /opt/unichat 으로 복사한다.
files: 
  - source: /
    destination: /opt/unichat
    overwrite: yes

# 권한 부여
# object : 권한을 부여할 대상 지정
permission:
  - object: /opt/unichat
    owner: ubuntu
    group: ubuntu
    mode: 755

# install 단계가 끝나면 deploy.sh 스크립트를 수행하도록 설정.
# deploy.sh 는 jar 파일을 실행하는 스크립트이다.
# runas: root 는 deploy.sh 를 root 권한으로 실행한다는 의미이다.
hooks:
  AfterInstall:
    - location: deploy.sh
      timeout: 60
      runas: root

hooks 에 있는 AfterInstall 은 다음 사진을 봐야 이해가 된다.

사진41

jar 실행은 AfterInstall 이나 ApplicationStart 에 하는 것이 적절하다.
ValidateService 는 배포 완료 후 성공적으로 수행되었는지 확인하는 스크립트가 있는게 적절하다.

deploy.sh

이제 마지막으로 jar 파일을 실행하는 스크립트를 작성한다.

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
32
33
#!/usr/bin/env bash

# 내 프로젝트의 위치
REPOSITORY=/opt/unichat

APP_NAME=unichat
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME

echo "> 현재 구동 중인 애플리케이션 pid 확인"

CURRENT_PID=$(pgrep -f $APP_NAME)

echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"

if [ -z "$CURRENT_PID" ]; then
  echo "현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -9 $CURRENT_PID"
  kill -9 $CURRENT_PID
  sleep 5
fi

echo "> 새 애플리케이션 배포를 시작합니다."

echo "> JAR PATH: $JAR_PATH"

echo "> $JAR_PATH 에 실행권한 추가"
chmod +x $JAR_PATH

echo "> $JAR_PATH 실행"

nohup java -Duser.timezone=Asia/Seoul -jar $JAR_PATH >> $REPOSITORY/nohup.out 2>&1 &

마지막 줄의 -Duser.timezone=Asia/Seoul 은 서울시간으로 실행한다는 의미
-jar 프로젝트.jar » 프로젝트위치/nohup.out 2>&1 는 프로젝트의 stdout(표준 출력)과 stderr(표준 에러) 를 nohup.out 파일에 추가한다는 의미다. Spring 실행 시 콘솔창에 출력되는 로그를 전부 nohup.out 에서 확인할 수 있다.

AWS RDS(MySQL)

프로젝트에 MySQL 이 있어야 한다면 아래 사진을 따라하면 된다.

사진42

사진43

사진44

사진45

사진46

사진47

사진48

사진49

복사 후 내 프로젝트의 sql 연동 하는 파일로 가서 다음과 같이 수정한다.
spring.datasource.url=jdbc:mysql://방금-복사한-내-엔드포인트:3306/DB이름?createDatabaseIfNotExist=true

테스트

워크플로.yml 파일을 push 하기 전에 EC2 를 두 개 켜자.
첫 테스트에 바로 성공하면 좋겠지만 그러지 않을 경우 어디서 문제가 발생했는지 알기 위해 EC2 를 두 개 켜놓는다.

첫번째 EC2 에서는 deploy.sh 파일에서 echo 로 출력한 문장을 확인하려고 한다.

1
2
3
4
cd /opt/codedeploy-agent/deployment-root/deployment-logs/
ls -l

tail -1f codedeploy-agent-deployments.log

두번째 EC2 에서는 프로젝트의 출력을 확인하는 방법이다.
아까 프로젝트의 stdout 과 stderr 를 nohup.out 파일에서 확인하기로 했다.

1
2
3
4
cd 프로젝트경로
ls -l

tail -1f nohup.out

두 로그를 띄워놓은 채로 yml 파일을 push 하여 어디서 오류가 나는지 확인하고 틀린부분 수정하면 된다.
오류를 모두 수정했다면 http://EC2-엔드포인트:8080/ 으로 접속해보면 정상적으로 접속할 수 있다.

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

Bean 생명주기 콜백

Bean Scope