Gitlab CI 与 DevOps

2,845 阅读4分钟

上一篇讲到了扇贝的微服务实践,尤其是关于“人”的部分。

本文将就“技术”方案部分做一个简单的分享。

区分不同的环境

在扇贝,我们维护了 “集成测试环境” 和 “生产环境” 两个 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.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 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 完成的