如何发生
这里我们将聚焦在如何在遍布全球的多个Kubernetes集群上部署我们的应用。
为了一次性能够部署多套Kubernetes对象,我们使用Helm并把所有的charts都存放在单独的Git仓库里。为了能够部署一套完整的多个服务构成的应用栈,我们使用了叫作Umbrella的chart。它支持声明依赖并且允许我们使用一条命令行来启动我们的API和对应的服务。
除此之外,我们在Helm之上创建了一个python脚本,用来做一些检查、chart构建、添加秘钥以及部署我们的应用。所有的这些任务都通过一个使用了Docker镜像的中心化CI平台实现。
现在是时候走近真相了。
注意:在你读这篇博文的时候,Helm 3已经发布了第一个RC版本。这个大版本带来了很多有用的新特性,解决了我们过去一直考虑的那些问题。
Charts开发工作流
对于我们的应用,我们使用分支工作流进行管理,并且我们决定同样应用到Charts开发上。
每个环境都有它独立的私有仓库,用来存放charts。我们还使用了Chartmuseum暴露对应的API。这样我们可以加强环境之间的隔离性,并保证这些charts在应用到生产环境经过了“激烈”的测试。
图:每个环境的chart仓库
值得注意的是当开发者向dev分支推送时,对应chart的一个新的版本就被自动推送到了开发环境的Chartmuseum。这样所有的开发者都使用同一个开发仓库并且要非常小心地指明他们自己的chart版本,以免使用到别人的chart改动。
更进一步,我们的python脚本在推送chart到Chartmuseum前,使用kubeval利用Kubernetes OpenAPI定义来验证对应的Kubernetes对象。
下图是对chart开发工作流的总结:
管理集群差异
集群联邦
在某些场合,我们使用Kubernetes集群联邦在一个单独的API访问端点生命Kubernetes对象。但是我们面临的问题是有些对象不能在联邦访问端点中创建,这就让维护联邦对象和其他单集群对象更加困难。
为了缓解这个问题,我们决定独立管理我们的集群,让整个过程更加容易地结束(我们使用v1的集群联邦,v2会有相应的变化)。
地理分布的平台
目前我们的平台跨越6个区域,拥有3个自建和3个云上的部署。
图:分布式部署
Helm全局值
4个全局的Helm值让我们可以在不同的集群环境中定义相应的差异,这些是对于我们所有集群而言最小化的默认值。
全局值
这些信息帮助我们定义了应用的上下文,并且它们也被用于监控、追踪、日志以及生成外部调用、扩容等等。
这是一个完整的例子:
{{/* Returns Horizontal Pod Autoscaler replicas for GraphQL*/}}
{{- define "graphql.hpaReplicas" -}}
{{- if eq .Values.global.env "prod" }}
{{- if eq .Values.global.region "europe-west1" }}
minReplicas: 40
{{- else }}
minReplicas: 150
{{- end }}
maxReplicas: 1400
{{- else }}
minReplicas: 4
maxReplicas: 20
{{- end }}
{{- end -}}
view rawhelm_template.yaml hosted with ❤ by GitHub
helm模板示例
注意这个逻辑定义在一个帮助模板里从而保证Kubernetes YAML可读。
声明一个应用
我们的部署工具基于多个YAML文件,下面是一个我们声明每个集群的服务和对应扩容拓扑(副本数量)的示例:
releases:
- foo.world
foo.world: # Release name
services: # List of dailymotion's apps/projects
foobar:
chart_name: foo-foobar
repo: git@github.com:dailymotion/foobar
contexts:
prod-europe-west1:
deployments:
- name: foo-bar-baz
replicas: 18
- name: another-deployment
replicas: 3
view rawscaling-v1.yaml hosted with ❤ by GitHub
服务定义
这是一个定义我们部署工作流的所有步骤的示意图。最后一步将会同步在多个生产集群上进行部署:
图:Jenkins部署步骤
秘钥怎么办
在安全领域,我们专注于追踪所有可能散布在不同位置的密钥,并把它们统一存储在位于巴黎的Vault后台中。
我们的部署工具负责从Vault取回密钥并将它们在实际部署的时候注入到Helm中。
为了实现这个效果,我们定义了一个存储在Vault密钥和应用需求间的映射,如下:
secrets:
- secret_id: "stack1-app1-password"
contexts:
- name: "default"
vaultPath: "/kv/dev/stack1/app1/test"
vaultKey: "password"
- name: "cluster1"
vaultPath: "/kv/dev/stack1/app1/test"
vaultKey: "password"
view rawsecret_example.yaml hosted with ❤ by GitHub
apiVersion: v1
data:
{{- range $key,$value := .Values.secrets }}
{{ $key }}: {{ $value | b64enc | quote }}
{{ end }}
kind: Secret
metadata:
name: "{{ .Chart.Name }}"
labels:
chartVersion: "{{ .Chart.Version }}"
tillerVersion: "{{ .Capabilities.TillerVersion.SemVer }}"
type: Opaque
view rawsecret_template.yaml hosted with ❤ by GitHub
警告和限制
目前,我们应用开发的过程和charts解耦。这意味着开发者不得不工作在两个Git仓库,一个用于应用,另一个用来定义它如何部署到Kubernetes上。确实,两个Git仓库意味着两个工作流,对于一个新人来说容易混淆。
管理Umbrella Charts很混乱
正如前面提到的关于Umbrella Charts,它对于定义依赖和快速部署多个应用大有裨益。然而我们使用了选项 --resue-values
来避免每次部署应用时都传入所有的值,这也是Umbrella Chart的一部分。
在我们的持续发布工作流中,只有两个值经常变化:副本数和镜像tag(版本)。对于另一个来说,更加稳定的值,要改变它们需要手动更新,这很难确定。而且,Umbrella chart一个微小的错误都可能带阿里严重的后果。我们都亲眼见过。
多个配置文件更新
当添加一个新的应用时,开发者需要修改多个文件:应用声明、密钥列表,并且如果该应用是一个Umbrella Chart的一部分,那么要把它添加到相应的依赖里。
Jenkins权限在Vault上过度扩展
目前,我们有一个AppRole可以读取Vault里所有的Secret。
回滚过程无法自动化
回滚需要在多个集群上执行命令,这是很容易出错的。我们手动执行这个运维操作,因为我们想保证我们使用了正确的版本号。
我们的GitOps远景
我们的目标
想法是把chart放在要部署的应用仓库里。
这个工作流与开发是一样的。例如,不论何时一个分支合并到了master都会自动触发一次部署。这个方法和当前的工作流的区别在于每个部分都由Git所管理(应用本身和它部署到Kubernetes的方式)。
这样带来很多好处:
两步迁移
我们的开发者使用这些描述的工作流已经超过了两年,所以我们需要尽可能平滑地完成迁移。这也是为什么我们决定在实现我们目标前添加一个中间步骤的原因。
第一步是很简单的:
apiVersion: "v1"
kind: "DailymotionRelease"
metadata:
name: "app1.ns1"
environment: "dev"
branch: "mybranch"
spec:
slack_channel: "#admin"
chart_name: "app1"
scaling:
- context: "dev-us-central1-0"
replicas:
- name: "hermes"
count: 2
- context: "dev-europe-west1-0"
replicas:
- name: "app1-deploy"
count: 2
secrets:
- secret_id: "app1"
contexts:
- name: "default"
vaultPath: "/kv/dev/ns1/app1/test"
vaultKey: "password"
- name: "dev-europe-west1-0"
vaultPath: "/kv/dev/ns1/app1/test"
vaultKey: "password"
view rawscalerelease.yaml hosted with ❤ by GitHub
我们开始在我们所有的开发者中间传播这个名词,而且迁移过程早已开始。第一步仍然由CI平台进行控制。我最近会在另外一篇博客中描述第二步:我们如何使用Flux迁移到GitOps工作流。我们会描述我们的设置以及面临的挑战(多仓库、密钥)。所以请保持关注!
我们试着描述我们最近几年里的应用开发工作流引导我们走向GitOps方式。这段旅程并未完结,我们会继续发布博文,我们现在相信让事情尽可能简单并且接近开发者的习惯是正确的选择。