Deployment – CI/CD

Application의 배포를 위해서 채택한 구조는 Github -> Jenkins -> Docker -> Kubernetes 입니다. 배포를 위한 예시는 Vue.js로 개발된 application으로, build 된 파일이 dockerfile을 통해 Nginx application으로 복사가 됩니다.

FROM nginx:latest
MAINTAINER jinn.k <jinn.k@kakaocorp.com>
EXPOSE 80

ADD ./dist /usr/local/dist
CMD ["nginx", "-g", "daemon off;"]

Nginx의 configmap과 deployment는 미리 띄워놓은 상태입니다. 아래는 Nginx의 configmap과 deployment의 설정 파일입니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: xxx
data:
  nginx.conf: |
    worker_processes  auto;
    events {
            worker_connections  1024;
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
        sendfile        on;
        keepalive_timeout  65;

        server {
            listen 80;
            server_name  xxx;

            root /usr/local/dist;
            location / {
                try_files $uri $uri/ /index.html;
            }
            location /health {
                return 200;
            }
        }
    }

---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: xxx
spec:
  replicas: 3
  minReadySeconds: 10
  revisionHistoryLimit: 5
  strategy:
    rollingUpdate:
      maxSurge: 100%
      maxUnavailable: 0%
  selector:
    matchLabels:
      app: xxx
  template:
    metadata:
      labels:
        app: xxx
    spec:
      containers:
      - name: xxx
        image: xxx
        imagePullPolicy: "Always"
        readinessProbe:
          initialDelaySeconds: 5
          periodSeconds: 1
          successThreshold: 1
          failureThreshold: 3
          httpGet:
            path: /health
            port: 80
        ports:
        - containerPort: 80
        volumeMounts:
        - name: xxx
          readOnly: true
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
        - name: cache-volume
          mountPath: /cache
      volumes:
      - name: xxx
        configMap:
          name: xxx
      - name: cache-volume
        emptyDir:
          medium: Memory
          sizeLimit: "1Gi"

우선 Jenkins의 managed files에 파일들을 만들어 놓았습니다.

  • .env: Github등을 통해서 외부에 노출하지 않을 데이터
  • .env.$phase: phase별로 데이터를 구분하기 위한 파일
  • key: Kubernetes cluster에 접근하기 위한 파일
  • password: Dockerhub에 접근하기 위한 password 파일

Bash script가 익숙하기 때문에 pipeline을 사용하지 않고 아래의 script로 진행했습니다. Github에 release tag를 달고 push를 했을 경우 Jenkins가 webhook을 받아서 아래의 script를 실행하는 구조입니다.

#!/bin/bash
PASSWORD=`cat ./password`
PHASE='dev'
VERSION=`echo $GIT_BRANCH | awk -F '/' '{print $3}'`

npm install
yarn run build$PHASE

docker build -t jinn.k/$PHASE:$VERSION .
docker tag jinn.k/$PHASE:$VERSION 사내repo/jinn.k/$PHASE:$VERSION
docker login -u jinn.k -p $PASSWORD idock.daumkakao.io
docker push 사내repo/jinn.k/$PHASE:$VERSION

sleep 3
kubectl --kubeconfig=./key set image deployment $PHASE $PHASE=사내repo/jinn.k/$PHASE:$VERSION --record

Npm과 Yarn으로 Vue.js를 build할 때, Node 명령어가 /usr/bin에 존재하지 않으면 정상적으로 동작하지 않는 문제가 있었습니다. Jenkins에서 인식을 못한 것이라고 판단하고, 해당 위치에 link를 걸었습니다.

정상적으로 build가 된 후, Github repo에 넣어둔 Dockerfile을 통해서 build 후 hub에 이미지를 올리는 동작을 하게 됩니다.

Docker image 전송이 완료되면 미리 배포해 놓은 Kubernetes deployment를 set image deployment를 통해서 update 시켜줍니다. 이 때 –record를 통해서 언제든 rollback이 가능하도록 만들어 놓았습니다. rollback은 kubectl rollout undo deployment $PHASE를 통해서 이전으로 되돌릴 수 있습니다.

Update의 경우에는 bluegreen 방식을 사용하였습니다. 우선 canary 방식를 사용하지 않은 이유는 이미 Dev phase를 통해서 충분히 검증을 한 후에 Prod phase로 내보내기 때문입니다. Rolling update는 특정 시간동안 사용자가 서로 다른 version의 service를 보는 것을 방지 위해서 사용하지 않았습니다.

Bluegreen 배포는 deployment의 rollingUpdate 값을 “maxSurge: 100%, maxUnavailable: 0%”로 setting하여 가능하도록 하였습니다. 추가로 pod이 정상적으로 가동되도록 하기 위하여 “minReadySeconds: 10”와 readiness probe를 설정해 놓았습니다.

readinessProbe:
  failureThreshold: 3
  httpGet:
    path: /health
    port: 80
    scheme: HTTP
  initialDelaySeconds: 5
  periodSeconds: 1
  successThreshold: 1
  timeoutSeconds: 1

Jenkins config 파일 생성: https://know-one-by-one.tistory.com/91
Jenkins webhook 연동: https://bcho.tistory.com/1237

Post Author: Jinn