본 프로젝트에서 로컬에서 푸쉬한 프로젝트를 github actions를 통해 build하고 빌드된 파일을 도커 이미지로 업로드하여 EC2에서 pull 받아 사용하는 방식으로 아키텍쳐를 설계하였습니다.
CI / CD란?
CI/CD는 약어로, 몇 가지의 다른 의미를 가지고 있다.
CI/CD의 "CI"는 개발자를 위한 자동화 프로세스인 지속적인 통합(Continuous Integration)을 의미한다.
CI를 성공적으로 구현할 경우 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 리포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있다.
CI/CD의 "CD"는 지속적인 서비스 제공(Continuous Delivery) 및/또는 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용된다. 두 가지 의미 모두 파이프라인의 추가 단계에 대한 자동화를 뜻하지만 때로는 얼마나 많은 자동화가 이루어지고 있는지를 설명하기 위해 별도로 사용되기도 한다.
1. CI 구현
먼저 GitHub Actions를 사용하여 코드를 push할 때마다 해당 spring build 파일을 docker에 image로 빌드해주는 자동화 과정을 구현해보도록 하겠다.
전체 workflow 코드
name: CI / CD
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
# Spring Boot 애플리케이션을 빌드하여 도커허브에 푸시
application-build:
runs-on: ubuntu-latest
permissions:
contents: read
# 지정한 저장소(현재 REPO)에서 코드를 워크플로우 환경으로 가져오도록 하는 github action
steps:
- uses: actions/checkout@v4
# 1. Java 17 세팅
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# 2. application-private.yml 설정
- name: Set application-private.yml file
run: |
# application-private.yml 파일 생성
cd ./src/main/resources
touch ./application-private.yml
# GitHub-Actions 에서 설정한 값을 application-private.yml 파일에 쓰기
echo "${{ secrets.APPLICATION_YML }}" >> ./application-private.yml
shell: bash
# 3. gradle을 통해 소스를 빌드.
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew clean build
# { username/repository } 로 빌드 & . 이 의미하는 것은 Dockerfile의 경로를 의미함.
- name: Docker build & push to docker repo
run: |
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USER }}/alpha_dlink .
docker push ${{ secrets.DOCKER_USER }}/alpha_dlink
전체 워크플로우는 다음과 같다. 해당 코드는 CI 구현 부분이다. 하나하나 중요한 work들을 뜯어보면서 분석해보자
외부 환경변수 사용
먼저 workflow에서 환경변수를 사용하기 위해 해당 repository의 설정에서 application.yml 이외에 추가적인 yml 파일에 대한 secret key를 추가한다.
이후 해당 워크플로우에서 yml 파일을 생성하는 로직을 추가한다.
- name: Set application-private.yml file
run: |
# application-private.yml 파일 생성
cd ./src/main/resources
touch ./application-private.yml
# GitHub-Actions 에서 설정한 값을 application-private.yml 파일에 쓰기
echo "${{ secrets.APPLICATION_YML }}" >> ./application-private.yml
shell: bash
touch 파일명 : 해당 경로에 파일을 생성하는 명령어
등록된 시크릿 키는 secrets.APPLICATION_YML 로 사용할 수 있다.
> 와 >>의 차이
>: 명령어 뒤에 나오는 파일에 쓸 때 사용(=write or overwrite)
>>: 명령어 뒤에 나오는 파일에 추가할 때 사용(=append)
Spring boot 빌드 파일 생성.
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew clean build
./gradlew [clean] build [-x test] 처럼 옵션을 추가할 수 있다.
- clean: 이전에 생성된 빌드 아티팩트를 삭제하는 옵션이다. 이전 빌드의 결과가 현재 빌드에 영향을 미치지 않도록 한다.
- -x test: -x 옵션은 특정 태스크를 실행하지 않도록 지정하는 옵션이다. test 태스크를 실행하지 않고 빌드 프로세스를 진행. 즉, 유닛 테스트나 통합 테스트를 건너뛰고 코드를 빌드한다.
Docker에 빌드 파일 업로드
워크플로우를 분석하기 전에 다음과 같은 과정이 필요하다.
1. Dockerfile 생성
# jdk17 Image Start
FROM openjdk:17
# 인자 설정 - JAR_File
ARG JAR_FILE=build/libs/*.jar
# jar 파일 복제
COPY ${JAR_FILE} app.jar
# 실행 명령어
ENTRYPOINT ["java", "-jar", "app.jar"]
필자는 Java 17로 빌드하기 때문에 openjdk:17로 선언해줬다.
해당 프로젝트의 루트 경로에서 생성해주어야 한다
2. create Repository
도커허브에서 repository를 생성해주었다. 빌드 파일을 해당 Repository에 업로드한다. 즉, image를 업로드할 것이다.
(위에서는 image명 = alpha_dlink 로 설정해주었다.)
3. workflow 수정.
# 4. Docker에 image 업로드
# { username/repository } 로 빌드 & . 이 의미하는 것은 Dockerfile의 경로를 의미함.
- name: Docker build & push to docker repo
run: |
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USER }}/alpha_dlink .
docker push ${{ secrets.DOCKER_USER }}/alpha_dlink
앞서 secret key를 등록할 때 docker_username과 password를 설정해주었을 것이다.
성공적으로 빌드되어 도커 허브에 이미지가 업로드된 걸 확인할 수 있었다!
CI 구현 중 문제
DlinkApplicationTests > contextLoads() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: org.springframework.beans.factory.BeanCreationException at AbstractAutowireCapableBeanFactory.java:1773
Caused by: jakarta.persistence.PersistenceException at AbstractEntityManagerFactoryBean.java:421
Caused by: java.lang.RuntimeException at DriverDataSource.java:110
위 에러는 3일간 끙끙댔던 에러였다... 열심히 검색해본 결과 gradle.yml에서 DB 설정을 해주지 않아서 나온 에러였다.
application.yml 관련 설정이니 위의 github secret 환경변수 내용을 살펴보자.
참고
로컬에서는 접근이 가능한 데이터베이스가 GitHub Actions에서는 접근 불가능할 수 있다.
예를 들어, 로컬 데이터베이스 서버(예: localhost)에 연결하는 설정은 GitHub Actions 환경에서 작동하지 않는다고 한다!
외부에서 접근 가능한 데이터베이스 서버를 사용하거나, GitHub Actions에서 동적으로 데이터베이스 서비스를 시작하는 스텝을 추가해야 한다.
2. EC2에 Docker Image pull 받기.
Docker Hub에 이미지가 올라간것을 확인 했기 때문에 EC2에서 해당 이미지를 다운로드 받고, 실행 해보자.
먼저 시작 전, EC2에 다음과 같이 docker를 설치 및 실행해준다.
sudo apt-get update
sudo apt-get install docker.io
sudo service docker start
1. 최신 image pull 받기
도커에 업로드 된 최신 이미지를 받아오는 명령어
sudo docker pull {docker_username}/{docker_image}
2. Docker Image 컨테이너화
- 컨테이너: 실제로 실행되고 있는 도커 이미지. (실행되고 있는 이미지)
# docker run --rm -d -p [로컬 port]:[도커 port] [dockerHub ID]/[이미지명]
$sudo docker run --rm -d -p 8080:8080 [dockerHub ID]/[image]
- 옵션 설명
- -d : 백그라운드에서 실행.
- Spring Boot 의 경우 실행되면, Console 창이 나와서 다른 작업을 할 수가 없기 때문이다.
- 또한 백그라운드 실행 시 다른 작업을 할 수 있다.
- -p : 로컬의 포트로 접속시 docker의 어떤 포트와 연결할 것인지 설정.
- 로컬에 8080포트로 요청이 오면, 도커의 어떤 포트와 연결할것인지?(server가 1개가 아닐수도 있고, 포트번호가 다를수도 있기 때문이다.)
- --rm : 컨테이너 종료 시 해당 컨테이너를 삭제하는 옵션이다.
- -it : i, t옵션을 같이 사용하는 것이다, 키보드의 입력을 표준입력으로 전달하는 옵션이다.
- -d : 백그라운드에서 실행.
3. 컨테이너 로그 확인
컨테이너의 모든 로그를 보여주고, 로그를 실시간 모니터링 가능.
sudo docker logs -f [container_id]
4. 컨테이너 실행 확인
# Docker 실행중인 컨터이너 확인
sudo docker ps
# Docker 모든 컨테이너 확인 (정지된것도 포함)
sudo docker ps -a
만약, 컨테이너를 종료하고 싶으면 다음 명령어를 실행해라
sudo docker stop [containerID]
5. 미사용 컨테이너 삭제
다음 명령어로 미사용 컨테이너를 삭제하자. EC2에서 image를 pull 받을 때마다 미사용 image가 쌓인다! 없애주는게 좋겠지?
sudo docker system prune -f
위 과정을 CI 빌드 이후 수작업으로 진행해야하는데, 로컬 환경에서 수정된 코드를 git push할 때 자동으로 ec2에서 docker image를 pull 받아 해당 image를 실행하는 과정이 자동적으로 이루어지면 어떨까?
다음엔, CD를 구현해보도록하자.
참고
'Infra & Cloud > Docker' 카테고리의 다른 글
[Docker] Docker 사용 명령어 총정리 (0) | 2024.05.14 |
---|---|
[Infra] EC2 + GitHub Action + Docker를 이용한 CI / CD 구현 (2) (0) | 2024.05.14 |