

 **帮助改进此页面** 

要帮助改进本用户指南，请选择位于每个页面右侧窗格中的**在 GitHub 上编辑此页面**链接。

# 混合节点的 Kubernetes 概念
<a name="hybrid-nodes-concepts-kubernetes"></a>

本页面详细介绍了支持 EKS 混合节点系统架构的关键 Kubernetes 概念。

## VPC 中的 EKS 控制面板
<a name="hybrid-nodes-concepts-k8s-api"></a>

EKS 控制面板 ENI 的 IP 存储在 `default` 命名空间的 `kubernetes` `Endpoints` 对象中。EKS 创建新的 ENI 或移除较旧的 ENI 时，EKS 会更新此对象，因此 IP 列表始终是最新的。

您可以通过 `kubernetes` 服务使用这些端点，也可以在 `default` 命名空间中使用。`ClusterIP` 类型的这种服务总是会获分配集群服务 CIDR 的第一个 IP。例如，对于服务 CIDR `172.16.0.0/16`，服务 IP 将为 `172.16.0.1`。

通常，这就是容器组（pod）（无论是在云端还是混合节点中运行）访问 EKS Kubernetes API 服务器的方式。容器组（pod）使用服务 IP 作为目标 IP，然后将其转换为其中一个 EKS 控制面板 ENI 的实际 IP。主要例外是 `kube-proxy`，因为它设置了转换。

## EKS API 服务器端点
<a name="hybrid-nodes-concepts-k8s-eks-api"></a>

`kubernetes` 服务 IP 并不是访问 EKS API 服务器的唯一途径。您创建集群时，EKS 还会创建一个 Route53 DNS 名称。这是调用 EKS `DescribeCluster` API 操作时您的 EKS 集群的 `endpoint` 字段。

```
{
    "cluster": {
        "endpoint": "https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.gr7.us-west-2.eks.amazonaws.com",
        "name": "my-cluster",
        "status": "ACTIVE"
    }
}
```

在公有端点访问或公有和私有端点访问集群中，您的混合节点默认会将此 DNS 名称解析为可通过互联网路由的公有 IP。在私有端点访问集群中，DNS 名称解析为 EKS 控制面板 ENI 的私有 IP。

这就是 `kubelet` 和 `kube-proxy` 访问 Kubernetes API 服务器的方式。如果您希望所有 Kubernetes 集群流量都通过 VPC，则需要在私有访问模式下配置集群，或者修改本地 DNS 服务器，将 EKS 集群端点解析为 EKS 控制面板 ENI 的私有 IP。

## `kubelet` 端点
<a name="hybrid-nodes-concepts-k8s-kubelet-api"></a>

`kubelet` 公开了多个 REST 端点，允许系统的其他部分与每个节点交互并从每个节点收集信息。在大多数集群中，流向 `kubelet` 服务器的大部分流量来自控制面板，但某些监控代理也可能与该服务器交互。

通过此接口，`kubelet` 可以处理各种请求：获取日志 (`kubectl logs`)、在容器内执行命令 (`kubectl exec`) 以及端口转发流量 (`kubectl port-forward`)。这些请求中的每一个都通过 `kubelet` 与底层容器运行时进行交互，在集群管理员和开发人员看来，这是一个无缝的过程。

此 API 最常见的使用器是 Kubernetes API 服务器。您使用前面提到的任何 `kubectl` 命令时，`kubectl` 会向 API 服务器发出 API 请求，然后服务器调用运行容器组（pod）的节点的 `kubelet` API。这就是需要从 EKS 控制面板访问节点 IP 的主要原因，也解释了为什么即使您的容器组（pod）正在运行，但如果节点路由配置错误，您也无法访问它们的日志或 `exec`。

 **节点 IP** 

EKS 控制面板与节点通信时，它会使用 `Node` 对象状态 (`status.addresses`) 中报告的地址之一。

对于 EKS 云节点，kubelet 通常会在节点注册期间将 EC2 实例的私有 IP 报告为 `InternalIP`。然后，云控制器管理器（CCM）会验证此 IP，确保它属于 EC2 实例。此外，CCM 通常会将实例的公有 IP（作为 `ExternalIP`）和 DNS 名称（`InternalDNS` 和 `ExternalDNS`）添加到节点状态。

但是，没有适用于混合节点的 CCM。您向 EKS 混合节点 CLI (`nodeadm`) 注册混合节点时，它会将 kubelet 配置为直接以节点状态报告计算机的 IP，而不使用 CCM。

