istio流量拦截机制

流量拦截机制

pod运行环境要求

service association(服务关联)

pod必须属于某个service,哪怕pod没有暴露端口
pod同时属于多个svc时,这些svc不在使用同一个端口时,指定的协议必须一致

应用运行uid

uid 1137预留给sidecar使用,业务pod不能使用这个uid运行

capabilities授权

  • 强制启用PSP(pod安全策略)的k8s环境中,必须允许在网格内的pod上使用net_admin(允许生成网络命名空间中的iptables规则)、net_raw(允许使用裸tcp、套接字)2个
  • 若未启用PSP,或使用的istio自己的CNI插件,也可以不用(对应部署istio时,是否需要加载相关内核模块)

capabilities:

原先内核对权限分root和普通用户,后来对普通用户授权特殊权限比方便,加了capabilities机制,可用于对某种特殊权限赋予普通用户

pod标签

每个pod必须使用:app、version两个标签指明应用名和版本
app标签用于分布式追踪生成context,version用于显示应用的版本

svc端口命名

每个svc端口都要指定名称、协议,方便istio识别协议做流量调度
k8s 1.18后,可直接在svc中appProtocol项定义协议,就可省去下面的命名格式:

协议-后缀 http-80

协议选择

支持任何类型tcp流量,但不代理udp

  • http
  • https
  • grpc
  • tcp(原始raw tcp)
  • tls
  • grpc-web
  • mysql
  • redis
  • mongo。

注:可自动检测识别http和http2,未检测出的协议全是视为普通tcp,或者手动指定协议

sidecar envoy代理方式

sidecar envoy和应用容器,在pod中共享网络、uts、ipc等命名空间,因此也使用共同的网络协议栈

envoy sidecar基于init容器设置iptables规则进行流量拦截,iptables将拦截后的流量转给envoy,envoy根据配置完成代理,出站流量也使用iptables做拦截转发envoy

拦截模式

  • redirect重定向模式
  • tproxy透明代理模式

image-20231215180516857

劫持过程

  • init:pod启动时,注入的init特权容器开启流量劫持,并设置流量劫持规则,规则分为Inbound规则和Outbound规则
  • Inbound:pod外部请求进来时,被traffic intercetion劫持,它根据Inbound规则将请求转到sidecar,再转给业务应用
  • Outbound:业务应用向pod请求时被traffic interception劫持,它根据outbound规则转给sidecar,再发往外部

image-20231215181356621

注入的sidecar容器

istio基于k8s admission controller webhook完成sidecar自动注入,为每个pod注入2个容器

端口

特殊端口
    15000/tcp       管理端口
    15001/tcp       出站端口
    15004/http      debug端口
    15006/tcp       入站端口
    15008/h2        hbone mtls隧道端口
    15009/h2c       hbone安全网络端口
    15020/http      内置合并指标数据的端口
    15021/http      健康检测
    15053/dns       dns端口,默认未启用
    15090/http      内置普罗米修斯端口
控制平面端口
    443/https
    15010/grpc      xds、ca服务
    15012/grpc      xds、ca
    15014/http      控制平面监控
    15017/https     转发443

istio-init容器

属于init-containers,负责在pod中生成iptables规则(k8s-1.29版本信息中有说明开始在未来弃用iptables改nfttables,我猜以后istio可能也会改用nfttables吧)

使用istio/proxyv2镜像启动,所有流量拦截后发给2个端口:15006、15001

规则生成工具:

使用istio-iptables程序生成拦截规则,新版本使用此

istio-iptables [选项]

-z 15006        #入站劫持端口,只用于重定向模式
-p 15001        #出站劫持端口
-m 拦截模式		#支持REDIRECT、TCPPROXY
-b 端口          #流量拦截目标端口列表
-d 端口           #排除的目标端口列表

规则生成脚本:

旧版本使用脚本生成iptables规则

脚本:https://github.com/istio/cni/blob/master/tools/packaging/common/istio-iptables.sh

规则查看

方法1:使用nsenter

nsenter命令可以在宿主机,直接进入容器的网络命名空间,运行iptables命令

注:查看文档,显示nsenter方法很多,但rhel8中查看不到规则信息(可能需要用cni方式部署istio)

yum install -y util-linux

nsenter -t 2479332 -n iptables -t nat -S

