소프트웨어 개발 라이프 사이클을 생각해 보겠습니다.
소프트웨어 개발은 크게 세 단계로 구분됩니다. 먼저, 요구사항을 파악하여 기획자가 전체적인 계획을 세웁니다. 이후 디자이너가 피그마 등의 도구를 이용하여 와이어프레임을 제작합니다. 개발자는 기획과 와이어프레임을 참고하여 적절한 기술 스택을 선택하고 구현을 진행합니다.
소프트웨어가 구현되면, 소스코드를 컴파일하여 배포 가능한 형태로 패키징 합니다. 예를 들어 자바 프로젝트의 경우 jar 파일로 패키징 될 수 있습니다. 패키징 된 파일은 도커 이미지 같은 형태로 변환되어 이미지 저장소에 공유될 수 있습니다.
빌드 과정과 병행하여 진행되는 유닛, 통합 테스팅과 전체 애플리케이션 워크플로우를 테스팅하는 시스템 테스팅을 진행합니다.
마지막으로 개발 서버 혹은 프로덕션 서버에 배포됩니다.
지속적 통합(CI)은 위 소프트웨어 라이프 사이클 중 개발 ~ 테스팅을 의미합니다.
지속적 통합(CI) 과정에 배포 과정을 추가하면 지속적 배포(CD)가 됩니다.
이는 소프트웨어 라이프 사이클 중 개발 ~ 배포를 의미합니다.
개발자는 기능 구현과 트러블 슈팅 과정에서 이러한 단계들을 반복하게 됩니다.
이 과정이 수동으로 이루어지면 많은 시간이 소요되므로, 자동화 환경을 구축하는 것은 매우 중요한데요.
이에 저는 프로젝트를 진행하기 전 GitHub Actions를 이용하여 CI/CD 환경을 구축하려합니다.
CI/CD를 구성하기 위한 서비스는 Jenkins, AWS CodePipeline 등이 있지만 저는 GitHub Actions를 선택했습니다.
제가 생각하는 워크플로우는 아래 그림과 같은데요.
모든 기능을 GitHub 하나의 플랫폼에서 관리할 수 있다는 것이 가장 큰 이유였습니다.
덤으로 Jenkins는 서버가 추가로 필요하고 AWS는 과금에 대한 부담감이 살짝 있는 반면, GitHub Actions는 개인으로 사용할 시 거의 무료나 다름이 없습니다.
이번 포스팅에서는 Spring Boot 프로젝트를 이용하여 아래의 단계를 자동화 해보겠습니다.
Actions 탭의 New workflow를 누르시면 GitHub에서 제공하는 이미 작성된 yaml 파일이 존재합니다. 이를 기본으로 시작합니다.
기본 파일에서 아래의 두 가지는 변경하였습니다.
- 자바 17을 사용하기 때문에 11 → 17
- 라이센스를 corretto를 사용하기에 temurin → corretto
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
처음에는 복잡해 보일 수 있지만, 간단하게 생각해 봅시다.
runs-on
이라고 붙은 것은 가상의 환경을 의미합니다.
즉, 우분투 운영체제의 가상의 컴퓨터가 하나 만들어졌고, 해당 컴퓨터에서 내가 입력하는 명령어들이 실행되고 있다고 생각하면 이해하기가 쉽습니다.
actions
, action
는 GitHub Actions에서 제공하는 라이브러리로 복잡한 설정 없이 필요한 환경을 구성할 수 있도록 도와줍니다.
actions/checkout@v3
는 저장소의 소스코드를 가상 컴퓨터로 복사합니다.
actions/setup-java@v3
는 JDK를 설치해 줍니다. 물론 직접 설치해도 상관은 없지만 이미 잘 구성된 설정을 사용하는 것이 효율적입니다.
gradle/gradle-build-action
는 Gradle을 이용하여 빌드를 진행합니다.
즉 로컬에서 아래의 명령어를 수행하는 것과 똑같습니다.
$ ./gradlew build
따라서 아래와 같이 작성해도 됩니다.
- name: Build with Gradle
run: ./gradlew build
하지만 역시 남이 만들어놓은 기능은 캐싱이라던지 조금 더 특별한 기능이 존재할 가능성이 크기 때문에, 저는 이해 가능한 범주라면 사용하는 편입니다.
라는 내용이 있으니 참고해 봅시다.
재미있는건 기본적으로 제공하는 파일을 생성만 했을 뿐인데 jar 파일을 생성하는 패키징 과정이 끝났다는 것입니다.
패키징이 완료되면 아래의 그림처럼 build/libs
폴더 하위에 [rootProject.name]-[version].jar
파일이 생성됩니다.
생성된 jar
파일은 아래와 같이 실행합니다.
$ java -jar build/libs/app-0.0.1-SNAPSHOT.jar
따라서, 이미지 생성을 위한 도커 파일을 아래와 같이 작성할 수 있습니다.
touch Dockerfile
# 베이스 이미지 - corretto:17
FROM amazoncorretto:17
# jar 복사
COPY ./build/libs/app-0.0.1-SNAPSHOT.jar ./app.jar
# jar 실행
CMD ["java", "-jar", "app.jar"]
이미지 생성은 build 명령어로 할 수 있습니다.
$ docker build -t [IMAGE_NAME] .
최종 목표는 생성한 이미지를 GitHub Container Registry에 푸시하는 것인데요.
이를 위해서는 이미지 이름을 아래와 같이 생성하여야 합니다. (참고)
ghcr.io/NAMESPACE/IMAGE_NAME:latest
예를 들면,
$ docker build -t ghcr.io/ber01/lo-gak-gye:latest .
저는 workflows를 작성하기 전에 로컬환경에서 먼저 실행을 해보는데요.
위 명령어를 실행해 보니 이미지 생성이 잘 됩니다.
그렇다면 그대로 workflows에 작성해 주면 됩니다.
- name: Docker image Build
run: docker build -t ghcr.io/ber01/lo-gak-gye:latest .
이미지 빌드까지 완료됐습니다.
생성한 이미지를 GitHub Container Registry에 푸시해 보겠습니다.
이번에도 로컬 환경에서 먼저 해보겠습니다.
이미지 푸시를 위해서는 Personal Access Token(이하 PAT)이 필요한데요.
scope를 write, delete, workflows를 선택한 뒤 생성해 줍니다.
이후 CR_PAT 환경 변수에 발급받은 PAT를 넣어줍니다.
$ export CR_PAT=[발급받은_PAT]
컨테이너 레지스트리에 로그인합니다.
$ echo $CR_PAT | docker login ghcr.io -u [GITHUB_ID] --password-stdin
아까 생성했던 이미지를 푸시합니다.
$ docker push ghcr.io/[GITHUB_ID]/[IMAGE_NAME]:latest
이 과정을 workflows에 작성해 주면 됩니다.
컨테이너 레지스트리에 로그인하는 과정은 docker/login-action
이 존재합니다.
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.CR_TOKEN }}
여기서 username
은 본인의 GITHUB_ID를 넣어주면 됩니다.
password
는 방금 발급받은 PAT를 작성해 주면 되는데요.
직접 작성하면 토큰이 유출 되므로 secrets
을 통해 접근하여야 합니다.
저장소 -> Settings -> Secrets and variables -> Actions -> New repository secret
위 절차대로 CR_TOKEN
시크릿을 생성하면 {{ secrets.CR_TOKEN }}
문법으로 workflows내에서 접근할 수 있습니다.
앞으로 민감한 정보는 여기에 담아둡시다.
컨테이너 레지스트리에 로그인하는 workflows의 예시는 아래와 같습니다.
username까지 시크릿으로 생성할 필요는 없어보입니다.
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ber01
password: ${{ secrets.CR_TOKEN }}
이미지 푸시는 간단합니다.
- name: Docker image Push
run: docker push ghcr.io/ber01/lo-gak-gye:latest
셰어링까지 완료되었습니다.
작성된 최종 workflows는 아래와 같습니다.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
# 소스코드 체크아웃
- uses: actions/checkout@v3
# JDK 17 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
# jar 패키징
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
# 이미지 빌드
- name: Docker image Build
run: docker build -t ghcr.io/ber01/lo-gak-gye:latest .
# Container Registry 로그인
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ber01
password: ${{ secrets.CR_TOKEN }}
# 이미지 푸시
- name: Docker image Push
run: docker push ghcr.io/ber01/lo-gak-gye:latest
1편에서는 GitHub에 소스코드를 푸시하면 GitHub Container Registry의 이미지 저장소에 이미지가 자동으로 생성되도록 자동화 하였습니다.
2편에서는 AWS EC2에 접근하여 자동으로 배포하는 것 까지 다뤄보겠습니다.
감사합니다.