티스토리 뷰

Gitlab의 CI/CD 파이프라인은 Jobs와 Stages로 구성됩니다.  Jobs는 수행할 작업을 정의하며 Stages는 jobs를 실행할 시기를 정의합니다. Stage가 성공적으로 끝난다면 다음 Stage로 넘어가게 됩니다. 금번 포스팅에서는 .gitlab-ci.yml 파일을 활용하여 Kubernetes에 Application을 배포하는 방법을 공유하고자 합니다. CD는 Gitlab-Runner를 활용합니다.

 

간단한 프로세스는 다음과 같습니다. gitlab pipeline을 돌릴때는 Triggering pipelines API를 사용하여 돌려보겠습니다.

 

□ kubectl 명령어를 쓸수있는 서버 즉, kubernetes api 서버와 통신할수있는 서버에 Gitlab-Runner를 설치

□ gitlab-runner  계정에서 kubernetes api와 통신할수있게 config 복사

□ .gitlab-ci.yml 파일 작성

□ pipeline triggers token 생성 및 API 호출

 

1. Gitlab-Runner 설치

 

□ install

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
sudo yum install gitlab-runner

Runner가 정상적으로 설치되었다면 기 구축되어 있는 gitlab에 정보를 register 해줍니다.

[hskim@node1 ~]$ sudo gitlab-runner register
Runtime platform                                    arch=amd64 os=linux pid=128887 revision=21cb397c version=13.0.1
Running in system-mode.


Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://hskim.kubernetes.test.io
Please enter the gitlab-ci token for this runner:
WLRhu4VGeR6YPHadsf
Please enter the gitlab-ci description for this runner:
[node1]: hskim-k8s
Please enter the gitlab-ci tags for this runner (comma separated):
deploy, hskimdemo, k8s
Registering runner... succeeded                     runner=WLRhu4VG
Please enter the executor: docker, docker-ssh, shell, ssh, virtualbox, docker-ssh+machine, kubernetes, custom, docker+machine, parallels:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
[hskim@node1 ~]$ sudo gitlab-runner verify
Runtime platform                                    arch=amd64 os=linux pid=129811 revision=21cb397c version=13.0.1
Running in system-mode.


Verifying runner... is alive                        runner=brnwjFd5

위의 설정까지 정상적으로 마쳤다면 gitlab에서 Runners에 정상적으로 추가된걸 확인할 수 있습니다.

 

2. Gitlab-runner 계정에서 config 복사

 

일반적으로 kubernetes api 서버와의 통신을 위해 kubeconfig 파일을 사용합니다. 기본적으로 kubectl은 $HOME/.kube 디렉토리에서 config라는 이름의 파일을 찾습니다. 그래서 kubectl을 사용할 서버에 $HOME/.kube 디렉토리에 config를 가져다 놓습니다. gitlab-runner를 설치하면 자동으로 gitlab-runner 계정이 만들어지는데요. gitlab pipeline에서 실행하는 모든 명령어는 gitlab-runner 계정으로 실행되기때문에 gitlab-runner계정에 .kube 디렉토리에도 config파일을 복사해줍니다.

[gitlab-runner@bastion ~/.kube]$ pwd
/home/gitlab-runner/.kube
[gitlab-runner@bastion ~/.kube]$ ls
cache  config

 

3. .gitlab-ci.yml 파일 작성

 

테스트 Application을 배포해 볼텐데, 간단하게 상황을 재현해서 .gitlab-ci.yml 스크립트를 상세하고 여러가지 문법으로 작성해보겠습니다. 하나의 namespace를 생성하고 해당 namespace에 db service / adminer 2가지 서비스를 deploy 해보겠습니다. 하나의 예시를 들어 설명하므로, 실제 Kubernetes에 deploy시 필요한 설정값들에대한 자세한 설명은 패스하겠습니다. 중점은 .gitlab-ci.yml 파일 작성 요령 및 tag, Triggering pipelines API 적용방식입니다.

 

□ adminer.yaml / mariadb-service.yaml

 

- adminer.yaml

아래의 yaml파일은 Kubernetes에 deploy할때 Service 와 Deployment를 정의하는 기본적인 스크립트이기 때문에 설명은 생략하겠습니다, 단지 ${WORKSPACE}라는 변수를 넣어놨는데요. 위에서 gitlab pipeline을 돌릴때는 Triggering pipelines API를 사용한다고했습니다. 이후에 .gitlab-ci.yml 파일 및 실제 해당 프로젝트를 API를 통해서 POST 형식으로 요청할때 아래 yaml 파일에 정의한 ${WORKSPACE}변수에 값을 넣어 요청하게 될 것입니다.