yum install -y util-linux jq
C_CID=`crictl ps |grep admin |awk 'NR==2{print $1}'`
C_PID=`crictl inspect -o json $C_CID |jq .info.pid`
#rhel8中使用没有规则输出,不知道什么问题
nsenter -t $C_PID -n iptables -t nat -S
#rhel8系列使用,但我显示为空,不知道什么问题
nsenter -t $C_PID -n nft list table nat

image-20230911192013858

方法2:日志查看
kubectl logs admin istio-init

规则解读:

image-20231215201624693

入站流量:

prerouting --> istio_inbound

  1. PREROUTING、INPUT、POSTROUTING、OUTPUT链策略全部为允许
  2. 新建自定义链
  3. 将所有入站PREROUTING的tcp数据交给ISTIO_INBOUND处理
  4. 在ISTIO_INBOUND链中对tcp端口:15008、15020、15021、15090,直接返回(返回意味回到主链,而主链是允许所有且无规则,就代表直接不拦截),其他所有tcp流量交给ISTIO_IN_REDIRECT链处理
  5. ISTIO_REDIRECT链对所有tcp流量重定向给端口15006
出站流量:

uid和gid 1337为envoy程序

output --> istio_output

  1. 将所有出站OUTPUT的tcp数据交给ISTIO_OUTPUT处理
  2. 对源地址是127.0.0.6/32、或者是此源地址且发送网卡是lo的,直接返回主链,对于非这个源地址且网卡、uid和gid不是1337的流量交给ISTIO_IN_REDIRECT链
  3. 对于lo网卡,且uid和gid不是1337的流量直接返回主链,对于uid和gid是1337的流量也返回主链
  4. 其他所有流量交给ISTIO_REDIRECT链处理
  5. ISTIO_REDIRECT对所有tcp流量重定向到15001端口

istio-proxy容器

2个进行组成:

  • pilot-agent:基于k8s的api-server为envoy初始化bootstrap配置并启动envoy,监控并管理envoy,如出错时重启,提供xds、配置重载等
  • envoy:由pilot生成bootstrap配置后启动,通过xds从pilot中获取动态配置,代理出入站流量

image-20231215205057817

pilot

命令行工具
pilot-agent [选项] 命令
  request                 #可用于替代没有curl命令时,向envoy管理端口发请求
    GET /listeners      #请求envoy的管理api中侦听器
配置转换

pilot基于k8s的svc,发现svc的所有端点,然后将转换后的实际配置分发出去。所有svc都被配置为egress,只有属于svc的pod,才会定义1个igress对应该svc

虚拟侦听器

“虚拟”意为,实际上envoy是匹配iptables的入站、出站拦截后转发的端口,配置了一个总的侦听器,将数据再次划分匹配给其他对应的侦听器

请求到达时,经过iptables拦截后转给监听15006端口的虚拟侦听器,此虚拟侦听器会匹配数据报文特征,把数据转给实际侦听器,如再转给80侦听器

查看方法:

将配置导出后,找个在线json解析查看,内容非常多,几w行

kubectl exec -it admin -c istio-proxy -- pilot-agent request GET /config_dump > cfg.json
virtual outbound listener

流量 -->15001侦听器(匹配目标ip和端口是否有对应的egress侦听器)-->egress侦听器(实际处理流量)

image-20231215223919457

通过15001端口接收所有出站流量,此侦听器配置:use_origin_dest: true,实现将接收的请求交给真正接收请求的egress侦听器

若不存在能接收转发报文的侦听器,则envoy将根据istio的全局配置选项:outboundTrafficPolicy的值来做处理:

  • ALLOW_ANY:允许所有,直接由tco_proxy过滤器做passthrough cluster进行透传外部,默认配置
  • REGISTRY_ONLY:只允许外发请求到注册在pilot的服务。由侦听器上的tcp_proxy过滤器指向的BlackHoleCluster将流量直接丢弃
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
...
spec: 
  meshConfig:
    outboundTrafficPolicy:
      mode: REGISTRY_ONLY
内置出站侦听器端口
  • 9080 处理发往details、reviews、rating等服务流量
  • 8411 发往zipkin
  • 3000 发往grafana
  • 9090 发往普罗米修斯
virtual inbound listener

通过15006端口创建侦听器,此侦听器下有多个filterChain过滤链,每个链中有多个filterChainMath匹配,每个filterChainMath都匹配目标端口、协议,匹配到1个就不继续匹配,然后交给过滤器处理,路由给对应集群

image-20231215224058100

