istio流量劫持总结

envoy 启动流程

数据面组件启动流程

  1. initContainer执行初始化脚本,为Pod添加iptables规则
  2. Pilot-agent根据启动参数和K8S API Server中的配置信息生成Envoy的初始配置文件envoy-rev0.json,该文件告诉Envoy从xDS server中获取动态配置信息,并配置了xDS server的地址信息,即控制面的Pilot。
  3. Pilot-agent使用envoy-rev0.json启动Envoy进程。
  4. Envoy根据初始配置获得Pilot地址,采用xDS接口从Pilot获取到Listener,Cluster,Route等d动态配置信息。
  5. Envoy根据获取到的动态配置启动Listener,并根据Listener的配置,结合Route和Cluster对拦截到的流量进行处理。

istio劫持流量总流程

进流量:
downstream -> iptables -> envoy Inbound -> app
出流量:
app -> iptables -> envoy Outbound -> upstream

image.png

1. IPTABLES 劫持转发流量的过程

  1. 启动流程中有讲到第一步是envoy执行初始化脚本,添加iptables规则
    参考文章
    istio sidecar中创建iptables流程

2. envoy Inbound 和 Outbound 流程

istio 的监听端口

$ nsenter -t 20533 -n netstat -nl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:15090           0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:15000         0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:15001           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:15006           0.0.0.0:*               LISTEN
tcp6       0      0 :::15020                :::*                    LISTEN
tcp6       0      0 :::8889                 :::*                    LISTEN

由上图可以看到envoy有多个端口在监听
15000 端口: 管理端口
15001 端口:Outbound流量重定向端口
15006 端口:Inbound流量重定向端口
15020 端口:健康检查端口
15090 端口: 普罗米修斯端口

应用端口:8899

envoy Inbound处理流量过程

Inbound 流程是将 iptables 拦截到的 downstream 的流量转交给 localhost,与 Pod 内的应用程序容器建立连接。

下面以进入calculator的流量为例子,来分析一下Inbound流程

calculator Inbound流程图

calculator

Inbound配置

  1. listener
  • virtual listener
{
    "version_info": "2020-02-11T01:52:11Z/5",
    "listener": {
        "name": "virtualInbound",
        "address": {
            "socket_address": {
                "address": "0.0.0.0",
                "port_value": 15006
            }
        },
        "filter_chains": [{
            "filter_chain_match": {
                "prefix_ranges": [{
                    "address_prefix": "::0",
                    "prefix_len": 0
                }]
            },
            "filters": [{
                "name": "envoy.tcp_proxy",
                "typed_config": {
                    "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
                    "stat_prefix": "InboundPassthroughClusterIpv6",
                    "cluster": "InboundPassthroughClusterIpv6"
                }
            }],
            "filter_chain_match": {
                "prefix_ranges": [{
                    "address_prefix": "192.xxx.xx.163",
                    "prefix_len": 32
                }],
                "destination_port": 8888
            },
            "filters": [{
                "name": "envoy.http_connection_manager",
                "typed_config": {
                    "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
                    "stat_prefix": "inbound_192.xxx.xx.163_8888",
                    "http_filters": [{
                            "name": "istio_authn"
                        },
                        {
                            "name": "mixer"
                        },
                        {
                            "name": "envoy.router"
                        }
                    ],
                    "route_config": {
                        "name": "inbound|8888|grpc|calculator.vm.svc.cluster.local",
                        "virtual_hosts": [{
                            "name": "inbound|http|8888",
                            "domains": [
                                "*"
                            ],
                            "routes": [{
                                "match": {
                                    "prefix": "/"
                                },
                                "decorator": {
                                    "operation": "calculator.vm.svc.cluster.local:8888/*"
                                },
                                "name": "default",
                                "route": {
                                    "timeout": "0s",
                                    "max_grpc_timeout": "0s",
                                    "cluster": "inbound|8888|grpc|calculator.vm.svc.cluster.local"
                                }
                            }]
                        }],
                        "validate_clusters": false
                    }
                }
            }]
        }]
    }
}
  • 该Listener中第三个filterchain用于处理Calculator服务的入向请求。
  • 该filterchain的匹配条件为Calculator服务的 IP和8888端口,配置了一个http_connection_manager filter
  • http_connection_manager 中又嵌入了istio_auth,Mixer,envoy.router等http filter,经过这些filter进行处理后,请求最终将被转发给""cluster": "inbound|8888|grpc|calculator.vm.svc.cluster.local""

