使用 kustomize 管理 Kubernetes 应用

2019/5/13 posted in  Kubernetes

随着 Kubernetes 1.14 的发布,大家发现原来只是 Kube 兴趣小组的 Yaml 管理工具 kustomize 被集成到 kubectl 中,从此大家可以利用 kubectl apply -k 将指定目录的 kustomization.yaml 应用到集群中。

什么是 kustomize

用工具肯定先搞清楚该工具的定位,kustomize(Github链接)在代码仓库的描述为:

Customization of kubernetes YAML configurations

kustomize 明显就是解决 kubernetes yaml 应用管理的问题的,然而对于 9102 年的现在,提到 Kube Yaml 的管理,肯定会想到 Helm,kustomize 是怎么解决管理问题的,其又和 helm 有什么不同,我们应该怎么取舍,都是下面我们要讨论的问题。

工作原理

kustomize 将对 K8s 应用定义为由 Yaml 表示的资源描述,对 K8s 应用的变更可以映射到对 Yaml 的变更上。而这些变更操作可以利用 git 等版本控制程序来管理,因此用户得以使用 git 风格的流程对 K8s 应用进行管理。

对于一个受 kustomize 管理的 App,都有若干个 Yaml 组成。Github 中描述了一个 Demo 目录结构如下:

~/someApp
├── deployment.yaml
├── kustomization.yaml
└── service.yaml

其中包含用来描述元数据的 kustomization.yaml,以及两个资源文件,其内容如下图所示:

该目录下的 Yaml 是不完整的,可以被补充、覆盖,因此该 Yaml 被称为 Base,而补充 Base 的 Yaml 被称作 Overlay,可以补充适用不同环境、场景的 overlay yaml 到该 app 中,从而使得 K8s 应用描述更完整,其目录结构为:

~/someApp
├── base
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    ├── development
    │   ├── cpu_count.yaml
    │   ├── kustomization.yaml
    │   └── replica_count.yaml
    └── production
        ├── cpu_count.yaml
        ├── kustomization.yaml
        └── replica_count.yaml

kustomize 通过描述文件的叠加,来生成完整的部署用 Yaml,可以直接使用 build 命令可以生成并部署:

kustomize build ~/someApp/overlays/development | kubectl apply -f -

在 Kubernetes 1.14 之后,也支持直接 apply 该应用。

kubectl apply -k ~/someApp/overlays/development

工作流

在 Kubernetes 应用管理系统中,应用的描述文件(Yaml)是一个非常核心的组成部分,用户通过描述文件来向集群声明自己应用的资源和服务编排要求,kustomize 也是社区对描述文件管理的一个重要的尝试(下图来自:从Kubernetes 1.14 发布,看技术社区演进方向)。

对于 kustomize,用户可以使用 Git 对 Kubernetes 应用进行管理,通过 fork 现有 App,拓展 Base 或者定制 overlay,基本流程如下:

在官方 Github 仓库中,有一篇完整的工作流文档可以参考:https://github.com/kubernetes-sigs/kustomize/blob/master/docs/zh/workflows.md

和 Helm 比较

通过对 kustomize 工作原理的了解,可以看到其和 Helm 的差别最直观的在于 Yaml 渲染上。Helm 通过编写 Yaml 模板,在部署时进行渲染,而 kustomize 是 overlay 叠加的方式,制定不同 patch,在部署时选择使用。

但更重要的,是 Helm 和 kustomize 致力解决的问题不同,Helm 更努力做一个自称体系的生态圈,可以方便管理应用的制品(镜像 + 配置),而对于一个已经发布的制品来说,Chart 相对固定、稳定,是一个静态的管理,更适合对外交付。而 kustomize 则不然,kustomize 管理的是正在变更的应用,可以随时 fork 出一个新版本,也可以创建新的 overlay 将应用推向新的环境,是一个动态的管理,而这种动态,非常适合集成到 DevOps 的流程中。

安装

kustomize 的安装非常简单,因为其本身只是一个 cli,只有一个二进制文件,可以在 release 页面下载:https://github.com/kubernetes-sigs/kustomize/releases

使用 kustomize 管理 K8s 应用

我们从零开始创建一个 Web 应用,并通过 kustomize 区分开发、测试、生产环境进行不同的配置并部署。完整的 Demo 可以见 Github:https://github.com/Coderhypo/kustomize-demo

首先在应用根目录中创建 base 文件夹,作为 base yaml 管理目录,在其中编写 kustomization.yaml:

commonLabels:
  app: dao-2048
resources:
  - deployment.yml
  - service.yml

对于该 Yaml 怎么编写,可以查看官方的实例:https://github.com/kubernetes-sigs/kustomize/blob/master/docs/zh/kustomization.yaml

