무중단 배포 & Docker

UNG·2022년 1월 20일
2

DevOps

목록 보기
1/2
post-thumbnail

혼자 모든 걸 다 해야하는 개발자를 위한 가성비 최고의 기술

개요

소규모 서비스 운영을 위한
Spring Boot 서비스를
gitlab.com의 CI/CD를 이용하여 Docker Image를 생성하고
Docker service를 이용한 무중단 롤링 업데이트를 해보자.

적용

준비사항

  • Spring Boot 프로젝트
    • actuator 설정: health 체크를 하기 위해서 필요하다. actuator가 없다면 health체크를 위한 컨트롤러를 생성해도 된다.
    • Dockerfile 설정
  • gitlab 저장소
    • docker-ci 또는 원하는 브랜치: docker image 빌드 전용으로 사용할 브랜치이다.
  • 운영할 서버에 Docker 설치

Dockerfile 설정

  1. Dockerfile
    프로젝트 루트 폴더에 Dockerfile 생성(확장자 없음)
FROM openjdk:8-jdk-alpine
ENV TZ="Asia/Seoul"
ARG JAR_FILE=build/libs/*.war
COPY ${JAR_FILE} app.war

HEALTHCHECK --interval=5s --timeout=5s --start-period=15s --retries=10 CMD wget http://localhost:8080/actuator/health --quiet --output-document - >/dev/null 2>&1

ENTRYPOINT ["java","-jar","/app.war"]

키 포인트는 HEALTHCHECK 커맨드가 있어야 한다.
Spring boot 서비스를 시작하면 요청을 처리할 수 있기까지 약 10초에서 길게는 30초까지 걸리는데, Docker 서비스는 그 시간을 기다려주지 않는다. 일단 컨테이너가 준비되면 바로 트래픽을 보내기 때문에 Docker 서비스에게 실제 준비가 되었을 때 트래픽을 보내라고 알려줘야 한다.

.gitlab-ci.yml 설정

Docker Image 생성을 직접해도 되지만, 프로젝트 빌드 + Docker Image생성 + Registry에 Push 하는 것이 여간 귀찮지 않다.
gitlab.com의 CI/CD 를 사용한다.

  1. .gitlab-ci.yml
    프로젝트 루트 폴더에 생성하고 프로젝트에 맞게 수정한다
    # 프로젝트에맞게수정 부분 검색
image: openjdk:11-stretch

stages:
  - build
  - docker-build

before_script:
  - echo "Start CI/CD"

build:
  stage: build
  script:
    - chmod +x ./gradlew
    - ./gradlew bootWar # 프로젝트에_맞게_수정
  artifacts:
    paths:
      - build/libs/*.war # 프로젝트에_맞게_수정
    expire_in: 1 week
  only:
    - docker-ci # 프로젝트에_맞게_수정

docker-build:
  image: docker:latest
  stage: docker-build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - docker pull $CI_REGISTRY_IMAGE:latest || true
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - docker-ci # 프로젝트에_맞게_수정
after_script:
  - echo "End CI/CD"

참고

only
gitlab CI/CD는 지정한 브랜치가 Push되면 자동으로 빌드가 되기 때문에, 특정 브랜치에서만 빌드되도록 only 옵션을 사용한다. 여기서는 docker-ci가 그것이다.

stages
이름은 자유롭게 다만 stages에 있는 이름과 stage에 지정한 이름은 맞아겠지.

  • build: Spring 프로젝트 빌드 담당.
    gradle 빌드 task는 각 프로트에 맞게
    빌드가 끝나면 artifacts(저장소)에 .war 파일이 저장된다.
    artifacts에 저장된 파일은 다른 stage에서 사용할 수 있다.
  • docker-build
    Docker Image 빌드 담당.
    수정할 부분은 원하면 tag정도?
    Docker Image 빌드 시 build/libs/*.war 를 사용한다. Dockerfile에 명시되어 있다.
  • $CI_REGISTRY_IMAGE
    프로젝트 저장소 이름이 곧 이미지 이름이 된다.

배포를 위한 Docker Image 생성

docker-ci브랜치를 원격 저장소에 push하면 gitlab ci/cd에 의해 자동으로 빌드가 시작된다.
gitlab.com 사이트에서 진행 상황을 확인하자.

Pipelines
프로젝트 메뉴 > Pipelines 에서 빌드 상황을 확인할 수 있다.
시간이 꽤 걸린다.

Container Registry
프로젝트 메뉴 > Package & Registries > Container Registry 에서 생성된 Docker Image를 확인 할 수 있다.
이미지 이름 옆의 복사하기 아이콘을 마우스 커서를 올리면 URL이 보인다.
이미지 이름은 registry.gitlab.com/프로젝트저장소 로 생성된다.
이미지 이름이나 태그는 .gitlab-ci.yml에서 수정 가능하다.

서버에서 Docker Image 내려받기

  1. Deploy tokens 생성
    서버에서 gitlab.com의 registry에 로그인할 때 사용하기 위한 토큰을 생성해야 한다.
    1. gitlab.com 프로젝트 메뉴의 settings > repository > Deploy tokens 의 'expand' 버튼 클릭
    2. Scopes는 최소 read_registry를 선택한다.
    3. 토큰 생성
    발급된 토큰은 서버에서 로그인할 때 사용된다.

  2. 서버에서 gitlab.com에 로그인

$ echo 토큰값 | docker login registry.gitlab.com -u docker-ci --password-stdin
  1. 서버에서 이미지 내려받기
    이미지명:태그 는 상황에 맞게 수정한다.
$ docker image pull registry.gitlab.com/이미지명:태그

Docker 서비스 생성

$ docker service create \
 --name myapp \
 --publish 8080:8080 \
 --replicas=2 \
registry.gitlab.com/이미지명:태그

참고

--replicas=2
나중에 롤링 업데이트를 하기 위해 2개의 컨테이너를 띄운다.
1로 지정 후 업데이트가 필요할 때 1개의 컨테이너를 추가할 수도 있다.

Docker 서비스 업데이트

버전 업데이트가 필요한 시점에 docker-ci 브랜치에 변경사항을 병합하고 push해서 새 docker image가 빌드되기를 기다린다.
3~5분...
새로운 이미지가 빌드되었으면 내려받는다.

$ docker image pull registry.gitlab.com/이미지명:새로운태그

현재 2개의 컨테이너가 생성되어 있으며,
아래 명령어를 수행하면 1개씩 롤링 업데이트를 수행한다.

$ docker service update -d --force --image registry.gitlab.com/이미지명:새로운태그 myapp

참고

첫번째 컨테이너를 새 버전으로 재생성하고 healthcheck를 통해 성공적으로 컨테이너가 실행되었을 때
두번째 컨테이너의 업데이트를 시작한다.
물론 docker service 설정으로 업데이트 규칙을 변경할 수도 있다.

업데이트 주의사항

동일한 Docker Image Tag를 사용하여 업데이트하면 이전 버전으로 롤백할 수 없다.

Docker 서비스 컨테이너 하나 더?

최초 생성할 때 --replicas=2 를 지정하여 2개의 컨테이너를 생성했다.
1개의 컨테이너를 추가하여 총 3개로 서비스를 하고 싶다면 하면 된다.
컨테이너 수를 줄이는 것도 당연히 가능.

$ docker service update --replicas=3 myapp

아름답다.

Docker Image 생성되면 알람 받기

gitlab ci에 의해 docker image가 빌드되는건 너무 편한데, 좀 느려.
느리다보니 빌드가 다 끝났는지 더 기다려야 하는지 서버에서 언제쯤 이미지를 내려받을 수 있을지 모르는 것도 문제.
주기적으로 사이트 새로고침을???

gitlab.com의 프로젝트 메뉴의 settings > Integrations > Slack notifications
을 선택하고
Pipeline 옵션만 체크. 나머지 옵션은 필요하면 선택.
Webhook 에 알람 받을 슬랙 웹훅 url 설정
Username 알람 수신 시 보낸 사람 이름
Branches for which notifications are to be sent All Branches 선택
어차피 docker-ci 브랜치만 pipeline을 사용하고 있기 때문에 All Branches를 해도 무관하다.

저장 후 docker-ci에 또 한번 push를 하면 빌드가 시작되고 빌드가 종료되면 슬랙으로 알림이 온다.

무중단 배포시 고민하는 문제들

  • 클라이언트는 서비스 업데이트와 상관없이 요청이 성공적으로 처리되어야 하는 것은 당연.
  • 배포 후 문제가 생겼을 때 이전 버전으로 rollback하는 것이 가능하고 쉽게.
  • 배포 및 롤백 처리가 최소한의 명령어로 처리되면 좋겠다.

귀찮지만 잘 작동하는 무중단 배포 방법

Blue-Green deployments?

여지껏 운영했던 서비스들이 1~2대의 서버로 운영이 되었기 때문에

  • nginx, apache 같은 웹서비스 1개
  • 어플리케이션 서비스 2개(App1, App2)를 셋팅한 후

App1이 현재 v1.0을 서비스 중이고, v2.0을 올려야할 때.
1. App2에 v2.0 배포
2. nginx에서 App2의 포트를 지정
3. nginx 재시작하여 App2로 트래픽을 보냄

문제가 생기면 롤백
1. nginx에서 App1의 포트를 지정
2. nginx에서 재시작하여 App1로 트래픽을 보냄

가장 명확하고 쉬운 방법인데 nginx를 재시작하는 것이 귀찮다.
설정을 바꾸는 것도 귀찮다. 서버에서 vi로 편집해야 하는데, 오타나면 좀 그래.

스크립트를 만들면 좀 덜 귀찮겠지만 App3이 추가된다면?

profile
우웅

0개의 댓글