接下来我们看下inbound cluster的配置

  1. cluster
  • inbound cluster
    {
     "version_info": "2020-02-10T08:16:25Z/3",
     "cluster": {
      "name": "inbound|8888|grpc|calculator.vm.svc.cluster.local",
      "type": "STATIC",
      "connect_timeout": "10s",
      "circuit_breakers": {
       "thresholds": [
        {
         "max_connections": 4294967295,
         "max_pending_requests": 4294967295,
         "max_requests": 4294967295,
         "max_retries": 4294967295
        }
       ]
      },
      "http2_protocol_options": {
       "max_concurrent_streams": 1073741824
      },
      "load_assignment": {
       "cluster_name": "inbound|8888|grpc|calculator.vm.svc.cluster.local",
       "endpoints": [
        {
         "lb_endpoints": [
          {
           "endpoint": {
            "address": {
             "socket_address": {
              "address": "127.0.0.1",
              "port_value": 8888
             }
            }
           }
          }
         ]
        }
       ]
      }
     }
    }
  • 这个Inbound Cluster,由于该Inbound Cluster中配置的Upstream为127.0.0.1:8888(?如何查看其upstream)
  • 由于iptable设置中127.0.0.1不会被拦截,该请求将发送到Calculator服务的8888端口上进行业务处理。

Inbound流程代码

//todo

envoy Outbound处理流量过程

webapp outbound流程图

webapp

outbound配置

  • virtual outbound istener
{
    "listener": {
        "name": "virtualOutbound",
        "address": {
            "socket_address": {
                "address": "0.0.0.0",
                "port_value": 15001
            }
        },
        "filter_chains": [{
                "filter_chain_match": {
                    "prefix_ranges": [{
                        "address_prefix": "10.244.2.73",
                        "prefix_len": 32
                    }]
                },
                "filters": [{
                    "name": "envoy.tcp_proxy",
                    "typed_config": {
                        "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
                        "stat_prefix": "BlackHoleCluster",
                        "cluster": "BlackHoleCluster"
                    }
                }]
            },
            {
                "filters": [{
                        "name": "mixer"
                    },
                    {
                        "name": "envoy.tcp_proxy",
                        "typed_config": {
                            "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
                            "stat_prefix": "PassthroughCluster",
                            "cluster": "PassthroughCluster"
                        }
                    }
                ]
            }
        ],
        "use_original_dst": true
    }
}

改listener的最后一行有个配置 "use_original_dst": true,表示用原有地址,不进行路由,这样就会转发到0.0.0.0_8888这个监听器上

下面看一下0.0.0.0_8888这个监听器的配置

  • outbound listener
{
    "listener": {
        "name": "0.0.0.0_8888",
        "address": {
            "socket_address": {
                "address": "0.0.0.0",
                "port_value": 8888
            }
        },
        "filter_chains": [{
                "filter_chain_match": {
                    "prefix_ranges": [{
                        "address_prefix": "10.244.2.73",
                        "prefix_len": 32
                    }]
                },
                "filters": [{
                    "name": "envoy.tcp_proxy",
                    "typed_config": {
                        "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
                        "stat_prefix": "BlackHoleCluster",
                        "cluster": "BlackHoleCluster"
                    }
                }]
            },
            {
                "filters": [{
                    "name": "envoy.http_connection_manager",
                    "typed_config": {
                        "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
                        "stat_prefix": "outbound_0.0.0.0_8888",
                        "http_filters": [{
                                "name": "mixer"
                            },
                            {
                                "name": "envoy.cors"
                            },
                            {
                                "name": "envoy.fault"
                            },
                            {
                                "name": "envoy.router"
                            }
                        ],
                        "use_remote_address": false,
                        "generate_request_id": true,
                        "upgrade_configs": [{
                            "upgrade_type": "websocket"
                        }],
                        "stream_idle_timeout": "0s",
                        "normalize_path": true,
                        "rds": {
                            "config_source": {
                                "ads": {}
                            },
                            "route_config_name": "8888"
                        }
                    }
                }]
            }
        ],
        "deprecated_v1": {
            "bind_to_port": false
        },
        "listener_filters_timeout": "0.100s",
        "traffic_direction": "OUTBOUND",
        "continue_on_listener_filters_timeout": true
    }
}

