还记得在 应用 Cilium 加强 Kubernetes 网络安全 示例中,咱们通过设置网络策略限度钛战机 tiefighter 拜访死星 deathstar 的 /v1/exhaust-port
端点,但放行着陆申请 /v1/request-landing
。在提起 Cilium 时,都说其是应用 eBPF 技术推动的用于提供、爱护和察看容器工作负载之间的网络连接的开源软件。eBPF 能够解决 L3/4 的数据包,然而对简单的 L7 的协定解决的老本比拟高,并且无奈应答 L7 协定策略的灵活性。Cilium 引入 Envoy Proxy(Cilium 定制的发行版)作为 L7 代理,来解决该场景。
那 Cilium 是如何解决 L7 流量的呢?明天就让咱们一探到底。
注,这篇的内容是基于目前最新的 Cilium 1.13.3 和 proxy 1.23.9,不同版本间会有差别。
在开始之前先搭建先前的“星球大战”环境,或者你也能够间接跳到 Debug 阶段。
环境搭建
集群
export INSTALL_K3S_VERSION=v1.27.1+k3s1
curl -sfL https://get.k3s.io | sh -s - --disable traefik --disable local-storage --disable metrics-server --disable servicelb --flannel-backend=none --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config
装置 Cilium
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/master/stable.txt)
CLI_ARCH=amd64
if ["$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
cilium install
装置示例利用
kubectl apply -n default -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: deathstar
labels:
app.kubernetes.io/name: deathstar
spec:
type: ClusterIP
ports:
- port: 80
selector:
org: empire
class: deathstar
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deathstar
labels:
app.kubernetes.io/name: deathstar
spec:
replicas: 1
selector:
matchLabels:
org: empire
class: deathstar
template:
metadata:
labels:
org: empire
class: deathstar
app.kubernetes.io/name: deathstar
spec:
containers:
- name: deathstar
image: docker.io/cilium/starwars
---
apiVersion: v1
kind: Pod
metadata:
name: tiefighter
labels:
org: empire
class: tiefighter
app.kubernetes.io/name: tiefighter
spec:
containers:
- name: spaceship
image: docker.io/tgraf/netperf
---
apiVersion: v1
kind: Pod
metadata:
name: xwing
labels:
app.kubernetes.io/name: xwing
org: alliance
class: xwing
spec:
containers:
- name: spaceship
image: docker.io/tgraf/netperf
EOF
设置策略
kubectl apply -n default -f - <<EOF
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "L7 policy to restrict access to specific HTTP call"
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "POST"
path: "/v1/request-landing"
EOF
测试
kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
#Access denied
kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
#Ship landed
查看 pod 信息。
kubectl get po -o wide -n default
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deathstar-7848d6c4d5-58jc8 1/1 Running 0 6h57m 10.0.0.111 ubuntu-dev3 <none> <none>
xwing 1/1 Running 0 6h57m 10.0.0.209 ubuntu-dev3 <none> <none>
tiefighter 1/1 Running 0 6h57m 10.0.0.123 ubuntu-dev3 <none> <none>
前面 debug 的操作咱们会间接在 cilium 的 agent pod 进行。
agent=$(kubectl get po -l app.kubernetes.io/name=cilium-agent -n kube-system -o jsonpath='{.items[0].metadata.name}')
Debug
先贴上总结的图。
怎么下手呢?
在 深刻摸索 Cilium 的工作机制 时,咱们对 Cilium 的网络策略解决机制一笔带过:
Cilium Agent 中运行着大量的 watcher,其中一个就是
CiliumNetworkPolicy
watcher。当策略创立或者更新时,Agent 会对策略进行转换并将规定存储到 BPF Map 中。在网络通信时,BPF 程序会对网络流量进行查看并决定该当容许或者回绝拜访。
实际上这里的解决比较复杂,咱们从 watcher 的初始化动手。
- #enableK8sWatchers 开启一些列的 watcher
-
#ciliumNetworkPoliciesInit 开启
CiliumNetworkPolicy
watcher-
#addCiliumNetworkPolicyV2 增加
CiliumNetworkPolicy
的解决- #PolicyAdd 将规定写入 Daemon 的策略仓库中,理论发
PolicyAddEvent
到repository-change-queue
队列中。 -
#policyAdd 对规定进行预处理,并收集与规定相干的 endpoint(须要从新生成 endpoint 的数据,如加载 BPF 程序、更新 map 等),推送
PolicyReactionEvent
事件-
PolicyReactionEvent.Handle 事件处理的过程,顺次解决所有策略相干的 endpoint,最初有收回
EndpointRegenerationEvent
事件-
EndpointRegenerationEvent#Handle 事件的处理过程
-
Endpoint.regenerate
- Endpoint.regenerateBPF 从新加载 datapath BPF 程序,刷新 Map。
-
-
-
- #PolicyAdd 将规定写入 Daemon 的策略仓库中,理论发
-
至此咱们 apply 的网络策略被写入到 map 中。
接下来看下 ebpf 程序有任何应用该策略。
eBPF
还记得在 Kubernetes 网络学习之 Cilium 与 eBPF 中咱们剖析容器收回的数据包,被 LXC BPF Ingress
程序处理。这里不再赘述,解决流程能够看那篇文章。
咱们先查看 死星 的 endpoint id 和 identity 别离为 863
和 2033
。
kubectl get ciliumendpoint -n default
NAME ENDPOINT ID IDENTITY ID INGRESS ENFORCEMENT EGRESS ENFORCEMENT VISIBILITY POLICY ENDPOINT STATE IPV4 IPV6
tiefighter 2216 29439 <status disabled> <status disabled> <status disabled> ready 10.0.0.123
deathstar-7848d6c4d5-58jc8 863 2033 <status disabled> <status disabled> <status disabled> ready 10.0.0.111
xwing 775 5513 <status disabled> <status disabled> <status disabled> ready 10.0.0.209
应用 endpoint id 通过通过命令查看为死星配置的网络策略,能够看到其中的两条 ingress 的策略,其代理端口 19313
,这个端口就是 Cilium 中 L7 代理的监听端口。
kubectl exec $agent -n kube-system -c cilium-agent -- cilium bpf policy get 863
POLICY DIRECTION LABELS (source:key[=value]) PORT/PROTO PROXY PORT BYTES PACKETS
Allow Ingress reserved:host ANY NONE 0 0
reserved:kube-apiserver
Allow Ingress k8s:app.kubernetes.io/name=deathstar 80/TCP 19313 0 0
k8s:class=deathstar
k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
Allow Ingress k8s:app.kubernetes.io/name=tiefighter 80/TCP 19313 0 0
k8s:class=tiefighter
k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
Allow Egress reserved:unknown ANY NONE 0 0
BPF 程序处理流量在查看策略时 bpf_lxc.c#L1842,查看配置的策略带有代理端口执行 POLICY_ACT_PROXY_REDIRECT
将流量重定向给代理(端口 19313
,地址为主机地址)。
Cilium Proxy
Cilium agent 提供了 xds server 实现,通过 Unix Domain Socket /var/run/cilium/xds.sock
与 proxy 进行通信,下发配置。
咱们参考 cilium-bugtool
的 dump 源码,dump 代理的配置。
kubectl exec $agent -n kube-system -c cilium-agent -- curl -s --unix-socket /var/run/cilium/envoy-admin.sock http://admin/config_dump?include_eds
从配置 config.json 中能够看到 Cilium 在 envoy proxy 中实现了如下三个不同类型的过滤器(Filter):
- listener filter
- filter
- http filter
监听器过滤器
监听器过滤器(Listener Filter)cilium.BpfMetadata
会从几个数据源中筹备元数据:策略、监听器设置、申请方的标识等。数据源包含 xds 配置、BPF map cilium_ipcache
、cilium_ct4_global
(ct:connection tracking。当然还包含 ct6 相干的 map)。
从数据源中获取的数据保留在 socket option 中(proxy 源码 bpf_metadata.cc#L364),作为上下文元数据的在其余的过滤器中应用。
元数据数据源
xds filter 配置,这里提供了 bpf map 的根目录 /sys/fs/bpf
,以及 is_ingress: true
示意以后 filter 是在入口监听器上(ingress listener):
{
"name": "cilium.bpf_metadata",
"typed_config": {
"@type": "type.googleapis.com/cilium.BpfMetadata",
"bpf_root": "/sys/fs/bpf",
"is_ingress": true
}
xds network policy 配置(截取了 proxy 的局部配置),从配置中能够找到 endpoint 的 IP 和 id,以及后面咱们设置的 规定:
{
"@type": "type.googleapis.com/cilium.NetworkPoliciesConfigDump",
"networkpolicies": [
{
"endpoint_ips": ["10.0.0.111"],
"endpoint_id": "863",
"ingress_per_port_policies": [
{
"port": 80,
"rules": [
{
"http_rules": {
"http_rules": [
{
"headers": [
{
"name": ":method",
"safe_regex_match": {"google_re2": {},
"regex": "POST"
}
},
{
"name": ":path",
"safe_regex_match": {"google_re2": {},
"regex": "/v1/request-landing"
}
}
]
}
]
}
}
]
}
],
"egress_per_port_policies": [{}
],
"conntrack_map_name": "global"
},
...
}
Map cilium_ipcache
,能够通过连贯信息中的 IP 地址获取身份标识,如死星的 identity
为 2033
(见 proxy 源码 bpf_metadata.cc#L165):
kubectl exec $agent -n kube-system -c cilium-agent -- cilium bpf ipcache list
IP PREFIX/ADDRESS IDENTITY
10.0.0.67/32 identity=1 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.111/32 identity=2033 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.123/32 identity=29439 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.243/32 identity=4 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.160/32 identity=19608 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
10.0.0.209/32 identity=5513 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
192.168.1.13/32 identity=1 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
0.0.0.0/0 identity=2 encryptkey=0 tunnelendpoint=0.0.0.0 nodeid=0
Map cilium_ct4_global
,从连贯跟踪(connection tracking)中获取申请方的 identity
(SourceSecurityID 29439,钛战机 的标识):
cilium bpf ct list global
TCP OUT 10.0.0.123:48954 -> 10.0.0.111:80 expires=58774 RxPackets=4 RxBytes=435 RxFlagsSeen=0x1b LastRxReport=58764 TxPackets=6 TxBytes=522 TxFlagsSeen=0x1b LastTxReport=58764 Flags=0x0013 [RxClosing TxClosing SeenNonSyn] RevNAT=4 SourceSecurityID=29439 IfIndex=0
TCP IN 10.0.0.67:33988 -> 10.0.0.111:80 expires=58776 RxPackets=6 RxBytes=659 RxFlagsSeen=0x1b LastRxReport=58766 TxPackets=4 TxBytes=386 TxFlagsSeen=0x1b LastTxReport=58766 Flags=0x0013 [RxClosing TxClosing SeenNonSyn] RevNAT=0 SourceSecurityID=29439 IfIndex=0
TCP IN 10.0.0.123:48954 -> 10.0.0.111:80 expires=80364 RxPackets=6 RxBytes=522 RxFlagsSeen=0x1b LastRxReport=58764 TxPackets=0 TxBytes=0 TxFlagsSeen=0x00 LastTxReport=0 Flags=0x0051 [RxClosing SeenNonSyn ProxyRedirect] RevNAT=0 SourceSecurityID=29439 IfIndex=0
过滤器
过滤器(Filter)cilium.NetworkFilter
工作在 L4,用于解决已建设的链接,利用端口级的策略,即 L4 策略。
从上下文元数据中保留的 endpoint 相干的策略中查找与指标端口相干的策略,查看申请方证书中的 sni 和申请方的身份标识 identity 是否在白名单中,见 proxy 源码 network_filter.cc#L169。
如果策略上设置了 L7 的协定,会应用 Golang 编写的解析器对 L7 的数据进行解析。
在本示例中并未应用 L4 的策略。
HTTP 过滤器
HTTP 过滤器(HTTP Filter)cilium.L7Policy
是本文的重点,但绝对其余两个过滤器来说逻辑就简略多了。
"http_filters": [
{
"name": "cilium.l7policy",
"typed_config": {
"@type": "type.googleapis.com/cilium.L7Policy",
"access_log_path": "/var/run/cilium/access_log.sock"
}
}
在过滤器对 HTTP 申请头进行解码时(见 proxy 源码 l7policy.cc#L97),仍然是从上下文元数据中获取策略等内容。拿到策略后,与申请方(对于这里 ingress 的场景查看申请方,如果是 egress 的场景,查看上游的标识)的标识、申请头的信息进行比对,决定放行还是拒绝请求。
总结
整篇看下来,Cilium 在解决 L7 流量上的实现还是比较复杂的,牵扯多个组件协同。eBPF 在 L3/L4 流量解决上有着优异的性能劣势,然而对 L7 流量解决依然无奈脱离 sidecar 代理(不管 sidecar 是 per pod 还是 per node)。而 L7 流量解决也恰好有着十分多的应用场景,不仅仅是 HTTP 协定。
关注 ” 云原生指北 ” 公众号
(转载本站文章请注明作者和出处盛世浮生,请勿用于任何商业用途)