CI/CD 파이프라인은 개발 머신에서 테스트와 스테이징을 거쳐 최종적으로 출시하여 사용자에게 전달하기 위해 코드가 거치는 일련의 단계를 말한다.
CI/CD 전략은 이 프로세스를 정기적으로(일반적으로 하루에 여러 번) 실행하는 것이기 때문에 단계 마다 다음 단계를 트리거하거나 문제가 발생한 경우 플래그를 표시하여 가능한 한 많은 프로세스를 자동화하는 것이 중요하다.
Gitlab 에서는 프로젝트의 root 아래에 .gitlab-ci.yml 파일에 파이프라인을 작성하여 트리거하도록 설정되어 있다.
나는 아래의 단계로 파이프라인을 구성하여 빌드부터 배포를 진행할 것이다.
stages:
- build - 프로젝트의 라이브러리를 Build 하여 Docker Image로 생성
- publish - 생성된 Docker Image를 Private Registry에 저장
- deploy-dev - Private Registry 에 저장된 Docker Image를 Dev 환경에 배포
- deploy-stage - Private Registry 에 저장된 Docker Image를 Stage 환경에 배포
- deploy-prod - Private Registry 에 저장된 Docker Image를 Prod 환경에 배포
- promote-prod - Prod 환경에 배포한 Canary 를 적용할 것인지 결정

파이프라인에 포함된 Variable의 설정을 선행해야 한다.
내가 구성한 파이프라인에 쓰인 Variable은 아래와 같다.
| Key | Environment |
|---|---|
| AWS_ACCESS_KEY_ID | AWS_ACCESS_KEY_ID |
| AWS_ACCOUNT_ID | AWS_ACCOUNT_ID |
| AWS_REPOSITORY | AWS_REPOSITORY |
| AWS_SECRET_ACCESS_KEY | AWS_SECRET_ACCESS_KEY |
| KUBE_CONFIG | .kube/config |
| AWS_DEFAULT_REGION | ap-northeast-2 |
| CI_USERNAME | CI Job을 실행시킬 계정(GitLab) |
| CI_PUSH_TOKEN | CI Job 계정의 Access token |
여러 프로젝트에 상속을 시키기 위해 프로젝트 그룹을 만들어서 그룹밑에 생기는 모든 프로젝트에서 상속받을 수 있게 설정하였다.
Group > Settings > CI/CD > Variables 에 추가해준다.

