上一篇講到了扇貝的微服務實踐,尤其是關於“人”的部分。
本文將就“技術”方案部分做一個簡單的分享。
區分不同的環境
在扇貝,我們維護了 “整合測試環境” 和 “生產環境” 兩個 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 完成的