Docker + Gitlab으로 CICD 구축

개발하는 구황작물·2024년 3월 14일
0

간단히 Gitlab를 설명하자면

Gitlab
소프트웨어 개발 및 협업을 위한 올인원 솔루션을 제공하는 웹 기반 DevOps 플랫폼

Gitlab은 개인 서버에 설치가 가능하고, 비공개 프로젝트를 무료로 진행할 수 있어 본인만의 서버관리를 할 수 있다.


Docker + Gitlab로 CI/CD 구축하기

  1. node.js 서버 Docker로 컨테이너 화
    기존 nodejs 서버를 컨테이너로 운영하기 위해 Dockerfile과 docker-compose.yml을 작성하였다.
    원래는 하나의 서버로만 운용하려 했으나 하나의 프로세스에만 작동하는 node.js 특성상 여러 서버를 운영하는게 좋을 것 같아 컨테이너 2개를 운용하기 위해 docker-compose.yml도 작성하였다.

Dockerfile

FROM node:18

# create /app dir
RUN mkdir -p /app 

# set workdir to /app
WORKDIR /app

#copy package.json to app
COPY package*.json ./
RUN npm install

ADD . /app

EXPOSE 8080

CMD [ "npm","start" ]

docker-compose.yml

version: '3'
services:
  msp_be1:
    container_name: msp_be1
    build: 
      context: .
      dockerfile: ./Dockerfile 
    image: ${BE1_IMAGE}  # .env에 변수 설정
    ports: 
      - "8080:8080" 
    networks: 
      - msp
  msp_be2:
    container_name: msp_be2
    build: 
      context: .
      dockerfile: ./Dockerfile
    image: ${BE2_IMAGE}
    ports: 
      - "8081:8080" 
    networks: 
      - msp

networks:  # 외부 network 설정
  msp:
    external: true 
  1. .gitlab-ci.yml
    .gitlab-ci.yml로 CICD 로직을 구축하였다.

.gitlab-ci.yml

image: alpine:latest # gitlab CICD 파이프라인에서 사용할 도커 이미지를 지정하는 부분

variables:
  BE1_IMAGE: $CI_URL_DEV/$CI_BE_PRJ/$BE_REPO1:latest
  BE2_IMAGE: $CI_URL_DEV/$CI_BE_PRJ/$CI_BE_REPO2:latest

stages:
  - start
  - package
  - deploy
  • image : gitlab CICD 파이프라인이 기반으로 사용할 이미지
  • variables : 전역 변수 설정
  • stage : gitlab 파이프라인에서 진행될 단계들의 순서를 정의한다.

민감한 변수같은 경우 Gitlab에서 따로 지정을 해줄 수 있다.

Gitlab Settings > CI/CD > Variables

start stage

dev-start:
  stage: start
  tags:
    - common
  script:
    - echo "start pipeline"

깃랩 파이프라인이 시작되었다는것을 나타내기위해 추가하였다

package stage
node.js 서버를 docker-compose.yml를 통해 빌드 후 도커 허브에 올리기 위해 추가하였다

dev-package: 
  image: docker:stable
  stage: package
  services:
    - name: docker:dind
  tags:
    - common
  before_script:   
    - echo "BE1_IMAGE=$BE1_IMAGE" >> .env
    - echo "BE2_IMAGE=$BE2_IMAGE" >> .env # 변수들을 .env 파일에 저장
    - docker login -u "$USER" -p "$PASSWORD" # Harbor(Docker registry) 로그인
    - apk update && apk add --no-cache docker-compose # alpine 업데이트 및 docker-compose 다운로드
  script: 
    - docker-compose down --remove-orphans # 이전 docker container 삭제
    - docker-compose build # 도커 빌드
    - echo "##### docker Build Success"
    - docker push $BE1_IMAGE # Harbor(Docker registry)로 푸시
    - docker push $BE2_IMAGE
    - echo "##### Image Push Success"

Gitlab의 장점 중 하나는 Docker Registry를 공짜로 사용가능하다는 것이다.
덕분에 private 한 Docker hub를 공짜로 사용할 수 있는데
우리 회사 같은 경우는 Harbor를 사용하였다

Harbor를 간략하게 설명하자면 오픈소스 컨테이너 이미지 레지스트리로 이미지 저장/관리 및 엑서스 제어 및 권한 관리를 지원해준다.
Harbor를 사용하면 private한 docker registry를 사용할 수 있고 이미지 복제나 복구가 쉬워 Harbor를 사용하게 되었다.

(여기서 더 깊게 들어가면 이번글 주제에 많이 벗어나게 되므로 Harbor에 대한 설명은 줄이도록 하겠습니다. 언젠간 Harbor 소개글도 써보도록 해보겠습니다.)

deploy stage
ec2 에 접속하여 ec2에 위치한 deploy.sh를 실행시켜 Harbor에 올라간 docker image를 실행시키기 위해 추가하였다.

dev-deploy:
  stage: deploy
  tags: 
    - common
  before_script: 
    - apk update && apk add openssh-client
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - eval $(ssh-agent -s)
    - echo "$SSH_KEY" | base64 -d | tr -d '\r' > ~/.ssh/id_rsa # Gitlab Variables에 저장된 SSH KEY 디코딩 후 ~/.ssh/id_rsa에 저장
    - chmod 600 ~/.ssh/id_rsa # 접근 권한 설정
    - ssh-add ~/.ssh/id_rsa # SSH 에이전트에 프라이빗 키 추가
    - ssh-keyscan -H "$DEPLOY_SERVER" >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - ssh ec2-user@"$DEPLOY_SERVER" " bash /home/ec2-user/befo/deploy.sh"

그 전에 ec2 접속을 위한 ssh 키를 Gitlab Variables에 저장해야 한다.
Gitlab Variables에 저장시 Value에 띄어쓰기(\n)가 존재하면 masked로 설정이 불가하기 때문에 base64로 encoding 후 Gitlab Variables에 저장하였다.

.gitlab-ci.yml에서는 echo "$SSH_KEY" | base64 -d | tr -d '\r' > ~/.ssh/id_rsa를 통해 base64로 디코딩 하고 ~/.ssh/id_rsa에 저장한다.

ssh-add ~/.ssh/id_rsa 를 통해 SSH agent에 키를 저장한다.

ssh-keyscan -H "$DEPLOY_SERVER" >> ~/.ssh/known_hosts를 추가한 이유는 추가하지 않으면 아래와 같은 메시지가 뜨는데

특정 호스트에 처음 접속 시 RSA key fingerprint로 접속여부를 확인하여 중간자 공격을 예방하기 위해 있는 절차인데

우리가 직접 yes를 치는 대신 gitlab pipeline이 대신 해줘야 하기 때문에 ssh-keyscan을 통해 호스트의 공개 키를 가져와 로컬의 ~/.ssh/known_hosts에 저장하는 것으로 대신한다.

(그냥 냅다 StrictHostKeyChecking=no명령어로 Host Key Checking을 비활성화 해도 되긴 하지만 중간자 공격의 위험성이 있다.)

이후 ec2에 접속하여 ec2 내부의 deploy.sh를 실행 시키면 된다.

#!/bin/bash \

echo "docker script"

docker rmi -f "${BE1_IMAGE}"
docker rmi -f "${BE2_IMAGE}"

docker pull "${BE1_IMAGE}"
docker pull "${BE2_IMAGE}"

docker run --name msp_be1 -p 8080:8080 -d "${BE1_IMAGE}"
docker run --name msp_be2 -p 8081:8080 -d "${BE2_IMAGE}"
profile
어쩌다보니 개발하게 된 구황작물

0개의 댓글