Kubernetes 原生配置管理工具, 它自定义引入了一种无需模板的方式来定制应用程序配置,从而简化了对现成应用程序的使用。目前,在kubectl
中内置了,通过 apply -k
即可使用。
Kustomize 遍历 Kubernetes 清单以添加、删除或更新配置选项,而无需分叉。它既可以作为独立的二进制文件使用,也可以作为kubectl
的原生特性使用。
kubectl
中📚️ Reference:
在 Kubernetes 中我们使用 YAML 文件来声明我们的应用应该如何部署到底层的集群中,这些 YAML 文件中包含应用定义、治理需要的标签、日志、安全上下文定义、资源依赖关系等,当我们应用扩展到成百上千个 Pod 以后,管理这些 YAML 文件就会成为一场噩梦了。
最典型的就是,有很多项目要管理,同时又有多套不同的部署环境:开发环境、测试环境、UAT 环境、生产环境。甚至可能有不同的公有云 Kubernetes 发行版。那么每一套环境都需要一套各种各样的 YAML 文件, 但是它们直接只有部分细节有差异。比如:镜像 Tag,服务 Name,Label,有没有存储等。
如果全是手动,维护工作量非常巨大,同时也容易出错。
Kustomize 相比 Helm, 更适合解决这种场景的痛点:有一个基础(base)的模板管理一个项目的所有基础 YAML,更多高级的需求通过 overlay 来实现叠加覆盖。
另外还有一类典型场景,就是融合来自不同用户的配置数据(例如来自 devops/SRE 和 developers).
同时也可以结合 helm, 进行一些更高级的配置。
今天就以一个典型场景为例:生产环境 Deployment 自动注入商业(如:AppDynamics) 或开源 (SkyWalking/pinpoint) 的开箱即用的 Java Agent.
以 Java 为例,这里的 APM Agent 包是商业(如:AppDynamics) 或开源 (SkyWalking/pinpoint) 产品提供的开箱即用的 Agent jar 包。
在 Kubernetes 场景中,出于以下几点考虑:
Agent jar 包做成了一个通用镜像,通过 init container 方式拷贝到运行中的应用容器中,并通过配置环境变量进行参数的自动设置。
✍️ 笔者注:
其实商业 APM 都有 Helm 或 Operator 实现自动化安装配置的功能,但是实际使用中体验不佳,不太适合我们的实际场景。
步骤简述如下:(以 AppDynamics Java Agent 为例)
kubectl
和 kubectl neat
插件)patches
实现以下步骤:
initContainers
: appd-agent-attach-java
, 该 initContainers
有:
volumeMounts
: 把 java agent jar 包挂载到 volume 实现共享;volumeMounts
: 把 java agent jar 包挂载到 volume 实现共享;volumes
通过 tmpdir 实现共享。目录结构如下:
inject-appd-agent/ ├── base │ └── kustomization.yaml ├── overlays └── prod ├── appd_agent.yaml └── kustomization.yaml
其中,后续所有需要注入 APM Agent 的应用的 Deployment, 都放在 base
目录中。
base/kustomization.yaml
resources: - ./foo-deployment.yml
🐾注意:
这里提一句,目前的 resources
是不支持文件通配符 (file glob) 匹配的,具体 issue 可以见这里:
但是有个临时解决方案,就是通过执行命令:kustomize edit add resource base/*.yml
运行后会遍历 file blob, 将结果一个个加到 kustomization.yaml
中。
运行后的文件可能是这样:
resources: - ./foo-deployment.yml - ./bar-deployment.yml - ./a-deployment.yml - ./b-deployment.yml - ./c-deployment.yml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization
overlays/prod/kustomization.yaml
bases: - ../../base # patchesStrategicMerge: # - appd_agent.yaml patches: - path: appd_agent.yaml target: kind: Deployment
🐾 注意:
这里没用 patchesStrategicMerge
, 而是用了patches
. 目的就是为了批量 patch. 上面 YAML 的意思是,将appd_agent.yaml
应用于所有的 Deployment manifests 中。
overlays/prod/appd_agent.yaml
具体内容如下:
apiVersion: apps/v1 kind: Deployment metadata: name: not-important spec: template: spec: containers: - name: app volumeMounts: - mountPath: /opt/appdynamics-java name: appd-agent-repo-java env: - name: APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY value: xxxxxxxxxxxxxxxxxxxxxxxxxxxx - name: APPDYNAMICS_CONTROLLER_HOST_NAME value: 192.168.1.10 - name: APPDYNAMICS_CONTROLLER_PORT value: '8090' - name: APPDYNAMICS_CONTROLLER_SSL_ENABLED value: 'false' - name: APPDYNAMICS_AGENT_ACCOUNT_NAME value: my_account - name: APPDYNAMICS_AGENT_APPLICATION_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: APPDYNAMICS_AGENT_TIER_NAME valueFrom: fieldRef: fieldPath: metadata.namespace - name: APPDYNAMICS_AGENT_REUSE_NODE_NAME_PREFIX valueFrom: fieldRef: fieldPath: metadata.name - name: JAVA_TOOL_OPTIONS value: ' -Dappdynamics.agent.accountAccessKey=$(APPDYNAMICS_AGENT_ACCOUNT_ACCESS_KEY) -Dappdynamics.agent.reuse.nodeName=true -Dappdynamics.socket.collection.bci.enable=true -javaagent:/opt/appdynamics-java/javaagent.jar' - name: APPDYNAMICS_NETVIZ_AGENT_HOST valueFrom: fieldRef: apiVersion: v1 fieldPath: status.hostIP - name: APPDYNAMICS_NETVIZ_AGENT_PORT value: '3892' initContainers: - command: - cp - -r - /opt/appdynamics/. - /opt/appdynamics-java image: appdynamics/appdynamics-java-agent:1.8-21.11.3.33314 imagePullPolicy: IfNotPresent name: appd-agent-attach-java resources: limits: cpu: 200m memory: 75M requests: cpu: 100m memory: 50M terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /opt/appdynamics-java name: appd-agent-repo-java volumes: - name: appd-agent-repo-java
上面内容就不详细说明了,具体可以参考这篇文章,了解 APM Agent 注入的几种思路:
🐾 注意:
实践中,上面内容有几点需要注意的:
spec/template/spec/containers/name
这里往往是应用的名字,如foo
, 如果 appd_agent.yaml
的内容要正确地 patch, 也需要写上同样的 container name; 这对于一个应用来说不是什么问题,但是对于批量自动化/GitOps 来说,就非常不方便。
nameReference
来实现,但是没搞出来,有知道的可以教教我metadata.name
进行 merge 的, 所以通过 Kustomize 无法解决; 但是可以通过 yq
(类似jq, 但是针对 yaml)来解决, 示例命令为: i=foo yq -i '.spec.template.spec.containers[0].name="strenv(i)"' appd_agent.yaml
之前的环境变量,手动部署的时候如这个:
- name: APPDYNAMICS_AGENT_APPLICATION_NAME value: foo
直接写死,这样不便于批量自动化/GitOps. 解决办法就是利用 Kubernetes env 的 valueFrom
能力。改为如下就可以了。
valueFrom: fieldRef: fieldPath: metadata.name
appd_agent.yaml
的 Deployment name 是无所谓的,因为仍会是被 patch 清单的名字;另外 appd_agent.yaml
也不要带 namespace
, 方便批量自动化/GitOps, 可以适应所有 NameSpace.
最后,自动化脚本 demo 如下:(假设脚本位于 /inject-appd-agent/scripts/
目录下)
#!/bin/bash # USAGE: # bash inject-appd-agent.sh default # NAMESPACE=default NAMESPACE=$1 # only get deploy name deployments=$(kubectl get deploy --no-headers=true -o custom-columns=:.metadata.name -n "${NAMESPACE}") for i in ${deployments}; do # get deploy yaml cd ../base && kubectl get deploy -n "${NAMESPACE}" "${i}" -o yaml | kubectl neat >./"${i}.yml" # add the deploy yaml to base kustomize edit add resource ./"${i}.yml" # change appd_agent.yaml .spec.template.spec.containers[0].name # = <the deploy name> cd ../overlays/prod && DEPLOY="${i}" yq -i '.spec.template.spec.containers[0].name = strenv(DEPLOY)' ./appd_agent.yaml # dry run # kustomize build . >../../examples/output/"${i}.yml" # deploy rollout kubectl apply -k . # remove the deploy yaml resource from base cd ../../base && kustomize edit remove resource ./"${i}.yml" done # clean up kustomize edit remove resource ./*.yml cd ../overlays/prod && yq -i '.spec.template.spec.containers[0].name = "app"' ./appd_agent.yaml
运行上面脚本,即可实现对所有 Deployment 自动注入 AppD Java Agent, 并 Rollout 生效。
🎉🎉🎉
三人行, 必有我师; 知识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.