# Kustomize 中的增删改查

在之前的文章中，我曾经说过，相对于 Helm Chart，Kustomize 具有更平缓的学习曲线，更灵活的派生方式，当然这也不是没有代价的，Kustomize 的分享方式和规范性都弱于 Helm Chart，而且上游对下游也是相当缺乏控制能力的。

本文会从实际出发，列出一系列的场景，看看拿到一个上游应用的标准 YAML 之后，如何使用 Kustomize 对其进行增删改查，形成新的 Kubernetes 资源组，完成对派生应用的定义。

## 准备工作

首先我们创建实验目录 `lab/base`，在其中生成一组 YAML 文件，作为后续实验的基础（`base`），这是一个很简单的应用，只有一个 Deployment：

deploy.yaml：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
        version: v1
    spec:
      containers:
        - name: flaskapp
          image: dustise/flaskapp
          imagePullPolicy: IfNotPresent
          env:
            - name: version
              value: v1
          ports:
            - containerPort: 80
~~~

如果使用 Kubectl 提交，会在当前命名空间中新建一个 Deployment 和一个 Pod，当然我们暂时不会这么做。

接下来是创建一个 Kustomize 的定义，作为后续实验的基础，基础定义很简单，它甚至可能不会由上游应用的开发者编写，而是下游的定制者用它来做一个基础。

`kustomization.yaml`：

~~~yaml
resources:
- deploy.yaml
~~~

对它进行渲染，会毫无意外的出现一个和 `deploy.yaml` 一样的输出内容：

~~~yaml
kubectl kustomize base
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
...
~~~

## 增

> 创建一个新的工作目录，`lab/overlays/c`，本节的内容都会使用这个目录来完成。

在继承上游应用的 YAML 时，经常需要向其中加入定制元素，比如加入新的 Ingress、Service、Configmap 等，还有可能新增一些对象内的定义。下面的两个例子分别展示了 Kustomize 完成这类任务的方法。

### 加入新对象

上面的 Base 中，有了一个孤零零的 Deployment，多数的业务应用还是需要有个 Service 提供服务的，我们就给它加入一个新对象。

在 `c` 目录中，新建一个 `service.yaml`：

~~~yaml
apiVersion: v1
kind: Service
metadata:
  name: flaskapp
  labels:
    app: flaskapp
spec:
  selector:
    app: flaskapp
    version: v1
  ports:
    - name: http
      port: 80
      targetPort: 80
~~~

然后新建一个 `kustomization.yaml`：

~~~yaml
bases:
- ../../base
resources:
- service.yaml
~~~

执行命令 `kustomize build overlays/c`，可以看到，在原有 `Deployment` 的基础上，新增了一个服务。

### 新增字段

很多应用都会声明运行时的资源需求，但是我们的 `base` 中并没有这个字段，Kustomize 可以用补丁的方式加入这个字段。

首先创建一个补丁文件 `overlays/c/resource-patch.yaml`：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: flaskapp
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "64Mi"
            cpu: "250m"
~~~

上面的补丁文件用最简单的字段赋值标识出了要打补丁的位置，并加入了资源限制。

修改 `overlays/c/kustomization.yaml`，加入补丁：

~~~yaml
bases:
- ../../base
resources:
- service.yaml
patchesStrategicMerge:
- resource-patch.yaml
~~~

再次使用 `kustomize build overlays/c`，或者直接使用 `kubectl apply -k overlays/c`，都能看到，资源限制已经应用。

### 创建 Configmap 并加载

在 `overlays/c` 下面新建一个配置文件 `config.ini`：

~~~ini
key=value
~~~

新增的 `configMapGenerator` 要求使用 `config.ini` 生成一个 ConfigMap。

然后再做一个补丁文件 `env-patch.yaml`，用来加载 Config：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: flaskapp
        env:
        - name: CONFIG
          valueFrom:
            configMapKeyRef:
              name: config-ini
              key: config.ini
~~~

修改 `kustomization.yaml`，加入我们的 Configmap 和补丁，结果为如下内容：

~~~yaml
bases:
- ../../base
resources:
- service.yaml
configMapGenerator:
- name: config-ini
  files:
    - config.ini
patchesStrategicMerge:
- resource-patch.yaml
- env-patch.yaml
generatorOptions:
  disableNameSuffixHash: true
~~~

`kubectl apply -k overlays/c` 之后，会看到 Pod 中加入了对 Configmap 的引用，进入 Pod 也能看到新增的环境变量，例如：

~~~shell
$ kubectl exec -it flaskapp-5b886b8dfc-qq68w env | grep CONFIG
CONFIG=key=value
~~~

## 删

> 创建一个新的工作目录，`lab/overlays/d`，本节的内容都会使用这个目录来完成。

使用 `patchesStrategicMerge` 的特定语法，可以删除指定内容。

### 删除一个对象

在 `c` 案例中新建的 Service 对象，我并不想要，如何通过 `Kustomize` 删掉？同样也使用打补丁的方式来解决。首先创建一个继承自 `overlays/c` 的 Kustomization，在 `overlays/d` 中创建 `kustomization.yaml`：

