diff --git a/SUMMARY.md b/SUMMARY.md index 933250590..eda5c92e1 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -225,11 +225,13 @@ * [总结](usecases/service-mesh-conclusion.md) * [Istio](usecases/istio.md) * [使用 Istio 前需要考虑的问题](usecases/before-using-istio.md) - * [Istio 中 sidecar 的注入规范及示例](usecases/sidecar-spec-in-istio.md) - * [如何参与 Istio 社区及注意事项](usecases/istio-community-tips.md) - * [Sidecar 的注入与流量劫持](usecases/understand-sidecar-injection-and-traffic-hijack-in-istio-service-mesh.md) + * [Sidecar 模式](usecases/sidecar-pattern.md) + * [Istio 中 sidecar 的注入规范](usecases/sidecar-spec-in-istio.md) + * [Sidecar 的注入与透明流量劫持](usecases/understand-sidecar-injection-and-traffic-hijack-in-istio-service-mesh.md) + * [Istio 中的流量路由过程详解](usecases/istio-traffic-routing.md) * [Istio 如何支持虚拟机](usecases/how-to-integrate-istio-with-vm.md) * [Istio 支持虚拟机的历史](usecases/istio-vm-support.md) + * [如何参与 Istio 社区及注意事项](usecases/istio-community-tips.md) * [Envoy](usecases/envoy.md) * [Envoy 的架构与基本术语](usecases/envoy-terminology.md) * [Envoy 作为前端代理](usecases/envoy-front-proxy.md) diff --git a/images/envoy-sidecar-traffic-interception-jimmysong-blog.png b/images/envoy-sidecar-traffic-interception-jimmysong-blog.png deleted file mode 100644 index 430ecb65f..000000000 Binary files a/images/envoy-sidecar-traffic-interception-jimmysong-blog.png and /dev/null differ diff --git a/images/envoy-sidecar-traffic-interception-zh-20220424.jpg b/images/envoy-sidecar-traffic-interception-zh-20220424.jpg new file mode 100644 index 000000000..ecc6cf3cb Binary files /dev/null and b/images/envoy-sidecar-traffic-interception-zh-20220424.jpg differ diff --git a/images/istio-route-iptables.jpg b/images/istio-route-iptables.jpg new file mode 100644 index 000000000..acdb3f7af Binary files /dev/null and b/images/istio-route-iptables.jpg differ diff --git a/usecases/istio-traffic-routing.md b/usecases/istio-traffic-routing.md new file mode 100644 index 000000000..c01b38d1a --- /dev/null +++ b/usecases/istio-traffic-routing.md @@ -0,0 +1,316 @@ +# Istio 中的流量路由过程详解 + +本文以 Istio 官方的 bookinfo 示例来讲解在进入 Pod 的流量被 iptables 转交给 Envoy sidecar 后,Envoy 是如何做路由转发的,详述了 Inbound 和 Outbound 处理过程。 + +下面是 Istio 官方提供的 bookinfo 的请求流程图,假设 bookinfo 应用的所有服务中没有配置 DestinationRule。 + +![Bookinfo 示例](../images/bookinfo-sample-arch.png) + +我们将要解析的是 `reviews-v1` 这个 Pod 中的 Inbound 和 Outbound 流量。 + +### 理解 Inbound Handler + +Inbound Handler 的作用是将 iptables 拦截到的 downstream 的流量转发给 Pod 内的应用程序容器。在我们的实例中,假设其中一个 Pod 的名字是 `reviews-v1-545db77b95-jkgv2`,运行 `istioctl proxy-config listener reviews-v1-545db77b95-jkgv2 --port 15006` 查看该 Pod 中 15006 端口上的监听器情况 ,你将看到下面的输出。 + +```ini +ADDRESS PORT MATCH DESTINATION +0.0.0.0 15006 Addr: *:15006 Non-HTTP/Non-TCP +0.0.0.0 15006 Trans: tls; App: istio-http/1.0,istio-http/1.1,istio-h2; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4 +0.0.0.0 15006 Trans: raw_buffer; App: http/1.1,h2c; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4 +0.0.0.0 15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4 +0.0.0.0 15006 Trans: raw_buffer; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4 +0.0.0.0 15006 Trans: tls; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4 +0.0.0.0 15006 Trans: tls; App: istio,istio-peer-exchange,istio-http/1.0,istio-http/1.1,istio-h2; Addr: *:9080 Cluster: inbound|9080|| +0.0.0.0 15006 Trans: raw_buffer; Addr: *:9080 Cluster: inbound|9080|| +``` + +下面列出了以上输出中各字段的含义: + +- ADDRESS:下游地址 +- PORT:Envoy 监听器监听的端口 +- MATCH:请求使用的传输协议或匹配的下游地址 +- DESTINATION:路由目的地 + +`reviews` Pod 中的 Iptables 将入站流量劫持到 15006 端口上,从上面的输出我们可以看到 Envoy 的 Inbound Handler 在 15006 端口上监听,对目的地为任何 IP 的 9080 端口的请求将路由到 `inbound|9080||` Cluster 上。 + +从该 Pod 的 Listener 列表的最后两行中可以看到,`0.0.0.0:15006/TCP` 的 Listener(其实际名字是 `virtualInbound`)监听所有的 Inbound 流量,其中包含了匹配规则,来自任意 IP 的对 `9080` 端口的访问流量,将会路由到 `inbound|9080||` Cluster,如果你想以 Json 格式查看该 Listener 的详细配置,可以执行 `istioctl proxy-config listeners reviews-v1-545db77b95-jkgv2 --port 15006 -o json` 命令,你将获得类似下面的输出。 + +```json +[ + /*省略部分内容*/ + { + "name": "virtualInbound", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 15006 + } + }, + "filterChains": [ + /*省略部分内容*/ + { + "filterChainMatch": { + "destinationPort": 9080, + "transportProtocol": "tls", + "applicationProtocols": [ + "istio", + "istio-peer-exchange", + "istio-http/1.0", + "istio-http/1.1", + "istio-h2" + ] + }, + "filters": [ + /*省略部分内容*/ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "inbound_0.0.0.0_9080", + "routeConfig": { + "name": "inbound|9080||", + "virtualHosts": [ + { + "name": "inbound|http|9080", + "domains": [ + "*" + ], + "routes": [ + { + "name": "default", + "match": { + "prefix": "/" + }, + "route": { + "cluster": "inbound|9080||", + "timeout": "0s", + "maxStreamDuration": { + "maxStreamDuration": "0s", + "grpcTimeoutHeaderMax": "0s" + } + }, + "decorator": { + "operation": "reviews.default.svc.cluster.local:9080/*" + } + } + ] + } + ], + "validateClusters": false + }, + /*省略部分内容*/ + } + } + ], + /*省略部分内容*/ + ], + "listenerFilters": [ + /*省略部分内容*/ + ], + "listenerFiltersTimeout": "0s", + "continueOnListenerFiltersTimeout": true, + "trafficDirection": "INBOUND" + } +] +``` + +既然 Inbound Handler 的流量中将来自任意地址的对该 Pod `9080` 端口的流量路由到 `inbound|9080||` Cluster,那么我们运行 `istioctl pc cluster reviews-v1-545db77b95-jkgv2 --port 9080 --direction inbound -o json` 查看下该 Cluster 配置,你将获得类似下面的输出。 + +```json +[ + { + "name": "inbound|9080||", + "type": "ORIGINAL_DST", + "connectTimeout": "10s", + "lbPolicy": "CLUSTER_PROVIDED", + "circuitBreakers": { + "thresholds": [ + { + "maxConnections": 4294967295, + "maxPendingRequests": 4294967295, + "maxRequests": 4294967295, + "maxRetries": 4294967295, + "trackRemaining": true + } + ] + }, + "cleanupInterval": "60s", + "upstreamBindConfig": { + "sourceAddress": { + "address": "127.0.0.6", + "portValue": 0 + } + }, + "metadata": { + "filterMetadata": { + "istio": { + "services": [ + { + "host": "reviews.default.svc.cluster.local", + "name": "reviews", + "namespace": "default" + } + ] + } + } + } + } +] +``` + +我们看其中的 `TYPE` 为 `ORIGINAL_DST`,将流量发送到原始目标地址(Pod IP),因为原始目标地址即当前 Pod,你还应该注意到 `upstreamBindConfig.sourceAddress.address` 的值被改写为了 `127.0.0.6`,而且对于 Pod 内流量是通过 `lo` 网卡发送的,这刚好呼应了上文中的 iptables `ISTIO_OUTPUT` 链中的第一条规则,根据该规则,流量将被透传到 Pod 内的应用容器。 + +### 理解 Outbound Handler + +在本示例中 `reviews` 会向 `ratings` 服务发送 HTTP 请求,请求的地址是:`http://ratings.default.svc.cluster.local:9080/`,Outbound Handler 的作用是将 iptables 拦截到的本地应用程序向外发出的流量,经由 Envoy 代理路由到上游。 + +Envoy 监听在 15001 端口上监听所有 Outbound 流量,Outbound Handler 处理,然后经过 `virtualOutbound` Listener、`0.0.0.0_9080` Listener,然后通过 Route 9080 找到上游的 cluster,进而通过 EDS 找到 Endpoint 执行路由动作。 + +**`ratings.default.svc.cluster.local:9080` 路由** + +运行 `istioctl proxy-config routes reviews-v1-545db77b95-jkgv2 --name 9080 -o json` 查看 route 配置,因为 sidecar 会根据 HTTP header 中的 domains 来匹配 VirtualHost,所以下面只列举了 `ratings.default.svc.cluster.local:9080` 这一个 VirtualHost。 + +```json +[ + { + "name": "9080", + "virtualHosts": [ + { + "name": "ratings.default.svc.cluster.local:9080", + "domains": [ + "ratings.default.svc.cluster.local", + "ratings.default.svc.cluster.local:9080", + "ratings", + "ratings:9080", + "ratings.default.svc", + "ratings.default.svc:9080", + "ratings.default", + "ratings.default:9080", + "10.8.8.106", + "10.8.8.106:9080" + ], + "routes": [ + { + "name": "default", + "match": { + "prefix": "/" + }, + "route": { + "cluster": "outbound|9080||ratings.default.svc.cluster.local", + "timeout": "0s", + "retryPolicy": { + "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes", + "numRetries": 2, + "retryHostPredicate": [ + { + "name": "envoy.retry_host_predicates.previous_hosts" + } + ], + "hostSelectionRetryMaxAttempts": "5", + "retriableStatusCodes": [ + 503 + ] + }, + "maxStreamDuration": { + "maxStreamDuration": "0s", + "grpcTimeoutHeaderMax": "0s" + } + }, + "decorator": { + "operation": "ratings.default.svc.cluster.local:9080/*" + } + } + ], + "includeRequestAttemptCount": true + }, + /*省略部分内容*/ + ], + "validateClusters": false + } +] +``` + +从该 Virtual Host 配置中可以看到将流量路由到`outbound|9080||ratings.default.svc.cluster.local` 集群。 + +**`outbound|9080||ratings.default.svc.cluster.local` 集群的端点** + +运行 `istioctl proxy-config endpoint reviews-v1-545db77b95-jkgv2 --port 9080 -o json --cluster "outbound|9080||ratings.default.svc.cluster.local"` 查看集群的 Endpoint 配置,结果如下。 + +```json +[ + { + "name": "outbound|9080||ratings.default.svc.cluster.local", + "addedViaApi": true, + "hostStatuses": [ + { + "address": { + "socketAddress": { + "address": "10.4.1.12", + "portValue": 9080 + } + }, + "stats": [ + { + "name": "cx_connect_fail" + }, + { + "name": "cx_total" + }, + { + "name": "rq_error" + }, + { + "name": "rq_success" + }, + { + "name": "rq_timeout" + }, + { + "name": "rq_total" + }, + { + "type": "GAUGE", + "name": "cx_active" + }, + { + "type": "GAUGE", + "name": "rq_active" + } + ], + "healthStatus": { + "edsHealthStatus": "HEALTHY" + }, + "weight": 1, + "locality": { + "region": "us-west2", + "zone": "us-west2-a" + } + } + ], + "circuitBreakers": { + "thresholds": [ + { + "maxConnections": 4294967295, + "maxPendingRequests": 4294967295, + "maxRequests": 4294967295, + "maxRetries": 4294967295 + }, + { + "priority": "HIGH", + "maxConnections": 1024, + "maxPendingRequests": 1024, + "maxRequests": 1024, + "maxRetries": 3 + } + ] + }, + "observabilityName": "outbound|9080||ratings.default.svc.cluster.local" + } +] +``` + +我们看到端点的地址是 `10.4.1.12`。实际上,Endpoint 可以是一个或多个,sidecar 将根据一定规则选择适当的 Endpoint 来路由。至此 `review` Pod找到了它上游服务 `rating` 的 Endpoint。 + +## 参考 + +- [Istio 中的 Sidecar 注入、透明流量劫持及流量路由过程详解 - jimmysong.io](https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/) \ No newline at end of file diff --git a/usecases/sidecar-pattern.md b/usecases/sidecar-pattern.md new file mode 100644 index 000000000..8dfa9909d --- /dev/null +++ b/usecases/sidecar-pattern.md @@ -0,0 +1,17 @@ +# Sidecar 模式 + +将应用程序的功能划分为单独的进程运行在同一个最小调度单元中(例如 Kubernetes 中的 Pod)可以被视为 **sidecar 模式**。如下图所示,sidecar 模式允许您在应用程序旁边添加更多功能,而无需额外第三方组件配置或修改应用程序代码。 + +![Sidecar 模式示意图](../images/sidecar-pattern.jpg) + +就像连接了 Sidecar 的三轮摩托车一样,在软件架构中, Sidecar 连接到父应用并且为其添加扩展或者增强功能。Sidecar 应用与主应用程序松散耦合。它可以屏蔽不同编程语言的差异,统一实现微服务的可观察性、监控、日志记录、配置、断路器等功能。 + +### 使用 Sidecar 模式的优势 + +使用 sidecar 模式部署服务网格时,无需在节点上运行代理,但是集群中将运行多个相同的 sidecar 副本。在 sidecar 部署方式中,每个应用的容器旁都会部署一个伴生容器,这个容器称之为 sidecar 容器。Sidecar 接管进出应用容器的所有流量。在 Kubernetes 的 Pod 中,在原有的应用容器旁边注入一个 Sidecar 容器,两个容器共享存储、网络等资源,可以广义的将这个包含了 sidecar 容器的 Pod 理解为一台主机,两个容器共享主机资源。 + +因其独特的部署结构,使得 sidecar 模式具有以下优势: + +- 将与应用业务逻辑无关的功能抽象到共同基础设施,降低了微服务代码的复杂度。 +- 因为不再需要编写相同的第三方组件配置文件和代码,所以能够降低微服务架构中的代码重复度。 +- Sidecar 可独立升级,降低应用程序代码和底层平台的耦合度。 diff --git a/usecases/sidecar-spec-in-istio.md b/usecases/sidecar-spec-in-istio.md index 90b0ea678..8dfc78329 100644 --- a/usecases/sidecar-spec-in-istio.md +++ b/usecases/sidecar-spec-in-istio.md @@ -2,20 +2,20 @@ **注意:本文基于 Istio 1.0。** -我们知道 Istio 通过向 Pod 中注入一个 sidecar 容器来将 Pod 纳入到 Istio service mesh 中的,那么这些 sidecar 容器的注入遵循什么样的规范,需要给每个 Pod 增加哪些配置信息才能纳入 Istio service mesh 中呢?这篇文章将给您答案。 +我们知道 Istio 通过向 Pod 中注入一个 sidecar 容器来将 Pod 纳入到 Istio 服务网格中的,那么这些 sidecar 容器的注入遵循什么样的规范,需要给每个 Pod 增加哪些配置信息才能纳入 Istio 服务网格中呢?这篇文章将给您答案。 ## Pod Spec 中需满足的条件 -为了成为 Service Mesh 中的一部分,kubernetes 集群中的每个 Pod 都必须满足如下条件,这些规范不是由 Istio 自动注入的,而需要 生成 kubernetes 应用部署的 YAML 文件时需要遵守的: +为了成为 服务网格中的一部分,Kubernetes 集群中的每个 Pod 都必须满足如下条件,这些规范不是由 Istio 自动注入的,而需要 生成 Kubernetes 应用部署的 YAML 文件时需要遵守的: 1. **Service 关联**:每个 pod 都必须只属于某**一个** [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/) (当前不支持一个 pod 同时属于多个 service)。 2. **命名的端口**:Service 的端口必须命名。端口的名字必须遵循如下格式 `[-]`,可以是 `http`、`http2`、 `grpc`、 `mongo`、 或者 `redis` 作为 `` ,这样才能使用 Istio 的路由功能。例如 `name: http2-foo` 和 `name: http` 都是有效的端口名称,而 `name: http2foo` 不是。如果端口的名称是不可识别的前缀或者未命名,那么该端口上的流量就会作为普通的 TCP 流量来处理(除非使用 `Protocol: UDP` 明确声明使用 UDP 端口)。 -3. **带有 app label 的 Deployment**:我们建议 kubernetes 的`Deploymenet` 资源的配置文件中为 Pod 明确指定 `app`label。每个 Deployment 的配置中都需要有个与其他 Deployment 不同的含有意义的 `app` label。`app` label 用于在分布式追踪中添加上下文信息。 -4. **Mesh 中的每个 pod 里都有一个 Sidecar**:最后,Mesh 中的每个 pod 都必须运行与 Istio 兼容的 sidecar。以下部分介绍了将 sidecar 注入到 pod 中的两种方法:使用`istioctl` 命令行工具手动注入,或者使用 Istio Initializer 自动注入。注意 sidecar 不涉及到流量,因为它们与容器位于同一个 pod 中。 +3. **带有 app label 的 Deployment**:我们建议 Kubernetes 的`Deploymenet` 资源的配置文件中为 Pod 明确指定 `app`label。每个 Deployment 的配置中都需要有个与其他 Deployment 不同的含有意义的 `app` label。`app` label 用于在分布式追踪中添加上下文信息。 +4. **Mesh 中的每个 pod 里都有一个 Sidecar**:最后,网格中的每个 pod 都必须运行与 Istio 兼容的 sidecar。以下部分介绍了将 sidecar 注入到 pod 中的两种方法:使用`istioctl` 命令行工具手动注入,或者使用 Istio Initializer 自动注入。注意 sidecar 不涉及到流量,因为它们与容器位于同一个 pod 中。 -## 将普通应用添加到 Istio service mesh 中 +## 将普通应用添加到 Istio 服务网格中 -Istio官方的示例[Bookinfo](https://istio.io/docs/guides/bookinfo.html)中并没有讲解如何将服务集成 Istio,只给出了 YAML 配置文件,而其中需要注意哪些地方都没有说明,假如我们自己部署的服务如何使用 Istio 呢?现在我们有如下两个普通应用(代码在 GitHub 上),它们都不具备微服务的高级特性,比如限流和熔断等,通过将它们部署到 kubernetes 并使用 Istio 来管理: +Istio官方的示例[Bookinfo](https://istio.io/docs/guides/bookinfo.html)中并没有讲解如何将服务集成 Istio,只给出了 YAML 配置文件,而其中需要注意哪些地方都没有说明,假如我们自己部署的服务如何使用 Istio 呢?现在我们有如下两个普通应用(代码在 GitHub 上),它们都不具备微服务的高级特性,比如限流和熔断等,通过将它们部署到 Kubernetes 并使用 Istio 来管理: - [k8s-app-monitor-test](https://github.com/rootsongjc/k8s-app-monitor-test):用来暴露 json 格式的 metrics - [k8s-app-monitor-agent](https://github.com/rootsongjc/k8s-app-monitor-agent):访问上面那个应用暴露的 metrics 并生成监控图 @@ -136,23 +136,23 @@ spec: - `Deployment` 和 `Service` 中的 label 名字必须包含 `app`,zipkin 中的 tracing 需要使用到这个标签才能追踪 - `Service` 中的 `ports` 配置和必须包含一个名为 `http` 的 port,这样在 Istio ingress 中才能暴露该服务 -**注意**:该 YAML 文件中 `annotations` 是因为我们一开始使用 `docker-compose` 部署在本地开发测试,后来再使用 [kompose](https://github.com/kubernetes/kompose) 将其转换为 kubernetes 可识别的 YAML 文件。 +**注意**:该 YAML 文件中 `annotations` 是因为我们一开始使用 `docker-compose` 部署在本地开发测试,后来再使用 [kompose](https://github.com/kubernetes/kompose) 将其转换为 Kubernetes 可识别的 YAML 文件。 -然后执行下面的命令就可以基于以上的 YAML 文件注入 sidecar 配置并部署到 kubernetes 集群中。 +然后执行下面的命令就可以基于以上的 YAML 文件注入 sidecar 配置并部署到 Kubernetes 集群中。 ```bash kubectl apply -n default -f <(istioctl kube-inject -f manifests/istio/k8s-app-monitor-istio-all-in-one.yaml) ``` -如何在本地启动 kubernetes 集群进行测试可以参考 [kubernetes-vagrant-centos-cluster](https://github.com/rootsongjc/kubernetes-vagrant-centos-cluster) 中的说明。 +如何在本地启动 Kubernetes 集群进行测试可以参考 [kubernetes-vagrant-centos-cluster](https://github.com/rootsongjc/kubernetes-vagrant-centos-cluster) 中的说明。 ## Sidecar 注入说明 -手动注入需要修改控制器的配置文件,如 deployment。通过修改 deployment 文件中的 pod 模板规范可实现该deployment 下创建的所有 pod 都注入 sidecar。添加/更新/删除 sidecar 需要修改整个 deployment。 +手动注入需要修改控制器的配置文件,如 deployment。通过修改 deployment 文件中的 pod 模板规范可实现该 deployment 下创建的所有 pod 都注入 sidecar。添加/更新/删除 sidecar 需要修改整个 deployment。 自动注入会在 pod 创建的时候注入 sidecar,无需更改控制器资源。Sidecar 可通过以下方式更新: - 选择性地手动删除 pod - 系统得进行 deployment 滚动更新 -手动或者自动注入都使用同样的模板配置。自动注入会从 `istio-system` 命名空间下获取 `istio-inject` 的 ConfigMap。手动注入可以通过本地文件或者 Configmap 。 +手动或者自动注入都使用同样的模板配置。自动注入会从 `istio-system` 命名空间下获取 `istio-inject` 的 ConfigMap。手动注入可以通过本地文件或者 ConfigMap 。 diff --git a/usecases/understand-sidecar-injection-and-traffic-hijack-in-istio-service-mesh.md b/usecases/understand-sidecar-injection-and-traffic-hijack-in-istio-service-mesh.md index 4e2559ca5..4e08a8b48 100644 --- a/usecases/understand-sidecar-injection-and-traffic-hijack-in-istio-service-mesh.md +++ b/usecases/understand-sidecar-injection-and-traffic-hijack-in-istio-service-mesh.md @@ -1,31 +1,4 @@ -# Sidecar 的注入与流量劫持 - -本文基于 Istio 1.11 版本,将为大家介绍以下内容: - -- 什么是 sidecar 模式和它的优势在哪里。 -- Istio 中是如何做 sidecar 注入的? -- Sidecar proxy 是如何做透明流量劫持的? -- 流量是如何路由到 upstream 的? - -## Sidecar 模式 - -将应用程序的功能划分为单独的进程运行在同一个最小调度单元中(例如 Kubernetes 中的 Pod)可以被视为 **sidecar 模式**。如下图所示,sidecar 模式允许您在应用程序旁边添加更多功能,而无需额外第三方组件配置或修改应用程序代码。 - -![Sidecar 模式示意图](../images/sidecar-pattern.jpg) - -就像连接了 Sidecar 的三轮摩托车一样,在软件架构中, Sidecar 连接到父应用并且为其添加扩展或者增强功能。Sidecar 应用与主应用程序松散耦合。它可以屏蔽不同编程语言的差异,统一实现微服务的可观察性、监控、日志记录、配置、断路器等功能。 - -### 使用 Sidecar 模式的优势 - -使用 sidecar 模式部署服务网格时,无需在节点上运行代理,但是集群中将运行多个相同的 sidecar 副本。在 sidecar 部署方式中,每个应用的容器旁都会部署一个伴生容器,这个容器称之为 sidecar 容器。Sidecar 接管进出应用容器的所有流量。在 Kubernetes 的 Pod 中,在原有的应用容器旁边注入一个 Sidecar 容器,两个容器共享存储、网络等资源,可以广义的将这个包含了 sidecar 容器的 Pod 理解为一台主机,两个容器共享主机资源。 - -因其独特的部署结构,使得 sidecar 模式具有以下优势: - -- 将与应用业务逻辑无关的功能抽象到共同基础设施,降低了微服务代码的复杂度。 -- 因为不再需要编写相同的第三方组件配置文件和代码,所以能够降低微服务架构中的代码重复度。 -- Sidecar 可独立升级,降低应用程序代码和底层平台的耦合度。 - -## Istio 中的 sidecar 注入 +# Sidecar 的注入与透明流量劫持 Istio 中提供了以下两种 sidecar 注入方式: @@ -48,7 +21,7 @@ istioctl kube-inject -f ${YAML_FILE} | kuebectl apply -f - 注入完成后您将看到 Istio 为原有 pod template 注入了 `initContainer` 及 sidecar proxy相关的配置。 -### Init 容器 +## Init 容器 Init 容器是一种专用容器,它在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。 @@ -249,7 +222,7 @@ Chain OUTPUT (policy ACCEPT 18M packets, 1916M bytes) Init 容器通过向 iptables nat 表中注入转发规则来劫持流量的,下图显示的是三个 reviews 服务示例中的某一个 Pod,其中有 init 容器、应用容器和 sidecar 容器,图中展示了 iptables 流量劫持的详细过程。 -![Sidecar 流量劫持示意图](../images/envoy-sidecar-traffic-interception-jimmysong-blog.png) +![Sidecar 流量劫持示意图](../images/envoy-sidecar-traffic-interception-zh-20220424.jpg) Init 容器启动时命令行参数中指定了 `REDIRECT` 模式,因此只创建了 NAT 表规则,接下来我们查看下 NAT 表中创建的规则,这是全文中的**重点部分**,前面讲了那么多都是为它做铺垫的。 @@ -270,63 +243,117 @@ Istio 向 pod 中自动注入的 sidecar 容器(名为 `istio-proxy`)其中 下面是查看 nat 表中的规则,其中链的名字中包含 `ISTIO` 前缀的是由 Init 容器注入的,规则匹配是根据下面显示的顺序来执行的,其中会有多次跳转。 ```bash -# 查看 NAT 表中规则配置的详细信息 -$ iptables -t nat -L -v -# PREROUTING 链:用于目标地址转换(DNAT),将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上 -Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) +# PREROUTING 链:用于目标地址转换(DNAT),将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上。 +Chain PREROUTING (policy ACCEPT 2701 packets, 162K bytes) pkts bytes target prot opt in out source destination - 2 120 ISTIO_INBOUND tcp -- any any anywhere anywhere + 2701 162K ISTIO_INBOUND tcp -- any any anywhere anywhere -# INPUT 链:处理输入数据包,非 TCP 流量将继续 OUTPUT 链 -Chain INPUT (policy ACCEPT 2 packets, 120 bytes) +# INPUT 链:处理输入数据包,非 TCP 流量将继续 OUTPUT 链。 +Chain INPUT (policy ACCEPT 2701 packets, 162K bytes) pkts bytes target prot opt in out source destination -# OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上 -Chain OUTPUT (policy ACCEPT 41146 packets, 3845K bytes) +# OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上。 +Chain OUTPUT (policy ACCEPT 79 packets, 6761 bytes) pkts bytes target prot opt in out source destination - 93 5580 ISTIO_OUTPUT tcp -- any any anywhere anywhere + 15 900 ISTIO_OUTPUT tcp -- any any anywhere anywhere -# POSTROUTING 链:所有数据包流出网卡时都要先进入POSTROUTING 链,内核根据数据包目的地判断是否需要转发出去,我们看到此处未做任何处理 -Chain POSTROUTING (policy ACCEPT 41199 packets, 3848K bytes) +# POSTROUTING 链:所有数据包流出网卡时都要先进入 POSTROUTING 链,内核根据数据包目的地判断是否需要转发出去,我们看到此处未做任何处理。 +Chain POSTROUTING (policy ACCEPT 79 packets, 6761 bytes) pkts bytes target prot opt in out source destination -# ISTIO_INBOUND 链:将所有目的地为 9080 端口的入站流量重定向到 ISTIO_IN_REDIRECT 链上 +# ISTIO_INBOUND 链:将所有入站流量重定向到 ISTIO_IN_REDIRECT 链上。目的地为 15090(Prometheus 使用)和 15020(Ingress gateway 使用,用于 Pilot 健康检查)端口的流量除外,发送到以上两个端口的流量将返回 iptables 规则链的调用点,即 PREROUTING 链的后继 POSTROUTING 后直接调用原始目的地。 Chain ISTIO_INBOUND (1 references) pkts bytes target prot opt in out source destination - 2 120 ISTIO_IN_REDIRECT tcp -- any any anywhere anywhere tcp dpt:9080 + 0 0 RETURN tcp -- any any anywhere anywhere tcp dpt:ssh + 2 120 RETURN tcp -- any any anywhere anywhere tcp dpt:15090 + 2699 162K RETURN tcp -- any any anywhere anywhere tcp dpt:15020 + 0 0 ISTIO_IN_REDIRECT tcp -- any any anywhere anywhere -# ISTIO_IN_REDIRECT 链:将所有的入站流量跳转到本地的 15006 端口,至此成功的拦截了流量到 Envoy -Chain ISTIO_IN_REDIRECT (1 references) +# ISTIO_IN_REDIRECT 链:将所有的入站流量跳转到本地的 15006 端口,至此成功的拦截了流量到 sidecar 代理的 Inbound Handler 中。 +Chain ISTIO_IN_REDIRECT (3 references) pkts bytes target prot opt in out source destination - 2 120 REDIRECT tcp -- any any anywhere anywhere redir ports 15006 + 0 0 REDIRECT tcp -- any any anywhere anywhere redir ports 15006 -# ISTIO_OUTPUT 链:选择需要重定向到 Envoy(即本地) 的出站流量,所有非 localhost 的流量全部转发到 ISTIO_REDIRECT。为了避免流量在该 Pod 中无限循环,所有到 istio-proxy 用户空间的流量都返回到它的调用点中的下一条规则,本例中即 OUTPUT 链,因为跳出 ISTIO_OUTPUT 规则之后就进入下一条链 POSTROUTING。如果目的地非 localhost 就跳转到 ISTIO_REDIRECT;如果流量是来自 istio-proxy 用户空间的,那么就跳出该链,返回它的调用链继续执行下一条规则(OUPT 的下一条规则,无需对流量进行处理);所有的非 istio-proxy 用户空间的目的地是 localhost 的流量就跳转到 ISTIO_REDIRECT +# ISTIO_OUTPUT 链:规则比较复杂,将在下文解释 Chain ISTIO_OUTPUT (1 references) pkts bytes target prot opt in out source destination - 0 0 ISTIO_REDIRECT all -- any lo anywhere !localhost - 40 2400 RETURN all -- any any anywhere anywhere owner UID match istio-proxy - 0 0 RETURN all -- any any anywhere anywhere owner GID match istio-proxy - 0 0 RETURN all -- any any anywhere localhost - 53 3180 ISTIO_REDIRECT all -- any any anywhere anywhere + 0 0 RETURN all -- any lo 127.0.0.6 anywhere #规则1 + 0 0 ISTIO_IN_REDIRECT all -- any lo anywhere !localhost owner UID match 1337 #规则2 + 0 0 RETURN all -- any lo anywhere anywhere ! owner UID match 1337 #规则3 + 15 900 RETURN all -- any any anywhere anywhere owner UID match 1337 #规则4 + 0 0 ISTIO_IN_REDIRECT all -- any lo anywhere !localhost owner GID match 1337 #规则5 + 0 0 RETURN all -- any lo anywhere anywhere ! owner GID match 1337 #规则6 + 0 0 RETURN all -- any any anywhere anywhere owner GID match 1337 #规则7 + 0 0 RETURN all -- any any anywhere localhost #规则8 + 0 0 ISTIO_REDIRECT all -- any any anywhere anywhere #规则9 -# ISTIO_REDIRECT 链:将所有流量重定向到 Envoy(即本地) 的 15001 端口 -Chain ISTIO_REDIRECT (2 references) +# ISTIO_REDIRECT 链:将所有流量重定向到 Envoy 代理的 15001 端口。 +Chain ISTIO_REDIRECT (1 references) pkts bytes target prot opt in out source destination - 53 3180 REDIRECT tcp -- any any anywhere anywhere redir ports 15001 + 0 0 REDIRECT tcp -- any any anywhere anywhere redir ports 15001 ``` -`iptables` 显示的链的顺序,即流量规则匹配的顺序。其中要特别注意 `ISTIO_OUTPUT` 链中的规则配置。为了避免流量一直在 Pod 中无限循环,所有到 istio-proxy 用户空间的流量都返回到它的调用点中的下一条规则,本例中即 OUTPUT 链,因为跳出 `ISTIO_OUTPUT` 规则之后就进入下一条链 `POSTROUTING`。 +这里着重需要解释的是 `ISTIO_OUTPUT` 链中的 9 条规则,为了便于阅读,我将以上规则中的部分内容使用表格的形式来展示如下: -`ISTIO_OUTPUT` 链规则匹配的详细过程如下: +| **规则** | **target** | **in** | **out** | **source** | **destination** | +| -------- | ----------------- | ------ | ------- | ---------- | ------------------------------- | +| 1 | RETURN | any | lo | 127.0.0.6 | anywhere | +| 2 | ISTIO_IN_REDIRECT | any | lo | anywhere | !localhost owner UID match 1337 | +| 3 | RETURN | any | lo | anywhere | anywhere !owner UID match 1337 | +| 4 | RETURN | any | any | anywhere | anywhere owner UID match 1337 | +| 5 | ISTIO_IN_REDIRECT | any | lo | anywhere | !localhost owner GID match 1337 | +| 6 | RETURN | any | lo | anywhere | anywhere !owner GID match 1337 | +| 7 | RETURN | any | any | anywhere | anywhere owner GID match 1337 | +| 8 | RETURN | any | any | anywhere | localhost | +| 9 | ISTIO_REDIRECT | any | any | anywhere | anywhere | -- 如果目的地非 localhost 就跳转到 ISTIO_REDIRECT 链 -- 所有来自 istio-proxy 用户空间的非 localhost 流量跳转到它的调用点 `OUTPUT` 继续执行 `OUTPUT` 链的下一条规则,因为 `OUTPUT` 链中没有下一条规则了,所以会继续执行 `POSTROUTING` 链然后跳出 iptables,直接访问目的地 -- 如果流量不是来自 istio-proxy 用户空间,又是对 localhost 的访问,那么就跳出 iptables,直接访问目的地 -- 其它所有情况都跳转到 `ISTIO_REDIRECT` 链 +下图展示了 `ISTIO_ROUTE` 规则的详细流程。 -其实在最后这条规则前还可以增加 IP 地址过滤,让某些 IP 地址段不通过 Envoy 代理。 +![ISTIO_ROUTE iptables 规则流程图](../images/istio-route-iptables.jpg) -以上 iptables 规则都是 Init 容器启动的时使用 [istio-iptables](https://github.com/istio/istio/tree/master/tools/istio-iptables) 命令生成的,详细过程可以查看该命令行程序。 +我将按照规则的出现顺序来解释每条规则的目的、对应文章开头图示中的步骤及详情。其中规则 5、6、7 是分别对规则 2、3、4 的应用范围扩大(从 UID 扩大为 GID),作用是类似的,将合并解释。注意,其中的规则是按顺序执行的,也就是说排序越靠后的规则将作为默认值。出站网卡(out)为 `lo` (本地回环地址,loopback 接口)时,表示流量的目的地是本地 Pod,对于 Pod 向外部发送的流量就不会经过这个接口。所有 `review` Pod 的出站流量只适用于规则 4、7、8、9。 + +**规则 1** + +- 目的:**透传** Envoy 代理发送到本地应用容器的流量,使其绕过 Envoy 代理,直达应用容器。 +- 对应图示中的步骤:6 到 7。 +- 详情:该规则使得所有来自 `127.0.0.6`(该 IP 地址将在下文解释) 的请求,跳出该链,返回 iptables 的调用点(即 `OUTPUT`)后继续执行其余路由规则,即 `POSTROUTING` 规则,把流量发送到任意目的地址,如本地 Pod 内的应用容器。如果没有这条规则,由 Pod 内 Envoy 代理发出的对 Pod 内容器访问的流量将会执行下一条规则,即规则 2,流量将再次进入到了 Inbound Handler 中,从而形成了死循环。将这条规则放在第一位可以避免流量在 Inbound Handler 中死循环的问题。 + +**规则 2、5** + +- 目的:处理 Envoy 代理发出的站内流量(Pod 内部的流量),但不是对 localhost 的请求,通过后续规则将其转发给 Envoy 代理的 Inbound Handler。该规则适用于 Pod 对自身 IP 地址调用的场景。 +- 对应图示中的步骤:6 到 7。 +- 详情:如果流量的目的地非 localhost,且数据包是由 1337 UID(即 `istio-proxy` 用户,Envoy 代理)发出的,流量将被经过 `ISTIO_IN_REDIRECT` 最终转发到 Envoy 的 Inbound Handler。 + +**规则 3、6** + +- 目的:**透传** Pod 内的应用容器的站内流量。适用于在应用容器中发出的对本地 Pod 的流量。 +- 详情:如果流量不是由 Envoy 用户发出的,那么就跳出该链,返回 `OUTPUT` 调用 `POSTROUTING`,直达目的地。 + +**规则 4、7** + +- 目的:**透传** Envoy 代理发出的出站请求。 +- 对应图示中的步骤:14 到 15。 +- 详情:如果请求是由 Envoy 代理发出的,则返回 `OUTPUT` 继续调用 `POSTROUTING` 规则,最终直接访问目的地。 + +**规则 8** + +- 目的:**透传** Pod 内部对 localhost 的请求。 +- 详情:如果请求的目的地是 localhost,则返回 OUTPUT 调用 `POSTROUTING`,直接访问 localhost。 + +**规则 9** + +- 目的:所有其他的流量将被转发到 `ISTIO_REDIRECT` 后,最终达到 Envoy 代理的 Outbound Handler。 + +以上规则避免了 Envoy 代理到应用程序的路由在 iptables 规则中的死循环,保障了流量可以被正确的路由到 Envoy 代理上,也可以发出真正的出站请求。 + +**关于 RETURN target** + +你可能留意到上述规则中有很多 RETURN target,它的意思是,指定到这条规则时,跳出该规则链,返回 iptables 的调用点(在我们的例子中即 `OUTPUT`)后继续执行其余路由规则,在我们的例子中即 `POSTROUTING` 规则,把流量发送到任意目的地址,你可以把它直观的理解为**透传**。 + +**关于 127.0.0.6 IP 地址** + +127.0.0.6 这个 IP 是 Istio 中默认的 `InboundPassthroughClusterIpv4`,在 Istio 的代码中指定。即流量在进入 Envoy 代理后被绑定的 IP 地址,作用是让 Outbound 流量重新发送到 Pod 中的应用容器,即 **Passthought(透传),绕过 Outbound Handler**。该流量是对 Pod 自身的访问,而不是真正的对外流量。至于为什么选择这个 IP 作为流量透传,请参考 [Istio Issue-29603](https://github.com/istio/istio/issues/29603)。 ## 使用 iptables 做流量劫持时存在的问题 @@ -368,4 +395,5 @@ tproxy 可以用于 inbound 流量的重定向,且无需改变报文中的目 - [Debugging Envoy and Istiod - istio.io](https://istio.io/docs/ops/diagnostic-tools/proxy-cmd/) - [揭开 Istio Sidecar 注入模型的神秘面纱 - istio.io](https://istio.io/latest/zh/blog/2019/data-plane-setup/) -- [MOSN 作为 Sidecar 使用时的流量劫持方案 - mosn.io](https://mosn.io/docs/concept/traffic-hijack/) \ No newline at end of file +- [MOSN 作为 Sidecar 使用时的流量劫持方案 - mosn.io](https://mosn.io/docs/concept/traffic-hijack/) +- [Istio 中的 Sidecar 注入、透明流量劫持及流量路由过程详解 - jimmysong.io](https://jimmysong.io/blog/sidecar-injection-iptables-and-traffic-routing/) \ No newline at end of file