转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com

本文使用的Istio源码是 release 1.5。

这篇文章打算讲一下sidecar,我在刚学习Istio的时候会有一些疑惑,sidecar是如何做到无感知的注入的,很多学习资料都没有详细去讲这部分的内容,下面打算解析一下。

Sidecar 介绍

在Sidecar部署方式中会为每个应用的容器部署一个伴生容器。对于Istio,Sidecar接管进出应用程序容器的所有网络流量。

使用 Sidecar 模式部署服务网格时,无需在节点上运行代理,但是集群中将运行多个相同的 Sidecar 副本。在 Kubernetes 的 Pod 中,在原有的应用容器旁边运行一个 Sidecar 容器,可以理解为两个容器共享存储、网络等资源,可以广义的将这个注入了 Sidecar 容器的 Pod 理解为一台主机,两个容器共享主机资源。

Sidecar 注入过程

注入 Sidecar的时候会在生成pod的时候附加上两个容器:istio-init、istio-proxy。istio-init这个容器从名字上看也可以知道它属于k8s中的Init Containers,主要用于设置iptables规则,让出入流量都转由 Sidecar 进行处理。istio-proxy是基于Envoy实现的一个网络代理容器,是真正的Sidecar,应用的流量会被重定向进入或流出Sidecar。

我们在使用Sidecar自动注入的时候只需要给对应的应用部署的命名空间打个istio-injection=enabled标签,这个命名空间中新建的任何 Pod 都会被 Istio 注入 Sidecar。

