简介
从Calico v3.13 开始集成了 eBPF
数据平面。关于什么是 eBPF
, 以及 Calico
为什么引入了 eBPF
, 并不是本篇文章的重点,感兴趣的读者可以自行阅读相关文档。在尝试了解 eBPF
相关技术的过程中,除了 eBPF
相比于 Calico 的默认基于 iptables
数据平面具有更高的吞吐量以外,便是source IP preservation
的这个 feature 。在 K8s 中通常都是直接或者间接以nodeport
方式对外暴露接口的。而对于 K8s 这个分布式集群来讲,通常客户端连接 node port 端口的节点和负责响应请求的后端业务 pod 所在的节点不是同一个节点,为了打通整个数据链路,就不可避免的引入了SNAT
。但是这样显然也会带来一个 side effect,即业务 pod 在收到 packet 以后,其 src ip 已经不再是客户端的实际 ip(被伪装成节点的内网 IP )。另一方面,对于一些业务应用来讲,获取客户端 IP 是一个实实在在的刚需。比如:业务应用需要通过客户端 IP 来获取客户登陆的 geo 信息。当前 K8s 主要是通过设置 externaltrafficpolicy
来规避这个问题的,但是这个方案本身并不能完全令人满意。Calico 从 v3.13 开始通过集成 eBPF
优雅地解决了这个问题。
在本篇文章中,我们将首先演示通过KubeKey创建一个标准的 K8s 集群,并切换数据平面到 eBPF
,最后基于该数据平面做一个简单的演示。
前提条件
较新的内核, 一般 v4.18+ 即可。
笔者的测试集群:
部署 K8s 集群
Kubekey 默认的 CNI 插件为 Calico(ipip模式)。这里为了部署方便,直接使用KubeKey部署了一个全新的 K8s 集群,版本 v1.18.6 。KubeKey 的详细用法参见文档。
切换 Calico 数据平面
Calico 支持多种数据平面,通过修改配置可以方便地进行切换,详细信息可以参见官方文档。
主要分为以下几步:
- 确认 BPF 文件系统已经挂载:
mount | grep "/sys/fs/bpf"
如果能看到以下信息,则代表BPF文件系统已经挂载:
创建Calico配置文件:
首先获取ks-apiserver endpoints信息:
kubectl get endpoints kubernetes -o wide
- 由于KubeKey是通过manifest方式安装的Calico,这里我们只需要创建一个cm即可:
kind: ConfigMap
apiVersion: v1
metadata:
name: kubernetes-services-endpoint
namespace: kube-system
data:
KUBERNETES_SERVICE_HOST: "<API server host>"
KUBERNETES_SERVICE_PORT: "<API server port>"
- 重启Calico pods,并等待Calico pod重新变为running状态
kubectl delete pod -n kube-system -l k8s-app=calico-node
kubectl delete pod -n kube-system -l k8s-app=calico-kube-controllers
kubectl patch ds -n kube-system kube-proxy -p '{"spec":{"template":{"spec":{"nodeSelector":{"non-calico": "true"}}}}}'
calicoctl patch felixconfiguration default --patch='{"spec": {"bpfEnabled": true}}'
- 由于我们需要保留客户端IP,所以需要开启
DSR
模式。
calicoctl patch felixconfiguration default --patch='{"spec": {"bpfExternalServiceMode": "DSR"}}'
至此,Calico 的整个网络环境已经配置完毕。
体验
为了验证 Calico 切换到 eBPF
数据平面以后,后端确实可以拿到客户端的真实 IP ,下面我们会在集群中部署一个 Nginx 服务,并通过 nodeport 方式暴露接口。
创建 Nginx 实例并暴露外部接口:
master:~$ kubectl apply -f - <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
nodePort: 30604
EOF
等待 Pod 变为 Running 状态:
外部调用 Nginx 服务:
curl http://<external-ip>:30604
查询 Nginx 日志,查看client IP:
注意:如果集群本身部署在云平台环境中,如果节点位于 VPC 网络当中,需要设置相应的端口转发规则,并开启相应的防火墙端口。