Submariner 是一个完全开源的项目,可以帮助我们在不同的 Kubernetes 集群之间(无论是在本地还是云端)实现网络通信。Submariner 有以下功能:
- 跨集群的 L3 连接
- 跨集群的服务发现
- Globalnet 支持 CIDR 重叠
- 提供命令行工具 subctl 简化部署和管理
- 兼容各种 CNI
1 Submariner 架构
Submariner 由几个主要部分组成:
- Broker: 本质上是两个用于交换集群信息的 CRD(Endpoint 和 Cluster),我们需要选择一个集群作为 Broker 集群,其他集群连接到 Broker 集群的 API Server 来交换集群信息:
- Endpoint:包含了 Gateway Engine 建立集群间连接需要的信息,例如 Private IP 和 Public IP,NAT 端口等等。
- Cluster:包含原始集群的静态信息,例如其 Service 和 Pod CIDR。
- Gateway Engine:管理连接到其他集群的隧道。
- Route Agent:负责将跨集群的流量路由到 active Gateway Node。
- Service Discovery: 提供跨集群的 DNS 服务发现。
- Globalnet(可选):处理具有重叠 CIDR 的集群互连。
- Submariner Operator:负责在 Kubernetes 集群中安装 Submariner 组件,例如 Broker, Gateway Engine, Route Agent 等等。
1.1 Service Discovery
Submariner 中跨集群的 DNS 服务发现由以下两个组件基于 Kubernetes Multi-Cluster Service APIs 的规范来实现:
- Lighthouse Agent:访问 Broker 集群的 API Server 与其他集群交换 ServiceImport 元数据信息。
- 对于本地集群中已创建 ServiceExport 的每个 Service,Agent 创建相应的 ServiceImport 资源并将其导出到 Broker 以供其他集群使用。
- 对于从其他集群导出到 Broker 中的 ServiceImport 资源,它会在本集群中创建它的副本。
- Lighthouse DNS Server:
- Lighthouse DNS Server 根据 ServiceImport 资源进行 DNS 解析。
- CoreDNS 配置为将
clusterset.local
域名的解析请求发往 Lighthouse DNS server。
MCS API 是 Kubernetes 社区定义的用于跨集群服务发现的规范,主要包含了 ServiceExport 和 ServiceImport 两个 CRD。
- ServiceExport 定义了暴露(导出)到其他集群的 Service,由用户在要导出的 Service 所在的集群中创建,与 Service 的名字和 Namespace 一致。
apiVersion: multicluster.k8s.io/v1alpha1
kind: ServiceExport
metadata:
name: my-svc
namespace: my-ns
- ServiceImport:当一个服务被导出后,实现 MCS API 的控制器会在所有集群(包括导出服务的集群)中自动生成一个与之对应的 ServiceImport 资源。
apiVersion: multicluster.k8s.io/v1alpha1
kind: ServiceImport
metadata:
name: my-svc
namespace: my-ns
spec:
ips:
- 42.42.42.42 # 跨集群访问的 IP 地址
type: "ClusterSetIP"
ports:
- name: http
protocol: TCP
port: 80
sessionAffinity: None
1.2 Gateway Engine
Gateway Engine 部署在每个集群中,负责建立到其他集群的隧道。隧道可以由以下方式实现:
- IPsec,使用 Libreswan 实现。这是当前的默认设置。
- WireGuard,使用 wgctrl 库实现。
- VXLAN,不加密。
可以在使用 subctl join
命令加入集群的时候使用 --cable-driver
参数设置隧道的类型。
Gateway Engine 部署为 DaemonSet,只在有 submariner.io/gateway=true
Label 的 Node 上运行,当我们使用 subctl join
命令加入集群的时候,如果没有 Node 有该 Label,会提示我们选择一个 Node 作为 Gateway Node。
Submariner 也支持 active/passive 高可用模式的 Gateway Engine,我们可以在多个节点上部署 Gateway Engine。在同一时间内,只能有一个 Gateway Engine 处于 active 状态来处理跨集的流量,Gateway Engine 通过领导者选举的方式确定 active 的实例,其他实例在 passive 模式下等待,准备在 active 实例发生故障时接管。
1.3 Globalnet
Submariner 的一大亮点是支持在不同集群间存在 CIDR 重叠的情况,这有助于减少网络重构的成本。例如,在部署过程中,某些集群可能使用了默认的网段,导致了 CIDR 重叠。在这种情况下,如果后续需要更改集群网段,可能会对集群的运行产生影响。
为了支持集群间重叠 CIDR 的情况,Submariner 通过一个 GlobalCIDR 网段(默认是 242.0.0.0/8)在流量进出集群时进行 NAT 转换,所有的地址转换都发生在 active Gateway Node 上。在 subctl deploy
部署 Broker 的时候可以通过 --globalnet-cidr-range
参数指定所有集群的全局 GlobalCIDR。在 subctl join
加入集群的时候还可以通过 --globalnet-cidr
参数指定该集群的 GlobalCIDR。
导出的 ClusterIP 类型的 Service 会从 GlobalCIDR 分配一个 Global IP 用于入向流量,对于 Headless 类型的 Service,会为每个关联的 Pod 分配一个 Global IP 用于入向和出向流量。
2 环境准备
在本次实验中,我们使用一台运行 Ubuntu 20.04 的虚拟机,并通过 Kind 启动多个 Kubernetes 集群来进行测试。
root@seven-demo:~# seven-demo
Static hostname: seven-demo
Icon name: computer-vm
Chassis: vm
Machine ID: f780bfec3c409135b11d1ceac73e2293
Boot ID: e83e9a883800480f86d37189bdb09628
Virtualization: kvm
Operating System: Ubuntu 20.04.5 LTS
Kernel: Linux 5.15.0-1030-gcp
Architecture: x86-64
安装相关软件和命令行工具。
# 安装 Docker,根据操作系统安装 https://docs.docker.com/engine/install/
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
curl \
gnupg
sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 安装 Kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.18.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
# 安装 kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin
apt install -y bash-completion
echo 'source <(kubectl completion bash)' >>~/.bashrc
# 安装 subctl
curl -Lo subctl-release-0.14-linux-amd64.tar.xz https://github.com/submariner-io/subctl/releases/download/subctl-release-0.14/subctl-release-0.14-linux-amd64.tar.xz
tar -xf subctl-release-0.14-linux-amd64.tar.xz
mv subctl-release-0.14/subctl-release-0.14-linux-amd64 /usr/local/bin/subctl
chmod +x /usr/local/bin/subctl
3 快速开始
3.1 创建集群
使用 Kind 创建一个 3 节点的集群,这里读者需要将 SERVER_IP
替换成自己服务器的 IP。默认情况下,Kind 将 Kubernetes API 服务器 IP
127.0.0.1
): 随机端口,这对于从本机与集群交互来说很好,但是在本实验中多个 Kind 集群之间需要通信,因此我们需要把 Kind 的 apiServerAddress 改成本机 IP。
# 替换成服务器 IP
export SERVER_IP="10.138.0.11"
kind create cluster --config - <<EOF
kind: Cluster
name: broker
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
networking:
apiServerAddress: $SERVER_IP
podSubnet: "10.7.0.0/16"
serviceSubnet: "10.77.0.0/16"
EOF
kind create cluster --config - <<EOF
kind: Cluster
name: c1
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
networking:
apiServerAddress: $SERVER_IP
podSubnet: "10.8.0.0/16"
serviceSubnet: "10.88.0.0/16"
EOF
kind create cluster --config - <<EOF
kind: Cluster
name: c2
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
networking:
apiServerAddress: $SERVER_IP
podSubnet: "10.9.0.0/16"
serviceSubnet: "10.99.0.0/16"
EOF
3.2 部署 Broker
在本次实验中,我们专门将一个集群配置为 Broker 集群。Broker 集群可以是专用集群,也可以是连接的集群之一。执行 subctl deploy-broker
命令部署 Broker,Broker 只包含了一组 CRD,并没有部署 Pod 或者 Service。
subctl --context kind-broker deploy-broker
部署完成后,会生成 broker-info.subm
文件,文件以 Base64 加密,其中包含了连接 Broker 集群 API Server 的地址以及证书信息,还有 IPsec 的密钥信息。
{
"brokerURL": "https://10.138.0.11:45681",
"ClientToken": {
"metadata": {
"name": "submariner-k8s-broker-admin-token-f7b62",
"namespace": "submariner-k8s-broker",
"uid": "3f949d19-4f42-43d6-af1c-382b53f83d8a",
"resourceVersion": "688",
"creationTimestamp": "2023-04-05T02:50:02Z",
"annotations": {
"kubernetes.io/created-by": "subctl",
"kubernetes.io/service-account.name": "submariner-k8s-broker-admin",
"kubernetes.io/service-account.uid": "da6eeba1-b707-4d30-8e1e-e414e9eae817"
},
"managedFields": [
{
"manager": "kube-controller-manager",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-04-05T02:50:02Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:data": {
".": {},
"f:ca.crt": {},
"f:namespace": {},
"f:token": {}
},
"f:metadata": {
"f:annotations": {
"f:kubernetes.io/service-account.uid": {}
}
}
}
},
{
"manager": "subctl",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-04-05T02:50:02Z",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:annotations": {
".": {},
"f:kubernetes.io/created-by": {},
"f:kubernetes.io/service-account.name": {}
}
},
"f:type": {}
}
}
]
},
"data": {
"ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1EUXdOVEF5TkRVMU1Wb1hEVE16TURRd01qQXlORFUxTVZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSytXCmIzb0h1ZEJlbU5tSWFBNXQrWmI3TFhKNXRLWDB6QVc5a0tudjQzaGpoTE84NHlSaEpyY3ZSK29QVnNaUUJIclkKc01tRmx3aVltbU5ORzA4c2NLMTlyLzV0VkdFR2hCckdML3VKcTIybXZtYi80aHdwdmRTQjN0UDlkU2RzYUFyRwpYYllwOE4vUmlheUJvbTBJVy9aQjNvZ0MwK0tNcWM0NE1MYnBkZXViWnNSckErN2pwTElYczE3OGgxb25kdGNrClIrYlRnNGpjeS92NTkrbGJjamZSeTczbUllMm9DbVFIbE1XUFpSTkMveDhaTktGekl6UHc4SmZSOERjWk5Xc1YKa1NBUVNVUkpnTEhBbjY5MlhDSEsybmJuN21pcjYvYVZzVVpyTGdVNC9zcWg3QVFBdDFGQkk3NDRpcithTjVxSwpJRnRJenkxU3p2ZEpwMThza3EwQ0F3RUFBYU5aTUZjd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZFQUhFbndHditwTXNVcHVQRXNqbkQwTEgvSFpNQlVHQTFVZEVRUU8KTUF5Q0NtdDFZbVZ5Ym1WMFpYTXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQTFGckk1cGR1VTFsQzluVldNNwowYlc2VFRXdzYwUTlFVWdsRzc4bkRFZkNKb3ovY2xWclFNWGZrc2Zjc1VvcHZsaE5yWFlpbmd0UEE4aEMrTnRJCmdPZElDZDJGaWFOTjRCYkt3a1NmRkQvbmhjWDU1WmQ0UzN1SzZqb2JWVHIzaXVJRVhIdHg0WVIyS1ZuZitTMDUKQTFtbXdzSG1ZbkhtWEllOUEyL3hKdVhtSnNybWljWTlhMXhtSXVyYzhNalBsa1pZWVU1OFBvZHJFNi9XcnBaawpBbW9qcERIWWIrbnZxa0FuaG9hYUV3b2FEVGxYRjY0M3lVLy9MZE4wTmw5MWkvSHNwQ2tZdVFrQjJmQXNkSGNaCkMrdzQ4WVhYT21pSzZXcmJGYVJnaEVKdjB6UjdsZk50UEVZVWJHWEFxV0ZlSnFTdnM5aUYwbFV1NzZDNkt3YWIKbmdnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"namespace": "c3VibWFyaW5lci1rOHMtYnJva2Vy",
"token": "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklqaHZWVnBuZVVoZk1uVTFjSEJxU1hOdE1UTk1NbUY0TFRaSlIyVlZVRGd4VjI1dmMyNXBNMjFYZFhjaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUp6ZFdKdFlYSnBibVZ5TFdzNGN5MWljbTlyWlhJaUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sWTNKbGRDNXVZVzFsSWpvaWMzVmliV0Z5YVc1bGNpMXJPSE10WW5KdmEyVnlMV0ZrYldsdUxYUnZhMlZ1TFdZM1lqWXlJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbk4xWW0xaGNtbHVaWEl0YXpoekxXSnliMnRsY2kxaFpHMXBiaUlzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVnlkbWxqWlMxaFkyTnZkVzUwTG5WcFpDSTZJbVJoTm1WbFltRXhMV0kzTURjdE5HUXpNQzA0WlRGbExXVTBNVFJsT1dWaFpUZ3hOeUlzSW5OMVlpSTZJbk41YzNSbGJUcHpaWEoyYVdObFlXTmpiM1Z1ZERwemRXSnRZWEpwYm1WeUxXczRjeTFpY205clpYSTZjM1ZpYldGeWFXNWxjaTFyT0hNdFluSnZhMlZ5TFdGa2JXbHVJbjAub1JHM2d6Wno4MGVYQXk5YlZ5b1V2NmoyTERvdFJiNlJyOTF4d0ZiTDMwdFNJY3dnS3FYd3NZbVV1THhtcFdBb2M5LWRSMldHY0ZLYklORlZmUUttdVJMY2JsenlTUFFVMlB3WVVwN1oyNnlxYXFOMG1UQ3ZNWWxSeHp6cWY3LXlXUm8yNE9pWS1nMnNmNmNrRzRPMkdwa2MwTlNoOWRTUGY4dXJTbjZSVGJwbjFtcFZjTy1IQjJWeU5hTE9EdmtWS3RLVFJfVS1ZRGc1NzVtczM0OXM0X2xMZjljZjlvcjFaQXVvXzcyN0E5U0VvZ0JkN3BaSndwb0FEUHZRb1NGR0VLQWZYYTFXXzJWVE5PYXE4cUQxOENVbXVFRUFxMmtoNElBN0d5LVRGdUV2Q0JYUVlzRHYzUFJQTjZpOGlKSFBLVUN1WVNONS1NT3lGX19aNS1WdlhR"
},
"type": "kubernetes.io/service-account-token"
},
"IPSecPSK": {
"metadata": {
"name": "submariner-ipsec-psk",
"creationTimestamp": null
},
"data": {
"psk": "NL7dUK+RagDKPQZZj+7Q7wComj0/wLFbfvnHe12hHxR8+d/FnkEqXfmh8JMzLo6h"
}
},
"ServiceDiscovery": true,
"Components": [
"service-discovery",
"connectivity"
],
"CustomDomains": null
}