了解了 KT-env 的基本功能、原理和使用之后,我们最后再来了解一下 KT-env 相关的源码实现。

KT-env 的源码仓库地址: virtual-environment

其中,我们先来了解一下它们的目录结构和功能:

  • cmd: 二进制程序的入口文件 - 核心目录。
  • pkg: 相关功能模块的封装模块 - 核心目录。
  • version: 版本信息。
  • build: 编译脚本。
  • deploy: 部署脚本。
  • docs: 文档。
  • examples: 示例相关代码。
  • sdk: 透传 headers 相关的SDK。

其中,核心的源码目录是 cmd 和 pkg 两个目录。

查看 cmd 目录下的内容,可以很容易的看出,它一共涉及到三个二进制可执行程序,分别是 operatorinspectorwebhook

其中,inspector 的功能非常简单,就是一个 HTTP 客户端,可以调用 operator 的接口查询 operator 模块的版本信息、状态以及修改日志级别,不再赘述。

我们重点来分析 operatorwebhook 两个程序的功能。

operator

Operator 的程序入口为 main.go,我们来看一下 main.go 入口程序的相关功能:
KT-env源码解读 - 图1

针对 Operator 而言,其核心逻辑主要集中在 Controller 的逻辑。
下面,我们来重点剖析 KT-env 中包含了哪些 Controller 以及每个 Controller 的功能逻辑与实现原理。

virtualenv-controller

service-controller

deployment-controller

webhook

在 KT-env 中包含了一个全局的Mutating Admission Webhook组件
它的主要作用是将Pod上的环境标信息通过环境变量注入到Sidecar容器里,便于Sidecar为出口流量的Header添加恰当的环境标。

那么什么是 Mutating Admission Webhook 呢?这是 K8s 中的一个特有的概念,我们先来了解一下。

Admission webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制。
K8s 中可以定义两种类型的 admission webhook,即 validating admission webhook 和 mutating admission webhook。
其中,Mutating admission webhook 会先被调用。
它们可以更改发送到 API 服务器的对象以执行自定义的设置默认值操作。

K8s的具体的处理流程如下图所示:

KT-env源码解读 - 图2

而 Kt-env 中的 Webhook 其实就是这么一个组件,它主要用于将Pod上的环境标信息通过环境变量注入到Sidecar容器里,
即在 Webhook 阶段,修改了 Sidecar 的配置,向其中设置了对应的环境变量。

了解了 webhook 的原理之后,我们就来看一下 KT-env 中的 webhook 是如何实现的吧。

首先,我们先来简单看一下 Webhook 对应的 yaml 配置文件:

  1. apiVersion: admissionregistration.k8s.io/v1beta1
  2. kind: MutatingWebhookConfiguration
  3. metadata:
  4. name: virtual-environment-webhook
  5. webhooks:
  6. - name: webhook-server.kt-virtual-environment.svc
  7. failurePolicy: Fail
  8. clientConfig:
  9. service:
  10. name: webhook-server
  11. namespace: kt-virtual-environment
  12. path: "/inject"
  13. caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURYVENDQWtXZ0F3SUJBZ0lVZG42TEl2bDNaV2ltbndEVGwxS3U3ODhKcDBrd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1BqRThNRG9HQTFVRUF3d3pWbWx5ZEhWaGJDQkZiblpwY205dWJXVnVkQ0JCWkcxcGMzTnBiMjRnUTI5dQpkSEp2Ykd4bGNpQlhaV0pvYjI5cklFTkJNQjRYRFRJd01EZ3lOVEUxTURrMU5Wb1hEVE13TURneU16RTFNRGsxCk5Wb3dQakU4TURvR0ExVUVBd3d6Vm1seWRIVmhiQ0JGYm5acGNtOXViV1Z1ZENCQlpHMXBjM05wYjI0Z1EyOXUKZEhKdmJHeGxjaUJYWldKb2IyOXJJRU5CTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQwpBUUVBeDYrQkZIaXlkZk9uR3FMRmt4Y2lpL21ZMG9XU3dRV2krTHYwdmNqeklQTndKa2c0V0FJYTRWK0RqdTV2CmZ5NlE2RFhUaitlRnhlK212MmtVSEFtYjNsWS9iT3RaWGp2VnQ4bnBHS1VLdlBBN0hVRFdhUkZKOTR1eUJpQm8KQnlXTnZtTGNka0VFTjRVMVVGTlVIV3B1L1lHNXNaSC9ZQWZjZGEyMDhIUzVkQmllNTNYMmJjdjQrNGhzS3oyOAp0UUR2MmR3ZkxFT2crZ2lVUWRWRHUrN0lXUXpjRkp4NmdpdlBpVkl1UVRHVk04K0tya2dITlhXVjU4OGIwcTU1CnNPNjR2YWQ4cW5XT0t5bk5oSThyZzN0dGVJVkFHdkEvUnJGczFocytERHVBZlMxamhiRWUyUEJHSHVMeGx6TGIKNjR2NUV5VEhEdDVVOS93bFpxS0hMWlZQeFFJREFRQUJvMU13VVRBZEJnTlZIUTRFRmdRVVg1cEI4dkYzbnducQpUUjA1TlVhVlhaQzd4VjB3SHdZRFZSMGpCQmd3Rm9BVVg1cEI4dkYzbnducVRSMDVOVWFWWFpDN3hWMHdEd1lEClZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBbFU0YkxNMGlzbm5KaDA1dCs3TDkKWkFtS2M1eXhGdkUremlsK2Y5aEUzdzJpYjI0bVNYTVpOSWEvUWd6akxGK0owQVlIbUxXZTRQa2I4eXliQnVjRQp6d2VRNEc5Y3U2QWV5VFRHTk9zbU5lREx4WGRVOUJ1aElvRmZsR2ZDV1pudHA5ajZsbnFJbndqdjZJWDBEQmc1CkFEbXRNdVVrS0gyMXdUTHhXNVBWSmhQWnZiL3p1ZGNlNUxWRG1zT0Z5cjFkK2lnVnZPVzJJam51QUw3eGpXWFQKenVGYng5NnZBYTJjS0hWRjYyVzdoekp6NURiN0cwdWxJMXptOTF0ZXJaZjRYSHlUT3FzUC8wczFsYjV1V1k5cAo2NUZiZCtHMXJOL2NBcUM4NXo0K1Rrc2QrdTV1ZDFVREc5MEsvRG9UdS9vcis3U3o2bWNWdjVBejlLSHVxRzE2CnlBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
  14. rules:
  15. - operations: ["CREATE"]
  16. apiGroups: [""]
  17. apiVersions: ["v1"]
  18. resources: ["pods"]
  19. namespaceSelector:
  20. matchLabels:
  21. environment-tag-injection: enabled