```
apiVersion: v1
kind: Node
metadata:
  name: my-node-1
spec:
  providerID: eks-hybrid:///us-west-2/my-cluster/my-node-1
status:
  addresses:
  - address: 10.1.1.236
    type: InternalIP
  - address: my-node-1
    type: Hostname
```

如果您的计算机有多个 IP，kubelet 会按照自己的逻辑选择其中一个 IP。您可以使用 `--node-ip` 标志控制选定的 IP，并且可以在 `spec.kubelet.flags` 的 `nodeadm` 配置中传入该标志。只有 `Node` 对象中报告的 IP 需要来自 VPC 的路由。您的计算机可能有无法从云端访问的其他 IP。

## `kube-proxy`
<a name="hybrid-nodes-concepts-k8s-kube-proxy"></a>

 `kube-proxy` 负责在每个节点的网络层实现服务抽象。它充当流向 Kubernetes 服务的流量的网络代理和负载均衡器。通过持续监控 Kubernetes API 服务器中与服务和端点相关的更改，`kube-proxy` 会动态更新底层主机的网络规则，确保流量得到正确引导。

在 `iptables` 模式下，`kube-proxy` 会对多个 `netfilter` 链进行编程以处理服务流量。这些规则构成以下层次结构：

1.  **KUBE-SERVICES 链**：所有服务流量的入口点。它具有与每项 `ClusterIP` 服务和端口匹配的规则。

1.  **KUBE-SVC-XXX 链**：特定于服务的链对每项服务都有负载均衡规则。

1.  **KUBE-SEP-XXX 链**：特定于端点的链具有实际的 `DNAT` 规则。

让我们来看看 `default` 命名空间中的 `test-server` 服务会发生什么：\$1 服务 ClusterIP：`172.16.31.14` \$1 服务端口：`80` \$1 支持性容器组（pod）：`10.2.0.110`、`10.2.1.39` 和 `10.2.2.254` 

检查 `iptables` 规则（使用 `iptables-save 0 grep -A10 KUBE-SERVICES`）时：

1. 在 **KUBE-SERVICES** 链中，我们找到了一条与该服务匹配的规则：

   ```
   -A KUBE-SERVICES -d 172.16.31.14/32 -p tcp -m comment --comment "default/test-server cluster IP" -m tcp --dport 80 -j KUBE-SVC-XYZABC123456
   ```
   + 此规则与发往 172.16.31.14:80 的数据包匹配
   + 注释指出了此规则的用途：`default/test-server cluster IP`
   + 匹配的数据包跳转到 `KUBE-SVC-XYZABC123456` 链

1. **KUBE-SVC-XYZABC123456** 链具有基于概率的负载均衡规则：

   ```
   -A KUBE-SVC-XYZABC123456 -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-POD1XYZABC
   -A KUBE-SVC-XYZABC123456 -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-POD2XYZABC
   -A KUBE-SVC-XYZABC123456 -j KUBE-SEP-POD3XYZABC
   ```
   + 第一条规则：有 33.3% 的概率跳转到 `KUBE-SEP-POD1XYZABC` 
   + 第二条规则：剩余流量（占总流量的 33.3%）有 50% 的概率跳转到 `KUBE-SEP-POD2XYZABC` 
   + 最后一条规则：所有剩余流量（占总流量的 33.3%）都将跳转到 `KUBE-SEP-POD3XYZABC` 

1. 各个 **KUBE-SEP-XXX** 链都执行 DNAT（目标 NAT）：

   ```
   -A KUBE-SEP-POD1XYZABC -p tcp -m tcp -j DNAT --to-destination 10.2.0.110:80
   -A KUBE-SEP-POD2XYZABC -p tcp -m tcp -j DNAT --to-destination 10.2.1.39:80
   -A KUBE-SEP-POD3XYZABC -p tcp -m tcp -j DNAT --to-destination 10.2.2.254:80
   ```
   + 这些 DNAT 规则会重写目标 IP 和端口，将流量引导至特定容器组（pod）。
   + 每条规则处理大约 33.3% 的流量，从而在 `10.2.0.110`、`10.2.1.39` 和 `10.2.2.254` 之间提供均衡的负载均衡。

这种多级链结构让 `kube-proxy` 能够通过内核级别的数据包操作高效地实现服务负载均衡和重定向，而无需在数据路径中使用代理进程。

