[CI/CD] GitOps 방식의 CI/CD 구축하기 - GitLab Pipeline

@isle·2022년 8월 18일
0

CI/CD

목록 보기
2/3

Pipeline

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 를 적용할 것인지 결정

Platform Layout

파이프라인에 포함된 Variable의 설정을 선행해야 한다.
내가 구성한 파이프라인에 쓰인 Variable은 아래와 같다.

KeyEnvironment
AWS_ACCESS_KEY_IDAWS_ACCESS_KEY_ID
AWS_ACCOUNT_IDAWS_ACCOUNT_ID
AWS_REPOSITORYAWS_REPOSITORY
AWS_SECRET_ACCESS_KEYAWS_SECRET_ACCESS_KEY
KUBE_CONFIG.kube/config
AWS_DEFAULT_REGIONap-northeast-2
CI_USERNAMECI Job을 실행시킬 계정(GitLab)
CI_PUSH_TOKENCI Job 계정의 Access token

여러 프로젝트에 상속을 시키기 위해 프로젝트 그룹을 만들어서 그룹밑에 생기는 모든 프로젝트에서 상속받을 수 있게 설정하였다.


Group > Settings > CI/CD > Variables 에 추가해준다.

설정해준 Variables 값이 어떻게 작동하는지는 파이프라인을 작성하고 실행하면서 이해하면 편하다.
파이프 라인의 예제이다.

.gitlab-ci.yml

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 에 작성되어 있다.

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를 만들때 제외할 파일들을 나열해준다.

.dockerignore

.git
.dockerignore
deployment
node_modules
npm-debug.log
.gitlab

package.json

{
  "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"
  }
}

server.js

'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}`);
profile
사용자의 기억법

1개의 댓글

comment-user-thumbnail
2023년 3월 7일

Hello, can you share me project on gitlab?

답글 달기