博客/工程

使用Tanka进行大规模一致配置管理的最佳实践

2021年7月12日6分钟

在Grbob电竞频道afana实验室,我们使用短歌将工作负载部署到我们的Kubernetes集群。随着我们组织的发展,我们问自己:我们应该如何以一致的方式大规模地管理工作负载配置?

讲一点背景知识

一开始,工程师手动调用Tanka,从本地机器应用配置。因为构建Tanka是为了将Tanka环境紧密耦合到单个Kubernetes集群并设置默认名称空间,所以他们经常会发现本地Kube上下文与他们想要应用的集群不匹配。

从那时起,我们的工程团队迅速成长,Kubernetes集群的数量增加了,Tanka环境的数量也出现了爆炸式增长。这给工程师们带来了沉重的负担,他们经常需要应用尽可能多的Tanka环境,因为人们会犯错误,经常会忘记一个或另一个环境。为了解决这个问题,我们自然地实现了持续部署。这解决了现有集群和环境的许多问题。

随着Gbob电竞频道rafana实验室的不断发展,我们的工程师平台也需要发展。这意味着更多的Kubernetes集群和更多的Tanka环境。你已经发现问题所在了吗?这是一个永无止境的故事:管理所有这些Tanka环境变成了一个真正的麻烦。不同集群的应用程序环境开始相互偏离,因为有些集群需要与其他集群略有不同的配置。由于经常添加或迁移新集群,工程师需要手动引导新环境,手动将其与新的API服务器耦合,创建名称空间并重新考虑特定于集群的异常。

使Tanka环境保持一致

以上问题都对业务有潜在的影响。例如,集群之间的漂移使事件期间的调试变得更加困难,缺少名称空间会阻塞CD进程,而引导增加了工程成本。

让我们把这个问题归结为三个问题:

  1. 同一应用程序在不同集群之间的配置漂移。
  2. 在新的集群中引导新的Tanka环境。
  3. 引导新的集群(例如,创建名称空间)。

我们在Jsonnet中用Tanka解决了这些问题内联的环境在基地。内联环境允许我们动态地创建具有Jsonnet必须提供的所有函数和结构的环境。内联环境不过是spec.json与一个数据:字段,该字段包含Kubernetes资源。与tanka-util库,我们可以快速配置一个新的Tanka环境:

/ /环境/ grafana /主要。Jsonnet local grafana = import 'github.com/grafana/jsonnet-libs/grafana/grafana.libsonnet';Local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';{local this = self, data:: {grafana: grafana + grafana. withanonymous (),}, env: tanka.environment。New (name='grafana/' + cluster.name, namespace='grafana', apiserver='https://127.0.1.1:6443',) + tanka.environment. New (name='grafana/' + cluster.name, namespace='grafana', apiserver='https://127.0.1.1:6443',)withLabels({cluster: cluster.name}) + tanka.environment.withData(this.data),}

要将其添加到新集群中,我们可以复制env:使用相同的数据块,让我们有了更多的一致性。然而,这仍然是体力劳动。让我们更进一步,在Jsonnet中描述我们的集群:

