Z.S.K.'s Records

Kubernetes学习(使用kustomize实现不同环境的配置派生)

最近在思考作为一个小团队来说,在没有配置管理平台的情况下怎么样的配置管理才能更好地支持多个Kubernetes环境的差异呢, 大多数的人跟我的反应一样, 使用Helm, 这也是我一直的想法,Helm对于复杂的应用来说,其实比较实用,特别是在Helm3去除了Tiller之后,可是业务中的应用基本都属于轻应用,有更优的解决方案吗?

直到我看到了kustomize,不得不说, 现在K8s都1.18的版本了, 在1.14的时候kustomize就被直接整合在kubectl里,然而,我却从来都没关注过.

简介

官方的描述:

1
2
Kubernetes native configuration management
Kustomize introduces a template-free way to customize application configuration that simplifies the use of off-the-shelf applications. Now, built into kubectl as apply -k.

Kustomize 允许用户以一个应用描述文件 (YAML 文件)为基础(Base YAML),然后通过 Overlay 的方式生成最终部署应用所需的描述文件,而不是像 Helm 那样只提供应用描述文件模板,然后通过字符替换(Templating)的方式来进行定制化

既然有了helm,为何还需要kustomize,换句话说kustomize 解决了什么问题?

一般应用都会存在多套部署环境:开发环境、测试环境、生产环境,多套环境意味着存在多套 K8S 应用资源 YAML。而这么多套 YAML 之间只存在微小配置差异,比如镜像版本不同、Label 不同等,而这些不同环境下的YAML 经常会因为人为疏忽导致配置错误。再者,多套环境的 YAML 维护通常是通过把一个环境下的 YAML 拷贝出来然后对差异的地方进行修改。一些类似 Helm 等应用管理工具需要额外学习DSL 语法。总结以上,在 k8s 环境下存在多套环境的应用,经常遇到以下几个问题:

  • 如何管理不同环境或不同团队的应用的 Kubernetes YAML 资源
  • 如何以某种方式管理不同环境的微小差异,使得资源配置可以复用,减少 copy and change 的工作量
  • 如何简化维护应用的流程,不需要额外学习模板语法

Kustomize 通过以下几种方式解决了上述问题:

  • kustomize 通过 Base & Overlays 方式(下文会说明)方式维护不同环境的应用配置
  • kustomize 使用 patch 方式复用 Base 配置,并在 Overlay 描述与 Base 应用配置的差异部分来实现资源复用
  • kustomize 管理的都是 Kubernetes 原生 YAML 文件,不需要学习额外的 DSL 语法

上面的表达引用自kustomize最佳实践

本人总结就是: 不需要通过模板系统,以特定结构合并(更新)多种资源以支撑在多个环境中的部署,以消除差异.

安装

1
2
3
# mac
brew install kustomize
# curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash

使用

kustomize的命令也比较简单, 最常用的就是下面两个命令:

1
2
3
4
# 直接使用kustomize命令
kustomize build ~/ldap/overlays/staging | kubectl apply -f -
# 在 kubernetes 1.14 版本, kustomize 已经集成到 kubectl 命令中,成为了其一个子命令,可使用 kubectl 来进行部署
kubectl apply -k ~/ldap/overlays/staging

术语

kustomize中有几个常用的关键字术语,这里简单介绍一下,方便后续的使用

  • base

base 指的是一个 kustomization , 任何的 kustomization 包括 overlay (后面提到),都可以作为另一个 kustomization 的 base (简单理解为基础目录)。base 中描述了共享的内容,如资源和常见的资源配置

  • overlay

overlay 是一个 kustomization, 它修改(并因此依赖于)另外一个 kustomization. overlay 中的 kustomization指的是一些其它的 kustomization, 称为其 base. 没有 base, overlay 无法使用,并且一个 overlay 可以用作 另一个 overlay 的 base(基础)。简而言之,overlay 声明了与 base 之间的差异。通过 overlay 来维护基于 base 的不同 variants(变体),例如开发、QA 和生产环境的不同 variants

  • variant

variant 是在集群中将 overlay 应用于 base 的结果。例如开发和生产环境都修改了一些共同 base 以创建不同的 variant。这些 variant 使用相同的总体资源,并与简单的方式变化,例如 deployment 的副本数、ConfigMap使用的数据源等。简而言之,variant 是含有同一组 base 的不同 kustomization

  • patch

修改文件的一般说明。文件路径,指向一个声明了 kubernetes API patch 的 YAML 文件

workflows 工作流

kustomize 将对 Kubernetes 应用的管理转换成对 Kubernetes manifests YAML 文件的管理,而对应用的修改也通过 YAML 文件来修改。这种修改变更操作可以通过 Git 版本控制工具进行管理维护, 因此用户可以使用 Git 风格的流程来管理应用。 workflows 是使用并配置应用所使用的一系列 Git 风格流程步骤。官网提供了两种方式,一种是定制配置,另一种是现成配置

定制场景

workflow如下:

现成配置