~~~yaml
bases:
- ../c
patchesStrategicMerge:
- remove-svc.yaml
~~~

以及 `remove-svc.yaml`：

~~~yaml
kind: Service
apiVersion: v1
metadata:
  name: flaskapp
$patch: delete
~~~

运行 `kustomize build overlays/d`，可以看到，输出内容中的 Service 不见了。

### 删除一个字段

接下来我们用类似的方法，删除前面设置的环境变量：

新建一个 `remove-field.yaml`：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: flaskapp
        version: v1
    spec:
      containers:
        - name: flaskapp
          image: dustise/flaskapp
          env:
          - name: CONFIG
            $patch: delete
~~~

再把补丁加入 `kustomization.yaml`：

~~~yaml
bases:
- ../c
patchesStrategicMerge:
- remove-svc.yamlƒ
- remove-field.yaml
~~~

再次 `kustomize build overlays/d`，可以看到新的环境变量已经被删除。

## 改

> > 创建一个新的工作目录，`lab/overlays/u`，本节的内容都会使用这个目录来完成。

除了 `patchesStrategicMerge` 之外，`kustomize` 还提供了一些方便快捷的方式能够对特定内容进行修改。

### 修改名字

可以用 namePrefix 和 nameSuffix 修改对象的名字，例如我们要给**所有**名字加上 `overlay-u`：

`kustomization.yaml`：

~~~yaml
bases:
- ../c
namePrefix: overlay-u
~~~

接下来可以看看结果：

~~~shell
$ kubectl apply -k overlays/u --dry-run -o yaml | grep -E "^\s+name:"
    name: overlay-u-config-ini
    name: overlay-u-flaskapp
    name: overlay-u-flaskapp
                name: overlay-u-config-ini
          name: flaskapp
~~~

输出内容中，前三个分别是 ConfigMap、Service 以及 Deployment，第四行中对 Configmap 的引用也修改成了新的名字。

除了修改名字之外，还有以下几个暴力的修改功能：

- 命名空间：namespaces
- 注解：commonAnnotations
- 标签：commonLabels

说这几个东西比较野蛮的原因是：他们的应用范围是整个 Kustomization，例如下面的定制：

~~~yaml
bases:
- ../c
namePrefix: overlay-u
commonLabels:
    auto: label
~~~

生成之后会发现，从 Service、Deployment、Configmap 一直到 Pod，都会打上新标签。

### 修改镜像

修改镜像也是个常见任务，相对上面的几个暴力修改，镜像的修改相对精确一点——从一种镜像修改为另一种，例如我们把 flaskapp 的镜像更换为 `nginx:alpine`：

`kustomization.yaml`：

~~~yaml
bases:
- ../c
namePrefix: overlay-u
commonLabels:
    auto: label
images:
- name: dustise/flaskapp
  newName: nginx
  newTag: alpine
~~~

### 修改副本数量

修改副本数量，指定资源名称以及副本数量即可：

例如如下的 `kustomization.yaml`：

~~~yaml
bases:
- ../c
replicas:
- name: flaskapp
  count: 2
~~~

使用 `kustomize build overlays/u`，会看到 Deployment 的该字段已经改为 2。

## 查

> 创建一个新的工作目录，`lab/overlays/r`，本节的内容都会使用这个目录来完成。

前面提到，`namePrefix` 修改了 ConfigMap 的名称之后，加载这个 CM 的 Deployment 也会修改引用的名称，然而如果在其它位置，例如命令行参数或者环境变量中引用的服务名称之类，就无法完成修改了。假设我们给 Deployment 中加入一个新的环境变量，叫 `deploy`，用如下的 `kustomization.yaml` 实现：

~~~yaml
bases:
- ../../base
namePrefix: overlay-r-
patchesStrategicMerge:
- env-patch.yaml
~~~

其中引用的 `env-patch.yaml`：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: flaskapp
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: flaskapp
        env:
        - name: deploy
          value: flaskapp
~~~

`kustomize build overlays/r` 渲染出来之后，很明显的 value 已经无法对应：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: overlay-r-flaskapp
...
      - env:
        - name: svc
          value: flaskapp
        - name: version
~~~

在 `kustomization.yaml` 中加入 `vars`，用一系列条件选择一个对象来进行引用，为变量赋值：

~~~yaml
...
vars:
- name: DEPLOY_NAME
  objref:
    kind: Deployment
    name: flaskapp
    apiVersion: extensions/v1beta1
~~~

把 `env-patch.yaml` 中的环境变量定义修改为引用新变量：

~~~yaml
...
        - name: deploy
          value: $(DEPLOY_NAME)
~~~

再次 `kustomize build overlays/r`，会看到符合预期的输出内容：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: overlay-r-flaskapp
...
        - name: deploy
          value: overlay-r-flaskapp

~~~

## 命令行

除了上面说的这些声明式的用法之外，`kustomize` 还提供了更为即兴的 `edit` 命令，可以进行现场修改，进一步的提高了灵活（混乱）程度，有兴趣的读者可以自行摸索。