同理最后有一个envoy.router filter,这个filter中route_config_name:"8888",所以我们可以找到名为"8888"的Route配置

  • outbound Route
{
    "route_config": {
        "name": "8888",
        "virtual_hosts": [{
                "name": "calculator.vm.svc.cluster.local:8888",
                "domains": [
                    "calculator.vm.svc.cluster.local",
                    "calculator.vm.svc.cluster.local:8888",
                    "calculator.vm",
                    "calculator.vm:8888",
                    "calculator.vm.svc.cluster",
                    "calculator.vm.svc.cluster:8888",
                    "calculator.vm.svc",
                    "calculator.vm.svc:8888",
                    "10.106.45.142",
                    "10.106.45.142:8888"
                ],
                "routes": [{
                    "match": {
                        "prefix": "/"
                    },
                    "route": {
                        "weighted_clusters": {
                            "clusters": [{
                                    "name": "outbound|8888|v1|calculator.vm.svc.cluster.local",
                                    "weight": 50
                                },
                                {
                                    "name": "outbound|8888|v2|calculator.vm.svc.cluster.local",
                                    "weight": 50
                                }
                            ]
                        }
                    },
                    "decorator": {
                        "operation": "calculator:8888/*"
                    }
                }]
            },
            {
                "name": "allow_any",
                "domains": [
                    "*"
                ],
                "routes": [{
                    "match": {
                        "prefix": "/"
                    },
                    "route": {
                        "cluster": "PassthroughCluster"
                    }
                }]
            }
        ],
        "validate_clusters": false
    }
}

根据Route 8888的配置可以找到outbound cluster

  • outbound cluster
    {
     "version_info": "2020-02-07T10:38:17Z/1847",
     "cluster": {
      "name": "outbound|8888|v2|calculator.vm.svc.cluster.local",
      "type": "EDS",
      "eds_cluster_config": {
       "eds_config": {
        "ads": {}
       },
       "service_name": "outbound|8888|v2|calculator.vm.svc.cluster.local"
      },
      "connect_timeout": "10s",
      "circuit_breakers": {
       "thresholds": [
        {
         "max_connections": 4294967295,
         "max_pending_requests": 4294967295,
         "max_requests": 4294967295,
         "max_retries": 4294967295
        }
       ]
      },
      "http2_protocol_options": {
       "max_concurrent_streams": 1073741824
      },
      "metadata": {
       "filter_metadata": {
        "istio": {
         "config": "/apis/networking/v1alpha3/namespaces/vm/destination-rule/calculator"
        }
       }
      }
     },
     "last_updated": "2020-02-07T10:38:17.893Z"
    }

我们会发现这个cluster没有直接的endpoint,因为这是动态资源,
可以通过Pilot的调试接口获取该Cluster的endpoint:

$ curl http://xxx.xxx.xxx.xxx.108:15014/debug/edsz > pilot_eds_dump

最后发现endpoint为192.xxx.xxx.163:8888
然后就把流量转发到calculator服务了

  • BlackHoleCluster
    这是一个特殊的Cluster,并没有配置后端处理请求的Host。如其名字所暗示的一样,请求进入后将被直接丢弃掉。如果一个请求没有找到其对的目的服务,则被发到cluste。
{
    "cluster": {
        "name": "BlackHoleCluster",
        "type": "STATIC",
        "connect_timeout": "1s"
    }
}

*PassthroughCluster
和BlackHoleCluter相反,发向PassthroughCluster的请求会被直接发送到其请求中要求的原始目地的,Envoy不会对请求进行重新路由。

{
    "cluster": {
        "name": "PassthroughCluster",
        "type": "ORIGINAL_DST",
        "connect_timeout": "1s",
        "lb_policy": "CLUSTER_PROVIDED",
        "circuit_breakers": {
            "thresholds": [
                {
                    "max_connections": 4294967295,
                    "max_pending_requests": 4294967295,
                    "max_requests": 4294967295,
                    "max_retries": 4294967295
                }
            ]
        }
    }
}

outbound流程代码

//todo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容