이 블로그는 개인의 공부 목적으로 작성된 블로그입니다. 왜곡된 정보가 포함되어 있을 수 있습니다.
CD
전 포스팅으로 우리는 CI를 이해했다. 그러면 CD는 뭘까 CD는 지속적 배포이다. CD는 비용문제와 연관이 높은데 일반적인 배포상황을 생각해보자 한번 배포하고 변경사항이 없다면 좋겠지만... 기능 추가, 리펙토링를 위해 여러번 배포과정을 겪여야한다.(프론트와 연결할때도 기능이 완료될때마다 배포할 수 있다면 좋을 것이다) 배포를 하는 건 어느정도 비용드는 작업이다. 대표적으로 ec2에 접속하여 변경된 코드를 받아 빌드하고 실행하는 프로세스만 생각해봐도 그렇다. 그러면 자동으로 배포할 수 있다면 어떨까? 비용을 아낄 수 있다. 이는 데브옵스와 관련 있는 내용인데 회사차원에서 이러한 자동화 구축으로 개발자가 하루에 10분을 절약할 수 있다면
개발자수가 많을 수록 절감되는 비용이 높아진다.(1달에 절약하는 비용이 개발자수 X 개발자 수 인것이다)
https://www.youtube.com/watch?v=MwveZf5tOuw
위내용은 인프콘에서 가져온 내용으로 DevOps에 관한 이해를 하는데 큰 도움을 주었다. 관심이 있다면 한번 보길 추천한다.
이제 우리는 CD의 필요성을 이해하였다 그러면 CD를 구현해보자
CD도 CI와 동일한 툴을 사용하는데 앞서 사용한 github action과 docker를 사용하였다
Docker-compose
CD를 구축하다가 문제가 발생하였는데, CI의 경우에 spring project를 실행하고 mysql만 도커를 사용하였다면, CD의 경우 spring project또한 도커 이미지로 만들어서 사용하게 된다.
왜 spring project또한 도커를 사용했는지 의문이 들 수 있는데 이는 개발환경에 영향을 받지 않는 도커의 특징 때문이다. 도커에 대해 잘모른다면 일단 도커를 사용하는경우 java를 배포환경에 설치할 필요없이 spring project로 생성한 이미지를 컨테이너에만 올리면 되고, 이때 환경에 상관없기 때문에 배포환경에서 발생할 수 있는 문제를 예방하기 위함으로 이해하자
다시 돌아와서 도커를 사용하는 경우 문제가 있는데 spring project와 mysql를 서로다른 도커 컨테이너에서 실행하기 때문에 서로 연결 할 수 없다는 것이다.(도커 컨테이너는 가상화된 공간이다. 그래서 컨테이너 실행 환경에 영향을 받지 않는다)
서로다른 도커 컨테이너를 연결하기 위해 docker compose를 사용할 수 있다. 따라서 기존 사용 기술에서 docker compose가 추가되었다.
docker compose는 정확하게는 컨테이너 오케스트레이션 툴이다. docker compose에 대해서 알아보는것도 중요하지만 현재는 CD를 구성하는 것이 목표이므로 자세한 설명한 생락한다.
docker-compose.yml으로 docker-compose를 이해해보자(반복되는 내용은 설명을 생략한다)
version: '3'
services:
database:
container_name: mysql_db
image: mysql/mysql-server:5.7
restart: unless-stopped
environment:
MYSQL_DATABASE: users_db
MYSQL_ROOT_HOST: '%'
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
TZ: 'Asia/Seoul'
ports:
- "8081:3306"
#volumes:
# - ./mysql/conf.d:/etc/mysql/conf.d # MySQL 설정 파일 위치
command:
- "mysqld"
- "--character-set-server=utf8mb4"
- "--collation-server=utf8mb4_unicode_ci"
networks:
- test_network
application:
container_name: docker-compose-test
restart: on-failure
image: ${DOCKER_IMAGE_REPO}
# build:
# context: ./
# dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
depends_on:
- database
networks:
- test_network
networks:
test_network:
version: compose version이다. version마다 문법이 다르다고 알고 있다. 현재 도커 컴포즈 버전에 맞게 사용해야한다,
services: 각각 container들을 정의한다.
database: mysql container이다. 컨테이너 이름을 mysql_db으로 지었다.
restart: 해당 컨테이너가 종료되었을때 다시 실행하도록 한다.
volumes: 도커 이미지는 컨테이너에 올려도 변경되지 않는데 컨테이너가 삭제되면 변경된 내용이 전부 삭제된다 만약 저장을 유지하고 싶으면 volumes를 통해 설정할 수 있다.(init sql를 구성할 수 있다)
command: mysql server 구성을 위한 command 추가
application: spring project container이다.
build: image를 받을 수도 있고 build를 통해 정의한 Dockerfile과 연결 할 수 도 있다.
depends_on: container 실행 순서를 정의한다. 실행 순서만 보장하기 때문에 mysql이 구성되기 전에 spring boot가 실행될 수 있다. 이경우 health check를 추가할수도 있다.(위 코드의 경우 restart에 의해 spring boot가 반복적으로 켜진다)
networks: 네트워크를 설정한다 기본값은 bridge이다
Github action
workflow 코드를 보면서 CD과정을 이해해보자 (반복되는 내용은 설명을 생략한다)
name: Java CD with Gradle
on:
issue_comment:
types: [ created, edited ]
CD 과정은 CI와 좀 다르다 매번 PR이 올라올때마다 무턱대고 배포할 수 없다. 내가 구현하고 싶은 workflow는 PR이 merge되는 경우 CD workflow가 진행되는 것인데, 해당 조건을 트리거하는 방법이 확인되지 않았다. merge_group이라는 조건이 있었는데 merge queue에 올라오는 경우라 의도한 상황과 달랐다. 따라서 직접 command으로 CD worflow가 진행되도록 issue_comment를 조건으로 추가했다.
jobs:
build:
if: github.event.issue.pull_request && contains(github.event.comment.body, '/아워메뉴') && contains(github.event.comment.body, '배포')
runs-on: ubuntu-latest
permissions:
write-all
services:
mysql:
image: mysql:8.0
ports:
- 3306:3306
env:
MYSQL_DATABASE: testdb
MYSQL_ROOT_PASSWORD: testdb
options: >-
--health-cmd="mysqladmin ping --silent"
--health-interval=10s
--health-timeout=5s
--health-retries=3
build는 CI와 동일하기 때문에 설명을 생략한다. if로 조건을 추가하여 "/아워메뉴 배포"라는 comment가 달리는 경우 workflow가 진행되도록 하였다(아이디어를 제공해주신 https://github.com/ku-ring/ios-app 에게 감사하다)
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# Configure Gradle for optimal use in GiHub Actions, including caching of downloaded dependencies.
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
- name: Build with Gradle Wrapper
env:
SPRING_DATASOURCE_URL: jdbc:mysql://127.0.0.1:3306/testdb
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: testdb
run: ./gradlew build --stacktrace
# dockerfile을 통해 이미지를 빌드하고, 이를 docker repo로 push
- name: docker login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Push Docker images
run: |
docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }} .
docker push ${{ secrets.DOCKER_REPO }}
docker login: docker hub 사용을 위해 docker 로그인을 진행한다. (docker hub는 github와 같이 docker image위한 원격 저장소이다)
docker build: 빌드한 파일을 Dockerfile으로 docker image를 생성한다
docker push: 생성한 docker image를 원격저장소에 push한다.
deploy:
needs: build
runs-on: ubuntu-latest
permissions:
write-all
steps:
# appleboy/ssh-action@master 액션을 사용하여 지정한 서버에 ssh로 접속하고, script를 실행합니다.
# script의 내용은 도커의 기존 프로세스들을 제거하고, docker repo로부터 방금 위에서 push한 내용을 pull 받아 실행하는 것입니다.
# 실행 시, docker-compose를 사용합니다.
- name: Deploy to server
uses: appleboy/ssh-action@master
env:
COMPOSE: ${{ secrets.DOCKER_COMPOSE }}
id: deploy
with:
host: ${{ secrets.HOST }}
username: ubuntu
port: 22
key: ${{ secrets.KEY }}
envs: GITHUB_SHA
script: |
sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
cd ${{secrets.PROJECT_PATH}}
sudo docker-compose down
sudo docker rm $(sudo docker ps -a -q)
sudo docker rmi $(sudo docker images -q)
sudo docker pull ${{secrets.DOCKER_REPO }}
sudo docker-compose up --build -d
- name: Notify
uses: actions/github-script@v5
with:
github-token: ${{secrets.TOKEN}}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🌎 배포하였습니다.'
})
- name: add label
uses: actions-ecosystem/action-add-labels@v1
with:
labels: ":satellite: 자동 배포"
ssh-action: ssh로 배포 서버에 접속하여 실행중인 docker-compose를 중지한다.
sudo docker rm $(sudo docker ps -a -q): 생성된 docker container를 전부 삭제한다
sudo docker rmi $(sudo docker images -q): 생성된 docker image를 전부 삭제한다
sudo docker pull ${{secrets.DOCKER_REPO }}: 원격저장소에 docker image를 받는다.
sudo docker-compose up --build -d: docker-compose 파일을 실행한다. -d는 백그라운드 실행 옵션이다.
Notify:배포 결과를 통보한다. comment를 남기도록한다.
add label: 자동배포 label를 부착해 해당 PR까지 자동 배포되었음을 알 수 있다.
배포과정을 정리하면 아래와 같다
트러블 슈팅
처음 해보는 거라 완료하는데 꽤많은 시간이 소요되었다. 특히 docker-compose를 로컬환경에서 구성하는 부분에서 시간을 많이 소요했는데(먼저 해보길 추천한다) JPA Dialete 에러가 발생했는데 DB와 커넥션 문제였다.
내가 해본 방법은 아래와 같다.
- 환경변수 확인 (.env으로 관리하였다)
- mysql에 접근 권한 확인: mysql에 user를 host에 따라 비밀번호를 관리하고 있었다 %으로 설정하면 상관없지만 한번 확인 해보자
- network 확인: network 구성에 문제가 생길 수 있다
- port 확인: docker compose은 이미지를 하나의 컨테이너를 사용하기 때문에 mysql port가 8080:3306이라면 spring에서는 3306으로 해야한다.
- application.yml: application.yml에 jdbc를 localhost가 아닌 mysql 컨테이너 이름으로 해야한다.
- jdbc url 환경변수: CI와 똑같이 jdbc url를 환경변수로 넣는 경우 정상작동하였지만 동일한 값을 application.yml에 넣는경우 실행되지 않았다(다른 문제일 수도 있다)
- 기존에 생성한 도커 환경이 영향을 주는 경우:컨테이너를 멈춘다고 컨테이너가 삭제되는 것이 아니다 잘못된 컨테이너가 계속 실행될 수 있다. 특히 설정 정보를 변경해도 컨테이너가 이미 존재한다면 변경되지 않고 컨테이너를 사용하는 것이 확인되었다. 컨테이너 및 이미지, 볼륨, 네트워크 전부 삭제하고 다시 시도하는 것을 권장한다.
- docker-compose cashing??: docker-compose파일이 어느정도 캐싱된다는 것이 확인되었다. docker-compose --build으로 변경된 내용을 반영할 수 있다고 알고 있는데 그래도 반영이 안되어 있다고 느껴진다면 docker-compose build --no-cache를 사용해보자(나는 이 문제 였다 몇일을 삽질했다...)
개선 사항
지금 CD는 단순하게 배포만 하고 있는데 이후에 develop와 deploy 서버를 분리하거나 nginx와 같이 배포 서버에 추가해볼만 한게 몇개보인다. 또한 배포 명령어가 다른사람이 작성해도 될 수 있다는 점에서 보안 취약점이 있어서 개션해야한다.
'인프라 > 도커' 카테고리의 다른 글
CI/CD 구축 (github action+Spring boot + mysql + docker-compose) (1) (0) | 2024.07.15 |
---|---|
도커 데스크탑, 젠킨스 설치 (1) | 2024.01.05 |