在这个工作流方式中,可从别人的 repo 中 fork kustomize 配置,并根据自己的需求来配置

workflow如下:

流程非常地简洁,相信不需要过多解释, 个人觉得这两种方式除了源头上有区别外,在使用上没什么差别.

Case Demo

下面会通过一个小Demo来体验kustomize是如何工作的.

目录结构

app-kustomize是一个代码git, 以golang为主,这里只列出了跟kustomize有关的文件,其它代码文档没有直接关系就先省略了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app-kustomize
deploy
base
config.yaml
deployment.yaml
service.yaml
kustomization.yaml
overlay
1box
1box-custom-env.yaml
kustomization.yaml
prod
deployment-patch.yaml
kustomization.yaml

这里对上述的文件做个简要说明:

base翻译过来就是基础的意思, 里面的资源可以理解为默认的配置或者是基础配置

overlay目录下则可以根据不同环境建立不同的文件夹, 比如我有一个测试环境,那么可以叫1box(这个名字随便), 还有一个生产环境,就叫prod, 这两个目录下都包含有kustomeization.yaml, 同时,两个目录下都可以通过打patch或者覆盖的方式来对base目录下对应的资源进行更新, 这也是overlay的由来.

使用kustomize build ~/ldap/overlays/1box来生成合并后的整体配置文件

再通过kustomize build ~/ldap/overlays/1obx | kubectl apply -f -进行发布到对应的环境中去

当然,这个流程就可以跟CI/CD进行结合使用了. 是不是非常简单.

其它的配置文件不需要过多解释了.

cat base/deployment.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
name: p-expoter-kustom
spec:
replicas: 1
template:
spec:
containers:
- args:
- -TEST
- "789"
image: localhost:5055/p-expoter:master-36cd406f
imagePullPolicy: IfNotPresent
name: p-expoter
volumeMounts:
- mountPath: "/etc/config/config.yml"
name: demo-config
readOnly: true
subPath: config.yml
imagePullSecrets:
- name: realty
volumes:
- secret:
items:
- key: alertmanager.yaml
path: config.yml
secretName: p-expoter-senserealty-cms
name: demo-config

cat base/service.yaml

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: p-expoter
spec:
ports:
- name: http
port: 80
targetPort: 80
type: ClusterIP

cat config.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: secrets.mz.com/v1alpha1
kind: ConfigMapSecret
metadata:
name: p-expoter-senserealty-cms
spec:
template:
metadata:
name: p-expoter-senserealty-cms
data:
alertmanager.yaml: |
global:
resolve_timeout: 5m
mongodb_password: $(MONGODB_PASSWORD) # 在 Var中定义
redis_password: $(REDIS_PASSWORD)
# special_how: $(CONFIGMAP_HOW)
# special_type: $(CONFIGMAP_TYPE)
route:
receiver: default
group_by: ["alertname", "job", "team"]
group_wait: 30s
vars:
- name: MONGODB_PASSWORD
secretValue: # 引用secret
name: senserealty-secret-data # secret 名字
key: mongodb_password # secret key
- name: REDIS_PASSWORD
secretValue:
name: senserealty-secret-data
key: redis_password
# - name: CONFIGMAP_HOW # 引用configmap
# configMapValue:
# name: special-config-hmmg28f4kd
# key: special.how
# - name: CONFIGMAP_TYPE
# configMapValue:
# name: special-config-hmmg28f4kd
# key: special.type

以上配置文件都是很常用的资源对象, 这里解释一下config.yaml并不是我们常用的configmap.yaml类型, 而是使用了CRD类型,主要是为了实现敏感信息的加密保存在git上.这块不是这篇post的重点,感兴趣的话, 大家可参考这篇post ,目前可直接把这个当做为等同于configmap

kustomization.yaml

可以发现在base目录或者是overlay目录下都有一个kustomization.yaml文件,该文件是kustomize的核心, 包含了需要部署的资源.

当然它本身是有一定语法的, 详细的使用可参考这里,下面会挑选一些常用的配置来说明如何使用

cat kustomization.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

commonLabels:
k8s-app: p-expoter # 会在所有的资源对象上都会加上该label
namespace: stage # 会在所有的资源对象上都指定该ns

# configMapGenerator: # secretGenerator
# - name: special-config
# files:
# - configmap.yaml

resources:
- config.yaml
- service.yaml
- deployment.yaml
resources

这个字段包含需要部署的资源文件, 这个应该很容易理解

commonLables/commonAnnotations

指定了commonLables的话, 则会在所有的资源resources包含的对象上都会加上该label, 支持指定多个,这样就省去了对k8s的yaml中手工地指定lable

commonAnnotations同理.

namespace

同上,

configMapGenerator/secretGenerator

这两者具有相同的功能, 可以从文件中直接生成configmap或者是secret

格式如上代码所示,这部分也会渲染到最终的yaml文件中, 因此,除了resources指定的资源外, 还能通过这种方式生成部署对象

patchesStrategicMerge