应用部署后我们可以通过kubectl describe查看pod内的容器:

  1. [root@localhost ~]# kubectl describe pod details-v1-6c9f8bcbcb-shltm
  2. Name: details-v1-6c9f8bcbcb-shltm
  3. Namespace: default
  4. ...
  5. Labels: app=details
  6. pod-template-hash=6c9f8bcbcb
  7. security.istio.io/tlsMode=istio
  8. service.istio.io/canonical-name=details
  9. service.istio.io/canonical-revision=v1
  10. version=v1
  11. Annotations: sidecar.istio.io/status:
  12. {"version":"3bc68d1f27d8b6b9bf1cb3e9904f5d5f8c2ecab1c93d933fbb3d0db76fae2633","initContainers":["istio-init"],"containers":["istio-proxy"]...
  13. Status: Running
  14. IP: 172.20.0.14
  15. IPs:
  16. IP: 172.20.0.14
  17. Controlled By: ReplicaSet/details-v1-6c9f8bcbcb
  18. Init Containers:
  19. istio-init:
  20. Container ID: docker://6d14ccc83bd119236bf8fda13f6799609c87891be9b2c5af7cbf7d8c913ce17e
  21. Image: docker.io/istio/proxyv2:1.5.10
  22. Image ID: docker-pullable://istio/proxyv2@sha256:abbe8ad6d50474814f1aa9316dafc2401fbba89175638446f01afc36b5a37919
  23. ...
  24. Ready: True
  25. Restart Count: 0
  26. ...
  27. Containers:
  28. details:
  29. Container ID: docker://ed216429216ea1b8a1ba20960590edb7322557467c38cceff3c3e847bcff0a14
  30. Image: docker.io/istio/examples-bookinfo-details-v1:1.15.1
  31. Image ID: docker-pullable://istio/examples-bookinfo-details-v1@sha256:344b1c18703ab1e51aa6d698f459c95ea734f8317d779189f4638de7a00e61ae
  32. ...
  33. istio-proxy:
  34. Container ID: docker://a3862cc8f53198c8f86a911089e73e00f4cc4aa02eea05aaeb0bd267a8e98482
  35. Image: docker.io/istio/proxyv2:1.5.10
  36. Image ID: docker-pullable://istio/proxyv2@sha256:abbe8ad6d50474814f1aa9316dafc2401fbba89175638446f01afc36b5a37919
  37. ...
  38. Ready: True

details-v1-6c9f8bcbcb-shltm这个应用是我们在上篇文章中创建的一个details服务,里面有istio-init、istio-proxy、details这三个container。

Sidecar 注入原理

Sidecar 注入主要是依托k8s的准入控制器Admission Controller来实现的。

准入控制器会拦截 Kubernetes API Server 收到的请求,拦截发生在认证和鉴权完成之后,对象进行持久化之前。可以定义两种类型的 Admission webhook:Validating 和 Mutating。Validating 类型的 Webhook 可以根据自定义的准入策略决定是否拒绝请求;Mutating 类型的 Webhook 可以根据自定义配置来对请求进行编辑。

我们可以看看配置详情:

  1. [root@localhost ~]# kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml
  2. apiVersion: admissionregistration.k8s.io/v1
  3. kind: MutatingWebhookConfiguration
  4. metadata:
  5. annotations:
  6. ...
  7. creationTimestamp: "2020-10-18T08:22:01Z"
  8. generation: 2
  9. labels:
  10. app: sidecar-injector
  11. operator.istio.io/component: Pilot
  12. operator.istio.io/managed: Reconcile
  13. operator.istio.io/version: 1.5.10
  14. release: istio
  15. ...
  16. webhooks:
  17. - admissionReviewVersions:
  18. - v1beta1
  19. clientConfig:
  20. caBundle: ...
  21. service:
  22. name: istiod
  23. namespace: istio-system
  24. path: /inject
  25. port: 443
  26. failurePolicy: Fail
  27. matchPolicy: Exact
  28. name: sidecar-injector.istio.io
  29. namespaceSelector:
  30. matchLabels:
  31. istio-injection: enabled
  32. rules:
  33. - apiGroups:
  34. - ""
  35. apiVersions:
  36. - v1
  37. operations:
  38. - CREATE
  39. resources:
  40. - pods
  41. scope: '*'
  42. ...

这里有一个namespaceSelector,match的标签是istio-injection: enabled的命名空间,请求规则rules是CREATE,表示匹配所有pod的创建请求。当apiserver收到一个符合规则的请求时,apiserver会给 Webhook 服务发送一个准入审核的请求,在上面的配置中webhook指定的是一个叫istiod的service。

  1. [root@localhost ~]# kubectl get svc --namespace=istio-system | grep istiod
  2. istiod ClusterIP 10.68.222.38 <none> 15012/TCP,443/TCP
  3. 32h

通常Sidecar注入由以下步骤完成:

  1. 解析Webhook REST请求,将AdmissionReview原始数据反序列化;
  2. 解析pod,将AdmissionReview中的AdmissionRequest反序列化;
  3. 利用Pod及网格配置渲染Sidecar配置模板;
  4. 利用Pod及渲染后的模板创建Json Patch;
  5. 构造AdmissionResponse;
  6. 构造AdmissionReview,将其发给apiserver;

源码流程差不多是这个样子:

下面我们来看看源码。

源码位置:pkg/kube/inject/webhook.go

  1. func NewWebhook(p WebhookParameters) (*Webhook, error) {
  2. wh := &Webhook{
  3. ...
  4. }
  5. ...
  6. if p.Mux != nil {
  7. p.Mux.HandleFunc("/inject", wh.serveInject)
  8. mux = p.Mux
  9. } else {
  10. wh.server = &http.Server{
  11. Addr: fmt.Sprintf(":%v", p.Port),
  12. TLSConfig: &tls.Config{GetCertificate: wh.getCert},
  13. }
  14. mux = http.NewServeMux()
  15. mux.HandleFunc("/inject", wh.serveInject)
  16. wh.server.Handler = mux
  17. }
  18. ...
  19. }

在初始化Webhook实例的时候会注册/inject对应的处理器,也就是当apiserver回调/inject请求的时候会调用到serveInject方法中。

然后我们进入到serveInject方法中:

文件位置:pkg/kube/inject/webhook.go

  1. func (wh *Webhook) serveInject(w http.ResponseWriter, r *http.Request) {
  2. totalInjections.Increment()
  3. var body []byte
  4. if r.Body != nil {
  5. //读取请求体
  6. if data, err := ioutil.ReadAll(r.Body); err == nil {
  7. body = data
  8. }
  9. }
  10. ...
  11. var reviewResponse *v1beta1.AdmissionResponse
  12. ar := v1beta1.AdmissionReview{}
  13. //解码请求体
  14. if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
  15. handleError(fmt.Sprintf("Could not decode body: %v", err))
  16. reviewResponse = toAdmissionResponse(err)
  17. } else {
  18. //解码成功调用inject方法,并传入AdmissionReview
  19. reviewResponse = wh.inject(&ar)
  20. }
  21. //构建AdmissionReview作为参数返回给调用方
  22. response := v1beta1.AdmissionReview{}
  23. if reviewResponse != nil {
  24. response.Response = reviewResponse
  25. if ar.Request != nil {
  26. response.Response.UID = ar.Request.UID
  27. }
  28. }
  29. resp, err := json.Marshal(response)
  30. if err != nil {
  31. log.Errorf("Could not encode response: %v", err)
  32. http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
  33. }
  34. if _, err := w.Write(resp); err != nil {
  35. log.Errorf("Could not write response: %v", err)
  36. http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
  37. }
  38. }