上述配置文件表示针对:

  • 设置了 environment-tag-injection: enabled 的 namespace 下
  • Pod 资源对象在创建时
  • 会触发 Mutating Admission Webhook 调用
  • 调用的请求地址是 webhook-server.kt-virtual-environment.svc/inject

而具体的注入环境变量的逻辑则在 webhook-server 模块内,
其核心代码见 main.go

我们选择其中部分的核心代码进行说明:

  1. envLabels := os.Getenv("envLabel")
  2. if envLabels == "" {
  3. logger.Fatal("Cannot determine env label !!")
  4. }
  5. envLabelList := strings.Split(envLabels, ",")
  6. envTag := ""
  7. for _, label := range envLabelList {
  8. if value, ok := pod.Labels[label]; ok {
  9. envTag = value
  10. break
  11. }
  12. }

从环境变量中读取 envLabel 的配置,并判断 Pod 上是否存在其中的某个 label,如果存在,则表示使用该 label 对应的 value 用于 headers 追加。

  1. var patches []PatchOperation
  2. if envVarIndex < 0 {
  3. patches = append(patches, PatchOperation{
  4. Op: "add",
  5. Path: fmt.Sprintf("/spec/containers/%d/env/0", sidecarContainerIndex),
  6. Value: corev1.EnvVar{Name: envVarName, Value: envTag},
  7. })
  8. } else {
  9. patches = append(patches, PatchOperation{
  10. Op: "replace",
  11. Path: fmt.Sprintf("/spec/containers/%d/env/%d/value", sidecarContainerIndex, envVarIndex),
  12. Value: envTag,
  13. })
  14. }

表示根据之前的计算规则,生成 patch 操作,用于向 Pod 中追加/修改对应的环境变量,
其中,key 为 VIRTUAL_ENVIRONMENT_TAG,取值为之前步骤中从 Pod 上获取到的 label 对应的值。

通过上述步骤,就完成了将业务 Pod 上中指定的 Label 对应的值设置到 Sidecar 容器中的环境变量中了。

那么,Sidecar 中的环境变量是如何追加到发送请求的 header 中的呢?

这个其实是用到了 Istio 中的功能 EnvoyFilter

如上文所示,在 operator 模块中,会创建 EnvoyFilter,在 EnvoyFilter 中,通过 lua 脚本,在出流量的阶段中,追加了 headers 信息。

示例 EnvoyFilter 配置如下:

  1. kind: EnvoyFilter
  2. apiVersion: networking.istio.io/v1alpha3
  3. metadata:
  4. name: demo-virtualenv
  5. namespace: kt-env1
  6. labels:
  7. envHeader: ali-env-mark
  8. envLabel: virtual-env
  9. spec:
  10. workloadSelector: ~
  11. configPatches:
  12. - applyTo: HTTP_FILTER
  13. match:
  14. context: SIDECAR_OUTBOUND
  15. listener:
  16. filterChain:
  17. filter:
  18. name: envoy.http_connection_manager
  19. patch:
  20. operation: INSERT_BEFORE
  21. value:
  22. name: virtual.environment.lua
  23. typed_config:
  24. '@type': type.googleapis.com/envoy.config.filter.http.lua.v2.Lua
  25. inline_code: |-
  26. local curEnv = os.getenv("VIRTUAL_ENVIRONMENT_TAG")
  27. function envoy_on_request(req)
  28. local env = req:headers():get("ali-env-mark")
  29. if env == nil and curEnv ~= nil then
  30. req:headers():add("ali-env-mark", curEnv)
  31. end
  32. end

其中,我们可以重点关注最下方的 lua 脚本,可以看出其基本的逻辑如下:
判断请求头部中是否包含 ali-env-mark,如果没有包含,
则从环境变量中取出 VIRTUAL_ENVIRONMENT_TAG 的值并设置为 ali-env-mark headers 的值。