Gitlab CI 與 DevOps

yandy在掘金發表於2018-04-16

上一篇講到了扇貝的微服務實踐,尤其是關於“人”的部分。

本文將就“技術”方案部分做一個簡單的分享。

區分不同的環境

在扇貝,我們維護了 “整合測試環境” 和 “生產環境” 兩個 kubernetes 叢集。

  • 整合測試環境負責:單元測試,構建映象,整合測試部署
  • 生產環境負責:預釋出,正式釋出

CI/CD 的搭建

我們的 CI/CD 是基於 GitLab Pipeline 搭建的。

架構組負責搭建和維護 runner(Pipeline 的執行環境),DevOps 小組負責 Pipeline 指令碼的編寫。

Gitlab Pipeline

Gitlab pipeline 指 一組按照 stage 執行的job(每個 stage 包含若干個 job),當一個 stagejob 都成功執行後,開始執行下一個 stagejobpipeline 定義在專案的 .gitlab-ci.yml裡。關於 .gitlab-ci.yml 的詳細參考文件,請見:Configuration of your jobs with .gitlab-ci.yml

Gitlab CI 與 DevOps

.gitlab-ci.yml 的編寫和維護

在扇貝,每個 DevOps 小組負責編寫和維護自己負責專案的 .gitlab-ci.yml,定義自己的 pipeline。當然,架構組會提供一個 .gitlab-ci.yml 模版。這個模版 包含 test, build-image, deploy-integration, deploy-staging, deploy-production 5個 stage。基於這樣的 pipeline 可以實現這樣的 CI/CD 工作流:

組員新建分支,開發功能,建立一個 Merge Request,這時候觸發第一個 stage: testtest 中包含所有單元測試的 job。當 test 通過後,組長 Review Merge Request,這其中可能還會提一些修改意見,組員進行對應的修改,再次觸發 test。當且僅當組長 Review 通過後,執行 Merge,這時候開始觸發第二個 stage: build-image(也就是構建 Docker Image)。構建成功後進入到 deploy-integration。整合測試沒有問題,再依次deploy-staging-> 預釋出驗證 -> deploy-production。至此整個 CI/CD 工作流就完成了。

一個大概的 .gitlab-ci.yml 模版如下:

stages:
  - test
  - build
  - deploy_integration
  - deploy_staging
  - deploy_production

variables:
  MYSQL_DATABASE: test
  MYSQL_ALLOW_EMPTY_PASSWORD: yes
  SEA_ENV: testing
  DOCKER_HOST: tcp://dockerd:2375
  IMAGE: registry.mydocker.com/devops/${CI_PROJECT_NAMESPACE}-${CI_PROJECT_NAME}

before_script:
   - IMAGE_TAG=${IMAGE}:${CI_COMMIT_SHA:0:8}

#========================================= Unit Testing ================================================
test_all:
  image: python:3.7
  stage: test
  services:
    - name: mysql:5.6
      alias: mysql
    - name: redis:4
      alias: redis
  before_script:
    - pip install -U -r requirements.txt
  script:
    - flake8 app jobs
    - sea test

#========================================== Build Image =================================================
build_image:
  stage: build
  only:
    - master
  tags:
    - build
  script:
    - docker build -t ${IMAGE_TAG} -f Dockerfile .
    - docker push ${IMAGE_TAG}


deploy_rpc_integration:
  stage: deploy_integration
  only:
    - master
  tags:
    - deploy-integration
  script:
    - kubectl -n xyz set image deploy/examples-rpc "app=${IMAGE_TAG}" --record

deploy_staging:
  stage: deploy_staging
  only:
    - master
  tags:
    - deploy-production
  when: manual
  script:
    - kubectl -n xyz-staging set image deploy/examples-celery "app=${IMAGE_TAG}" --record
    - kubectl -n xyz-staging set image deploy/examples-rpc "app=${IMAGE_TAG}" --record

deploy_production:
  stage: deploy_production
  only:
    - master
  tags:
    - deploy-production
  when: manual
  script:
    - kubectl -n xyz set image deploy/examples-celery "app=${IMAGE_TAG}" --record
    - kubectl -n xyz set image deploy/examples-rpc "app=${IMAGE_TAG}" --record
