update istio sections

pull/473/head
Jimmy Song 2022-04-27 12:12:43 +08:00
parent a3840f2559
commit 5a52f0c4ee
No known key found for this signature in database
GPG Key ID: CBA666E6EF8B2C3A
8 changed files with 442 additions and 79 deletions

View File

@ -225,11 +225,13 @@
* [总结](usecases/service-mesh-conclusion.md) * [总结](usecases/service-mesh-conclusion.md)
* [Istio](usecases/istio.md) * [Istio](usecases/istio.md)
* [使用 Istio 前需要考虑的问题](usecases/before-using-istio.md) * [使用 Istio 前需要考虑的问题](usecases/before-using-istio.md)
* [Istio 中 sidecar 的注入规范及示例](usecases/sidecar-spec-in-istio.md) * [Sidecar 模式](usecases/sidecar-pattern.md)
* [如何参与 Istio 社区及注意事项](usecases/istio-community-tips.md) * [Istio 中 sidecar 的注入规范](usecases/sidecar-spec-in-istio.md)
* [Sidecar 的注入与流量劫持](usecases/understand-sidecar-injection-and-traffic-hijack-in-istio-service-mesh.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/how-to-integrate-istio-with-vm.md)
* [Istio 支持虚拟机的历史](usecases/istio-vm-support.md) * [Istio 支持虚拟机的历史](usecases/istio-vm-support.md)
* [如何参与 Istio 社区及注意事项](usecases/istio-community-tips.md)
* [Envoy](usecases/envoy.md) * [Envoy](usecases/envoy.md)
* [Envoy 的架构与基本术语](usecases/envoy-terminology.md) * [Envoy 的架构与基本术语](usecases/envoy-terminology.md)
* [Envoy 作为前端代理](usecases/envoy-front-proxy.md) * [Envoy 作为前端代理](usecases/envoy-front-proxy.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -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下游地址
- PORTEnvoy 监听器监听的端口
- 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/)

View File

@ -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 可独立升级,降低应用程序代码和底层平台的耦合度。

View File