---
apiVersion: v1
kind: Service
metadata:
  namespace: ${WORKSPACE}
  name: ${WORKSPACE}-adminer
  labels:
    app: ${WORKSPACE}-adminer
spec:
  type: ClusterIP
  selector:
    app: ${WORKSPACE}-adminer
  ports:
    - port: 8080
      targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: ${WORKSPACE}
  name: ${WORKSPACE}-adminer
  labels:
    app: ${WORKSPACE}-adminer
spec:
  selector:
    matchLabels:
      app: ${WORKSPACE}-adminer
  template:
    metadata:
      labels:
        app: ${WORKSPACE}-adminer
    spec:
      containers:
        - name: ${WORKSPACE}-adminer
          image: adminer
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 120m
              memory: "30Mi"
            limits:
              cpu: 240m
              memory: "50Mi"

- mariadb-service.yaml

해당 yaml 파일 또한 deploy하기 위해 일반적으로 Deployment와 Service를 정의하는 스크립트입니다. 위의 adminer.yaml 보다는 pvc 및 rollingUpdate 설정도 추가하였고 env에 환경변수도 설정하였습니다.(예시를 가지고온거기때문에 참고만해주세요!) 환경변수의 Value값에 똑같이 ${DB_USER}와 같이 추후에 변수를 주입해 실행하도록 하겠습니다. 여기서 중요한건 volume, rollingupdate 등 어떤 들어가있는지보다는 변수값을 어떤식으로 주입시키는지 흐름과 형태만 참고해주시면 될 것 같습니다.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: ${WORKSPACE}
  name: ${SERVICE_NAME}
  labels:
    app: ${SERVICE_NAME}
    version: "${DB_SERVICE_VERSION}"
    type: deployment
spec:
  replicas: 1
  minReadySeconds: 30
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 3
      maxUnavailable: 1
  selector:
    matchLabels:
      app: ${SERVICE_NAME}
  template:
    metadata:
      labels:
        app: ${SERVICE_NAME}
        version: "${DB_SERVICE_VERSION}"
        type: pod
    spec:
      containers:
        - name: ${SERVICE_NAME}
          image: ${DB_SERVICE_IMAGE_NAME}:${DB_SERVICE_VERSION}
          ports:
            - containerPort: 3306
          resources:
            requests:
              cpu: 360m
              memory: "500Mi"
            limits:
              cpu: 540m
              memory: "750Mi"
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: ${MYSQL_ROOT_PASSWORD}
            - name: MYSQL_DATABASE
              value: ${DB_DATABASE}
            - name: MYSQL_USER
              value: ${DB_USER}
            - name: MYSQL_PASSWORD
              value: ${DB_PASSWORD}
            - name: LC_ALL
              value: C.UTF-8
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: ${SERVICE_NAME}-pvc

---
apiVersion: v1
kind: Service
metadata:
  namespace: ${WORKSPACE}
  name: ${SERVICE_NAME}
  labels:
    app: ${SERVICE_NAME}
    type: service
spec:
  selector:
    app: ${SERVICE_NAME}
  ports:
    - port: 3306

 

이제 실제 위에서 정의한 yaml 파일들을 실제 .gitlab-ci.yml 스크립트로 배포될수있도록 작성해 보겠습니다. 일단 전체 .gitlab-ci.yml을 작성한 파일입니다. 아래에서 각 단계별로 알아보겠습니다.

variables:
  K8S_INGRESS: "nginx" # Default: nginx

  DB_SERVICE_IMAGE_NAME: "mariadb/server"
  DB_SERVICE_VERSION: "10.4"
  SERVICE_NAME: ${SERVICE_NAME}
  MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
  MYSQL_DATABASE: ${MYSQL_DATABASE}
  MYSQL_USER: ${MYSQL_USER}
  MYSQL_PASSWORD: ${MYSQL_PASSWORD}

stages:
  - variables-check
  - workspace
  - mariadb

.hskim1-tags-template: &hskim1-tags-def
  tags:
    - deploy
    - k8s
    - hskim1
  variables:
    DEPLOY_TARGET_K8S: "hskim1"
    DOMAIN: "hskim1.test.io"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "trigger" && $CI_COMMIT_BRANCH == "master" && $RUNNER_LOC == "hskim1"'

