회사 내부 프로젝트로 POC를 진행하게 되었는데 이때 CICD 구축을 Gitlab로 하게 되었다.
간단히 Gitlab를 설명하자면
Gitlab
소프트웨어 개발 및 협업을 위한 올인원 솔루션을 제공하는 웹 기반 DevOps 플랫폼
Gitlab은 개인 서버에 설치가 가능하고, 비공개 프로젝트를 무료로 진행할 수 있어 본인만의 서버관리를 할 수 있다.
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
.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
민감한 변수같은 경우 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에 대한 설명은 여기까지...)
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}"