### 对 Kubernetes 操作的影响
<a name="hybrid-nodes-concepts-k8s-operations"></a>

节点上损坏的 `kube-proxy` 会阻止该节点正确路由服务流量，导致依赖集群服务的容器组（pod）超时或连接失败。首次注册节点时，这可能会造成特别大的干扰。CNI 需要先与 Kubernetes API 服务器通信以获取信息，例如节点的容器组（pod）CIDR，然后才能配置任何容器组（pod）网络。为此，它使用 `kubernetes` 服务 IP。但是，如果 `kube-proxy` 无法启动或未能设置正确的 `iptables` 规则，则发往 `kubernetes` 服务 IP 的请求不会转换为 EKS 控制面板 ENI 的实际 IP。因此，CNI 将进入崩溃循环，所有的容器组（pod）都无法正常运行。

我们知道容器组（pod）使用 `kubernetes` 服务 IP 与 Kubernetes API 服务器通信，但 `kube-proxy` 需要先设置 `iptables` 规则才能使其正常工作。

`kube-proxy` 如何与 API 服务器通信？

`kube-proxy` 必须配置为使用 Kubernetes API 服务器的实际 IP 或解析为它们的 DNS 名称。对于 EKS，EKS 将默认 `kube-proxy` 配置为指向您在创建集群时 EKS 创建的 Route53 DNS 名称。您可以在 `kube-system` 命名空间的 `kube-proxy` ConfigMap 中看到此值。此 ConfigMap 的内容是注入到 `kube-proxy` 容器组（pod）中的 `kubeconfig`，因此请寻找 `clusters0.cluster.server` 字段。此值将与 EKS 集群的 `endpoint` 字段匹配（在调用 EKS `DescribeCluster` API 时）。

```
apiVersion: v1
data:
  kubeconfig: |-
    kind: Config
    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        server: https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.gr7.us-west-2.eks.amazonaws.com
      name: default
    contexts:
    - context:
        cluster: default
        namespace: default
        user: default
      name: default
    current-context: default
    users:
    - name: default
      user:
        tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
kind: ConfigMap
metadata:
  name: kube-proxy
  namespace: kube-system
```

## 可路由的远程容器组（pod）CIDR
<a name="hybrid-nodes-concepts-k8s-pod-cidrs"></a>

[混合节点的联网概念](hybrid-nodes-concepts-networking.md) 页面详细说明了在混合节点上运行 Webhook 或让在云节点上运行的容器组（pod）与在混合节点上运行的容器组（pod）通信的要求。关键要求是，本地路由器需要知道哪个节点负责特定容器组（pod）IP。有多种方法可以实现这一点，包括边界网关协议（BGP）、静态路由和地址解析协议（ARP）代理。以下部分将介绍这些方法。

 **边界网关协议（BGP）** 

如果您的 CNI 支持这种协议（例如 Cilium 和 Calico），您可以使用 CNI 的 BGP 模式，将每节点容器组（pod）CIDR 的路由从节点传播到本地路由器。使用 CNI 的 BGP 模式时，您的 CNI 充当虚拟路由器，因此本地路由器认为容器组（pod）CIDR 属于不同的子网，而您的节点是该子网的网关。