查看:
istioctl pc cluster demoapp-10-5d667f96d4-zg7zm --port 15006
istioctl pc cluster demoapp-10-5d667f96d4-zg7zm --fqdn demoapp
  • 匹配15006端口,不做任何处理
  • tls、裸tcp直接透传
  • 匹配80端口,转给真实集群

image-20231215224820681

image-20231215225136303

虚拟侦听器和透传的直接原格式转发
image-20231216131500235

集群类型

静态集群由envoy-rev0.json配置static_resources,初始化普罗米修斯指标、xds服务、zipkin服务等
动态集群由xds api从pilot获取

  • inbound cluster:sidecar envoy反向代理
  • outbound cluster:网格中所有服务,包含自身正向代理的svc和其他直接代理的svc
  • passthrough cluster和inbound passthrough cluster IPv4:透传,发往此类集群的请求直接透传给原始目标地址,envoy不做处理
  • black hole cluster:特殊集群,没有端点,发往此处的请求直接丢弃,相当于黑洞作用,一般没有匹配到目标服务的请求发给此处

image-20231216132437900

sidecar资源

默认istio会为每个sidecar envoy,生成所有配置,其中包含不需要访问的资源,如果想要做到,只生成对应关联配置,则需要使用sidecar crd

为每个sidecar envoy单独配置,避免为所有sidecar都生成侦听器(看起来混乱且不易排查问题)

注:较少使用此资源,一把用不到

生效机制

  • sidecar资源通过workload selector字段选择同一个命名空间中1到多个workload(pod);
  • 对于没有提供workload selector字段的sidecar资源,配置会应用到命名空间中所有workload应用
  • 命名空间中同时存在有workload selector字段、没有workload selector字段的sidecar资源时,workload实例将优先应用带有此字段的sidecar对象
  • 每个命名空间中,只应该提供1个没有带有workload selector字段的sidecar资源,否则配置结果会难以确定。此外,每个workload也应该只应用1个带有workload selector字段的sidecar资源,否则也难以明确结果

配置:

kubectl explain sidecars

image-20230912170704681

apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
spec:
  egress:
  - bind: str
    captureMode: str    #流量拦截机制
      #DEFAULT,默认方法拦截
      #IPTABLES,iptables拦截
      #NONE,不拦截
    hosts: [str]          #为指定命名空间中的服务创建侦听器,其他服务不创建侦听器。以:命名空间/dns域名 格式配置,此处可以是svc,也可以是serviceEntry或、vs中配置的svc。dns名称为FQDN的主机名,支持*通配,ns也支持*通配,"."为当前命名空间。
    port:               #生成filter_chains中的链的匹配的端口条件
      name: str         #访问的目标svc名称
      number: int       #访问的目标端口
      targetPort: int
      protocol: 协议
  ingress:
  - bind: str
    captureMode: str
    defaultEndpoint: str
    port:
      name: str
      number: int
      targetPort: int
      protocol: 协议
    tls:
      caCertificates: str
      cipherSuites: [str]
      credentialName: str
      httpsRedirect: boolean
      maxProtocolVersion: str
      minProtocolVersion: str
      mode: str
      privateKey: str
      serverCertificate: str
      subjectAltNames: [str]
      verifyCertificateHash: [str]
      verifyCertificateSpki: [str]
  outboundTrafficPolicy:        #用于定义未匹配egress侦听器的流量的出站规则
    egressProxy:
      host: str
      port:
        number: int
      subset: str
    mode: str         #模式
      #ALLOW_ANY,允许出去
      #REGISTRY_ONLY,不允许,丢给黑洞
  workloadSelector:       #标签选择器,生效的pod范围
    labels: {}

案例:

例1:为proxy限定只访问当前命名空间的服务

1)配置Sidecar资源
cat <<EOF |kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
  name: proxy-sidecar
spec:
  workloadSelector:
    labels:
      app: proxy
  egress:
  - hosts:
    - './*'
    #- 'istio-system/*'
EOF

只剩当前命名空间下的所有egress侦听器配置

image-20231216135151648

2)再修改Sidecar资源
cat <<EOF |kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
  name: proxy-sidecar
spec:
  workloadSelector:
    labels:
      app: proxy
  egress:
  - hosts:
    - './*'
    #- 'istio-system/*'
EOF
posted @ 2023-12-20 19:49  suyanhj  阅读(526)  评论(0)    收藏  举报