这个方法很简单,主要就是读取请求体并解码,然后调用inject方法,构建AdmissionReview作为参数返回给调用方。

主要逻辑从这里可以看出都在inject方法里面,下面看看这个方法:

  1. func (wh *Webhook) inject(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
  2. req := ar.Request
  3. var pod corev1.Pod
  4. //json反序列化请求数据
  5. if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
  6. handleError(fmt.Sprintf("Could not unmarshal raw object: %v %s", err,
  7. string(req.Object.Raw)))
  8. return toAdmissionResponse(err)
  9. }
  10. ...
  11. //封装模板数据
  12. spec, iStatus, err := InjectionData(wh.Config.Template, wh.valuesConfig, wh.sidecarTemplateVersion, typeMetadata, deployMeta, &pod.Spec, &pod.ObjectMeta, wh.meshConfig.DefaultConfig, wh.meshConfig) // nolint: lll
  13. if err != nil {
  14. handleError(fmt.Sprintf("Injection data: err=%v spec=%v\n", err, iStatus))
  15. return toAdmissionResponse(err)
  16. }
  17. ...
  18. //将需要注入的有istio-init/istio-proxy container封装成patch操作
  19. //具体可以看这里:https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#response
  20. patchBytes, err := createPatch(&pod, injectionStatus(&pod), annotations, spec, deployMeta.Name)
  21. if err != nil {
  22. handleError(fmt.Sprintf("AdmissionResponse: err=%v spec=%v\n", err, spec))
  23. return toAdmissionResponse(err)
  24. }
  25. log.Debugf("AdmissionResponse: patch=%v\n", string(patchBytes))
  26. //将需要patch的配置封装成AdmissionResponse返回
  27. reviewResponse := v1beta1.AdmissionResponse{
  28. Allowed: true,
  29. Patch: patchBytes,
  30. PatchType: func() *v1beta1.PatchType {
  31. pt := v1beta1.PatchTypeJSONPatch
  32. return &pt
  33. }(),
  34. }
  35. totalSuccessfulInjections.Increment()
  36. return &reviewResponse
  37. }

inject方法逻辑主要分为以下几个步骤:

  1. json反序列化请求数据到pod中;
  2. 调用InjectionData根据模板封装数据,主要是构造istio-init、istio-proxy等容器配置;
  3. 调用createPatch方法将模板数据转化成json形式,到时候在创建容器的时候会patch到创建容器的配置中,具体可以看这里:https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#response
  4. 最后将数据封装成AdmissionResponse返回;

总结

本篇文章重点讲解Sidecar容器注入实现原理,通过使用k8s的准入控制器来做到在每个新建的pod里面都无感知的创建sidecar做流量托管。

Reference

https://github.com/istio/istio.io/blob/release-1.1/content/blog/2019/data-plane-setup/index.md

https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

https://jimmysong.io/blog/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/