/ / lib /元/元。libsonnet{集群:{“dev-01”:{名称:“dev-01”,状态:“开发”,apiserver:“https://127.0.1.1:6443”,},“prod-01”:{名称:“prod-01”,状态:“刺激”,apiserver: https://127.0.2.1:6443 ', }, }, }

这将允许我们在开发和生产集群中创建一致的Grafana部署,只需要几行额外的代码:

/ /环境/ grafana /主要。Jsonnet local grafana = import 'github.com/grafana/jsonnet-libs/grafana/grafana.libsonnet';Local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';+local meta = import 'meta/meta.libsonnet';{local this = self, data:: {grafana: grafana + grafana. withanonymous (),}, - env: + env(cluster):: tanka.environment。新建(name='grafana/' + cluster.name, namespace='grafana', - apiserver='https://127.0.1.1:6443', + apiserver=cluster. name)。Apiserver,) + tanka.environment。withLabels({cluster: cluster.name}) + tanka.environment.withData(this.data), + envs: {+ [name]: this.env(meta.clusters[name]) + for name in st . objectfields (meta.clusters) +},}

这可以防止问题1,集群之间的配置漂移。

让我们将Grafana添加到另一个集群中:

/ / lib /元/元。libsonnet{集群:{“dev-01”:{名称:“dev-01”,状态:“开发”,apiserver:“https://127.0.1.1:6443”,},+“dev-02”:{+名称:dev-02, +状态:“开发”,+ apiserver:“https://127.0.1.2:6443”+},“prod-01”:{名称:“prod-01”,状态:“刺激”,apiserver: https://127.0.2.1:6443 ', }, }, }

就是这样!不需要对Grafana应用程序配置或Tanka环境进行更多更改。这缓解了问题2,引导新的Tanka环境。

注意:在Grafabob电竞频道na实验室,我们创建Kubernetes集群使用起程拓殖.中的集群列表lib /元因此,从terrform生成,进一步减少了手工负担。

我听到你说:“但是但是但是……我有一簇和其他的不一样;那个‘漂移’是故意的!”方法可以简单地完成特定于集群的覆盖env对象:

/ /环境/ grafana /主要。Jsonnet local grafana = import 'github.com/grafana/jsonnet-libs/grafana/grafana.libsonnet';Local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';Local meta = import 'meta/meta.libsonnet';{local this = self, data:: {grafana: grafana + grafana. withanonymous (),}, env(cluster):: tanka.environment。新建(name='grafana/' + cluster.name, namespace='grafana', apiserver=cluster. name)。Apiserver,) + tanka.environment。withLabels({cluster: cluster.name}) + tanka.environment.withData(this.data), envs: {[name]: this.env(meta.clusters[name]) for name in st . objectfields (meta.clusters) +} + {+ 'prod-01'+: {+ data+: {grafana+: grafana. withtheme ('dark')}, +},},}

命名空间的空间

传统上,名称空间要么手动创建,要么在Tanka环境中创建。如果在Tanka环境中创建了一个名称空间,则有一个风险,即该Tanka环境不是该名称空间中具有资源的唯一环境。删除名称空间可能会破坏该名称空间中的所有资源。

幸运的是,我们有所有可以使用的Tanka环境Tk环境列表,我们可以根据Tanka环境规范简单地生成名称空间清单。让我们生成一个JSON数据文件,以便在lib /元

Tk env list——json environments/ | jq . txt> lib /元/生/ environments.json

看一看lib /元/生/ environments.json(修剪),并注意spec.namespace值:

[{"apiVersion": "tanka.dev/v1alpha1", "kind": "Environment", "metadata": {"name": "grafana", "namespace": "environments/grafana/main. properties ", "Environment", "Environment", "metadata": "Environment", "metadata": "jsonnet", "labels": {"cluster": "dev-01"}}, "spec": {"apiServer": "https://127.0.1.1:6443", "namespace": "grafana"}}, {"apiVersion": "tanka.dev/v1alpha1", "kind": "Environment", "metadata": {"name": "grafana", "namespace": "environments/grafana/main. net",jsonnet", "labels": {"cluster": "dev-02"}}, "spec": {"apiServer": "https://127.0.1.2:6443", "namespace": "grafana"}}, {"apiVersion": "tanka.dev/v1alpha1", "kind": "Environment", "metadata": {"name": "grafana", "namespace": "environments/grafana/main. net",jsonnet”、“标签”:{“集群”:“prod-01}},“规范”:{“apiServer”:“https://127.0.2.1:6443”、“名称”:“grafana}})

由此,我们可以生成一个命名空间/集群对列表:

/ / lib /元/元。Libsonnet本地envs = import './raw/environments.json';{集群:{/*…*/},命名空间:std.foldr(function(env, k) k + ({[env.spec.namespace]+: {clusters+: [env.metadata.labels.]Cluster],},}), envs, {}),}

最终我们可以在Tanka内联环境中生成命名空间清单,类似于Grafana环境:

/ /环境/集群资源/主要。Jsonnet local k = import 'github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet';Local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';Local meta = import 'meta/meta.libsonnet';{local this = self, data(cluster):: {namespaces: {[ns]: k.core.v1.namespace.new(ns) for ns in std.objectFields(meta.namespaces) if std.length(std.find(cluster.name, meta.namespaces[ns].clusters)) > 0},}, env(cluster):: tanka.environment. namespaces。新建(name='cluster-resources/' + cluster.name, namespace='namespace', apiserver=cluster. name)。Apiserver,) + tanka.environment。withLabels({cluster: cluster.name}) + tanka.environment.withData(this.data(cluster)), envs: {[name]: this.env(meta.clusters[name]) for name in st . objectfields (meta.clusters)},}

现在,如果我们创建一个带有新名称空间的Tanka环境,我们将进行更新lib /元/生/ environments.json,将创建一个新的名称空间。只要有疍家人的环境grafana命名空间,命名空间清单将在这里。最后,CI检查验证Tk env列表——json匹配原始文件。

个人而言

对我来说,最大的思想转变是将我们的Jsonnet代码库视为一个“数据库”,而不仅仅是作为代码的基础设施。这个“数据库”包含非常有价值的信息,允许我们在不同的情况下(“视图”)反复使用该数据,减少了复制数据的需要。