patchesStrategicMerge则主要是用于overlay下的kustomization.yaml中, 用于使用打patch的方式将该字段指定的文件合并到base目录下对应的内容上.

比如, 现在需要1box环境中,在base声明的deployment.yaml的基础上,给p-expoter容器增加一个环境变量

那么就新增overlay/1box/1box-custom-env.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: apps/v1
kind: Deployment
metadata:
name: p-expoter-kustom
spec:
template:
spec:
containers:
- args:
- "aaabbb"
name: p-expoter # 由于env是在spec.template.containers下的, 因此需要保留这种树型的结构,同时容器的名字很重要,需要与base/deployment.yaml对应上,要不然,会出现env找不到容器,自然无法为对应容器添加成功.
env:
- name: CUSTOM_ENV_VARIABLE
value: "Value defined by Kustomize"

这里需要注意的是,patch的字段都是通过Yaml的层及进行查询的, 所以要添加或者修改的东西一定要对应上层级,不然在base里是查找不到相应的字段的, 因此也无法修改, 但是不会报错

cat overlay/1box/kustomization.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# namePrefix: 1box # 所有的资源名称都会加上1box-

# replicas: # 直接修改资源副本数
# - name: p-expoter-kustom
# count: 2

# images: # 直接修改镜像
# - name: localhost:5055/p-expoter:master-36cd406f # name 为base/deployment中的镜像的名字
# newName: p-expoter
# newTag: alpine # 最终会替换成 images: localhost:5055/p-expoter:alpine

bases:
- ../../base

patchesStrategicMerge:
- 1box-custom-env.yaml

以上的代码实现修改容器的启动参数, 同时为容器添加env的功能

使用kustomize build deploy/overlay/1box会发现,p-expoter容器已经被加上了env环境变量了,而其它的部分保持不变.

通过patchesStrategicMerge可以实现,如果指定的字段不存在,则进行添加, 如果存在,则进行覆盖的功能

注意: 如果通过在kustomization.yaml修改了同时又使用patchesStrategicMerge进行了同一字段的修改,

比如修改replicas, 如果在kustomization.yaml中修改成了2, 在patchesStrategicMerge`又修改成了10

结果以kustomization.yaml中的为准,也就是最终replicas = 2

namePrefix/nameSuffix

这两个实现给所有的资源的名字加上前缀或者后缀,以-进行隔离, 非常容易理解,主要用于overlay下

常用的也就这些了,还有一些其它的配置就不一一介绍了,官网上都非常的详细.

部署

通过上面的介绍,应该对kustomize有个初步的了解, 最后的工作就是根据不同的环境将资源发布到集群中

1
kubectl apply -k ~/ldap/overlays/1box

注意:

如果 kubectl apply -k 出现error: rawResources failed to read Resources的错误

1
error: rawResources failed to read Resources: Load from path ../../base/ failed: '../../base/' must be a file (got d='/Users/zhoushuke/git_uni/gitlab.bj.sensetime.com/gitlabci-golang-demo/deploy/base')

出现这种情况是因为kustomizekubectl apply -k 版本不匹配(kubectl的版本其实是跟着集群的版本走的, 而kustomize二进制可能不一定会对着kubectl的版本安装,有可能就直接安装了最新版本)

可以使用kustomiz build . | kubectl apply -f -

另外一个情况下, bases已经在kustomize v2.1.0+ 版本中给去掉了,高版本的会直接将bases转换成resources,这个可以从渲染后的kustomization.yaml中看出

Helm vs kustomize

Differences

Kustomize 自称因为去掉了模板语法,更易使用,对此我保留看法,如果仅就入门使用来看,二者差异并不大。

Helm3也已经完全地抛弃了Tiller,在架构上更加的简洁, 因此在部署上,Kustomize 的优势也不是很大。

我认为他们的区别主要在工作流程上:

  • Helm 的基础流程比较瀑布:定义 Chart->填充->运行,在 Chart 中没有定义的内容是无法更改的;
  • Kustomize 的用法比较迭代:Base 和 Overlay 都是可以独立运作的,增加新对象,或者对编写 Base 时未预料的内容进行变更,都不在话下

Choice

helm出现的时间较早, kubernetes开发/运维大多数人或多或少都有过接触,kustomize在v1.14版本后默认集成到kubectl中大有后起之秀的能力,这两者如何选择呢?

对于要公开发布一个较为复杂的应用,可能涉及较多的资源类型, 例如 Istio,编写良好的 Chart 能给用户很大帮助,用户在缺失一点发挥空间的情况下,通过对 values.yaml 的阅读,就能对这种复杂的部署产生一个较为深入的认识。

如果是常见的业务应用,特别是在私有化部署场景中,这咱差异化并不是很大, 但是未必可以提前预料到变化,用 Kustomize 可能会是一个更好的选择,不过前提是需要有一点学习成本.

由于Kubernetes直接把kustomize融合进了kubectl, 可见官方也是会大力推广这种声明式配置管理方式的.

整体使用起来还是比较简单的, 对于小团队来说也是一个不错的选择

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)

 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?