[Spring] Github Actions과 Docker을 활용한 CI/CD 구축
Github Actions과 Docker을 활용한 CI/CD 구축
[1] CI / CD 자동화 배포 전체 흐름
1. CI (Continuous Integration) 배포
새로운 코드 변경 사항을 주기적으로
1)빌드하고
2)기존 파일과 병합시 오류가 없는지 테스트를 완료한 후
3) 문제가 발생한 경우 다시 코드 수정 후 빌드 / 문제가 없는 경우 배포를 진행한다
2. CD (Continuous Delivery / Continuous Deployment) 배포
개발자가 수정한 코드를 저장소뿐 아니라 사용자가 사용할 수 있는 프로덕션 환경까지 항상 신뢰 가능한 수준에서 배포될 수 있도록 관리한다
[2] Github Actions 선정 이유
Travis CI, Jenkins 등 잘 알려진 CI/CD 구축 방법들이 있지만 Github Actions을 사용하려는 이유는
먼저 무료플랜에서 진행이 가능하다는 점 :) 게다가 GitHub 저장소 내에서 직접 설정하고 사용할 수 있고
.github/workflows
에 다소 간단하게 구현할 수 있다고 생각이 들어 Github Actions 배포 방식을 택하게 되었다
[3] GitHub Actions 워크플로우 기본 설정 리뷰
1. 코드 변경시 배포 스크립트 실행
1
2
3
4
5
6
7
8
9
10
11
# github repository actions 페이지에 나타날 이름
name: CI/CD using github actions & docker
# event trigger
# master이나 dev 브랜치에 push가 되었을 때 실행
on:
push:
branches: [ "master", "dev" ]
permissions:
contents: read
- master이나 dev 브랜치에 push가 되었을 때 workflow를 실행한다
- master 브랜치 - 배포용 서버 코드
- dev 브랜치 - 개발용 서버 코드(merge용)
- feature 브랜치 - 기능별 코드
2. JDK 설정
1
2
3
4
5
6
7
8
9
10
11
12
jobs:
CI-CD:
runs-on: ubuntu-latest
steps:
# JDK setting - github actions에서 사용할 JDK 설정 (프로젝트나 AWS의 java 버전과 달라도 무방)
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- Github Actions용 Java 프로젝트를 빌드하거나 실행하기 위한 JDK 버전 설정
uses: actions/checkout@v3
: 새로운 코드 변경사항을 가져오기 위한 액션
3. gradle caching
1
2
3
4
5
6
7
8
9
10
# gradle caching - 빌드 시간 향상
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: $-gradle-$
restore-keys: |
$-gradle-
- Gradle 빌드 시간을 향상시키기 위해 캐싱을 설정하는 것
actions/cache@v3
: 캐싱 처리 액션path:
: 캐싱할 디렉토리 경로key
: 캐시를 식별하기 위한 키restore-keys
: 이전에 생성된 캐시를 복원하기 위한 키
4. 환경별 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
# 환경별 yml 파일 생성(1) - application.yml
- name: make application.yml
if: |
contains(github.ref, 'master') ||
contains(github.ref, 'dev')
run: |
mkdir ./src/main/resources # resources 폴더 생성
cd ./src/main/resources # resources 폴더로 이동
touch ./application.yml # application.yml 생성
echo "$" > ./application.yml # github actions에서 설정한 값을 application.yml 파일에 쓰기
shell: bash
# 환경별 yml 파일 생성(2) - dev
- name: make application-dev.yml
if: contains(github.ref, 'dev')
run: |
cd ./src/main/resources
touch ./application-dev.yml
echo "$" > ./application-dev.yml
shell: bash
# 환경별 yml 파일 생성(3) - prod
- name: make application-prod.yml
if: contains(github.ref, 'master')
run: |
cd ./src/main/resources
touch ./application-prod.yml
echo "$" > ./application-prod.yml
shell: bash
- 배포 환경용 파일을 분리하여 사용한다
- Github의 Secret Key같은 중요한 정보를 저장해주고
gitignore
에 저장해서 Github에는 올라가지 않도록 주의!!
5. Gradle build
1
2
3
# gradle build
- name: Build with Gradle
run: ./gradlew build -x test
- Gradle을 사용하여 프로젝트를 빌드하는 GitHub Actions 단계 지정
name
: 이 단계 이름run:
: 이 단계에서 실행할 명령어를 지정
6. Docker build & push
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# docker build & push to production
- name: Docker build & push to prod
if: contains(github.ref, 'master')
run: |
docker login -u $ -p $
docker build -t $/eroom-prod
docker push $/eroom-prod
# docker build & push to develop
- name: Docker build & push to dev
if: contains(github.ref, 'dev')
run: |
docker login -u $ -p $
docker build -t $/eroom-dev
docker push $/eroom-dev
- 배포용 서버와 개발용 서버는 설정 파일의 내용이 다르기 때문에 빌드 파일의 내용과 생성된 도커 이미지도 다르다.
- docker hub repository > 1) eroom-prod 2) eroom-dev 로 나뉜다
7. Deploy to 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
25
26
27
28
29
30
31
32
33
34
35
36
## deploy to production
- name: Deploy to prod
uses: appleboy/ssh-action@master
id: deploy-prod
if: contains(github.ref, 'master')
with:
host: $ # EC2 퍼블릭 IPv4 DNS
username: ubuntu
key: $
envs: GITHUB_SHA
script: |
sudo docker ps
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
sudo docker pull $/eroom_prod
sudo docker run -d -p 8080:8080 $/eroom_prod
sudo docker image prune -f
## deploy to develop
- name: Deploy to dev
uses: appleboy/ssh-action@master
id: deploy-dev
if: contains(github.ref, 'dev')
with:
host: $ # EC2 퍼블릭 IPv4 DNS
username: $ # ubuntu
password: $
port: 22
key: $
script: |
sudo docker ps
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
sudo docker pull $/eroom_dev
sudo docker run -d -p 8080:8080 $/eroom_dev
sudo docker image prune -f
- EC2에서 docker pull
[4] 현재까지의 GitHub Actions 워크플로우 전체 파일
gradle.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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# github repository actions 페이지에 나타날 이름
name: CI/CD using github actions & docker
# event trigger
# master이나 dev 브랜치에 push가 되었을 때 실행
on:
push:
branches: [ "master", "dev" ]
permissions:
contents: read
jobs:
CI-CD:
runs-on: ubuntu-latest
steps:
# JDK setting - github actions에서 사용할 JDK 설정 (프로젝트나 AWS의 java 버전과 달라도 무방)
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# gradle caching - 빌드 시간 향상
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: $-gradle-$
restore-keys: |
$-gradle-
# 환경별 yml 파일 생성(1) - application.yml
- name: make application.yml
if: |
contains(github.ref, 'master') ||
contains(github.ref, 'dev')
run: |
mkdir ./src/main/resources # resources 폴더 생성
cd ./src/main/resources # resources 폴더로 이동
touch ./application.yml # application.yml 생성
echo "$" > ./application.yml # github actions에서 설정한 값을 application.yml 파일에 쓰기
shell: bash
# 환경별 yml 파일 생성(2) - dev
- name: make application-dev.yml
if: contains(github.ref, 'dev')
run: |
cd ./src/main/resources
touch ./application-dev.yml
echo "$" > ./application-dev.yml
shell: bash
# 환경별 yml 파일 생성(3) - prod
- name: make application-prod.yml
if: contains(github.ref, 'master')
run: |
cd ./src/main/resources
touch ./application-prod.yml
echo "$" > ./application-prod.yml
shell: bash
# gradle build
- name: Build with Gradle
run: ./gradlew build -x test
# docker build & push to production
- name: Docker build & push to prod
if: contains(github.ref, 'master')
run: |
docker login -u $ -p $
docker build -t $/eroom-prod
docker push $/eroom-prod
# docker build & push to dev
- name: Docker build & push to dev
if: contains(github.ref, 'dev')
run: |
docker login -u $ -p $
docker build -t $/eroom_dev
docker push $/eroom_dev
## deploy to production
- name: Deploy to prod
uses: appleboy/ssh-action@master
id: deploy-prod
if: contains(github.ref, 'master')
with:
host: $ # EC2 퍼블릭 IPv4 DNS
username: ubuntu
key: $
envs: GITHUB_SHA
script: |
sudo docker ps
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
sudo docker pull $/eroom_prod
sudo docker run -d -p 8080:8080 $/eroom_prod
sudo docker image prune -f
## deploy to develop
- name: Deploy to dev
uses: appleboy/ssh-action@master
id: deploy-dev
if: contains(github.ref, 'dev')
with:
host: $ # EC2 퍼블릭 IPv4 DNS
username: $ # ubuntu
password: $
port: 22
key: $
script: |
sudo docker ps
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
sudo docker pull $/eroom_dev
sudo docker run -d -p 8080:8080 $/eroom_dev
sudo docker image prune -f
[5] Github Actions 적용 순서
1. Github에 public 레포지토리 생성 > Setting > Secrets and variables > Actions 탭
2. New repository secret에 각각 추가 (Settings > Secrets and variables > Actions)
DOCKER_USERNAME : 본인의 Docker Hub Username
DOCKER_PASSWORD : 본인의 Docker Hub Password
HOST_PROD: prod 환경의 EC2 인스턴스 ip (EC2 퍼블릭 IPv4 DNS)
HOST_PROD: dev 환경의 EC2 인스턴스 ip (EC2 퍼블릭 IPv4 DNS)
PRIVATE_KEY: 개인키
USERNAME: EC2 인스턴스 계정 ID(ec2-user) # ubuntu
PASSWORD: EC2 인스턴스에 SSH로 연결할 때 필요한 패스워드
YML: application.yml 파일을 생성할 때 사용되는 값
YML_DEV: application-dev.yml 파일을 생성할 때 사용되는 값
YML_PROD: application-prod.yml 파일을 생성할 때 사용되는 값
3. Github의 Actions 탭 > set up a workflow yourself
4. gradle.yml
설정하기
5. 빌드 시 plain jar 생성하지 않도록 설정
1
2
3
jar{
enabled = false
}
[6] Trouble Shooting
- 보기만 해도 마음이 아픈 “X” 표시!!!!!!
- 에러를 하나씩 차근차근 뜯어보기로 한다!!
첫번째 Trouble Shooting
- ./gradlew 스크립트에 실행 권한이 없어서 발생하는 에러
gradle build 되기 전 권한을 부여하는 스크립트 작성해서 해결한다
```yml # gradlew 스크립트에 실행 권한 부여 - name: gradlew 스크립트에 실행 권한 부여 run: | chmod +x ./gradlew
1
2
3
# gradle build
- name: Build with Gradle
run: ./gradlew build -x test ```
두번째 Trouble Shooting
- non-interactive 환경에서 Docker 명령어를 실행하려고 할 때 발생하는 에러
“Error: Cannot perform an interactive login from a non TTY device” 에러를 구글링 해 본 결과
AWS CLI 자격증명이 안되어 있다는 것을 알게 되었다!!
참고하면 좋을 블로그를 찾았다
AWSGithub-actions로-ECS를-통해-서비스-배포하기
aws --version
명령어를 통해 AWS CLI 가 최신버전으로 설치되어 있는지 확인 (2.XX.XX)
aws configure
명령어를 통해 AWS CLI에 사용될 AWS Access Key ID, AWS Secret Access Key 등을 설정한다
다시 GitHub Actions 작업을 실행한다
세번째 Trouble Shooting
- Error: Cannot perform an interactive login from a non TTY device 연이어 이런 오류가 떴다 GitHub 저장소에 “DOCKER_USERNAME” 및 “DOCKER_PASSWORD”를 다시 확인하라는 에러
곧바로 Repository secrets에 DOCKER_USERNAME/DOCKER_PASSWORD가 제대로 있는지 확인한 결과
DOCKERHUB_USERNAME/DOCKERHUB_PASSWORD로 “"”오타”"”가 있었다…ㅎ