Cilium 是什么:用 eBPF 把容器网络从「路过的转发」变成「内核里的策略执行点」
很多个人站长在单机上跑 Docker 很顺,一旦上了 Kubernetes 多节点,容器网络就变成一团黑箱:Pod 之间谁能访问谁、出网流量走了哪条路、某个请求被丢包到底丢在哪个环节,全靠猜。传统方案(如 Calico 的 iptables 模式、Flannel 的 vxlan 转发、kube-proxy 的 iptables/ipvs 规则)本质上是「在数据包路径上串接大量转发规则」,规则一多,性能掉、排错难、可视性差。
Cilium 的思路完全不同:它把网络策略和转发逻辑用 eBPF 程序直接挂载到 Linux 内核的 socket、TC(traffic control)、XDP 等钩子上,让数据包在进入协议栈的极早期就完成策略判定与转发决策。也就是说,传统方案是「包来了先按表转发,再在某个点过滤」,而 Cilium 是「包刚进内核就已经知道该放行、丢弃还是重定向」。这个差异带来的直接收益是:策略基于身份(Identity)而非 IP 地址,容器重建后 IP 变了策略依然生效;延迟更低(少走几层 netfilter 链);并且因为 eBPF 程序天然可以顺便采集指标,网络可观测性几乎是白送的。
对个人站长来说,Cilium 真正的价值不在于「性能世界第一」,而在于两点:一是 L3-L7 的细粒度网络策略(比如「只允许 frontend 这个 Deployment 的 Pod 用 GET 方法访问 backend 的 /api/health 路径」),这是 iptables 时代完全做不到的;二是 Hubble 可观测性,让你在浏览器里看到每一次 Pod 间连接的建立、拒绝、延迟分布,排错从「抓包猜」变成「看流量图」。
Cilium 的核心概念:身份、Endpoint、策略与 Hubble
在动手前,先把几个概念理清楚,否则后面看 YAML 会一头雾水。
Identity(安全身份):Cilium 给每一组标签相同的工作负载分配一个数字身份,这个身份被编码进数据包(或用 IP 缓存映射),策略就是「身份 A 能否访问身份 B 的某服务」。所以哪怕 Pod 重启换了 IP,只要标签没变,策略自动继续生效——这是它比「按 IP 写规则」省心的地方。
Endpoint(端点):每一个被 Cilium 纳管的容器网络接口都是一个 Endpoint,Cilium 会为它加载 eBPF 程序。你可以用 cilium endpoint list 查看所有端点及其身份、策略状态。
NetworkPolicy(网络策略):Cilium 完全兼容 Kubernetes 原生的 NetworkPolicy(L3/L4),同时提供自己的 CiliumNetworkPolicy(CRD),后者支持 L7(HTTP 方法/路径、gRPC 方法、DNS 域名)级别的规则。
Hubble(可观测性):Cilium 的观测组件,由 Hubble Server(每个节点运行)和 Hubble Relay(聚合多节点)组成,配合 Hubble UI 或 CLI,可以实时看到服务依赖图、连接日志、丢包原因。
安装 Cilium:三种模式与选型建议
安装 Cilium 最省事的方式是用官方 CLI cilium 配合 Helm,或直接用 Helm Chart。个人站长通常只有几台机器,先说清三种部署模式的取舍:
- Encapsulation(VXLAN/Geneve 隧道模式):默认模式,跨节点 Pod 通信靠隧道封装,不依赖底层网络支持 BGP,兼容性最好。缺点是有封装开销(每包多几十字节头部),且 MTU 需要相应调小。
- Native Routing(原生路由模式):Pod 网段直接由底层网络路由,性能最好,但要求你的网络(或云厂商路由表)能把 Pod CIDR 路由到对应节点,家用环境/普通 VPS 常常做不到。
- Cloud IPAM / Cluster Pool:在云上让 Pod 直接用 VPC 私有 IP,无需隧道。个人自建环境基本用不到。
对绝大多数个人站长,先用默认的隧道模式(VXLAN)跑通,别一上来追性能开 native routing——后者需要你确保节点之间能互相路由 Pod 网段,一配错就是全线不通。
安装前的基本检查,可以先看节点内核是否满足 eBPF 的版本要求:
uname -r
# Cilium 1.14+ 建议内核 5.4 以上,用 Hubble 与 L7 策略建议 5.10+
ls /sys/fs/bpf
# 应能看到该目录;挂载了 bpf 文件系统说明内核支持然后用 Helm 安装(示意,版本号按实际调整):
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium --version 1.15.6 \
--namespace kube-system \
--set ipam.mode=kubernetes \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set hubble.enabled=true注意 ipam.mode=kubernetes:这表示 Pod IP 由 K8s 的 IPAM 分配、Cilium 通过 Node 的 podCIDR 得知各节点的 Pod 网段,这是最省心的配置。装完后 cilium status 会给出健康总览,重点看 Cilium: Ok、Hubble: Ok,以及有没有节点显示 Not Ready。
写一条 L3/L4 网络策略:从「全通」到「按身份放行」
K8s 默认是「所有 Pod 可以互相访问」——这在多租户或个人多项目混合部署时是个隐患。第一步通常是给它加一条默认拒绝规则(注意:这条规则会让所有未明确放行的入站流量被拒绝,务必先确认 DNS 和已有依赖都被覆盖,否则一上就把集群搞瘫):
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: default-deny-ingress
namespace: default
spec:
endpointSelector: {}
ingress:
- fromEndpoints: []这条策略的语义是:endpointSelector: {} 选中本命名空间所有 Pod,ingress.fromEndpoints: [] 表示不匹配任何来源——即拒绝所有入站。放行的时候再逐条开:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: default
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"
protocol: TCP这条规则表达的是「只允许带 app=frontend 标签的 Pod,访问带 app=backend 标签的 Pod 的 8080 端口」。注意它是对标签(身份)操作,不是对 IP。如果你把 backend 的 Deployment 重建、Pod 换了一批 IP,这条策略一个字都不用改。
L7 策略:限制到「方法 + 路径」级别
CiliumNetworkPolicy 相比原生 NetworkPolicy 最大的增量就是 L7 能力。举例,你只想让前端以 GET 方式访问后端的健康检查接口,其余一概拒绝:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: backend-l7-policy
namespace: default
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: GET
path: "/api/health"L7 策略的实现方式是把请求代理到 Cilium 的一个用户态代理(如果你启用了 Envoy 集成,则走 Envoy;否则走内置的 HTTP 感知逻辑)。这意味着它会带来一定的性能开销和内存占用——所以原则是:L3/L4 能解决的绝不上 L7,只在真正需要区分 HTTP 语义时才用。
Hubble:把「谁在跟谁说话」可视化
装好 Hubble 后,最实用的几个命令:
# 看所有命名空间的实时流量
hubble observe --all
# 只看被拒绝(DROPPED)的连接,这是排错第一入口
hubble observe --verdict DROPPED --all
# 只看某个命名空间
hubble observe -n default --follow
# 看服务依赖图(需 Hubble UI 或 relay 端口转发)
cilium hubble uiHubble 的杀手锏是每条流记录都带 verdict(FORWARDED / DROPPED / ERROR)和原因。当你发现某个服务间调用莫名其妙 502,往往是策略把某个方向的流量挡了,hubble observe --verdict DROPPED 会直接告诉你「源是谁、目标是哪个端口、被哪条策略拒了」。
四个真实踩坑与排查手法
坑一:装了 Cilium 之后 DNS 解析全断。原因通常是加了默认拒绝策略但没放行到 kube-dns(CoreDNS)的流量。Cilium 提供了「toFQDNs」和「toService」的放行方式,最简单的做法是显式放行到 kube-system 命名空间的 DNS Pod:
ingress:
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system排查动作:hubble observe --verdict DROPPED 看到大量发往 53 端口被拒,就是这个问题。
坑二:MTU 不匹配导致大包丢、小请求正常。隧道模式会额外封装头部(VXLAN 约 50 字节),如果 Pod 网卡 MTU 还是 1500,超过路径 MTU 的包(比如大文件下载、TLS 握手含大证书链)会被静默丢弃,表现为「小页面能开、大文件卡死」。症状是 ping 小包正常但 ping -s 1400 不通。处理:把物理网卡和 Cilium 的 MTU 往下调(如 1450),或在云环境确认底层能透传 ICMP 以支持 PMTUD。可用 cilium status --verbose 查看 MTU 配置。
坑三:kube-proxy 与 Cilium 的 Service 实现打架。如果没把 kube-proxy 替换掉,Cilium 走 eBPF 的 Service 负载均衡和 kube-proxy 的 iptables 规则可能双份存在,导致偶发连接被丢。干净的做法是安装时设置 kubeProxyReplacement=true,然后停掉 kube-proxy(并考虑是否需要保留其健康检查路径)。这步一定要在测试环境先验证,直接在生产集群替换会导致短时服务中断。
坑四:内核版本不对,eBPF 程序加载失败。症状是 cilium status 里某些节点报 BPF 相关错误,或 cilium bpf 命令报错。先升级内核(个人站常用 Debian/Ubuntu,装 linux-image 新版并重启),再检查 bpftool feature probe 看内核支持哪些 BPF 特性。
排查网络问题时的通用顺序建议:cilium status 看全局健康 → cilium endpoint list 找目标 Pod 是否 Ready → hubble observe --verdict DROPPED 看策略拦截 → 若策略无拦截再回到内核层看 MTU/路由。这个顺序能把「容器网络黑箱」拆成四段逐一排除,比漫无目的地抓包快得多。
什么时候不该用 Cilium
Cilium 不是银弹。如果你的部署满足以下任一条件,引入它反而增加复杂度:
- 只有单台机器、几个容器:直接用 Docker 自带的 bridge 网络 + 少量 iptables 规则就够了,Cilium 的收益(身份策略、Hubble)你用不上。
- 内核版本低于 4.19 且无法升级:eBPF 特性不足,很多功能降级甚至不可用。
- 团队只有你一个人、且不打算学 eBPF 概念:Cilium 的排错需要理解数据包路径和身份模型,学习曲线比 Flannel 陡。
- 底层网络非常特殊(如某些受限的容器宿主):隧道模式也可能因禁用了特定模块而跑不起来。
我的判断标准很简单:当你的痛点从「容器跑不起来」变成「容器之间说不清谁能访问谁」时,才轮到 Cilium 上场。在那之前,它带来的心智负担大于收益。
总结
Cilium 用 eBPF 把容器网络策略下沉到内核,核心价值是「基于身份的策略 + L7 细粒度控制 + Hubble 可观测性」三件套。落地上记住三条主线:先用隧道模式跑通再用原生路由;先 L3/L4 后 L7(L7 有代理开销);加默认拒绝策略前务必确认 DNS 与既有依赖已被放行。排错时善用 hubble observe --verdict DROPPED 和 cilium status,能把大部分「容器网络玄学问题」定位到一个具体的策略或 MTU 配置上。对个人站长而言,它在多项目混部、需要明确隔离边界时才真正划算——单机小规模部署,Docker 原生网络依然是更务实的选择。