@ -2,20 +2,20 @@
**注意:本文基于 Istio 1.0。** **注意:本文基于 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 中需满足的条件 ## 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 1. **Service 关联**:每个 pod 都必须只属于某**一个** [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/) (当前不支持一个 pod 同时属于多个 service
2. **命名的端口**Service 的端口必须命名。端口的名字必须遵循如下格式 `<protocol>[-<suffix>]`,可以是 `http`、`http2`、 `grpc``mongo`、 或者 `redis` 作为 `<protocol>` ,这样才能使用 Istio 的路由功能。例如 `name: http2-foo``name: http` 都是有效的端口名称,而 `name: http2foo` 不是。如果端口的名称是不可识别的前缀或者未命名,那么该端口上的流量就会作为普通的 TCP 流量来处理(除非使用 `Protocol: UDP` 明确声明使用 UDP 端口)。 2. **命名的端口**Service 的端口必须命名。端口的名字必须遵循如下格式 `<protocol>[-<suffix>]`,可以是 `http`、`http2`、 `grpc``mongo`、 或者 `redis` 作为 `<protocol>` ,这样才能使用 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 用于在分布式追踪中添加上下文信息。 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 中。 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-test](https://github.com/rootsongjc/k8s-app-monitor-test):用来暴露 json 格式的 metrics
- [k8s-app-monitor-agent](https://github.com/rootsongjc/k8s-app-monitor-agent):访问上面那个应用暴露的 metrics 并生成监控图 - [k8s-app-monitor-agent](https://github.com/rootsongjc/k8s-app-monitor-agent):访问上面那个应用暴露的 metrics 并生成监控图
@ -136,15 +136,15 @@ spec:
- `Deployment``Service` 中的 label 名字必须包含 `app`zipkin 中的 tracing 需要使用到这个标签才能追踪 - `Deployment``Service` 中的 label 名字必须包含 `app`zipkin 中的 tracing 需要使用到这个标签才能追踪
- `Service` 中的 `ports` 配置和必须包含一个名为 `http` 的 port这样在 Istio ingress 中才能暴露该服务 - `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 ```bash
kubectl apply -n default -f <(istioctl kube-inject -f manifests/istio/k8s-app-monitor-istio-all-in-one.yaml) 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 注入说明 ## Sidecar 注入说明
@ -155,4 +155,4 @@ kubectl apply -n default -f <(istioctl kube-inject -f manifests/istio/k8s-app-mo
- 选择性地手动删除 pod - 选择性地手动删除 pod
- 系统得进行 deployment 滚动更新 - 系统得进行 deployment 滚动更新
手动或者自动注入都使用同样的模板配置。自动注入会从 `istio-system` 命名空间下获取 `istio-inject` 的 ConfigMap。手动注入可以通过本地文件或者 Configmap 。 手动或者自动注入都使用同样的模板配置。自动注入会从 `istio-system` 命名空间下获取 `istio-inject` 的 ConfigMap。手动注入可以通过本地文件或者 ConfigMap 。

View File

@ -1,31 +1,4 @@
# Sidecar 的注入与流量劫持 # 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 注入
Istio 中提供了以下两种 sidecar 注入方式: Istio 中提供了以下两种 sidecar 注入方式:
@ -48,7 +21,7 @@ istioctl kube-inject -f ${YAML_FILE} | kuebectl apply -f -
注入完成后您将看到 Istio 为原有 pod template 注入了 `initContainer` 及 sidecar proxy相关的配置。 注入完成后您将看到 Istio 为原有 pod template 注入了 `initContainer` 及 sidecar proxy相关的配置。
### Init 容器 ## Init 容器
Init 容器是一种专用容器,它在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。 Init 容器是一种专用容器,它在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。
@ -249,7 +222,7 @@ Chain OUTPUT (policy ACCEPT 18M packets, 1916M bytes)
Init 容器通过向 iptables nat 表中注入转发规则来劫持流量的,下图显示的是三个 reviews 服务示例中的某一个 Pod其中有 init 容器、应用容器和 sidecar 容器,图中展示了 iptables 流量劫持的详细过程。 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 表中创建的规则,这是全文中的**重点部分**,前面讲了那么多都是为它做铺垫的。 Init 容器启动时命令行参数中指定了 `REDIRECT` 模式,因此只创建了 NAT 表规则,接下来我们查看下 NAT 表中创建的规则,这是全文中的**重点部分**,前面讲了那么多都是为它做铺垫的。
@ -270,63 +243,117 @@ Istio 向 pod 中自动注入的 sidecar 容器(名为 `istio-proxy`)其中
下面是查看 nat 表中的规则,其中链的名字中包含 `ISTIO` 前缀的是由 Init 容器注入的,规则匹配是根据下面显示的顺序来执行的,其中会有多次跳转。 下面是查看 nat 表中的规则,其中链的名字中包含 `ISTIO` 前缀的是由 Init 容器注入的,规则匹配是根据下面显示的顺序来执行的,其中会有多次跳转。
```bash ```bash
# 查看 NAT 表中规则配置的详细信息 # PREROUTING 链用于目标地址转换DNAT将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上。
$ iptables -t nat -L -v Chain PREROUTING (policy ACCEPT 2701 packets, 162K bytes)
# PREROUTING 链用于目标地址转换DNAT将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination 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 链 # INPUT 链:处理输入数据包,非 TCP 流量将继续 OUTPUT 链
Chain INPUT (policy ACCEPT 2 packets, 120 bytes) Chain INPUT (policy ACCEPT 2701 packets, 162K bytes)
pkts bytes target prot opt in out source destination pkts bytes target prot opt in out source destination
# OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上 # OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上
Chain OUTPUT (policy ACCEPT 41146 packets, 3845K bytes) Chain OUTPUT (policy ACCEPT 79 packets, 6761 bytes)
pkts bytes target prot opt in out source destination 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 链,内核根据数据包目的地判断是否需要转发出去,我们看到此处未做任何处理 # POSTROUTING 链:所有数据包流出网卡时都要先进入 POSTROUTING 链,内核根据数据包目的地判断是否需要转发出去,我们看到此处未做任何处理
Chain POSTROUTING (policy ACCEPT 41199 packets, 3848K bytes) Chain POSTROUTING (policy ACCEPT 79 packets, 6761 bytes)
pkts bytes target prot opt in out source destination pkts bytes target prot opt in out source destination
# ISTIO_INBOUND 链:将所有目的地为 9080 端口的入站流量重定向到 ISTIO_IN_REDIRECT 链上 # ISTIO_INBOUND 链:将所有入站流量重定向到 ISTIO_IN_REDIRECT 链上。目的地为 15090Prometheus 使用)和 15020Ingress gateway 使用,用于 Pilot 健康检查)端口的流量除外,发送到以上两个端口的流量将返回 iptables 规则链的调用点,即 PREROUTING 链的后继 POSTROUTING 后直接调用原始目的地。
Chain ISTIO_INBOUND (1 references) Chain ISTIO_INBOUND (1 references)
pkts bytes target prot opt in out source destination 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 # ISTIO_IN_REDIRECT 链:将所有的入站流量跳转到本地的 15006 端口,至此成功的拦截了流量到 sidecar 代理的 Inbound Handler 中。
Chain ISTIO_IN_REDIRECT (1 references) Chain ISTIO_IN_REDIRECT (3 references)
pkts bytes target prot opt in out source destination 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) Chain ISTIO_OUTPUT (1 references)
pkts bytes target prot opt in out source destination pkts bytes target prot opt in out source destination
0 0 ISTIO_REDIRECT all -- any lo anywhere !localhost 0 0 RETURN all -- any lo 127.0.0.6 anywhere #规则1
40 2400 RETURN all -- any any anywhere anywhere owner UID match istio-proxy 0 0 ISTIO_IN_REDIRECT all -- any lo anywhere !localhost owner UID match 1337 #规则2
0 0 RETURN all -- any any anywhere anywhere owner GID match istio-proxy 0 0 RETURN all -- any lo anywhere anywhere ! owner UID match 1337 #规则3
0 0 RETURN all -- any any anywhere localhost 15 900 RETURN all -- any any anywhere anywhere owner UID match 1337 #规则4
53 3180 ISTIO_REDIRECT all -- any any anywhere anywhere 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 端口 # ISTIO_REDIRECT 链:将所有流量重定向到 Envoy 代理的 15001 端口
Chain ISTIO_REDIRECT (2 references) Chain ISTIO_REDIRECT (1 references)
pkts bytes target prot opt in out source destination 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_ROUTE` 规则的详细流程。
- 所有来自 istio-proxy 用户空间的非 localhost 流量跳转到它的调用点 `OUTPUT` 继续执行 `OUTPUT` 链的下一条规则,因为 `OUTPUT` 链中没有下一条规则了,所以会继续执行 `POSTROUTING` 链然后跳出 iptables直接访问目的地
- 如果流量不是来自 istio-proxy 用户空间,又是对 localhost 的访问,那么就跳出 iptables直接访问目的地
- 其它所有情况都跳转到 `ISTIO_REDIRECT`
其实在最后这条规则前还可以增加 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 做流量劫持时存在的问题 ## 使用 iptables 做流量劫持时存在的问题
@ -369,3 +396,4 @@ tproxy 可以用于 inbound 流量的重定向,且无需改变报文中的目
- [Debugging Envoy and Istiod - istio.io](https://istio.io/docs/ops/diagnostic-tools/proxy-cmd/) - [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/) - [揭开 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/) - [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/)