설정해준 Variables 값이 어떻게 작동하는지는 파이프라인을 작성하고 실행하면서 이해하면 편하다.
파이프 라인의 예제이다.
stages:
- build
- publish
- deploy-dev
- deploy-stage
- deploy-prod
- promote-prod
build:
stage: build
image:
name: node:18
script:
- npm install
- npm version
only:
- dev
- stage
- prod
publish:
stage: publish
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- echo "Build the image using kaniko"
- mkdir -p /kaniko/.docker
- echo "{\"credsStore\":\"ecr-login\",\"credHelpers\":{\"$AWS_ACCOUNT_ID.dkr.ecr.region.amazonaws.com\":\"ecr-login\"}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $AWS_REPOSITORY:$CI_COMMIT_SHORT_SHA
only:
- dev
- stage
- prod
deploy-dev:
stage: deploy-dev
image: alpine:latest
before_script:
- apk add --no-cache git curl bash
- apk add coreutils
- curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
- mv kustomize /usr/local/bin/
- git remote set-url origin http://${CI_USERNAME}:${CI_PUSH_TOKEN}@EXTERNAL_URL/group/${CI_PROJECT_NAME}.git
- git config --global user.email "${GITLAB_USER_EMAIL}"
- git config --global user.name "${GITLAB_USER_LOGIN}"
script:
- git checkout -B stage
- cd deployment/stage
- kustomize edit set image $AWS_REPOSITORY:$CI_COMMIT_SHORT_SHA
- cat kustomization.yaml
- kustomize build .
- git commit -am '[skip ci] STAGE image update'
- git push origin stage
only:
- dev
when: on_success
deploy-stage:
stage: deploy-stage
image: alpine:latest
before_script:
- apk add --no-cache git curl bash
- apk add coreutils
- curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
- mv kustomize /usr/local/bin/
- git remote set-url origin http://${CI_USERNAME}:${CI_PUSH_TOKEN}@EXTERNAL_URL/group/${CI_PROJECT_NAME}.git
- git config --global user.email "${GITLAB_USER_EMAIL}"
- git config --global user.name "${GITLAB_USER_LOGIN}"
script:
- git checkout -B stage
- cd deployment/stage
- kustomize edit set image $AWS_REPOSITORY:$CI_COMMIT_SHORT_SHA
- cat kustomization.yaml
- kustomize build .
- git commit -am '[skip ci] STAGE image update'
- git push origin stage
only:
- stage
when: on_success
deploy-prod:
stage: deploy-prod
image: alpine:latest
before_script:
- apk add --no-cache git curl bash
- apk add coreutils
- curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
- mv kustomize /usr/local/bin/
- git remote set-url origin http://${CI_USERNAME}:${CI_PUSH_TOKEN}@EXTERNAL_URL/group/${CI_PROJECT_NAME}.git
- git config --global user.email "${GITLAB_USER_EMAIL}"
- git config --global user.name "${GITLAB_USER_LOGIN}"
script:
- git checkout -B prod
- git pull origin prod
- cd deployment/prod
- kustomize edit set image $AWS_REPOSITORY:$CI_COMMIT_SHORT_SHA
- cat kustomization.yaml
- kustomize build .
- git commit -am '[skip ci] PROD image update'
- git push origin prod
when: manual
only:
- prod
promote-prod:
stage: promote-prod
image: amazon/aws-cli
script:
- apk update && apk add --no-cache curl
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
- chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl
- mkdir -p $HOME/.kube
- echo -n $KUBE_CONFIG | base64 -d > $HOME/.kube/config
- curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
- chmod +x ./kubectl-argo-rollouts-linux-amd64
- mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
- kubectl argo rollouts promote ${CI_PROJECT_NAME} -n avatar
dependencies:
- deploy-prod
when: manual
only:
- prod
rollback-prod:
stage: promote-prod
image: amazon/aws-cli
script:
- apk update && apk add --no-cache curl
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
- chmod +x ./kubectl && mv ./kubectl /usr/local/bin/kubectl
- mkdir -p $HOME/.kube
- echo -n $KUBE_CONFIG | base64 -d > $HOME/.kube/config
- curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
- chmod +x ./kubectl-argo-rollouts-linux-amd64
- mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
- kubectl argo rollouts abort ${CI_PROJECT_NAME} -n avatar
dependencies:
- deploy-prod
when: manual
only:
- prod
각 단계별로 자세하게 설명해보겠다.
stages 단계에서는 실제 파이프라인이 작동할 단계를 정의한다.
stages:
- build
- publish
- deploy-dev
- deploy-stage
- deploy-prod
- promote-prod
build 단계에서는 Docker Image를 만들기 위해 node js 18 버전을 설치하고 버전을 출력하게 작성하였다.
build:
stage: build
image:
name: node:18
script:
- npm install
- npm version
only:
- dev
- stage
- prod
실제 Docker Image에 대한 정보는 .Dockerfile 에 작성되어 있다.
# 베이스 이미지 사용
FROM node:18
#Set Working Directory
WORKDIR /usr/src/app
#Argument and Environment
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
#현재 프로젝트의 디렉터리에서 작업
COPY package*.json /usr/src/app/
#Docker Image에서 명령어 실행
RUN npm install
COPY . /usr/src/app
#8080포트를 사용하여 서비스를 실행한다.
EXPOSE 8080
#컨터이너의 주요 프로세스를 설정한다.
CMD [ "npm", "start" ]
결국 아래와 같은 폴더 구조를 가지고 Dockerfile을 참조하여 Docker Image를 생성한다.

.dockerignore 파일은 Docker Image를 만들때 제외할 파일들을 나열해준다.
.git
.dockerignore
deployment
node_modules
npm-debug.log
.gitlab
{
"name": "docker_web_app",
"version": "1.0.0",
"description": "Node.js on Docker",
"author": "isle <your@mail.com>",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.16.1"
}
}
'use strict';
const express = require('express');
// 상수
const PORT = 8080;
const HOST = '0.0.0.0';
// 앱
const app = express();
app.get('/', (req, res) => {
res.send('Hello world');
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
Hello, can you share me project on gitlab?