然后基于 2048 镜像生成简单的 Deployment 和 Service Yaml 文件:

# kubectl run dao-2048 --image=daocloud.io/daocloud/dao-2048 --dry-run -o yaml > deployment.yml

# kubectl expose deployment.apps/dao-2048 --port=80 -o yaml --dry-run > service.yml

这样我们就获得了一个最基础的应用 Yaml,然后在应用根目录中创建 overlays 文件夹,以备添加 overlay,下面我们设计三个场景:

  1. 开发环境:需要使用 nodeport 将服务暴露,在容器中添加 DEBUG=1 的环境变量
  2. 测试环境:需要使用 nodeport 将服务暴露,在容器中添加 TEST=1 的环境变量,配置 CPU 和 Mem 的资源限制
  3. 生产环境:需要使用 nodeport 将服务暴露,在容器中添加 PROD=1 的环境变量,创建名为 pord 的 configmap,作为环境变量挂入到容器,并配置 CPU 和 Mem 的资源限制

开发环境

在 overlays 目录中创建 dev 目录,然后添加 kustomization.yaml:

bases:
- ../../base
patchesStrategicMerge:
- nodeport_patch.yaml
- env_patch.yaml
namespace: 2048-dev

在 nodeport_patch.yaml 中对 Service 进行修改:

apiVersion: v1
kind: Service
metadata:
  name: dao-2048
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: NodePort

在 env_patch.yaml 中对 Deployment 中的容器进行修改:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dao-2048
spec:
  strategy: {}
  template:
    spec:
      containers:
      - name: dao-2048
        env:
          - name: DEBUG
            value: "1"

对开发环境进行部署:

# kustomize build . | kubectl apply -f -
service/dao-2048 created
deployment.apps/dao-2048 created
# kubectl -n 2048-dev get all
NAME                            READY   STATUS             RESTARTS   AGE
pod/dao-2048-79b9555477-rjh8n   1/1     Running   0          12s

NAME               TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/dao-2048   NodePort   10.104.217.152   <none>        80:31983/TCP   12m

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dao-2048   1/1     1            1           12s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/dao-2048-79b9555477   1         1         1       12s

测试环境

测试环境可以基于开发环境进一步修改,我们可以直接 copy 开发环境的配置:

cp -r dev test

然后修改 env_patch.yaml,将 DEBUG 改为,TEST,并在 kustomization.yaml 中添加新的 yaml:

bases:
- ../../base
patchesStrategicMerge:
- nodeport_patch.yaml
- env_patch.yaml
- resource_patch.yaml
namespace: 2048-test

在 resource_patch.yaml 添加资源限制:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dao-2048
spec:
  strategy: {}
  template:
    spec:
      containers:
      - name: dao-2048
        resources:
            limits:
              cpu: 100m
              memory: 100Mi
            requests:
              cpu: 100m
              memory: 100Mi

在测试环境中部署:

# kustomize build . | kubectl apply -f -
service/dao-2048 created
deployment.apps/dao-2048 created
# kubectl -n 2048-test get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/dao-2048-68d94b7d58-chkt5   1/1     Running   0          36s

NAME               TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/dao-2048   NodePort   10.106.215.65   <none>        80:30154/TCP   36s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dao-2048   1/1     1            1           36s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/dao-2048-68d94b7d58   1         1         1       36s

生产环境

生产环境可以基于测试环境继续修改,同上复制,然后编辑 kustomization.yaml 文件:

bases:
- ../../base
patchesStrategicMerge:
- nodeport_patch.yaml
- env_patch.yaml
- resource_patch.yaml
resources:
- config.yaml
namespace: 2048-prod

和之前不同的是,configmap 在 base 中并不存在,因此需要作为新资源添加,在 config.yaml 编写 configmap 的描述:

apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: pord
data:
  prodconfig: someconfig

在 env_patch.yaml 中添加对 configmap 的使用:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dao-2048
spec:
  strategy: {}
  template:
    spec:
      containers:
      - name: dao-2048
        env:
          - name: PROD
            value: "1"
          - name: SOMECONFIG
            valueFrom:
              configMapKeyRef:
                name: pord
                key: prodconfig

部署生产环境应用:

# kustomize build . | kubectl apply -f -
configmap/pord created
service/dao-2048 created
deployment.apps/dao-2048 created
# kubectl -n 2048-prod get all
NAME                            READY   STATUS    RESTARTS   AGE
pod/dao-2048-577584dc8c-9drrx   1/1     Running   0          31s

NAME               TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
service/dao-2048   NodePort   10.102.62.5   <none>        80:31885/TCP   31s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dao-2048   1/1     1            1           31s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/dao-2048-577584dc8c   1         1         1       31s