前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Helm在Kubernetes多集群上部署应用

使用Helm在Kubernetes多集群上部署应用

作者头像
kubernetes中文社区
发布2020-05-14 16:29:33
1.6K0
发布2020-05-14 16:29:33
举报
在DailyMotion,我们3年前就开始在生产环境实施Kubernetes。但在多个集群上部署应用却充满挑战,为此我们在过去几年里一直致力于打造相应的工具和工作流。

如何发生

这里我们将聚焦在如何在遍布全球的多个Kubernetes集群上部署我们的应用。

为了一次性能够部署多套Kubernetes对象,我们使用Helm并把所有的charts都存放在单独的Git仓库里。为了能够部署一套完整的多个服务构成的应用栈,我们使用了叫作Umbrella的chart。它支持声明依赖并且允许我们使用一条命令行来启动我们的API和对应的服务。

除此之外,我们在Helm之上创建了一个python脚本,用来做一些检查、chart构建、添加秘钥以及部署我们的应用。所有的这些任务都通过一个使用了Docker镜像的中心化CI平台实现。

现在是时候走近真相了。

注意:在你读这篇博文的时候,Helm 3已经发布了第一个RC版本。这个大版本带来了很多有用的新特性,解决了我们过去一直考虑的那些问题。

Charts开发工作流

对于我们的应用,我们使用分支工作流进行管理,并且我们决定同样应用到Charts开发上。

  • dev分支用来构建那些要在开发集群上进行测试的charts
  • 然后当一个pull request合并到master,它们会在staging环境里进行验证
  • 最后,我们创建一个pull request并把这些改动合并到prod分支以便在生产环境中应用这些改动

每个环境都有它独立的私有仓库,用来存放charts。我们还使用了Chartmuseum暴露对应的API。这样我们可以加强环境之间的隔离性,并保证这些charts在应用到生产环境经过了“激烈”的测试。

图:每个环境的chart仓库

值得注意的是当开发者向dev分支推送时,对应chart的一个新的版本就被自动推送到了开发环境的Chartmuseum。这样所有的开发者都使用同一个开发仓库并且要非常小心地指明他们自己的chart版本,以免使用到别人的chart改动。

更进一步,我们的python脚本在推送chart到Chartmuseum前,使用kubeval利用Kubernetes OpenAPI定义来验证对应的Kubernetes对象。

下图是对chart开发工作流的总结:

  1. 根据gazr.io对质量任务(lint, unit-test)的定义设置我们的流水线任务
  2. 推送包含用来部署我们应用的python工具的Docker镜像
  3. 根据分支名设置环境
  4. 使用kubeval检查Kubernetes yaml
  5. 自动增加Chart版本和对应的父亲关系(依赖的被修改的charts)
  6. 根据环境将chart推送到Chartmuseum

管理集群差异

集群联邦

在某些场合,我们使用Kubernetes集群联邦在一个单独的API访问端点生命Kubernetes对象。但是我们面临的问题是有些对象不能在联邦访问端点中创建,这就让维护联邦对象和其他单集群对象更加困难。

为了缓解这个问题,我们决定独立管理我们的集群,让整个过程更加容易地结束(我们使用v1的集群联邦,v2会有相应的变化)。

地理分布的平台

目前我们的平台跨越6个区域,拥有3个自建和3个云上的部署。

图:分布式部署

Helm全局值

4个全局的Helm值让我们可以在不同的集群环境中定义相应的差异,这些是对于我们所有集群而言最小化的默认值。

全局值

这些信息帮助我们定义了应用的上下文,并且它们也被用于监控、追踪、日志以及生成外部调用、扩容等等。

  • 云:我们有混合的Kubernetes平台。例如,我们的API同时部署在GCP区以及自建的数据中心里
  • 环境:某些值可能对于非生产环境发生变化。尤其是资源定义和自动扩容配置。
  • 区域:这个信息用来鉴别集群位置并且可以定义相邻最近的外部服务端点。
  • 集群名称:如果以及当我们需要为每个集群定义的一个值。

这是一个完整的例子:

代码语言:javascript
复制
{{/* 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文件,下面是一个我们声明每个集群的服务和对应扩容拓扑(副本数量)的示例:

代码语言:javascript
复制
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密钥和应用需求间的映射,如下:

代码语言:javascript
复制
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
  • 我们定义了将密钥写入Vault时要遵循的通用规则。
  • 如果密钥特定于上下文或集群,要添加一个特殊的条目(这里cluster1的上下文有它自己的密钥stack-app1-password)。
  • 否则,将使用默认值
  • 对于列表中的每个项目,会向Kubernetes Secret中插入一个键值对。这种方式中我们Charts中的Secret模板非常简单。
代码语言:javascript
复制
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的方式)。

这样带来很多好处:

  • 大幅简化了开发者视角的理解难度。学会如何在本地chart应用改变会更简单。
  • 在代码同样的位置定义了服务部署。
  • 移除了Umbrella Charts管理。每个服务都有自己的Helm发布。这使得应用生命周期管理(回滚,升级)变成原子操作,别的服务不会受到影响。
  • git特性为chart管理带来的好处:回滚、审计日志等等。如果你想要回滚一个chart那么可以使用git完成。部署也可以自动化触发。
  • 我们认为使用了Skaffold的开发工作流带来的提升是允许开发者在一个类似生产环境的上下文中测试它们的变化。

两步迁移

我们的开发者使用这些描述的工作流已经超过了两年,所以我们需要尽可能平滑地完成迁移。这也是为什么我们决定在实现我们目标前添加一个中间步骤的原因。

第一步是很简单的:

  • 我们保证配置我们应用部署使用相似的结构,但是使用一个单独的名叫DailymotionRelease对象
代码语言:javascript
复制
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
  • 每个应用一个Release(没有umbrella charts)
  • charts保存在应用的git仓库

我们开始在我们所有的开发者中间传播这个名词,而且迁移过程早已开始。第一步仍然由CI平台进行控制。我最近会在另外一篇博客中描述第二步:我们如何使用Flux迁移到GitOps工作流。我们会描述我们的设置以及面临的挑战(多仓库、密钥)。所以请保持关注!

我们试着描述我们最近几年里的应用开发工作流引导我们走向GitOps方式。这段旅程并未完结,我们会继续发布博文,我们现在相信让事情尽可能简单并且接近开发者的习惯是正确的选择。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 kubernetes中文社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用多个仓库
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档