1.深入Istio:Sidecar自动注入如何实现的?的更多相关文章

  1. istio sidecar自动注入过程分析

    目录 istio sidecar自动注入过程分析 sidecar自动注入检查 检查kube-apiserver 检查sidecar-injector的configmap 检查namespace标签 s ...

  2. Kubernetes-Istio之Sidecar自动注入

    前提: (官方提供) 1):确认使用的是Kubernetes服务器的受支持版本( 1.13.1.14.1.15):kubectl (官方提供,应该是1.13版本以上,我的是1.16版本) kubect ...

  3. istio实现自动sidecar自动注入(k8s1.13.3+istio1.1.1)

    一.自动注入的前提条件 自动注入功能需要kubernetes 1.9或更高版本: kubernetes环境需支持MutatingAdmissionWebhook: 二.在namespace中设置自动注 ...

  4. Istio技术与实践03:最佳实践之sidecar自动注入

    Istio通过对serviceMesh中的每个pod注入sidecar,来实现无侵入式的服务治理能力.其中,sidecar的注入是其能力实现的重要一环(本文主要介绍在kubernetes集群中的注入方 ...

  5. 注入 Istio sidecar

    注入 Istio sidecar 网格中的每个 Pod 都必须伴随一个 Istio 兼容的 Sidecar 一同运行. 下文中将会介绍两种把 Sidecar 注入到 Pod 中的方法:使用 istio ...

  6. Istio Sidecar注入原理

    概念 简单来说,Sidecar 注入会将额外容器的配置添加到 Pod 模板中.这里特指将Envoy容器注应用所在Pod中. Istio 服务网格目前所需的容器有: istio-init 用于设置 ip ...

  7. Istio Sidecar

    概念及示例 Sidecar描述了sidecar代理的配置.默认情况下,Istio 让每个 Envoy 代理都可以访问来自和它关联的工作负载的所有端口的请求,然后转发到对应的工作负载.您可以使用 sid ...

  8. istio sidecar流量处理机制及配置

    sidecar 介绍 在istio的流量管理等功能,都需要通过下发的配置应用到应用运行环境执行后生效,负责执行配置规则的组件在service mesh中承载应用代理的实体被称为side-car Ist ...

  9. 自己实现简单的AOP(五)使Demo适应webApi、亦可完成属性自动注入

    在前文的Demo中,webApi的Controller是不能自动注入的,原因是 IHttpController 和 IController 是通过两个不同的途径进行激活的. IHttpControll ...

随机推荐

  1. 编写一个Open Live Writer的VSCode代码插件

    起因 又是一年多没有更新过博客了,最近用Arduino做了一点有意思的东西,准备写一篇博客.打开尘封许久的博客园,发现因为Windows Live Writer停止更新,博客园推荐的客户端变为了Ope ...

  2. 认证授权:IdentityServer4 - 数据持久化

    前言: 前面的文章中IdentityServer4 配置内容都存储到内存中,本篇文章开始把配置信息存储到数据库中:本篇文章继续基于github的代码来实现配置数据持久化到MySQL中 一.基于EFCo ...

  3. 010 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 04 变量的三个元素的详细介绍之二——变量类型——即Java中的数据类型

    010 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 04 变量的三个元素的详细介绍之二--变量类型--即Java中的数据类型 Java中变量的三要素 变量名 变 ...

  4. 06 解决Sublime Text3输入法不跟随的问题

    安装原生的Sublime, 输入法是不会跟随Sublime的编译文件页面的,会失去焦点,这样写代码写文档时看起来会十分不方便,参考了一些资料,下载插件做了配置,已经在自己机器上用百度输入法测试成功,记 ...

  5. MeteoInfoLab脚本示例:inpolygon

    inpollygon函数是用来判断带坐标(x/y)的数据是否在某个或者一组多边形(Polygon)中,返回的结果中如果做多边形内则值为1,否则值为-1.下面一个例子演示了利用一个shape文件和inp ...

  6. c++11 R+字符串

    R+字符串 prefix(optional) R"delimiter(raw_characters)delimiter" (6) (since C++11) C++11引入了原始字 ...

  7. GCC编译选项笔记

    警告选项 -Wall:开启大多数的警告信息 -Wextra:开启额外的警告信息,比如参数未使用警告(-Wunused-parameter) -Werror:将警告当作错误,中断编译 优化选项 -O,- ...

  8. go内建方法 make方法

    package main import "fmt" func main() { // make函数 makeSlice() // 创建切片 makeMap() // 创建集合 ma ...

  9. linux(centos8):使用tree命令查看目录结构

    一,tree命令的用途 tree命令以树状图列出文件目录结构 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest 对应的源 ...

  10. Visual Studio2015 、2017中如何支持MYSQL数据源(转)

    转至:https://blog.csdn.net/ght886/article/details/80902457 Visual Studio默认只显示微软自己的SQL Server数据源,如下图所示: ...