Sidecar

问题:当创建一个Pod的时候,Istio需要对其进行观察,服务治理、信息采集等,这些操作都是通过Envoy处理的;那Envoy什么时候创建的呢?

k8s当中一个Pod里面可以有多个Container,如果想要创建一个Sidecar,那最简单的思路便是把原来创建一个PodDeployment配置文件修改一下,再创建一个Container即可,这便是Istio注入的操作过程;

在单个poddeployment创建的时候,我们添加一个拦截功能,“偷偷”修改掉deployment的配置即可完成这样的操作,这就需要用到k8sadmission controllers

注入

手工注入

从源码当中可以看到需要的参数信息

## istioctl/cmd/kubeinject.go
istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml \
    --injectConfigFile /tmp/inj-template.tmpl \
    --meshConfigFile /tmp/mesh.yaml \
    --valuesFile /tmp/values.json

官方的文档 生成inject-config.yamlmesh-config.yamlinject-values.yaml,执行即可创建出来一个2个ContainerPod

 ~ kubectl get pod  -l app=sleep
NAME                    READY   STATUS    RESTARTS   AGE
sleep-8f795f47d-96gpd   2/2     Running   0          3h14m

这里面发生了什么呢?这个可以通过命令行工具生成一个deployment.yaml文件

$ istioctl kube-inject \
    --injectConfigFile inject-config.yaml \
    --meshConfigFile mesh-config.yaml \
    --valuesFile inject-values.yaml \
    --filename samples/sleep/sleep.yaml  > sleep_delpoyment.yaml

对比生成的文件和``samples/sleep/sleep.yaml,可以看到对原deployment`进行了以下的改动

  • 添加annotationslabels

istio-sidecar-sleep-diff

  • 添加istio-proxy容器,添加代理,转发

istio-sidecar-injector-istio-proxy

  • 添加istio-init容器:主要作用是创建iptables规则

istio-sidecar-injector-istio-init

自动注入

自动注入 在官方文档里面写的比较清楚,主要步骤如下:

  • 开启允许istio注入的namespace标签,主要对default ns 进行处理
$ kubectl label namespace default istio-injection=enabled
  • 创建Pod即可
kubectl apply -f samples/sleep/sleep.yaml

这里面发生了什么事情呢?执行完上面的命令后,会进入istio-pilot/inject请求当中;在istiod容器内部的日志当中可以看到如下的信息

istio-pilot-inject-log

istio-sidecar-injector当中早早添加了配置如下,这个配置告诉了k8s-apiserver,会有如下的条件判断

  • namespace的标签里面带有istio-injection
  • apiserver当中存在CREATE POD 时;
  • 将此请求转发至istiod serverice /inject接口;
 ~ kubectl get  mutatingwebhookconfiguration istio-sidecar-injector -o yaml
webhooks:
- admissionReviewVersions:
  - v1beta1
  - v1
  clientConfig:
    caBundle: ......
    service:
      name: istiod
      namespace: istio-system
      path: /inject
      port: 443
  failurePolicy: Fail
  matchPolicy: Exact
  name: sidecar-injector.istio.io
  namespaceSelector:
    matchLabels:
      istio-injection: enabled
  objectSelector: {}
  reinvocationPolicy: Never
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: '*'
  sideEffects: None
  timeoutSeconds: 30

在启动容器istiod的时候会有一个pilot-discovery进程,这个便是istio-pilot的主进程

$ ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
istio-p+     1  0.2  0.8 852068 132900 ?       Ssl  Jan26  11:26 /usr/local/bin/pilot-discovery discovery --monitoringAddr=:15014 --log_output_level=default:info --domain cluster.local --keepaliveMaxServerConnectionAge 30m

通过源码可以看到在NewWebhook的时候,已经添加了/inject的路由

// NewWebhook creates a new instance of a mutating webhook for automatic sidecar injection.
func NewWebhook(p WebhookParameters) (*Webhook, error) {
   ......
   p.Mux.HandleFunc("/inject", wh.serveInject)
   p.Mux.HandleFunc("/inject/", wh.serveInject)
	.....
}

serverInject的时候,会去解析请求信息url pathcontent-type是否为application/jsonbody是否为空等,若初步的校验没有问题,则会进入webhook.inject逻辑;

//  pkg/kube/inject/webhook.go
func (wh *Webhook) inject(ar *kube.AdmissionReview, path string) *kube.AdmissionResponse {
  .......
	// Deal with potential empty fields, e.g., when the pod is created by a deployment
	podName := potentialPodName(&pod.ObjectMeta)
	if pod.ObjectMeta.Namespace == "" {
		pod.ObjectMeta.Namespace = req.Namespace
	}
  // 这个地方的日志信息便是上图日志打印的出处;
	log.Infof("Sidecar injection request for %v/%v", req.Namespace, podName)
	log.Debugf("Object: %v", string(req.Object.Raw))
	log.Debugf("OldObject: %v", string(req.OldObject.Raw))

	......
  // 获取 pod、deployment、注解等信息,下面就进行注入逻辑
	patchBytes, err := injectPod(params)
	......
	return &reviewResponse
}

注入逻辑也是比较清楚,即解析oldPod,再将pod配置进行组装,包括面提到的几个操作,修改spec/labelspec/annotations,添加istio-proxyistio-init容器,组装出来新的Pod配置信息后,放入http responseAdmissionResponse)当中返回给k8s-apiserver,并对注入成功的的Pod累加计数;

	reviewResponse := kube.AdmissionResponse{
		Allowed: true,
		Patch:   patchBytes,
		PatchType: func() *string {
			pt := "JSONPatch"
			return &pt
		}(),
	}
	totalSuccessfulInjections.Increment()

k8s-apisever拿到response后根据对应的配置信息创建Pod

整体的流程图如下:

istio-sidecar-inject-flow

  1. 首先注入MutatingWebhookConfiguration,配置k8s-apiserver创建pod时的转发规则
  2. 执行创建命令,请求至k8s-apisever
  3. k8s-apiserver转发injecdtpilot webhook,对pod配置进行重组
  4. 返回重组后的AdmissionResponsek8s-apiserver
  5. k8s-apisever根据Response当中的配置创建sleepistio-init容器;

参考