上一篇讲到了扇贝的微服务实践,尤其是关于“人”的部分。
本文将就“技术”方案部分做一个简单的分享。
区分不同的环境
在扇贝,我们维护了 “集成测试环境” 和 “生产环境” 两个 kubernetes
集群。
- 集成测试环境负责:单元测试,构建镜像,集成测试部署
- 生产环境负责:预发布,正式发布
CI/CD 的搭建
我们的 CI/CD 是基于 GitLab Pipeline 搭建的。
架构组负责搭建和维护 runner(Pipeline 的执行环境),DevOps 小组负责 Pipeline 脚本的编写。
Gitlab Pipeline
Gitlab pipeline
指 一组按照 stage
执行的job
(每个 stage
包含若干个 job
),当一个 stage
的 job
都成功执行后,开始执行下一个 stage
的 job
。pipeline
定义在项目的 .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
: test
。test
中包含所有单元测试的 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
,接下来就要架构组构建能够执行这些 pipeline
的 runner 了。
Gitlab 提供了 GitLab Runner 来管理 runner
。 GitLab 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
的认证。我们签发的流程就是:
- 有一个存放大家csr的 git repo
- 新人生成自己的csr,添加到 git repo,提交 merge request
- ci 开始 validate csr合法性(例如name的格式,是否包含什么信息,不包含什么信息等等)
- 集群管理员 validate csr name 和申请人是否相符,如果相符,则合并该 merge request
- ci 开始签发 integration, production 两个集群的 crt
在扇贝,几乎所有的日常运维工作都是基于 CI/CD 完成的