複製程式碼

下圖是一個執行的例子,圖中可以看到 stage 執行到哪一步,結果分別是什麼。

Gitlab CI 與 DevOps

GitLab Runner

除了各個小組能夠維護自己的 .gitlab-ci.yml,接下來就要架構組構建能夠執行這些 pipelinerunner 了。

Gitlab 提供了 GitLab Runner 來管理 runnerGitLab Runner 負責註冊,執行和反註冊 runner

我們可以利用 k8s 來很方便地執行 GitLab Runner,並且選擇 k8s 作為executor 來執行 job。一個示例配置如下:

apiVersion: v1
metadata:
  labels:
    app: gitlab-builder
  name: gitlab-builder-cm
  namespace: cicd
data:
  REGISTER_NON_INTERACTIVE: "true"
  REGISTER_LOCKED: "false"
  CI_SERVER_URL: "https://gitlab.com/ci"
  RUNNER_CONCURRENT_BUILDS: "4"
  RUNNER_REQUEST_CONCURRENCY: "4"
  RUNNER_TAG_LIST: "build"
  RUNNER_EXECUTOR: "kubernetes"
  KUBERNETES_NAMESPACE: "cicd"
  KUBERNETES_IMAGE: "docker:17.11"
  KUBERNETES_SERVICE_ACCOUNT: "builder"
kind: ConfigMap
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: builder
  namespace: cicd
  labels:
    app: builder
spec:
  replicas: 1
  selector:
    matchLabels:
      app: builder
  template:
    metadata:
      labels:
        app: builder
    spec:
      containers:
      - name: ci-builder
        image: gitlab/gitlab-runner:v10.6.0
        command:
        - /usr/bin/gitlab-ci-multi-runner
        - run
        imagePullPolicy: IfNotPresent
        envFrom:
        - configMapRef:
            name: gitlab-builder-cm
        volumeMounts:
        - mountPath: /etc/gitlab-runner/
          name: config-volume
        lifecycle:
          preStop:
            exec:
              command:
                - /bin/bash
                - -c
                - "/usr/bin/gitlab-ci-multi-runner unregister -t xxxxxx -n builder"
      initContainers:
      - name: register-runner
        image: gitlab/gitlab-runner:v10.6.0
        command: ["sh", "-c", "/usr/bin/gitlab-ci-multi-runner unregister -t xxxxxx -n builder; /usr/bin/gitlab-ci-multi-runner register -r xxxxxx;"]
        volumeMounts:
        - mountPath: /etc/gitlab-runner/
          name: config-volume
        envFrom:
        - configMapRef:
            name: gitlab-builder-cm
      volumes:
      - name: config-volume
        emptyDir: {}
      restartPolicy: Always
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: cicd
  name: builder
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/exec"]
    verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: cicd
  name: builder
subjects:
- kind: ServiceAccount
  name: builder
  namespace: cicd
roleRef:
  kind: Role
  name: builder
  apiGroup: rbac.authorization.k8s.io
複製程式碼

這樣我們就可以得到一個能夠build docker image 的runner。在 .gitlab-ci.yml 中指定 tag 為 build 就可以使用。

最小化運維

我們堅持“最小化運維”的理念,除了日常的 DevOps,我們儘可能地利用 git + pipeline 的方式完成日常工作。我們堅信這樣的工作方式能夠最大化降低手動運維帶來的風險和不確定性。

例如我們 k8s 的證照籤發就是基於 git + pipeline 來做的。大家知道,要能夠使用 kubectl,每個人得有經過 k8s 的簽發的 crt 才可以通過 k8s 的認證。我們簽發的流程就是:

  1. 有一個存放大家csr的 git repo
  2. 新人生成自己的csr,新增到 git repo,提交 merge request
  3. ci 開始 validate csr合法性(例如name的格式,是否包含什麼資訊,不包含什麼資訊等等)
  4. 叢集管理員 validate csr name 和申請人是否相符,如果相符,則合併該 merge request
  5. ci 開始簽發 integration, production 兩個叢集的 crt

在扇貝,幾乎所有的日常運維工作都是基於 CI/CD 完成的

相關文章