你的网络策略规定:允许来自10.0.1.45的流量通过。
昨天,10.0.1.45是你的支付服务所在的地址;而今天,在进行了滚动部署之后,它变成了你的日志采集代理。现在的支付服务地址是10.0.1.89。
Kubernetes已经更新了所有的端点和服务记录——但你的网络策略并不知道这一变化。它仍然基于那个不再属于你期望信任的工作负载的IP地址,允许流量通过。
这就是“工作负载身份识别问题”所在。IP地址代表的是位置,而不是身份;而在Kubernetes集群中,这些位置是会不断变化的。如果基于IP地址来制定安全策略,那么每当有一个Pod被调度、重新调度或进行扩展时,你的安全措施就会无形中变得脆弱起来。
解决这个问题的方法是使用加密技术来确定工作负载的身份:每个工作负载都会获得一份由证书支持的身份证明,这份证明能够说明它是谁,而不是它位于哪里。服务之间在交换数据之前,会先使用这些证书进行相互认证;如果证书不匹配,无论数据来自哪个IP地址,连接都会被拒绝。
SPIFFE和SPIRE正是为了解决这个问题而设计的。而Cilium则利用eBPF技术实现了这一功能,而且无需在每个Pod中添加额外的组件。
通过这篇文章,你将了解SPIFFE的身份识别模型是如何工作的,如何部署SPIRE来为工作负载生成加密身份证明,以及如何使用Cilium内置的SPIRE集成功能,在不修改应用程序代码的情况下,实现服务之间的相互TLS认证。
先决条件
-
需要熟悉Kubernetes的RBAC机制和Pod安全配置——这份手册介绍了相关基础知识
-
需要了解TLS证书以及Kubernetes的Secrets功能——这份手册讲解了cert-manager和证书的相关概念
-
需要安装Helm 3和Cilium CLI工具
-
需要使用kind类型的Kubernetes集群——在这篇文章中,你会使用Cilium作为CNI来创建一个新的kind集群
-
需要有足够的耐心:这是我在这一系列文章中介绍过的最复杂的演示案例。SPIRE涉及的技术组件比之前讨论的任何方案都要多。
所有的演示文件都保存在这个GitHub仓库中。
目录
工作负载身份识别问题
这种场景并非理论上的假设。在Kubernetes中,Pod是临时存在的对象。调度器可以将一个Pod放置在任何节点上,而Pod的IP地址会在调度时从该节点的IP地址池中分配而来。
当一个Pod通过滚动部署、节点资源回收或自动扩展机制被删除并重新创建时,它会获得一个新的IP地址。如果你编写了这样一条NetworkPolicy规则:“允许来自这个IP地址的流量”,那么这条规则现在就会指向无效的目标,甚至可能会指向另一个工作负载。
在这种情况下,Kubernetes的服务名称能够帮助处理东西向流量——无论支持该服务的Pod是什么,服务名称都能被一致地解析出来。但是,基于服务名称制定的NetworkPolicy仍然只是一种标签匹配机制,并不具备加密验证的功能。任何能够伪造正确标签的Pod都可以绕过这种安全机制。
你真正需要的是这样的解决方案:在服务A向服务B发送请求之前,服务B必须通过加密方式证明自己的身份;如果服务B无法证明自己就是它所声称的那个实体,服务A就应该拒绝建立连接。这就是所谓的“相互TLS认证”机制,而关键的问题在于:这些身份验证信息是从哪里获得的呢?
SPIFFE为这个问题提供了答案。
SPIFFE的工作原理
SPIFFE,即“适用于所有人的安全生产身份识别框架”,是一项由CNCF制定的标准。它定义了一种工作负载身份识别的模型,但本身并不实现任何具体的功能。它仅规定了身份信息的格式、用于请求这些信息的API接口,以及使这些身份信息能够在不同的服务、集群和云环境中被验证的信任机制。SPIRE则是这一标准的参考实现方案。
SPIFFE身份标识与信任域
一个SPIFFE身份标识是一个具有特定格式的URI:
spiffe:///
“信任域”是一个用于标识行政边界的信息,通常指的是你的组织、集群或所处的环境。同一信任域内的所有实体都可以互相验证彼此的身份;而来自不同信任域的实体则需要通过额外的配置才能实现身份验证。
下面是一些具体的例子:
spiffe://payments.corp/ns/production/sa/checkout
spiffe://analytics.corp/ns/data/sa/pipeline-worker
spiffe://cluster.local/ns/monitoring/sa/prometheus
“信任域”之后的路径是可自定义的——它由你的SPIRE配置文件决定,通常会包含该工作负载所属的Kubernetes命名空间和服务账户信息。
SVID:加密身份验证文档
SVID,即“SPIFFE可验证身份文档”,是一种将SPIFFE身份标识转化为服务可以直接使用的形式的方式。
目前存在两种SVID格式。
X.509 SVID是一种标准的TLS证书,其中SPIFFE身份标识被嵌入到“Subject Alternative Name”字段中。由于它属于标准的X.509证书,因此任何TLS库都可以直接使用它而无需进行任何修改。
在工作负载进行TLS握手时,会出示这份证书,对方会验证该证书确实是由可信的SPIRE服务器签署的。这种格式适用于gRPC流等长期连接的场景。
JWT SVID是一种经过签名的JSON Web Token,其中包含了SPIFFE ID这一身份信息。它非常适合通过HTTP进行基于请求的身份验证——只需将其放在Authorization头部中,接收方服务就会验证其签名是否有效。
与X.509 SVID相比,JWT SVID的有效期较短,并且其使用范围也是有限的,这样就可以防止不同服务之间重复使用同一令牌。
对于Cilium的相互认证机制来说,使用的是X.509 SVID。本文的其余部分将重点介绍X.509证书的相关内容。
信任包
如果服务A需要验证服务B的证书,那么服务A就必须知道这份证书是由哪个证书颁发机构签署的。在SPIFFE体系中,这个被称为“信任包”——也就是在某个信任域内被认可的所有证书颁发机构的证书集合。
SPIRE通过Workload API提供了这种信任包。当一个工作负载请求获取自己的身份信息时,它也会同时收到当前的信任包。一旦SPIRE服务器更新了其证书颁发机构列表,就会将新的信任包分发给所有代理节点,而这些代理节点又会将其推送给所有的工作负载。因此,你的应用程序完全不需要手动管理这些信任包。
SPIRE的工作原理
SPIRE是负责生成SVID并管理身份生命周期的组件。理解它的架构结构,才能真正理解Cilium如何与它进行集成。
SPIRE服务器与SPIRE代理节点
SPIRE主要由两个部分组成。SPIRE服务器是整个系统的核心证书颁发机构。它负责维护一份工作负载记录清单,确定哪些SPIFFE ID应该发放给哪些工作负载,并代表这些工作负载生成SVID,因此它是整个信任域的权威认证源。
SPIRE代理节点则以DaemonSet的形式运行在每个节点上。它的职责主要有两项:首先,它需要向SPIRE服务器证明自己确实运行在合法的节点上;其次,它在本地提供SPIFFE Workload API接口,供工作负载通过这些接口请求获取SVID。
代理节点会将SVID缓存在本地上,这样即使与SPIRE服务器的连接暂时中断,工作负载的身份认证过程也不会受到影响。
这种“中央服务器+分布式代理节点”的架构设计是经过深思熟虑的。工作负载从来不会直接与SPIRE服务器进行通信,它们只与自己所在节点上的代理节点交互。代理节点负责处理所有的身份验证请求,这样一来,即使某个节点遭到攻击,其影响范围也会被限制在较小的范围内。
节点认证
当一个SPIRE代理节点在新节点上启动时,它首先需要向SPIRE服务器证明自己的身份,才能开始为其他工作负载提供身份验证服务。这个过程就是节点认证。
在Kubernetes环境中,SPIRE使用PSAT(即专为SPIRE服务器设计的Service Account Tokens)来进行节点认证。代理节点会提供一个专门为SPIRE服务器准备的Kubernetes服务账户令牌,SPIRE服务器会通过Kubernetes API来验证这个令牌的有效性,确认该代理节点确实运行在预期的命名空间中,并使用预期的服务账户进行操作,之后才会向该代理节点颁发相应的SVID。
这就是为什么SPIRE需要特定的Kubernetes API标志。必须配置kube-apiserver以支持具有正确访问权限的投影服务账户令牌,因此下面演示中的cluster config配置了--api-audiences和--service-account-issuer这些参数。
工作负载认证
一旦某个节点完成了认证过程,其代理就可以对该节点上的工作负载进行认证。当某个工作负载连接到Workload API接口并请求获取SVID时,代理会通过查询Kubernetes API来收集该工作负载的相关信息(如它的Kubernetes命名空间、服务账户、Pod名称以及标签等),然后将这些信息与SPIRE服务器中注册的工作负载记录进行比对。如果找到匹配的记录,代理就会生成相应的SVID并颁发给该工作负载。
一个工作负载记录的具体格式如下:
SPIFFE ID: spiffe://example.org/ns/production/sa/checkout
Parent ID: spiffe://example.org/spire/agent/k8s_psat/default/<node-uid>
Selectors:
k8s:ns:production
k8s:sa:checkout
这些选择器用于指定必须匹配的Kubernetes相关信息。那些运行在production命名空间中且使用checkout服务账户的Pod,将会获得SPIFFE ID spiffe://example.org/ns/production/sa/checkout;而其他Pod则不会获得这个ID。
SVID的颁发与更新
SVID被设计为具有较短的有效期。在SPIRE中,X.509格式的SVID默认的有效时间为1小时。SPIRE代理会自动在后台更新这些SVID:生成新的密钥对,向服务器请求新的SVID,并在旧SVID过期之前将其提供给Workload API接口。
直接使用Workload API或像SPIFFE CSI驱动程序这样的工具来处理工作负载的系统,能够透明地获取到新的SVID。
采用这种短期有效的凭证机制,实际上就是实现了“零信任”安全策略。如果某个工作负载的SVID被泄露,它的有效期也只有1小时而已;而传统的Kubernetes服务账户令牌,过去是可以永久使用的。
Cilium如何利用SPIFFE实现相互TLS认证
传统的服务网格TLS解决方案(比如Istio或Linkerd)会在每个Pod中插入一个侧车代理来处理所有的网络流量和TLS握手过程。应用程序本身并不知道这些TLS操作正在发生。此外,侧车代理还会增加内存占用(对于Envoy来说,每个Pod大约需要50–100MB的内存),并且每次请求都会增加一次网络传输环节,同时还需要复杂的证书注入机制。
Cilium采取了不同的实现方式。它并没有在每个Pod中插入代理程序,而是利用eBPF技术在网络层直接处理认证流程。运行在每个节点上的Cilium代理会拦截连接请求,使用SPIFFE SVID来完成相互TLS握手,并确保认证结果得到正确执行——所有这些操作都是在内核层面完成的,不需要任何用户空间代理程序。
具体来说,当Pod A尝试与Pod B建立连接时,位于Pod A所在节点上的Cilium代理会拦截这个连接请求。它会从SPIRE Workload API中获取Pod A的SVID,然后检查是否存在要求进行相互认证的CiliumNetworkPolicy规则。如果存在这样的规则,它就会与位于Pod B所在节点上的Cilium代理进行TLS握手,同时出示Pod A的SVID,并请求获得Pod B的SVID作为响应。
两种代理都会将SVID与SPIRE信任包进行验证。如果两个SVID都是有效的,并且政策允许建立连接,那么连接就会成功建立;如果其中一个SVID无效或缺失,连接就会被终止。
位于Pod A上的应用程序会从位于Pod B上的应用程序接收数据。这两款应用程序都没有编写任何TLS相关代码,也没有使用任何辅助工具;整个认证过程完全是在它们各自所在节点上的Cilium代理中完成的。
在Cilium的模型中,Cilium代理本身会从SPIRE系统中获取一个SPIFFE身份凭证。这个身份凭证可以代表相关工作负载来请求SVID。
这与独立的SPIRE模型略有不同——在独立模型中,每个工作负载都需要自行直接请求SVID。而在Cilium模型中,Cilium管理器会根据它所管理的Kubernetes身份信息自动为工作负载在SPIRE系统中注册相应的条目,因此你无需为每个Pod手动创建SPIRE条目。
演示1——使用SPIRE集成来安装Cilium
你只需通过一条Helm命令,就能创建一个以Cilium作为CNI的kind集群,并启用其内置的SPIRE集成功能。
步骤1:安装Cilium CLI
# 在macOS系统上
brew install cilium-cli
# 在Linux系统上
CILIUMCLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --remote-name-all \
https://github.com/cilium/cilium-cli/releases/download/${CILIUMCLI_VERSION}/cilium-linux-amd64.tar.gz
sudo tar -xzf cilium-linux-amd64.tar.gz -C /usr/local/bin
步骤2:创建一个不使用默认CNI的kind集群
必须禁用kind的默认CNI(kindnet),这样才能让Cilium取代它的位置。将以下配置保存为kind-cilium.yaml文件:
# kind-cilium.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
networking:
disableDefaultCNI: true # 必须设置此项,以便让Cilium担任CNI的角色
kubeProxyMode: none # Cilium也会取代kube-proxy的功能
kind create cluster --name k8s-mtls --config kind-cilium.yaml
在Cilium安装完成之前,这些节点会处于NotReady状态——这是因为此时还没有CNI可用。
步骤3:启用SPIRE集成后安装Cilium
由于在步骤2中设置了kubeProxyMode: none,因此Cilium必须自行承担kube-proxy的角色。这意味着它的引导节点无法通过kubernetes Service的ClusterIP来访问API服务器,因为此时还没有任何路由机制能够实现这一连接。
你需要提前提供API服务器的实际地址。可以通过Docker获取kind控制平面的IP地址:
API_SERVER_IP=$(docker inspect k8s-mtls-control-plane \
--format='{{ .NetworkSettings.Networks.kind.IPAddress }}')
echo "API_SERVER_IP=$API_SERVER_IP"
然后使用SPIRE来安装Cilium:
helm repo add cilium https://helm.cilium.io/
helm repo update
helm upgrade cilium cilium/cilium \
--install \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=6443 \
--set authentication.enabled=true \
--set authentication.mutual.spire.enabled=true \
--set authentication.mutual.spire.install.enabled=true \
--set authentication.mutual.spire.install.server.dataStorage.enabled=false
其中一些参数很容易被忽略,但每一个都至关重要:
-
kubeProxyReplacement=true:Cilium会安装自己基于eBPF的替代组件来替换kube-proxy。当配置文件中设置kubeProxyMode: none时,这个参数是必须指定的。 -
k8sServiceHost/k8sServicePort:在Cilium能够开始路由服务请求之前,这些参数用于指定API服务器的地址。在EKS/GKE/AKS环境中,由于安装过程中仍然存在kube-proxy,因此这些参数不是必需的。 -
authentication.enabled=true:这个参数与authentication.mutual.spire.enabled=true一起必须被设置。如果只设置了authentication.mutual.spire.enabled=true,Charmain的validate.yaml文件会提示安装失败,因为SPIRE集成要求这两个参数都必须为true。 -
dataStorage.enabled=false:这个参数用于将SPIRE服务器的数据存储方式从基于PVC的方式切换为内存存储方式。对于实验室环境来说这没问题,但在生产环境中,建议保持这一设置,并确保你的集群具备PersistentVolume支持。
需要注意的是,这里并没有--wait这个参数。在全新的集群上使用这个参数会导致安装失败,因为安装过程本身就是一个竞争性进程。SPIRE服务器需要先在状态为NotReady的节点上启动,然后Cilium代理才会通过SPIRE开始运行,最后这些节点的状态才会变为Ready。因此,安装完成后不需要等待太久,只需观察接下来约2分钟内是否有新的Pod启动即可:
kubectl get pods -A -w
步骤4:验证安装结果
cilium status --wait
/¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Envoy DaemonSet: OK
\__/¯¯\__/ Hubble Relay: disabled
\__/ ClusterMesh: disabled
DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3
DaemonSet cilium-envoy Desired: 3, Ready: 3/3, Available: 3/3
Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2
每个节点上都会有三个Cilium代理进程,包括控制平面节点(在配置文件中未设置任何限制)。你还可以检查cilium-spire命名空间中的SPIRE相关组件。
kubectl get all -n cilium-spire
名称 是否准备就绪 状态 重启次数 运行时间
pod/spire-agent-2cpsr 1/1 正在运行 0 3分钟
pod/spire-agent-klhjx 1/1 正在运行 0 3分钟
pod/spire-agent-vhsnc 1/1 正在运行 0 3分钟
pod/spire-server-0 2/2 正在运行 0 3分钟
名称 类型 集群IP地址 端口 运行时间
service/spire-server 集群IP 10.96.x.x 8081/TCP 3分钟
名称 期望状态 实际状态 是否准备就绪 运行时间
daemonset.apps/spire-agent 3 3 3 3分钟
名称 是否准备就绪 运行时间
statefulset.apps/spire-server 1/1 3分钟
每个节点上都有一个SPIRE代理程序。SPIRE服务器是一个StatefulSet,它由两个容器组成:一个是服务器本身,另一个是SPIRE控制器管理器,该管理器会自动为Cilium身份创建工作负载注册条目。
运行命令来检查SPIRE服务器的运行状态:
kubectl exec -n cilium-spire spire-server-0 -c spire-server -- \
/opt/spire/bin/spire-server healthcheck
kubectl exec -n cilium-spire spire-server-0 -c spire-server -- \
/opt/spire/bin/spire-server agent list
演示2——使用CiliumNetworkPolicy实施相互TLS加密包含了命名空间选择器、数据流过滤选项、继续在演示1中使用的同一个集群上进行操作,您将部署两个服务,并通过
CiliumNetworkPolicy强制它们之间进行相互认证,然后验证经过认证的流量是否能够正常传输,同时确认未经认证的连接会被阻断。在这里,所有的请求都会使用SPIRE服务器生成的SVID来进行身份验证。这两个演示是一个连续的整体流程,并非独立的练习环节。
步骤1:部署客户端和服务器
这个文件中同时包含了服务器端和客户端代码——其中客户端实际上是一个处于“睡眠”状态的curl容器,我们会使用它来执行后续操作。
# echo-workloads.yaml apiVersion: apps/v1 kind: Deployment metadata: name: echo-server namespace: default spec: replicas: 1 selector: matchLabels: app: echo-server template: metadata: labels: app: echo-server spec: containers: - name: echo-server image: ealen/echo-server:latest ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: echo-server namespace: default spec: selector: app: echo-server ports: - port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: echo-client namespace: default spec: replicas: 1 selector: matchLabels: app: echo-client template: metadata: labels: app: echo-client spec: containers: - name: client image: curlimages/curl:latest command: ["sleep", "infinity"]kubectl apply -f echo-workloads.yaml # kubectl rollout status一次只能检测一个资源的状态 kubectl rollout status deployment/echo-server -n default kubectl rollout status deployment/echo-client -n default步骤2:确认无需身份验证即可正常通信
在启用相互TLS认证之前,需要先确认客户端能够成功连接到服务器:
CLIENT=$(kubectl get pod -l app=echo-client -o jsonpath '{.items[0].metadata.name}') kubectl exec $CLIENT -- curl -s http://echo-server/你应该会收到来自echo服务器的JSON响应。此时,通信过程是无需任何身份验证即可正常进行的。
步骤3:应用要求相互认证的CiliumNetworkPolicy规则
在
CiliumNetworkPolicy规则中添加authentication.mode: required选项,就可以强制要求所有匹配的通信流量都必须进行相互TLS认证。连接双方都必须提供有效的SPIFFE SVID证书:# mtls-policy.yaml apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: echo-server-mtls namespace: default spec: endpointSelector: matchLabels: app: echo-server ingress: - fromEndpoints: - matchLabels: app: echo-client authentication: mode: required # 要求所有此类通信都必须进行相互TLS认证kubectl apply -f mtls-policy.yaml步骤4:验证经过认证的通信流量是否依然可以正常传输
kubectl exec $CLIENT -- curl -s http://echo-server/连接成功。Cilium拦截了该连接,在两个Pod节点上的Cilium代理之间完成了SPIFFE mTLS握手流程,验证了双方的SVID信息,随后允许数据流通过。客户端应用程序发送了一个普通的HTTP请求,并收到了响应——这种相互认证的过程是在网络层完成的,且整个过程是完全透明的。
步骤5:使用Hubble观察认证过程(可选)
Hubble是Cilium提供的可视化工具。使用它需要单独安装相应的CLI:
# 在macOS系统中 brew install hubble # 在Linux系统中 HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt) curl -L --remote-name-all \ https://github.com/cilium/hubble/releases/download/${HUBBLE_VERSION}/hubble-linux-amd64.tar.gz sudo tar -xzf hubble-linux-amd64.tar.gz -C /usr/local/bin在集群中启用Hubble工具,然后观察数据流。执行`cilium hubble enable`命令会部署Hubble Relay,并重新启动Cilium代理,从而激活其中的Hubble服务器;因此在进行端口转发之前,请等待这些服务完全启动。如果跳过这个等待步骤,端口转发操作可能会在Relay尚未开始监听时就被触发,从而导致连接失败,出现“connection reset by peer”或“rpc error … EOF”等错误信息。
cilium hubble enable cilium status --wait # 等待直到显示“Hubble Relay: OK”后再继续操作 cilium hubble port-forward & # 观察echo-server的数据流(按Ctrl-C可停止观察) hubble observe --namespace default --pod echo-server --follow在另一个终端中发送另一个请求:
kubectl exec $CLIENT -- curl -s http://echo-server/在Hubble的输出结果中,你会看到如下信息:
ℹ️ Hubble Relay已启动,地址为127.0.0.1:4245 Jul 7 12:44:42.380: default/echo-client-86d446b8f-9bn5v:47500 (ID:2822) -> default/echo-server-7467b4b54d-5tvkz:80 (ID:30854) policy-verdict:none TRAFFIC_direction_UNKNOWN ALLOWED (TCP Flags: SYN) Jul 7 12:44:42.380: default/echo-client-86d446b8f-9bn5v:47500 (ID:2822) -> default/echo-server-7467b4b54d-5tvkz:80 (ID:30854) to-endpoint FORWARDED (TCP Flags: SYN) Jul 7 12:44:42.381: default/echo-client-86d446b8f-9bn5v:47500 (ID:2822) <- default/echo-server-7467b4b54d-5tvkz:80 (ID:30854) to-endpoint FORWARDED (TCP Flags: SYN, ACK)“ALLOWED”这一判定结果以及“policy-verdict”的具体原因,都证明了CiliumNetworkPolicy规则得到了正确应用,认证过程也已经完成。整个过程中并没有使用任何额外的侧车组件——所有的操作都是由Cilium代理本身完成的。
更喜欢图形化界面吗?可以启用Hubble的UI工具。上述所有步骤其实都是通过API或终端命令来执行的;而Hubble还提供了带有实时服务地图的Web管理界面,但这个功能是需要单独启用的,执行`cilium hubble enable`命令后,默认情况下并不会自动启动该界面。
# 添加用户界面功能:允许重新运行任务,保留Relay组件,并添加hubble-ui部署配置 cilium hubble enable --ui # 等待该配置准备好后再打开用户界面——这个过程与配置Relay组件的过程是同时进行的。如果跳过这一步,执行`cilium hubble ui`命令时会遇到“连接被拒绝”的错误,因为此时hubble-ui的用户界面前端容器尚未开始运行。 kubectl -n kube-system rollout status deployment/hubble-ui --timeout=90s # 将hubble-ui的端口映射到本地主机,并在浏览器中访问http://localhost:12000 cilium hubble ui从下拉列表中选择
default命名空间。演示用Pod和策略就位于这个命名空间中。该服务映射是实时更新的:它会随着数据流的变化立即显示相应的连接路径,因此空闲的命名空间在界面上会显示为空白状态。要让它“活跃”起来,请执行以下命令:kubectl exec $CLIENT -- curl -s http://echo-server/你会看到一条被标记为
echo-client → echo-server的连接路径。点击这条路径(或查看底部的流表),你会看到policy-verdict: ALLOWED这一提示,说明该连接是被允许通过的。请保持这个界面打开状态,直到完成第6步操作。当你在那里运行unauthorized-client测试时,其连接会以一条红色的dropped路径显示出来,这种视觉表现实际上反映了curl请求的超时情况。
该用户界面由三个部分组成。
顶部的服务映射会将每个工作负载的标识显示为一个个框,而每条被检测到的连接则会以不同颜色来表示其状态:
echo-client → echo-server:80这条路径用绿色实线表示,表示请求被成功转发;而标记为default的框(实际上对应的是unauthorizedPod)与echo-server之间的连接则用红色虚线表示,意味着请求被拒绝了。echo-server上的→ 80 TCP端口处显示的🔒锁形符号,说明该端口已经通过了策略验证。下方的流表会为每条数据流记录一行信息,包括源地址、目标地址、目标端口、L7层协议信息、
处理结果以及时间戳。这样你就可以将echo-client → echo-server这类被允许通过的请求与default → echo-server这类被拒绝的请求并排查看。flow table中的记录方式与CLI命令行工具是一样的,即每条数据流都会占用一行。顶部的导航栏
任何处理结果/可视化显示切换按钮,以及实时显示的flows/s吞吐量统计信息和报告节点的数量(例如3/3)。第6步:验证没有相应标签的Pod是否会被阻止
部署第三个没有
echo-client标签的Pod,然后尝试连接服务器:# unauthorized-client.yaml apiVersion: v1 kind: Pod metadata: name: unauthorized namespace: default spec: containers: - name: client image: curlimages/curl:latest command: ["sleep", "infinity"]kubectl apply -f unauthorized-client.yaml kubectl wait --for=condition=Ready pod/unauthorized --timeout=60s kubectl exec unauthorized -- curl -sS --max-time 5 http://echo-server/curl: (28) 连接在 5000 毫秒后超时连接发生了超时。
CiliumNetworkPolicy仅允许标签为app: echo-client的Pod进行入站通信。没有该标签的Pod既无法匹配到SVID,也无法符合任何策略规则,因此Cilium会默默地丢弃这些数据包。这里有两个需要注意的地方:在执行相关操作之前,请先运行
kubectl wait命令。如果在执行apply命令后太快就运行exec,就会收到 “container not found (“client”)” 的错误提示,因为此时Pod的容器尚未启动。另外,请使用
curl -sS而不是单纯的-s。如果只使用-s,curl会忽略错误信息,你只会看到 “command terminated with exit code 28” 这样的提示。虽然结果是一样的——28确实表示连接超时——但使用-S> 可以让你看到详细的错误信息。出现超时现象而不是 “连接被拒绝”,正是这种策略导致数据包被默默地丢弃的体现;如果真的是被主动拒绝,系统会立即返回不同的错误信息。步骤 7:检查SPIRE中的工作负载条目
Cilium的SPIRE控制器管理器会自动为该集群中的Cilium安全身份创建相应的SPIFFE身份。你可以这样查看这些信息:
kubectl exec -n cilium-spire spire-server-0 -c spire-server -- \ /opt/spire/bin/spire-server entry show \ -selector cilium:mutual-auth每条记录都会将一个Cilium安全身份与一个SPIFFE ID对应起来。Cilium的操作系统会自动管理这个注册表,因此在使用Cilium的内置集成功能时,你根本不需要手动注册工作负载。
结论
IP地址代表的是位置信息,而非身份信息。在Kubernetes环境中,每次部署都会导致IP地址发生变化,因此任何基于IP地址匹配的策略都会随着时间的推移而失效。
而使用加密技术来标识工作负载,则可以从根本上解决这个问题。SPIFFE定义了这种识别模型:一个SPIFFE ID可以用来标识某个信任域内的工作负载,而X.509 SVID则可以将这个身份信息转化为任何TLS库都能验证的证书;SPIRE则负责实现这一机制:服务器同时充当CA和注册机构的角色,而每个节点上的代理则会通过Kubernetes PSAT机制来生成短期有效、会自动更新的SVID。
Cilium将这种身份识别机制融入到了网络架构中。只需在CiliumNetworkPolicy中添加
authentication.mode: required这一配置,其eBPF代理就会获取相关工作负载的SVID信息,执行相互TLS握手流程,并强制执行相应的访问控制规则。这种方式不需要额外的组件,也不需要修改应用程序代码,而且与服务网格相比,其开销几乎为零。整个解决方案只需通过一条Helm命令即可部署完成——所有的复杂性都体现在基础设施层面,而不会影响到你的代码。清理操作
# 删除演示用工作负载 kubectl delete deployment echo-server echo-client -n default kubectl delete service echo-server -n default kubectl delete pod unauthorized -n default kubectl delete ciliumnetworkpolicy echo-server-mtls -n default # 卸载Cilium组件(helm不会删除它创建的cilium-spire命名空间) helm uninstall cilium -n kube-system kubectl delete namespace cilium-spire # 删除整个集群(这是最简单的清理方式) kind delete cluster --name k8s-mtls



/filters:no_upscale()/articles/proactive-autoscaling-edge-kubernetes/en/resources/180figure-1-1770721060565.jpg)
