들어가기 전에
Github Actions와 shell script로 CI/CD를 구축했는데 이번에는 Docker hub에 이미지를 빌드하고 원격 서버에서 이 이미지를 pull 받아서 컨테이너로 실행하도록 했다.
- Github Actions: CI/CD 구축 (feat.shell script) - 준비편
- Github Actions: CI/CD 구축 (feat.shell script) - 실전편
그래서 밑작업들은 모두 이전 포스팅에 설명해놨기 때문에 설정 안 한 부분이 있다면 위의 링크를 참고하면 좋겠다.
참고로 이전 작업들은 Docker를 도입하기 전에 직접 jar 파일을 배포해서 실행하는 shell script도 작성도 해보고 리눅스 명령어도 최대한 경험할 수 있는 시간을 가지기 위함이었다. Docker를 선택한 이유는 개발 스펙 정하기에서도 나눈 적이 있었는데 결국에는 Docker를 사용함으로써 일관성 있는 환경을 제공할 수 있고 이식성 및 확장성과 같은 추가적인 이점들을 누리기 위해서이다. 그리고 Docker compose와 같은 도구로 배포 프로세스를 간소화할 수 있다는 점도 충분히 고려할 사항이라고 생각한다.
목차
1. Docker hub repository 생성
2. 원격 서버에 Docker 설치
3. CI
- Dockerfile 생성
- workflow 생성
4. CD
- workflow 생성
시작하기
1️⃣ Docker hub repository 생성
1) Docker hub에 로그인하고 Create repository 버튼을 클릭한다.
2) 정보를 기입하고 Create 버튼을 클릭해 생성한다.
3) workflow에서 활용하기 위해 관련 정보를 secret에 추가한다.
- Docker hub repository
- Docker 계정 정보 (username / password)
2️⃣ Docker 설치
1) 설치가 가능한 패키지 리스트를 업데이트하고 repository를 사용할 수 있게 패키지를 설치한다.
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
2) GPG 키를 추가한다.
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
3) repository를 설정한다.
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
4) 다시 apt 패키지 리스트를 업데이트하고 최신 버전의 도커를 설치한다.
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
3️⃣ CI
1) Dockerfile을 생성한다.
도커 이미지를 생성하기 위해 Dockerfile을 작성한다.
FROM openjdk:17.0.1-jdk-slim
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} budsdom.jar
ENTRYPOINT ["java","-jar","/budsdom.jar"]
- FROM : 이미지를 생성할 때 사용할 기반 이미지
- ARG : —build-arg 옵션에서 넘길 인자를 정의하기 위해 사용한다. 여기에서는 JAR_FILE을 넘긴다.
- COPY : 실행할 jar 파일을 도커 컨테이너 내부에 budsdom.jar라는 이름으로 복사한다. 여기서는 프로젝트명으로 정했지만 단순하게 app으로 지정하기도 한다.
- ENTRYPOINT : 컨테이너가 시작될 때 실행할 스크립트 혹은 명령
2) workflow에 Docker 이미지 빌드 과정을 추가한다.
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_PASSWORD }}
docker build --platform linux/amd64 --build-arg DEPENDENCY=build/dependency -t ${{ secrets.DOCKER_HUB_REPO }} .
docker tag ${{ secrets.DOCKER_HUB_REPO }} ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
docker push ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
- --platform linux/amd64 : mac m1을 쓰면 linux/arm64로 이미지가 빌드되는데 그러면 원격 서버에서 실행하려고 할 때 문제가 발생하기 때문에 linux/amd64로 설정했다. (mac m1이 아니라면 없어도 됨)
- ${GITHUB_SHA::7} : 커밋 해시 값으로 이미지의 태그 값을 수동으로 지정해 줄 필요없이 구분되는 값으로 사용할 수 있다. 해시 값의 길이가 길기 때문에 앞의 일곱 글자를 사용한다.
4️⃣ CD
1) workflow에 Docker 이미지 pull 및 실행 과정을 추가한다.
만약 CI&CD 과정이 한 workflow 파일에서 관리된다면 아래와 같이 그대로 ${GITHUB_SHA::7}을 사용하면 된다.
- name: Deploy
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_PRIVATE_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_PASSWORD }}
docker pull ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
docker tag ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7} ${{ secrets.DOCKER_HUB_REPO }}
docker stop server
docker run -d --rm --name server -p 8080:8080 ${{ secrets.DOCKER_HUB_REPO }}
그런데 분리되어있다면 해시 값이 변경되기 때문에 추가 작업이 필요하다. 아티팩트를 사용해서 데이터를 유지하는 방법도 있지만 여기서는 간단하게 최신 이미지의 태그 값을 가져오는 명령어를 사용한다.
- name: Deploy
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_PRIVATE_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_PASSWORD }}
DOCKER_TAG=$(docker images ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }} --format "{{.Tag}} {{.CreatedSince}}" | sort -k2 -r | head -n 1 | awk '{print $1}')
docker pull ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${DOCKER_TAG}
docker tag ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${DOCKER_TAG} ${{ secrets.DOCKER_HUB_REPO }}
docker stop server
docker run -d --rm --name server -p 8080:8080 ${{ secrets.DOCKER_HUB_REPO }}
정리
최종적으로 모든 작업을 적용한 CI/CD workflow는 이렇다.
name: CI
on:
pull_request:
branches: [ "main" ]
env:
YAML_PATH: ./buddy-wisdom-private/src/main/resources/application.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- name : Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.ACCESS_TOKEN }}
submodules: true
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Run chmod to make gradlew executable
run: chmod +x ./gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: build
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_PASSWORD }}
docker build --platform linux/amd64 --build-arg DEPENDENCY=build/dependency -t ${{ secrets.DOCKER_HUB_REPO }} .
docker tag ${{ secrets.DOCKER_HUB_REPO }} ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
docker push ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${GITHUB_SHA::7}
name: CD
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.ACCESS_TOKEN }}
submodules: true
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Run chmod to make gradlew executable
run: chmod +x ./gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: build
- name: Deploy
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_PRIVATE_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
docker login -u ${{ secrets.DOCKER_USERNAME}} -p ${{ secrets.DOCKER_PASSWORD }}
DOCKER_TAG=$(docker images ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }} --format "{{.Tag}} {{.CreatedSince}}" | sort -k2 -r | head -n 1 | awk '{print $1}')
docker pull ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${DOCKER_TAG}
docker tag ${{ secrets.DOCKER_USERNAME}}/${{ secrets.DOCKER_HUB_REPO }}:${DOCKER_TAG} ${{ secrets.DOCKER_HUB_REPO }}
docker stop server
docker run -d --rm --name server -p 8080:8080 ${{ secrets.DOCKER_HUB_REPO }}
참고
'노트 > F-lab' 카테고리의 다른 글
[Redis] 설치 및 간단한 테스트 해보기 (Ubuntu 18.04) (0) | 2023.06.28 |
---|---|
동시성 제어와 DB 설계의 고민 (0) | 2023.06.23 |
RabbitMQ를 활용한 메시지 처리 방식 구현: 설치부터 적용까지 (0) | 2023.06.17 |
Github Actions: CI/CD 구축 (feat.shell script) - 실전편 (2) | 2023.06.04 |
Github Actions: CI/CD 구축 (feat.shell script) - 준비편 (0) | 2023.06.04 |