check-and-submodule:
  stage: variables-check
  rules:
    - if: '$CI_PIPELINE_SOURCE == "trigger" && $CI_COMMIT_BRANCH == "master"'
  tags:
    - build
    - hskim1
  script:
    - export found=${RUNNER_LOC}
    - |
      if [[ "$found" == "" ]]; then
        echo "RUNNER_LOC Not Defined"
        exit 1;
      else
        echo "RUNNER_LOC=${RUNNER_LOC}";
      fi
    - echo ${WORKSPACE}
    - export found=${WORKSPACE}
    - |
      if [[ "$found" == "" ]]; then
        echo "WORKSPACE Not Defined"
        exit 1;
      else
        echo "WORKSPACE=${WORKSPACE}";
      fi

.resources-create: &resources-create-def
  stage: workspace
  before_script:
    - echo "before_script"
  script:
    - echo "resources-create-script"
    - export KUBE_RESOURCE_NS="namespace $WORKSPACE"
    - |
      if kubectl get $KUBE_RESOURCE_NS; then
        echo "${KUBE_RESOURCE_NS} is Exist";
      else
        kubectl create $KUBE_RESOURCE_NS && echo "NS Created";
      fi
    - echo "Create Adminer Service"      
    - |
      envsubst < ./k8s_yaml/adminer.yaml | kubectl apply -f -
      
hskim1-resources-create:
  <<: *hskim1-tags-def
  <<: *resources-create-def

.mariadb-connect: &mariadb-connect-def
  image: mysql
  stage: mariadb
  before_script:
    - echo "before_script"
  script:
    - |
      if [[ "$DB_KIND" == "mariadb" ]]; then
        echo "${DB_SERVICE_NAME} not found";
        envsubst < ./k8s_yaml/mariadb-service.yaml | kubectl apply -f -
      fi     
  after_script:
    - echo "Check Database Service Running"

hskim1-mariadb-connect:
  <<: *hskim1-tags-def
  <<: *mariadb-connect-def

□ .gitlab-ci.yml 파일에서 사용 될 변수와 값을 정의합니다. DB_SERVICE_VERSION 처럼 값을 지정할수도있고 SERVICE_NAME 처럼 ${SERVICE_NAME}으로 값을 채울수있도록 해줄수도 있습니다. 해당값은 추후에 API로 요청을 보낼때 값을 주입시켜줄 것입니다.

variables:
  K8S_INGRESS: "nginx" # Default: nginx

  DB_SERVICE_IMAGE_NAME: "mariadb/server"
  DB_SERVICE_VERSION: "10.4"
  SERVICE_NAME: ${SERVICE_NAME}
  MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
  MYSQL_DATABASE: ${MYSQL_DATABASE}
  MYSQL_USER: ${MYSQL_USER}
  MYSQL_PASSWORD: ${MYSQL_PASSWORD}

□ 해당 job이 실행되는 스테이지 이름을 지정해줍니다.

stages:
  - variables-check
  - workspace
  - mariadb

□ 해당 스크립트는 Gitlab 11.3 부터 적용되어있는 inherits를 사용했습니다. 해당 .hskim1-tags-template에 정의된 값들을 mariadb stages에 사용하게 됩니다. 현재 저는 같은 형태로 구성된 Kubernetes 클러스터가 여러개 있을때 해당 방법을 사용하여 각각의 클러스터에 필요한 값들을 정의한 후에 사용하게 됩니다. 예를들어 hskim1, hskim2 cluster에 똑같은 mariadb stages에 정의된 스크립트를 실행시켜야 할때 아래처럼 tags 및 설정값만 바꾸고 mariadb satges에 tags를 추가해줍니다. 간단하게 설명하자면 deploy, k8s, hskim1의 태그를 가진 runner가 실행되고 variables에는 DEPLOY_TARGET_K8S, DOMAIN 변수를 정의하고 $CI_PIPELINE_SOURCE 변수의 값이 trigger이고 $CI_COMMIT_BRANCH의 변수값이 master이며 $RUNNER_LOC의 변수값이 hskim1일때 해당 스크립트가 적용됩니다.

.hskim1-tags-template: &hskim1-tags-def
  tags:
    - deploy
    - k8s
    - hskim1
  variables:
    DEPLOY_TARGET_K8S: "hskim1"
    DOMAIN: "hskim1.test.io"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "trigger" && $CI_COMMIT_BRANCH == "master" && $RUNNER_LOC == "hskim1"'