![\[混合节点 BGP 路由\]](http://docs.aws.amazon.com/zh_cn/eks/latest/userguide/images/hybrid-nodes-bgp.png)


 **静态路由** 

您也可以在本地路由器中配置静态路由。这是将本地容器组（pod）CIDR 路由到您的 VPC 的最简单方法，但这也是最容易出错且最难维护的方法。您需要确保现有节点及其分配的容器组（pod）CIDR 的路由始终是最新的。如果您的节点数量很少且基础设施是静态的，那么这是一个可行的选择，并且无需在路由器中支持 BGP。如果您选择这样做，建议使用要分配给每个节点的容器组（pod）CIDR 切片来配置 CNI，而不是让其 IPAM 决定。

![\[混合节点静态路由\]](http://docs.aws.amazon.com/zh_cn/eks/latest/userguide/images/hybrid-nodes-static-routes.png)


 **地址解析协议（ARP）代理** 

ARP 代理是使本地容器组（pod）IP 可路由的另一种方法，当混合节点与本地路由器位于同一个第 2 层网络上时，这种方法特别有用。启用 ARP 代理后，节点会响应其托管的容器组（pod）IP 的 ARP 请求，即使这些 IP 属于不同的子网，也会如此。

本地网络上的设备尝试访问容器组（pod）IP 时，它会首先发送 ARP 请求，询问“谁拥有此 IP？”。托管该容器组（pod）的混合节点将使用自己的 MAC 地址进行响应，表示“我可以处理该 IP 的流量。” 这无需配置路由器，即可在本地网络上的设备和容器组（pod）之间创建直接路径。

要使此方法起作用，您的 CNI 必须支持代理 ARP 功能。Cilium 内置了对代理 ARP 的支持，您可以通过配置启用该功能。关键考虑因素是容器组（pod）CIDR 不得与环境中的任何其他网络重叠，因为这可能会导致路由冲突。

这种方法有几个优点：\$1 无需为路由器配置 BGP，也无需维护静态路由 \$1 在路由器配置不由您控制的环境中效果良好

![\[混合节点 ARP 代理\]](http://docs.aws.amazon.com/zh_cn/eks/latest/userguide/images/hybrid-nodes-arp-proxy.png)


## 容器组（pod）到容器组（pod）封装
<a name="hybrid-nodes-concepts-k8s-pod-encapsulation"></a>

在本地环境中，CNI 通常使用封装协议来创建叠加网络，这种网络无需重新配置即可在物理网络之上运行。此部分介绍了这种封装的工作原理。请注意，根据您使用的 CNI，某些细节可能会有所不同。

封装将原始容器组（pod）网络数据包包装在另一个网络数据包中，该数据包可以通过底层物理网络路由。这允许容器组（pod）在运行相同 CNI 的节点之间进行通信，物理网络不必理解如何路由这些容器组（pod）CIDR。

Kubernetes 中最常用的封装协议是虚拟可扩展局域网（VXLAN），但也可使用其他封装协议（例如 `Geneve`），具体取决于您的 CNI。

### VXLAN 封装
<a name="_vxlan_encapsulation"></a>

VXLAN 将第 2 层以太网帧封装在 UDP 数据包中。当一个容器组（pod）向不同节点上的另一个容器组（pod）发送流量时，CNI 会执行以下操作：

1. CNI 拦截来自容器组（pod）A 的数据包

1. CNI 将原始数据包封装在 VXLAN 标头中

1. 然后，这个封装后的数据包通过节点的常规网络堆栈发送到目标节点

1. 目标节点上的 CNI 解开数据包并将其传送到容器组（pod）B

以下是 VXLAN 封装期间，数据包结构的情况：

容器组（pod）到容器组（pod）的原始数据包：

```
+-----------------+---------------+-------------+-----------------+
| Ethernet Header | IP Header     | TCP/UDP     | Payload         |
| Src: Pod A MAC  | Src: Pod A IP | Src Port    |                 |
| Dst: Pod B MAC  | Dst: Pod B IP | Dst Port    |                 |
+-----------------+---------------+-------------+-----------------+
```

VXLAN 封装后：

```
+-----------------+-------------+--------------+------------+---------------------------+
| Outer Ethernet  | Outer IP    | Outer UDP    | VXLAN      | Original Pod-to-Pod       |
| Src: Node A MAC | Src: Node A | Src: Random  | VNI: xx    | Packet (unchanged         |
| Dst: Node B MAC | Dst: Node B | Dst: 4789    |            | from above)               |
+-----------------+-------------+--------------+------------+---------------------------+
```

VXLAN 网络标识符（VNI）用于区分不同的叠加网络。

### 容器组（pod）通信场景
<a name="_pod_communication_scenarios"></a>

 **同一个混合节点上的容器组（pod）** 

同一个混合节点上的容器组（pod）通信时，通常不需要封装。CNI 设置本地路由，通过节点的内部虚拟接口引导容器组（pod）之间的流量：

```
Pod A -> veth0 -> node's bridge/routing table -> veth1 -> Pod B
```

数据包永远不会离开节点，也不需要封装。

 **不同混合节点上的容器组（pod）** 

不同混合节点上的容器组（pod）之间的通信需要封装：

```
Pod A -> CNI -> [VXLAN encapsulation] -> Node A network -> router or gateway -> Node B network -> [VXLAN decapsulation] -> CNI -> Pod B
```

这样，容器组（pod）流量就可以遍历物理网络基础架构，而物理网络不必理解容器组（pod）IP 路由。