云原生 EgressGateway：适用于 Calico，Flannel，Weave 和 Spiderpool 等 CNI 的出口网关
Tags: kubernetes, egress, ingress, k8s
Published: 2023-12-29 10:14:05

Calico 是一套开源的网络和网络安全解决方案，也是作为 Kubernetes 容器网络解决方案 (CNI: Container Network Interface) 的一种实现。

## 当前痛点

在多个集群互相访问，企业管理员希望规范化跨集群网络请求。如下图集群 B 中的 Pod 为一组应用，管理员希望右侧同一业务组的 Pod 出集群的流量都使用特定 IP。

<img src="https://lh7-us.googleusercontent.com/p6UxrvG7KbXzPjvkHBcFH8JshrH4UEdVumAwYQrauGqRMIWrPIl0Ms8aIuzkduP45eqxfYfPzxN_ad_eCoH3ROfUqOLVagt6xwwQVtQApl01argjrB3ogKc9ujXs4wJbc2Mtzv2XCpPb8xGeYl94NzDiqg=s2048" alt="img" style="zoom:30%;" />

## EgressGateway 解决方式

👋 针对上述的需求，我们使用 https://github.com/spidernet-io/egressgateway 来解决这个问题。EgressGateway 是一个开源的云原生出口网关，兼容如下 [Calico](https://github.com/projectcalico/calico)，[Flannel](https://github.com/flannel-io/flannel)，[Weave](https://github.com/weaveworks/weave) 和 [Spiderpool](https://github.com/spidernet-io/spiderpool) 的 CNI 网络方案。EgressGateway 有如下特性：

* 支持通过 `EgressPolicy` 配置不通租户的的 Egress 策略
* 支持通过 `EgressClusterPolicy` 集群级 Egress 策略；
* 支持 IPv4/IPv6 双栈；
* 拥有低内核兼容性；
* 自动检测集群内部流量；
* 支持设置租户/集群默认 EgressGateway。

如果你想了解更多详情，请访问 EgressGateway https://spidernet-io.github.io/egressgateway/dev/ 文档站。

<img src="https://lh7-us.googleusercontent.com/ynkH_Fy1VURx9puFnQ5OZ4ZbgNhbgRDKjY2Divknk5BUkbfq5NuqPWRD_awtRiGDykPIyarZxoeAidF7LgPuR9QZirt9cHTLkZ_irgYKwQWji_8r_wGbmbKmtqocQcPugqjux8-6m6KxLO4DaWc9-vchMA=s2048" alt="img" style="zoom:29%;" />

### 实现原理

在数据面，我们通过实施了如下 datapath 来实现 EgressGateway 的功能。左侧节点被 EgressPolicy 策略命中的 Pod 流量，会通过 iptabels, route, ipset 规则并通过 vxlan 转发到网关节点，然后从网关节点将源 IP 转为 Egress IP 离开集群。

<img src="https://github.com/spidernet-io/egressgateway/blob/main/docs/proposal/01-egress-gateway/Egress-Gateway-Datapath.png?raw=true" alt="Egress-Gateway-Datapath.png" style="zoom: 31%;" />

### 安装实施

1. 将 Calico 的的 `FELIX_CHAININSERTMODE` 设置为 `Append`。

   ```bash
   kubectl set env daemonset -n kube-system calico-node FELIX_CHAININSERTMODE=Append
   ```


2. 安装 EgressGateway。

   ```bash
   helm repo add egressgateway https://spidernet-io.github.io/egressgateway/
   helm repo update
   
   helm install egressgateway egressgateway/egressgateway \
        -n kube-system \
        --set feature.tunnelIpv4Subnet="192.200.0.1/16" \ # 选择一个没有被使用的虚拟子网
        --wait --debug
   ```

3. 验证 EgressGateway 都已经启动并就绪。

   ```shell
   $ kubectl get pod -n kube-system | grep egressgateway
   egressgateway-agent-29lt5                  1/1     Running   0          9h
   egressgateway-agent-94n8k                  1/1     Running   0          9h
   egressgateway-agent-klkhf                  1/1     Running   0          9h
   egressgateway-controller-5754f6658-7pn4z   1/1     Running   0          9h
   ```

4. 选择一个或者多个节点作为出口网关。我们通过给 node 打上特点的 label 来标识这些节点。

   ```shell
   kubectl get node -o wide
   kubectl label node $NODE_NAME egressgateway="true"
   ```

5. 创建 EgressGateway 实例。在创建 EgressGateway 时，我们在 `spec.nodeSelector` 中将指定网关节点为上面打 label 的节点，并为这些网关分配了可用的 `spec.ippools` 的地址。

   ```shell
   cat <<EOF | kubectl apply -f -
   apiVersion: egressgateway.spidernet.io/v1beta1
   kind: EgressGateway
   metadata:
     name: default
   spec:
     ippools:
       ipv4:
       - "172.22.0.100-172.22.0.110"
     nodeSelector:
       selector:
         matchLabels:
           egressgateway: "true"
   EOF
   ```

6. 执行下面命令检查刚才创建的网关，我们可以在 `status` 中看到已经选择的 2 个节点已经就绪。稍后我们可以通过创建 `EgressPolicy` CR 来选择可使用的 EgressGateway，并通过 label 匹配业务 Pod，使业务流量通过网关节点离开集群。

   ```yaml
   $ kubectl get EgressGateway default -o yaml
   apiVersion: egressgateway.spidernet.io/v1beta1
   kind: EgressGateway
   metadata:
     name: default
     uid: 7ce835e2-2075-4d26-ba63-eacd841aadfe
   spec:
     ippools:
       ipv4:
       - 172.22.0.100-172.22.0.110
       ipv4DefaultEIP: 172.22.0.110
     nodeSelector:
       selector:
         matchLabels:
           egressgateway: "true"
   status:
     nodeList:
     - name: egressgateway-worker1
       status: Ready
     - name: egressgateway-worker2
       status: Ready
   ```

7. 在开始之前我们先在集群外的任意机器，启动一个容器模拟被访问的外部服务。

   ```shell
   docker run -d --net=host ghcr.io/spidernet-io/egressgateway-nettools:latest /usr/bin/nettools-server -protocol web -webPort 8080
   ```

8. 我们在集群内部创建一个 Deployment，用于模拟需要访问外部使用统一 Egress IP 的业务应用。

   ```shell
   kubectl create deployment visitor --image nginx
   ```

9. 此时我们创建一个 EgressPolicy，来为其实施 Egress 策略。我们通过 `spec.egressGatewayName` 制定了上面创建的 EgressGateway 名称。

   ```shell
   cat <<EOF | kubectl apply -f -
   apiVersion: egressgateway.spidernet.io/v1beta1
   kind: EgressPolicy
   metadata:
    name: test
    namespace: default
   spec:
    egressGatewayName: default
    appliedTo:
     podSelector:
      matchLabels:
       app: "visitor"
   EOF
   ```

10. 通过 kubectl exec 进我们创建的业务应用，执行测试。

    ```shell
    kubectl exec -it visitor-6764bb48cc-29vq9 bash
    $ curl 10.6.1.92:8080
    Remote IP: 172.22.0.110
    ```

    🎉 通过结果看到，我们的 Egress IP 已经变为了 EgressGateway 池中配置的 IP。当然也可以在创建 EgressPolicy 指定使用的 Egress IP。

## 总结

在本文中我们分析了 Calico 网络下的 Egress IP 网络需求，并通过 EgressGateway 来解决这个问题，同时对 EgressGateway 的网络原理做了阐述，最后我们介绍了 EgressGateway 的更多功能。