Kustomize 生產實戰-注入監控 APM Agent

Kustomize 簡介

Kubernetes 原生配置管理工具, 它自定義引入了一種無需模板的方式來定製應用程序配置,從而簡化了對現成應用程序的使用。目前,在kubectl中內置了,通過 apply -k 即可使用。

Kustomize 遍歷 Kubernetes 清單以添加、刪除或更新配置選項,而無需分叉。它既可以作爲獨立的二進制文件使用,也可以作爲kubectl的原生特性使用。

Kustomize 優勢

  • 📢 完全聲明式的配置定製方法
  • 🍸 原生構建進 kubectl
  • 🎶 管理任意數量的獨特定製的 Kubernetes 配置
  • ☸ 作爲獨立的二進制文件提供,用於擴展和集成到其他服務
  • 📃 定製使用的每個工件都是純 YAML,並且可以被驗證和處理
  • 🔁 Kustomize 支持 fork/modify/rebase 工作流
  • 🔧 GitOps 工具(如 ArgoCD) 對其的完美支持

Kustomize 可以做什麼

📚️ Reference:

👉️URL: https://mp.weixin.qq.com/s/gmwkoqZpKbq1hM0B8XxQNw
在 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.

實戰 - 通過 Kustomize 注入監控 APM Agent

背景概述

以 Java 爲例,這裏的 APM Agent 包是商業(如:AppDynamics) 或開源 (SkyWalking/pinpoint) 產品提供的開箱即用的 Agent jar 包。

在 Kubernetes 場景中,出於以下幾點考慮:

  1. 和應用鏡像分離;
  2. 複用

Agent jar 包做成了一個通用鏡像,通過 init container 方式拷貝到運行中的應用容器中,並通過配置環境變量進行參數的自動設置。

✍️ 筆者注:

其實商業 APM 都有 Helm 或 Operator 實現自動化安裝配置的功能,但是實際使用中體驗不佳,不太適合我們的實際場景。

通過 Kustomize 注入監控 APM Agent 步驟

步驟簡述如下:(以 AppDynamics Java Agent 爲例)

  1. 獲取原始的 Deployment yaml 文件(通過 kubectlkubectl neat 插件)
  2. 通過 Kustomize 對每個 Deployment 通過 patches 實現以下步驟:
    1. 注入 initContainers: appd-agent-attach-java, 該 initContainers 有:
      1. volumeMounts: 把 java agent jar 包掛載到 volume 實現共享;
    2. 在 Deployment -> 應用自己的 container 中,加入以下參數:
      1. volumeMounts: 把 java agent jar 包掛載到 volume 實現共享;
      2. 加入各種 AppD Java agent 需要的 env 信息;
    3. volumes 通過 tmpdir 實現共享。

Kustomize 目錄結構

目錄結構如下:

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 注入的幾種思路:

🐾 注意:
實踐中,上面內容有幾點需要注意的:

  1. spec/template/spec/containers/name 這裏往往是應用的名字,如foo, 如果 appd_agent.yaml 的內容要正確地 patch, 也需要寫上同樣的 container name; 這對於一個應用來說不是什麼問題,但是對於批量自動化/GitOps 來說,就非常不方便。

    1. 我之前想用 Kustomize 中的 nameReference 來實現,但是沒搞出來,有知道的可以教教我
    2. ✔️20220706 11:00 update: Kustomize 出於安全考慮, 是嚴格按照 metadata.name 進行 merge 的, 所以通過 Kustomize 無法解決; 但是可以通過 yq(類似jq, 但是針對 yaml)來解決, 示例命令爲: i=foo yq -i '.spec.template.spec.containers[0].name="strenv(i)"' appd_agent.yaml
  2. 之前的環境變量,手動部署的時候如這個:

    - name: APPDYNAMICS_AGENT_APPLICATION_NAME
      value: foo
    

    直接寫死,這樣不便於批量自動化/GitOps. 解決辦法就是利用 Kubernetes env 的 valueFrom 能力。改爲如下就可以了。

    valueFrom:
      fieldRef:
        fieldPath: metadata.name
    
  3. 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 生效。

🎉🎉🎉

📚️ Reference

三人行, 必有我師; 知識共享, 天下爲公. 本文由東風微鳴技術博客 EWhisper.cn 編寫.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章