CI/CD는 처음하는 분들에게는 꽤 난이도가 높은 작업이라고 생각합니다.
그래서 많은 방법 중에 제가 생각하는 가장 간단한 방법인 도커를 이용하여 CI/CD를 구축하는 것을 소개하고자 합니다.
단계 별로 기본적인 CI를 구성하고, 도커허브에 자동으로 푸시한 이후, AWS에 접속하여, 배포하는 총 4단계로 나누어 진행하겠습니다.
해당 방식의 가장 큰 장점은 쉽다!, 공짜다! 정도가 될 것 같습니다:)
준비물
준비물은 아래와 같습니다.
그리고 저는 아래의 환경에서 작업하였습니다.
- Ubuntu Server 22.04
- Gradle
- JDK 11
1. 기본 github action CI 설정
우선 가장 간단하게 깃허브 main 브랜치에 푸시가 되었을 때 빌드를 수행하는 워크플로우를 추가해 봅시다.
프로젝트 루트 폴더에서 .github/workflows/basic-ci.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
name: Basic CI
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# build
- name: Setup Java JDK
uses: actions/setup-java@v1
with:
java-version: 11
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- 3-5 Lines : main 브랜치에 push 되었을 때 해당 CI가 시작된다.
- 9 Lines : 우분투 환경에서 실행된다.
- 15-18 Lines : JDK 버전은 11
- 20-21 Lines : gradlew의 권한을 변경한다.
- 23-24 Lines : gradlew를 통해 build 파일을 생성한다.
해당 파일을 main브랜치에 푸시하면 github action탭에서 성공하는 것을 볼 수 있습니다.
succeed basic ci succeed basic ci 2
2. 자동으로 도커 허브에 푸시
이번에는 1번에서 빌드한 jar 파일을 도커에 넣고 도커허브에 푸시하는 것 까지 해보겠습니다.
도커파일 생성
루트 폴더에 Dockerfile을 생성하고 아래 내용을 입력해줍니다. (확장자 없이 그냥 만들면 됨)
jar 파일을 컨테이너 안에 넣고 실행시킨다 라는 내용입니다.
1
2
3
4
5
6
7
8
9
10
11
12
FROM openjdk:11
EXPOSE 8080
# The application's jar file
ARG JAR_FILE=build/libs/demo-0.0.1-SNAPSHOT.jar
# Add the application's jar to the container
ADD ${JAR_FILE} demo.jar
# Run the jar file
ENTRYPOINT ["java", "-jar", "/demo.jar"]
도커 허브 Repository 생성
도커 허브에 로그인하고 Create Repository로 새 Repository를 만들어 줍니다.
create docker hub repository 1 create docker hub repository 2
도커허브 토큰 생성
깃허브 액션에서 도커 허브에 접속할 때 사용할 토큰을 발급받아야 합니다.
Account Settings - Security - New Access Token
create docker hub token
github secret 추가
public repo라면 비밀번호를 숨길 필요가 있으므로 github secret에 아이디와 repository, 발급받은 토큰을 등록합니다.
깃허브 프로젝트 - Settings - Secrets And Variable - Actions - New Repository Secret
create docker hub github secret
워크플로우 추가
이제 도커 허브에 푸시를 하기 위한 준비는 끝났습니다. 이제 해당 작업을 워크플로우에 추가해 줍시다.
기존에 작성되어 있는 부분 아래에 추가합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# docker push
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
username: ${ secrets.DOCKERHUB_ID }
password: ${ secrets.DOCKERHUB_TOKEN }
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${ secrets.DOCKERHUB_REPO }
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${ steps.meta.outputs.tags }-${ steps.date.outputs.date }
labels: ${ steps.meta.outputs.labels }
- 2-4 Lines : 도커 태그로 빌드 날짜를 저장하기 위해 현재 날짜 데이터를 불러온다.
- 6-10 Lines : 도커 허브에서 발급받은 토큰을 이용해 로그인한다.
- 12-16 Lines : 도커의 메타정보를 추출한다.
- 18-24 Lines : 도커 허브에 푸시한다.
succeed docker push ci
3. AWS 접속
도커 허브에 빌드파일을 푸시했으니 이제 AWS에서 해당 이미지를 가져와 실행시켜주면 되겠죠.
그 전에 AWS에 먼저 접속을 해봅시다.
github secret 추가
AWS 접속 정보 역시 숨길 필요가 있으므로 github secret에 AWS EC2의 퍼블릭 IPv4 DNS와 pem키를 등록해줍니다.
여기서 pem키는 에디터로 열었을 때 나오는 문자들을 넣어주면 됩니다.
aws ip address
워크플로우 추가
기존에 작성되어 있는 부분 아래에 추가해 줍니다.
1
2
3
4
5
6
7
8
9
10
# server test
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: $ # EC2 인스턴스 퍼블릭 DNS
username: ubuntu
key: $ # pem 키
script: |
touch hello.txt
echo "hello world" > hello.txt
- EC2에 접속하여 루트 폴더에 hello.txt 파일을 생성하고 “hello world”를 저장한다.
EC2에 접속해서 hello.txt가 생겼고, 안에 “hello world”가 입력되어 있다면 성공입니다.
4. 도커 pull & run
이제 접속한 EC2에서 도커 허브에 푸시한 이미지를 가져와 실행만 시켜주면 끝입니다!
AWS 도커 설치
EC2에 도커를 설치해 줍시다. 해당 내용은 공식 문서를 참고해 주세요.
도커 권한 설정
설치 후 sudo 없이 docker ps
명령어를 입력했을 때 권한 오류가 발생한다면 아래 명령어를 입력합니다.
sudo chmod 666 /var/run/docker.sock
Ref : https://kyungyeon.dev/trouble-shooting/2
워크플로우 추가
드디어 마지막 워크플로우입니다. 3번에서 추가했던 워크플로우를 아래와 같이 수정해 줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
# docker run in server
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: $ # EC2 인스턴스 퍼블릭 DNS
username: ubuntu
key: $ # pem 키
script: |
docker pull $-$
docker stop demo
docker rm demo
docker run --restart always -d -p 80:8080 --name demo $-$
- EC2에 접속하여 도커 이미지를 pull 하고 80번 포트로 demo라는 이름으로 실행한다.
이제 배포된 서버의 IP주소로 접속해보면 반가운(?) 404페이지가 뜨는 걸 볼 수 있습니다:)
404 page
최종 워크플로우 파일
.github/workflows/basic-ci.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
name: Basic CI
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# build
- name: Setup Java JDK
uses: actions/setup-java@v1
with:
java-version: 11
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
# docker push
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
username: ${ secrets.DOCKERHUB_ID }
password: ${ secrets.DOCKERHUB_TOKEN }
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${ secrets.DOCKERHUB_REPO }
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${ steps.meta.outputs.tags }-${ steps.date.outputs.date }
labels: ${ steps.meta.outputs.labels }
# docker run in server
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: $ # EC2 인스턴스 퍼블릭 DNS
username: ubuntu
key: $ # pem 키
script: |
docker pull $-$
docker stop demo
docker rm demo
docker run --restart always -d -p 80:8080 --name demo $-$