簡單來說,Sidecar 註入會將額外容器的配置添加到 Pod 模板中。這裡特指將Envoy容器註應用所在Pod中。 Istio 服務網格目前所需的容器有: istio-init 用於設置 iptables 規則,以便將入站/出站流量通過 Sidecar 代理。 ...
概念
簡單來說,Sidecar 註入會將額外容器的配置添加到 Pod 模板中。這裡特指將Envoy容器註應用所在Pod中。
Istio 服務網格目前所需的容器有:
istio-init
用於設置 iptables 規則,以便將入站/出站流量通過 Sidecar 代理。
初始化容器與應用程式容器在以下方面有所不同:
- 它在啟動應用容器之前運行,並一直運行直至完成。
- 如果有多個初始化容器,則每個容器都應在啟動下一個容器之前成功完成。
因此,您可以看到,對於不需要成為實際應用容器一部分的設置或初始化作業來說,這種容器是多麼的完美。在這種情況下,istio-init
就是這樣做並設置了 iptables
規則。
istio-proxy
這個容器是真正的 Sidecar 代理(基於 Envoy)。
下麵的內容描述了向 pod 中註入 Istio Sidecar 的兩種方法:
- 使用
istioctl
手動註入 - 啟用 pod 所屬命名空間的 Istio Sidecar 註入器自動註入。
手動註入直接修改配置,如 deployment,並將代理配置註入其中。
當 pod 所屬namespace
啟用自動註入後,自動註入器會使用準入控制器在創建 Pod 時自動註入代理配置。
通過應用 istio-sidecar-injector
ConfigMap 中定義的模版進行註入。
自動註入
當你在一個namespace
中設置了 istio-injection=enabled
標簽,且 injection webhook 被啟用後,任何新的 pod 都有將在創建時自動添加 Sidecar. 請註意,區別於手動註入,自動註入發生在 pod 層面。你將看不到 deployment 本身有任何更改 。
kubectl label namespace default istio-inhection=enabled
kubectl get namespace -L istio-injection
NAME STATUS AGE ISTIO-INJECTION
default Active 1h enabled
istio-system Active 1h
kube-public Active 1h
kube-system Active 1h
註入發生在 pod 創建時。殺死正在運行的 pod 並驗證新創建的 pod 是否註入 sidecar。原來的 pod 具有 READY 為 1/1 的容器,註入 sidecar 後的 pod 則具有 READY 為 2/2 的容器 。
自動註入原理
自動註入是利用了k8s Admission webhook 實現的。 Admission webhook 是一種用於接收準入請求並對其進行處理的 HTTP 回調機制, 它可以更改發送到 API 伺服器的對象以執行自定義的設置預設值操作。 具體細節可以查閱 Admission webhook 文檔。
istio 對應的istio-sidecar-injector webhook配置,預設會回調istio-sidecar-injector service的/inject
地址。
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: istio-sidecar-injector
webhooks:
- name: sidecar-injector.istio.io
clientConfig:
service:
name: istio-sidecar-injector
namespace: istio-system
path: "/inject"
caBundle: ${CA_BUNDLE}
rules:
- operations: [ "CREATE" ]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchLabels:
istio-injection: enabled
回調API入口代碼在 pkg/kube/inject/webhook.go
中
// 創建一個用於自動註入sidecar的新實例
func NewWebhook(p WebhookParameters) (*Webhook, error) {
// ...省略一萬字...
wh := &Webhook{
Config: sidecarConfig,
sidecarTemplateVersion: sidecarTemplateVersionHash(sidecarConfig.Template),
meshConfig: p.Env.Mesh(),
configFile: p.ConfigFile,
valuesFile: p.ValuesFile,
valuesConfig: valuesConfig,
watcher: watcher,
healthCheckInterval: p.HealthCheckInterval,
healthCheckFile: p.HealthCheckFile,
env: p.Env,
revision: p.Revision,
}
//api server 回調函數,監聽/inject回調
p.Mux.HandleFunc("/inject", wh.serveInject)
p.Mux.HandleFunc("/inject/", wh.serveInject)
// ...省略一萬字...
return wh, nil
}
serveInject
邏輯
func (wh *Webhook) serveInject(w http.ResponseWriter, r *http.Request) {
// ...省略一萬字...
var reviewResponse *v1beta1.AdmissionResponse
ar := v1beta1.AdmissionReview{}
if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
handleError(fmt.Sprintf("Could not decode body: %v", err))
reviewResponse = toAdmissionResponse(err)
} else {
//執行具體的inject邏輯
reviewResponse = wh.inject(&ar, path)
}
// 響應inject sidecar後的內容給k8s api server
response := v1beta1.AdmissionReview{}
if reviewResponse != nil {
response.Response = reviewResponse
if ar.Request != nil {
response.Response.UID = ar.Request.UID
}
}
// ...省略一萬字...
}
// 註入邏輯實現
func (wh *Webhook) inject(ar *v1beta1.AdmissionReview, path string) *v1beta1.AdmissionResponse {
// ...省略一萬字...
// injectRequired判斷是否有設置自動註入
if !injectRequired(ignoredNamespaces, wh.Config, &pod.Spec, &pod.ObjectMeta) {
log.Infof("Skipping %s/%s due to policy check", pod.ObjectMeta.Namespace, podName)
totalSkippedInjections.Increment()
return &v1beta1.AdmissionResponse{
Allowed: true,
}
}
// ...省略一萬字...
// 返回需要註入Pod的對象
spec, iStatus, err := InjectionData(wh.Config.Template, wh.valuesConfig, wh.sidecarTemplateVersion, typeMetadata, deployMeta, &pod.Spec, &pod.ObjectMeta, wh.meshConfig, path) // nolint: lll
if err != nil {
handleError(fmt.Sprintf("Injection data: err=%v spec=%vn", err, iStatus))
return toAdmissionResponse(err)
}
// 執行容器註入邏輯
patchBytes, err := createPatch(&pod, injectionStatus(&pod), wh.revision, annotations, spec, deployMeta.Name, wh.meshConfig)
if err != nil {
handleError(fmt.Sprintf("AdmissionResponse: err=%v spec=%vn", err, spec))
return toAdmissionResponse(err)
}
reviewResponse := v1beta1.AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: func() *v1beta1.PatchType {
pt := v1beta1.PatchTypeJSONPatch
return &pt
}(),
}
return &reviewResponse
}
injectRequired
函數
func injectRequired(ignored []string, config *Config, podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta) bool {
// HostNetwork模式直接跳過註入
if podSpec.HostNetwork {
return false
}
// k8s系統命名空間(kube-system/kube-public)跳過註入
for _, namespace := range ignored {
if metadata.Namespace == namespace {
return false
}
}
annos := metadata.GetAnnotations()
if annos == nil {
annos = map[string]string{}
}
var useDefault bool
var inject bool
// 優先判斷是否申明瞭`sidecar.istio.io/inject` 註解,會覆蓋命名配置
switch strings.ToLower(annos[annotation.SidecarInject.Name]) {
case "y", "yes", "true", "on":
inject = true
case "":
// 使用命名空間配置
useDefault = true
}
// 指定Pod不需要註入Sidecar的標簽選擇器
if useDefault {
for _, neverSelector := range config.NeverInjectSelector {
selector, err := metav1.LabelSelectorAsSelector(&neverSelector)
if err != nil {
} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels))
// 設置不需要註入
inject = false
useDefault = false
break
}
}
}
// 總是將 sidecar 註入匹配標簽選擇器的 pod 中,而忽略全局策略
if useDefault {
for _, alwaysSelector := range config.AlwaysInjectSelector {
selector, err := metav1.LabelSelectorAsSelector(&alwaysSelector)
if err != nil {
log.Warnf("Invalid selector for AlwaysInjectSelector: %v (%v)", alwaysSelector, err)
} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)){ // 設置需要註入
inject = true
useDefault = false
break
}
}
}
// 如果都沒有配置則使用預設註入策略
var required bool
switch config.Policy {
default: // InjectionPolicyOff
log.Errorf("Illegal value for autoInject:%s, must be one of [%s,%s]. Auto injection disabled!",
config.Policy, InjectionPolicyDisabled, InjectionPolicyEnabled)
required = false
case InjectionPolicyDisabled:
if useDefault {
required = false
} else {
required = inject
}
case InjectionPolicyEnabled:
if useDefault {
required = true
} else {
required = inject
}
}
return required
}
從上面我們可以看出,是否註入Sidecar的優先順序為
Pod Annotations → NeverInjectSelector → AlwaysInjectSelector → Default Policy
createPath
函數
func createPatch(pod *corev1.Pod, prevStatus *SidecarInjectionStatus, revision string, annotations map[string]string,
sic *SidecarInjectionSpec, workloadName string, mesh *meshconfig.MeshConfig) ([]byte, error) {
var patch []rfc6902PatchOperation
// ...省略一萬字...
// 註入初始化啟動容器
patch = append(patch, addContainer(pod.Spec.InitContainers, sic.InitContainers, "/spec/initContainers")...)
// 註入Sidecar容器
patch = append(patch, addContainer(pod.Spec.Containers, sic.Containers, "/spec/containers")...)
// 註入掛載捲
patch = append(patch, addVolume(pod.Spec.Volumes, sic.Volumes, "/spec/volumes")...)
patch = append(patch, addImagePullSecrets(pod.Spec.ImagePullSecrets, sic.ImagePullSecrets, "/spec/imagePullSecrets")...)
// 註入新註解
patch = append(patch, updateAnnotation(pod.Annotations, annotations)...)
// ...省略一萬字...
return json.Marshal(patch)
}
總結:可以看到,整個註入過程實際就是原本的Pod配置反解析成Pod對象,把需要註入的Yaml內容(如:Sidecar)反序列成對象然後append到對應Pod (如:Container)上,然後再把修改後的Pod重新解析成yaml 內容返回給k8s的api server,然後k8s 拿著修改後內容再將這兩個容器調度到同一臺機器進行部署,至此就完成了對應Sidecar的註入。
卸載 sidecar 自動註入器
kubectl delete mutatingwebhookconfiguration istio-sidecar-injector
kubectl -n istio-system delete service istio-sidecar-injector
kubectl -n istio-system delete deployment istio-sidecar-injector
kubectl -n istio-system delete serviceaccount istio-sidecar-injector-service-account
kubectl delete clusterrole istio-sidecar-injector-istio-system
kubectl delete clusterrolebinding istio-sidecar-injector-admin-role-binding-istio-system
上面的命令不會從 pod 中移除註入的 sidecar。需要進行滾動更新或者直接刪除對應的pod,並強制 deployment 重新創建新pod。
手動註入 sidecar
手動註入 deployment ,需要使用 使用 istioctl kube-inject
istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f -
預設情況下將使用集群內的配置,或者使用該配置的本地副本來完成註入。
kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.values}' > inject-values.yaml
kubectl -n istio-system get configmap istio -o=jsonpath='{.data.mesh}' > mesh-config.yaml
指定輸入文件,運行 kube-inject
並部署
istioctl kube-inject
--injectConfigFile inject-config.yaml
--meshConfigFile mesh-config.yaml
--valuesFile inject-values.yaml
--filename samples/sleep/sleep.yaml
| kubectl apply -f -
驗證 sidecar 已經被註入到 READY 列下 2/2
的 sleep pod 中
kubectl get pod -l app=sleep
NAME READY STATUS RESTARTS AGE
sleep-64c6f57bc8-f5n4x 2/2 Running 0 24s
手動註入原理
手動註入的代碼入口在 istioctl/cmd/kubeinject.go
手工註入跟自動註入還是有些差異的。手動註入是改變了Deployment
。我們可以看下它具體做了哪些動作:
Deployment
註入前配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 7
selector:
matchLabels:
app: hello
tier: backend
track: stable
template:
metadata:
labels:
app: hello
tier: backend
track: stable
spec:
containers:
- name: hello
image: "fake.docker.io/google-samples/hello-go-gke:1.0"
ports:
- name: http
containerPort: 80
Deployment
註入後配置:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
name: hello
spec:
replicas: 7
selector:
matchLabels:
app: hello
tier: backend
track: stable
strategy: {}
template:
metadata:
annotations:
sidecar.istio.io/status: '{"version":"2343d4598565fd00d328a3388421ee637d25d3f7068e7d5cadef374ee1a06b37","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":null,"imagePullSecrets":null}'
creationTimestamp: null
labels:
app: hello
istio.io/rev: ""
security.istio.io/tlsMode: istio
tier: backend
track: stable
spec:
containers:
- image: fake.docker.io/google-samples/hello-go-gke:1.0
name: hello
ports:
- containerPort: 80
name: http
resources: {}
- image: docker.io/istio/proxy_debug:unittest
name: istio-proxy
resources: {}
initContainers:
- image: docker.io/istio/proxy_init:unittest-test
name: istio-init
resources: {}
securityContext:
fsGroup: 1337
status: {}
---
可以新增了一個容器鏡像
- image: docker.io/istio/proxy_debug:unittest
name: istio-proxy
resources: {}
那麼註入的內容模板從哪裡獲取,這裡有兩個選項。
- —injectConfigFile 指定對應的註入文件
- —injectConfigMapName 註入配置的 ConfigMap 名稱
如果在操作時發現Sidecar沒有註入成功可以根據註入的方式查看上面的註入流程來查找問題。
參考文獻
https://kubernetes.io/zh/docs/reference/access-authn-authz/admission-controllers/
https://istio.io/zh/docs/reference/commands/istioctl/#istioctl-kube-inject