□ 해당 스크립트는 추후에 Triggering pipelines API 사용할때 변수값을 모두 잘 넣었는지 체크하기위한 일종의 선행 스크립트입니다. 보통 해당 프로젝트에 Triggering pipelines API 사용하여 보내야 할 모든 변수들을 넣어서 하나라도 빠진다면 다음 stages가 실행되지 않게 합니다. 일종의 변수 체크하는 스크립트라고 보시면 됩니다.

check-and-submodule:
  stage: variables-check
  rules:
    - if: '$CI_PIPELINE_SOURCE == "trigger" && $CI_COMMIT_BRANCH == "master"'
  tags:
    - build
    - hskim1
  script:
    - export found=${RUNNER_LOC}
    - |
      if [[ "$found" == "" ]]; then
        echo "RUNNER_LOC Not Defined"
        exit 1;
      else
        echo "RUNNER_LOC=${RUNNER_LOC}";
      fi
    - echo ${WORKSPACE}
    - export found=${WORKSPACE}
    - |
      if [[ "$found" == "" ]]; then
        echo "WORKSPACE Not Defined"
        exit 1;
      else
        echo "WORKSPACE=${WORKSPACE}";
      fi

□ 해당 스크립트는 서비스를 올리기전에 namespace가 없다면 생성하고 있다면 스킵하는 stage입니다. 보시면 아시겠지만 script는 before_script 및 script로 나누어서 작성할 수 있으며, envsubst < ./k8s_yaml/adminer.yaml | kubectl apply -f - 이부분이 실제 해당경로에 adminer.yaml을 배포하는 구문입니다. *hskim1-tages-def는 위에 inherits을 적용한 스크립트 tags 이름입니다. workspace stage를 실행할때 *hskim1-tages-def 스크립트를 재귀적으로 병합해서 사용하는 것입니다. 도큐먼트에보니 3개 이상 상속하는걸 권장하지 않는다고 합니다. 하지만 지원대는 상속 개수는 10개라고 합니다.

.resources-create: &resources-create-def
  stage: workspace
  before_script:
    - echo "before_script"
  script:
    - echo "resources-create-script"
    - export KUBE_RESOURCE_NS="namespace $WORKSPACE"
    - |
      if kubectl get $KUBE_RESOURCE_NS; then
        echo "${KUBE_RESOURCE_NS} is Exist";
      else
        kubectl create $KUBE_RESOURCE_NS && echo "NS Created";
      fi
    - echo "Create Adminer Service"      
    - |
      envsubst < ./k8s_yaml/adminer.yaml | kubectl apply -f -
      
hskim1-resources-create:
  <<: *hskim1-tags-def
  <<: *resources-create-def

□ 해당 스크립트는 mariadb 서비스를 올리는 스크립트입니다. script에 보시면 if문에 $DB_KINE 변수값이 mariadb라면 해당 if문 안에 있는 스크립트를 실행하라는 구문을 추가하였습니다. 만약 여러개의 yaml파일을 변수값에 따라 실행시켜야 한다면 아래처럼 if문을 사용하여 작성할 수 있습니다.

.mariadb-connect: &mariadb-connect-def
  image: mysql
  stage: mariadb
  before_script:
    - echo "before_script"
  script:
    - |
      if [[ "$DB_KIND" == "mariadb" ]]; then
        echo "${DB_SERVICE_NAME} not found";
        envsubst < ./k8s_yaml/mariadb-service.yaml | kubectl apply -f -
      fi     
  after_script:
    - echo "Check Database Service Running"

hskim1-mariadb-connect:
  <<: *hskim1-tags-def
  <<: *mariadb-connect-def

4. pipeline triggers token 생성 및 API 호출

 

.gitlab-ci.yml 스크립까지 작성했으므로 실제 pipeline triggers token 생성하여 API를 사용해보겠습니다. 프로젝트의 Settings에 CI / CD의 Pipeline triggers탭을 누르면 해당 프로젝트의 triggers Token을 생성해줍니다. 

아래로 조금 내려보면 Use curl에 예시가 나와있습니다. POST 형식으로 projects 번호 잘 체크해서 url을 만들어주면 됩니다.

아래처럼 .gitlab-ci.yml에서 필요한 변수값을 Value값으로 넣어서 CD를 돌릴 수 있습니다.

댓글
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31