티스토리 뷰
GitLab 에서의 CI/CD
전형적인 배포의 단계들
1. 프로젝트가 빌드 준비를 마치면 Dockerfile 을 작성한다.
2. 파이프라인에서 사용하는 환경변수들을 설정한다.
3. 1번에서 작성한 Dockerfile 로 docker build 와 docker push 를 하는 YAML 파일로 파이프라인을 작성한다.
4. YAML 파일을 업데이트하면 파이프라인이 자동으로 실행되는 것을 목록에서 볼 수 있다.
5. 작성한 파이프라인이 성공적으로 실행되면 Container Registry 에 Docker 이미지가 생성되어 있음을 볼 수 있다.
📃 Note
여기까지가 Continuous Delivery 에 포함된다. Continuous Delivery, 지속적 제공이란 배포 직전 단계까지의 과정을 자동화하여 구현부터 빌드 후 테스트 및 배포 가능한 이미지 생성까지 끊기지 않도록 하는 소프트웨어 개발 방식을 말한다. Continuous Deployment, 지속적 배포는 지속적 제공에 남은 두 단계에서 이루어지는 배포까지의 과정을 포함한다.
여기서부터는 새로 리포지토리에 변경사항이 있는 상황을 가정해보자. 개발자는 코드를 수정한 후 새로운 코드를 커밋 후 푸쉬할 것이다. 그럼 아래와 같이 아까 생성한 YAML 파일을 기반으로 하는 파이프라인이 알아서 실행된다.
CI/CD 를 적용하기 이전에는 ec2 클라우드 컴퓨팅에서 빌린 자원을 활용해 가상 우분투 서버에 접속해서, docker 명령어를 사용해 빌드를 했겠지만 이제는 자동으로 파이프라인에 등록된 파일의 커맨드들이 실행돼 도커 이미지가 만들어진다.
6. docker pull 을 한다. 다음 커맨드를 사용한다:
docker pull registry.gitlab.com/fy16sj/<docker build 시 입력했던 프로젝트 이름>
7. docker image 가 생성되었으니 docker run 을 사용해서 컨테이너를 실행한다.
docker run --name $CONTAINER_NAME -p 8080:8080 \
-e SPRING_DATASOURCE_URL=$SPRING_DATASOURCE_URL \
-e SPRING_DATASOURCE_PASSWORD=$SPRING_DATASOURCE_PASSWORD \
-e JWT_SECRET=$JWT_SECRET \
-d $NAME
배포 자동화하기
셸 스크립트 파일 작성하기
셸 스크립트 (.sh) 파일을 작성하는 방법을 사용할 수 있다.
한 줄 한 줄 살펴보자면, 먼저 가장 위에서는 sh 파일을 실행할 때 파라미터로 입력 받는 값들을 변수에 넣는다:
SPRING_DATASOURCE_URL=$1
SPRING_DATASOURCE_PASSWORD=$2
JWT_SECRET_KEY=$3
NAME=$4
CONTAINER_NAME=$5
GITLAB_USER=$6
GITLAB_PW=$7
나의 스프링 부트 프로젝트에 필요한 변수들은 총 7개이다. 위에서부터 차례대로 설명하면 다음과 같은 의미를 갖는다:
- DB가 저장될 서버
- DB 비밀번호
- JSON Web Token 생성 시 필요한 비밀 키
- 프로젝트 이름
- 생성할 컨테이너의 이름
- 도커 이미지가 들어있는 Container Registry가 위치한 깃랩의 사용자 아이디
- 깃랩의 사용자 비밀번호
환경 변수는 프로젝트 내에서의 필요에 따라 상이해진다. 위 7개는 깃랩에서, Spring Security 와 JWT, 그리고 Spring Data JDBC 와 Spring Data JPA 를 사용하는 프로젝트가 배포될 때 사용되는 환경 변수인 것을 기억하자. 붉은 색으로 표시된 프로젝트 이름과 생성할 컨테이너의 이름은 환경 변수로 등록하면 어느 프로젝트의 배포에서도 동일하게 유용히 사용할 수 있으므로 환경 변수로 입력 받기를 권장한다.
만일 파라미터가 7개 모두 입력되지 않으면 멈추고 다시 입력받을 수 있도록 조건문을 아래에 추가했다:
if [ $# -ne 7 ]; then
echo "파라미터를 모두 입력하세요"
echo "SPRING_DATASOURCE_URL="$SPRING_DATASOURCE_URL
echo "SPRING_DATASOURCE_PASSWORD="$SPRING_DATASOURCE_PASSWORD
echo "JWT_SECRET_KEY="$JWT_SECRET_KEY
echo "NAME="$NAME
echo "CONTAINER_NAME="$CONTAINER_NAME
echo "GITLAB_USER="$GITLAB_USER
echo "GITLAB_PASSWORD="$GITLAB_PASSWORD
exit 0;
fi
다음은 Docker 에 로그인하는 명령어이다. 현재 이미지를 갖고 있는 컨테이너 레지스트리가 위치한 깃랩 프로젝트 리포지토리가 private 으로 생성되어 있어서 파이프라인에서 로그인을 해서 push 된 이미지이더라도 외부 환경에서 접근을 위해서는 한 번 더 Docker 로그인을 해야 한다:
echo $(docker login registry.gitlab.com -u $GITLAB_USER -p $GITLAB_PW)
Docker 에 로그인 되었으면, 깃랩 프로젝트 리포지토리의 컨테이너 레지스트리에 접근해서 이미지를 pull, ubuntu 환경의 배포 서버로 당겨올 수 있다.
pullContents=$(docker pull registry.gitlab.com/$GITLAB_USER/$NAME)
이후 가져온 이미지가 최신인지의 여부, 즉 기존에 서버에 있던 이전 이미지와 비교해서 변경사항이 있었는지의 여부에 따라 스크립트 실행을 종료할지, 이미지로 컨테이너를 생성해 서버에 띄워 실행시킬지 결정된다. Docker 에서는 pull 명령어를 통해 가져온 이미지가 서버에 기존에 존재하던 이미지와 다르지 않으면 다음과 같은 문자열을 출력한다:
Using default tag: latest latest: Pulling from fy16sj/finalproject_jeonseunghwan_team3
Digest: sha256:bf4bb269441f6a2ca0533a84eae4fd17f8d7307dab072e1ab4d77942223e469d Status:
Image is up to date for registry.gitlab.com/fy16sj/finalproject_jeonseunghwan_team3:latest
registry.gitlab.com/fy16sj/finalproject_jeonseunghwan_team3:latest
문자열의 다른 부분들은 프로젝트와 이미지에 따라 상이하지만, "Image is up to date" 는 항상 포함된다. 그러므로 아래와 같이 grep 을 사용해서 pull 명령어가 출력하는 문자열에 "Image is up to date" 가 포함되는지 확인하는 조건문을 통해 pull 된 이미지의 기존 이미지로부터의 변경 여부를 확인할 수 있다.
if echo $pullContents | grep "Image is up to date"; then
echo "Image is already up to date"
exit 0;
fi
조건문 이후에도 스크립트가 종료되지 않았다면, 이미지에 변경 사항이 있어서 서버에 배포된 프로젝트에 적용해야 한다는 것을 의미한다. 새로운 컨테이너를 서버에서 실행하기 전에,
1. 기존에 생성된 컨테이너를 중단시키고 삭제해야 하고
2. 기존 이미지를 삭제해야 한다.
아래는 차례대로 1번과 2번 동작을 실행시키는 구문이다.
pastContainer=$(docker ps -a --filter=name=$CONTAINER_NAME)
if [ ! -z "$pastContainer" ]; then
echo "기존 컨테이너를 종료합니다."
docker stop $CONTAINER_NAME
echo "기존 컨테이너를 삭제합니다."
docker rm $CONTAINER_NAME
fi
pastImages=$(docker images -a --filter=reference='registry.gitlab.com/$GITLAB_USER/$NAME')
if [ ! -z "$pastImages" ]; then
echo "기존 이미지를 삭제합니다."
docker rmi $(docker images -f "dangling=true" -q)
fi
- 1번 구문의 docker ps -a --filter=name=$CONTAINER_NAME 와
2번 구문의 docker images -a --filter=reference='registry.gitlab.com/$GITLAB_USER/$NAME'
은 각각 컨테이너 이름으로 프로젝트의 기존 컨테이너를 찾고 이미지의 참조하고 있는 프로젝트 출처로 프로젝트의 기존 이미지를 찾는다. - 2번 구문의 docker images -f "dangling=true" -q 는 같은 이름과 태그를 가진 이미지들 중 latest 를 제외한 나머지 이미지들, 즉 사용되지 않고 Docker 의 디스크 공간에 남아 있는 이미지를 삭제한다.
이제 모든 배포를 위한 준비가 끝났으니, 생성된 이미지를 사용해서 컨테이너를 실행하는 docker run 명령어를 작성한다.
echo "배포를 시작합니다."
docker run --name $CONTAINER_NAME -p 8080:8080 -e SPRING_DATASOURCE_URL=$SPRING_DATASOURCE_URL \
-e SPRING_DATASOURCE_PASSWORD=$SPRING_DATASOURCE_PASSWORD -e JWT_SECRET_KEY=$JWT_SECRET_KEY \
-d registry.gitlab.com/$GITLAB_USER/$NAME
이렇게 완성된 셸 스크립트는 아래와 같다.
SPRING_DATASOURCE_URL=$1
SPRING_DATASOURCE_PASSWORD=$2
JWT_SECRET_KEY=$3
NAME=$4
CONTAINER_NAME=$5
GITLAB_USER=$6
GITLAB_PW=$7
if [ $# -ne 7 ]; then
echo "파라미터를 모두 입력하세요"
echo "SPRING_DATASOURCE_URL="$SPRING_DATASOURCE_URL
echo "SPRING_DATASOURCE_PASSWORD="$SPRING_DATASOURCE_PASSWORD
echo "JWT_SECRET_KEY="$JWT_SECRET_KEY
echo "NAME="$NAME
echo "CONTAINER_NAME="$CONTAINER_NAME
echo "GITLAB_USER="$GITLAB_USER
echo "GITLAB_PASSWORD="$GITLAB_PASSWORD
exit 0;
fi
echo $(docker login registry.gitlab.com -u $GITLAB_USER -p $GITLAB_PW)
pullContents=$(docker pull registry.gitlab.com/$GITLAB_USER/$NAME)
if echo $pullContents | grep "Image is up to date"; then
echo "Image is already up to date"
exit 0;
fi
pastContainer=$(docker ps -a --filter=name=$CONTAINER_NAME)
if [ ! -z "$pastContainer" ]; then
echo "기존 컨테이너를 종료합니다."
docker stop $CONTAINER_NAME
echo "기존 컨테이너를 삭제합니다."
docker rm $CONTAINER_NAME
fi
pastImages=$(docker images -a --filter=reference='registry.gitlab.com/$GITLAB_USER/$NAME')
if [ ! -z "$pastImages" ]; then
echo "기존 이미지를 삭제합니다."
docker rmi $(docker images -f "dangling=true" -q)
fi
echo "배포를 시작합니다."
docker run --name $CONTAINER_NAME -p 8080:8080 -e SPRING_DATASOURCE_URL=$SPRING_DATASOURCE_URL \
-e SPRING_DATASOURCE_PASSWORD=$SPRING_DATASOURCE_PASSWORD -e JWT_SECRET_KEY=$JWT_SECRET_KEY \
-d registry.gitlab.com/$GITLAB_USER/$NAME
crontab 사용
셸 스크립트 작성이 끝났으면, 프로젝트 변경사항 push
후 스크립트를 매번 sh 명령어를 사용해 실행할 필요 없이, 정해진 주기로 일정 명령어를 실행해주는 crontab
을 설치함으로서 아예 손을 대지 않고 프로젝트의 업데이트 내용이 배포된 서버 내의 어플리케이션에 반영될 수 있게 할 수 있다.
지속적 배포 (CD, Continuous Deployment) 는 crontab 을 설정함으로서 마무리된다. 조금의 개입도 없이 프로젝트의 변경 (생성을 포함한) 과 배포의 중간 과정을 진행할 수 있다.
리눅스의 crontab 기능은 다음과 같은 명령어를 이용해 설정할 수 있다:
crontab -e
명령어를 치면, 아래와 같은 화면을 볼 수 있다.
분
시간
일 간격
주 간격
명령어
형태로 어떠한 명령어가 어떠한 주기로 실행될지 작성한다.
*
는 any
, 즉 아무 때나를 의미하며, 명령어의 각 위치에 쓰이면 단위별로 최소 주기로 반복될 것임을 의미한다.
예를 들어, 분
의 위치에 *
을 작성하면, `매 분`을 의미한다.
그러므로, 위 예시에서 * * * * * 는 매 분 매 시간 매일 매주 명령어를 실행한다는 의미가 된다.
명령어 뒤에는, 명령어 실행 기록을 로그로 저장하기 위해 우분투의 >>
커맨드를 사용할 것이다. >
는 기존 파일에 있던 텍스트에 내용 덮어쓰기이고, >>
는 내용 추가하기 이다. 위 예시에서 여러 전달 인자들과 함께 /root/deploy.sh
(deploy.sh
는 root
에 저장되어 있다) 를 실행하였고, >> /root/deploy.log
를 작성해 실행 기록이 root
경로에 있는 deploy.log
에 기록되도록 하였다.
deploy.log
에 기록되는 모습
- 이미지가 이미 최신이고 변경사항이 없을 때
Login Succeeded
Using default tag: latest latest: Pulling from fy16sj/finalproject_jeonseunghwan_team3
Digest: sha256:bf4bb269441f6a2ca0533a84eae4fd17f8d7307dab072e1ab4d77942223e469d
Status: Image is up to date for registry.gitlab.com/fy16sj/finalproject_jeonseunghwan_team3:latest
registry.gitlab.com/fy16sj/finalproject_jeonseunghwan_team3:latest
Image is already up to date
- 이미지에 변경 사항이 생겨서 새로운 이미지가 생성될 때
[이 부분은 이후 추가하겠다]
배포 스크립트는 매 분마다 Docker 컨테이너를 실행하고, 아래와 같이 docker ps
커맨드를 사용해 실행된 컨테이너를 확인할 수 있다.
https://stackoverflow.com/questions/40084044/how-to-remove-docker-images-based-on-name
How to remove docker images based on name?
I want to delete all versions of docker images with names that contain a given string (imagename). I have tried the below, but it doesn't seem to work: docker images | grep 'imagename' | xargs -...
stackoverflow.com
https://stackoverflow.com/questions/40084044/how-to-remove-docker-images-based-on-name
How to remove docker images based on name?
I want to delete all versions of docker images with names that contain a given string (imagename). I have tried the below, but it doesn't seem to work: docker images | grep 'imagename' | xargs -...
stackoverflow.com
https://www.baeldung.com/ops/docker-remove-dangling-unused-images
'Infrastructure' 카테고리의 다른 글
[CI/CD] GitLab CI 파이프라인 생성 시 주의사항 (0) | 2022.12.21 |
---|---|
[Infrastructure] 가상 서버 호스팅이란? (0) | 2022.12.17 |
[DevOps] CI 와 CD (0) | 2022.12.09 |
[아키텍처] 모바일 앱 아키텍처 설계 방법 (0) | 2022.12.02 |
[Gradle] build scan (0) | 2022.11.30 |
- Total
- Today
- Yesterday
- 알고리즘
- 가상 서버
- @RequestBody
- spring
- Java Data Types
- 도커
- Jackson
- DeSerialization
- Spring Boot
- DTO
- json web token
- 역직렬화
- docker
- google cloud
- 프로그래머스
- gitlab
- LazyInitializationException
- JOIN FETCH
- JPA
- 지연 로딩
- 인증/인가
- Firebase
- JPQL
- 깃랩
- N+1
- 코테
- 실시간데이터
- FCM
- ci/cd
- 기지국 설치
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |