

 **このページの改善にご協力ください** 

このユーザーガイドに貢献するには、すべてのページの右側のペインにある「**GitHub でこのページを編集する**」リンクを選択してください。

# ハイブリッドノードにおける Kubernetes の概念
<a name="hybrid-nodes-concepts-kubernetes"></a>

このページでは、EKS Hybrid Nodes システムアーキテクチャを支える Kubernetes の主要な概念について詳しく説明します。

## VPC の EKS コントロールプレーン
<a name="hybrid-nodes-concepts-k8s-api"></a>

EKS コントロールプレーン ENI の IP は、`default` デフォルトの名前空間にある `kubernetes` `Endpoints` オブジェクトに保存されます。EKS では、ENI の新規作成や削除が行わると、このオブジェクトが更新され、IP リストが常に最新状態に保たれます。

これらのエンドポイントは、`kubernetes` サービスを通じて使用できます。このサービスも `default` の名前空間に存在します。この `ClusterIP` タイプのサービスには、クラスターのサービス CIDR に含まれる最初の IP が割り当てられます。例えば、サービス CIDR が `172.16.0.0/16` の場合、サービス IP は `172.16.0.1` になります。

通常、ポッドは (稼働がクラウドノード上かハイブリッドノード上かに関係なく) そのように EKS Kubernetes API サーバーにアクセスします。サービス IP を宛先 IP として使用し、これをいずれかの EKS コントロールプレーン ENI に設定されている実際の IP に変換します。こうした動作の主な例外として `kube-proxy` があります。これによって変換を設定するからです。

## EKS API サーバーエンドポイント
<a name="hybrid-nodes-concepts-k8s-eks-api"></a>

EKS API サーバーには `kubernetes` サービス IP 以外の方法でもアクセスできます。EKS では、クラスター作成時に Route53 DNS 名も作成されます。これが、EKS の `endpoint` API アクションを呼び出す際に、EKS クラスターの `DescribeCluster` フィールド値になります。

```
{
    "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` サーバーに向かうトラフィックの大部分は、コントロールプレーンで生じたものですが、特定のモニタリングエージェントも kubelet と通信する場合があります。

このインターフェイスを通じて、`kubelet` では、ログの取得 (`kubectl logs`)、コンテナ内でのコマンド実行 (`kubectl exec`)、トラフィックのポート転送 (`kubectl port-forward`) といった各種リクエストが処理されます。こうしたリクエストでは、基盤のコンテナランタイム環境との通信が `kubelet` を介して行われるため、クラスター管理者や開発者にとってシームレスなユーザー体験が実現します。

この API で最も一般的なコンシューマーとなるのは Kubernetes API サーバーです。上記の `kubectl` コマンドのいずれかを使用すると、`kubectl` が API サーバーに API リクエストを送信し、その後 API サーバーが、ポッドが稼働しているノードの `kubelet` API を呼び出します。そのため、多くの場合、ノード IP が EKS コントロールプレーンから到達可能である必要があります。また、ノードルートを誤って設定すると、ポッドが稼働中であっても、ログや `exec` コマンドを利用できません。

 **ノード IP** 

EKS コントロールプレーンは、ノードと通信する際に、`Node` オブジェクトのステータス (`status.addresses`) で報告されるアドレスの 1 つを使用します。

EKS クラウドノードでは、一般的に、ノード登録時に kubelet が EC2 インスタンスのプライベート IP を `InternalIP` として報告します。その後、Cloud Controller Manager (CCM) がこの IP を検証し、それが EC2 インスタンスに属していることを確認します。さらに、CCM は通常、インスタンスのパブリック IP (`ExternalIP`) と DNS 名 (`InternalDNS` と `ExternalDNS`) をノードステータスに追加します。

ただし、ハイブリッドノードには CCM はありません。EKS Hybrid Nodes CLI (`nodeadm`) を使用してハイブリッドノードを登録すると、CCM を介さずにノードのステータスでマシンの IP が直接報告されるように kubelet が設定されます。

```
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 は独自のロジックに従ってそれらの 1 つを選択します。選択される IP アドレスは `--node-ip` フラグで制御でき、このフラグは、`nodeadm` config の `spec.kubelet.flags` に渡すことが可能です。`Node` オブジェクトで報告された IP アドレスにのみ VPC からのルートが必要であり、マシンには、クラウドからアクセスできない他の IP アドレスを割り当てることができます。

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

 `kube-proxy` は、各ノードのネットワークレイヤーでサービス抽象化を実装する役割を果たし、Kubernetes サービスに向かうトラフィックのネットワークプロキシおよびロードバランサーとして機能します。また、`kube-proxy` は、Kubernetes API サーバーを監視し、サービスとエンドポイントに関連した変更を継続的に確認することで、基盤となるホストのネットワークルールを動的に更新し、適切なトラフィック転送を維持します。

`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 バッキングポッド: `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` にジャンプさせる 
   + 2 番目のルール: 50% の確率で残りのトラフィック (全体の 33.3%) を `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 とポートを書き換えて、トラフィックを特定のポッドに転送します。
   + 各ルールによって、トラフィックの約 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` の機能が失われると、ノードはサービストラフィックを適切にルーティングできなくなり、クラスターサービスに依存するポッドのタイムアウトや接続の失敗が発生します。この問題が特に深刻になりうるのは、ノードが最初に登録されたときです。CNI では、ポッドネットワークを設定する前に、Kubernetes API サーバーと通信して、ノードのポッド CIDR などの情報を取得する必要があります。それを行うには、`kubernetes` サービス IP を使用します。ただし、`kube-proxy` が起動できなかったり、適切な `iptables` ルールを設定できなかったりした場合、`kubernetes` サービス IP へのリクエストは EKS コントロールプレーン ENI の実際の IP に変換されません。その結果、CNI がクラッシュループに陥り、どのポッドも正しく稼働しなくなります。

前述したように、ポッドは Kubernetes API サーバーとの通信に `kubernetes` のサービス IP を使用しますが、そのように動作させるには、最初に `kube-proxy` に `iptables` ルールを設定する必要があります。

`kube-proxy` は、どのように API サーバーと通信するのでしょうか?

`kube-proxy` は、Kubernetes API サーバーの実際の IP アドレス、またはそれらに解決される DNS 名を使用するように設定する必要があります。EKS の場合、デフォルトの `kube-proxy` が、クラスター作成時に EKS によって作成される Route53 DNS 名を指すように設定されます。この値は、`kube-system` 名前空間の `kube-proxy` ConfigMap で確認できます。この ConfigMap の内容は、`kube-proxy` ポッドに注入される `kubeconfig` であるため、`clusters0.cluster.server` フィールドを確認してください。この値は、EKS `DescribeCluster` API を呼び出した結果にある、EKS クラスターの `endpoint` フィールドと一致しています。

```
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
```

## ルーティング可能なリモートポッド CIDR
<a name="hybrid-nodes-concepts-k8s-pod-cidrs"></a>

この [ハイブリッドノードにおけるネットワーキングの概念](hybrid-nodes-concepts-networking.md) のページでは、ハイブリッドノードでウェブフックを実行したり、クラウドノードで稼働しているポッドがハイブリッドノードで稼働しているポッドと通信したりするための要件について詳しく説明します。ここでは重要な要件があります。特定のポッド IP を担当するノードを、オンプレミスルーターに認識させる必要があることです。これを実現する方法として、ボーダーゲートウェイプロトコル (BGP)、静的ルート、アドレス解決プロトコル (ARP) プロキシなどが挙げられます。以下のセクションでは、これらの方法について説明します。

 **ボーダーゲートウェイプロトコル (BGP)** 

CNI でサポートされている場合 (Cilium や Calico など)、CNI の BGP モードを使用して、ノードごとのポッド CIDR へのルートをノードからローカルルーターに伝達できます。CNI の BGP モードを使用する場合、CNI は仮想ルーターとして機能するため、ローカルルーターは、ポッド CIDR が別のサブネットに属し、ノードはそのサブネットへのゲートウェイであると認識します。

![\[ハイブリッドノード BGP ルーティング\]](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/hybrid-nodes-bgp.png)


 **静的ルート** 

または、ローカルルーターで静的ルートを設定することもできます。この方法を取ると、オンプレミスポッド CIDR を VPC に非常に簡単にルーティングできますが、最もエラーが発生しやすく、保守も困難になります。ルートが既存のノードと割り当てられたポッド CIDR で、常に最新の状態であることを確認しなければなりません。ノードの数が少なく、インフラストラクチャが静的である場合、これは実行可能なオプションであり、ルーターでの BGP 対応も不要になります。この方法を取る場合は、アドレスを IPAM によって決定せず、各ノードに割り当てるポッド CIDR スライスを使用して CNI を設定することをお勧めします。

![\[ハイブリッドノードの静的ルーティング\]](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/hybrid-nodes-static-routes.png)


 **アドレス解決プロトコル (ARP) プロキシ** 

ARP プロキシは、オンプレミスのポッド IP をルーティング可能にするもう 1 つのアプローチであり、特に、ハイブリッドノードがローカルルーターと同じレイヤー 2 ネットワークにある場合に便利です。ARP プロキシを有効にすると、ノードは、その IP が別のサブネットに属している場合でも、ホストするポッド IP の ARP リクエストに応答します。

ローカルネットワーク上のデバイスがポッド IP に到達を試みる場合、最初に「この IP を持っているなら応答せよ」という ARP リクエストを送信します。該当するポッドをホストしているハイブリッドノードは、自分の MAC アドレスで応答し、「その IP のトラフィックを処理できます」と伝えます。これによって、ルーター設定を必要とせずに、ローカルネットワーク上のデバイスとポッドの間に直接のパスが作成されます。

これを機能させるには、CNI がプロキシ ARP 機能に対応している必要があります。Cilium は、設定によってプロキシ ARP を有効化できるように設計されています。ここで特に考慮すべき点は、ポッド CIDR が環境内の他のネットワークと重複しないようにすることです。重複すると、ルーティングの競合が生じる可能性があるからです。

このアプローチには、次のような利点があります。\$1 BGP でルーターを設定する必要はなく、静的ルートを維持する必要もない。\$1 ルーター設定を制御できない環境でも効果的に機能する。

![\[ハイブリッドノード ARP プロキシ\]](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/hybrid-nodes-arp-proxy.png)


## ポッド間でのカプセル化
<a name="hybrid-nodes-concepts-k8s-pod-encapsulation"></a>

オンプレミス環境の CNI では、通常、カプセル化プロトコルによって、物理ネットワーク上で動作するオーバーレイネットワークが作成されます。この場合、再設定が不要になります。このセクションでは、このカプセル化の仕組みについて説明します。使用している CNI によっては、一部の詳細情報が異なる場合があります。

カプセル化では、元のポッドネットワークパケットを、基盤の物理ネットワークを介してルーティング可能な別のネットワークパケット内にラップします。これによって、ポッドの CIDR のルーティング方法が物理ネットワークで認識されていなくても、同じ CNI を実行しているノード間でポッドが通信できます。

Kubernetes で使用される最も一般的なカプセル化プロトコルは Virtual Extensible LAN (VXLAN) ですが、CNI に応じて、他のプロトコル (`Geneve` など) も利用できます。

### VXLAN カプセル化
<a name="_vxlan_encapsulation"></a>

VXLAN では、UDP パケット内にレイヤー 2 イーサネットフレームをカプセル化します。ポッドが別のノードにある別のポッドにトラフィックを送信すると、CNI では、以下が実行されます。

1. ポッド A からのパケットをインターセプトする

1. 元のパケットを VXLAN ヘッダーにラップする

1. ラップしたパケットを、ノードの通常のネットワークスタックを介して宛先ノードに送信する

1. 宛先ノードの CNI が、パケットをラップ解除し、ポッド B に配信する

VXLAN カプセル化中のパケット構造の変化を次に示します。

元のポッド間パケット

```
+-----------------+---------------+-------------+-----------------+
| 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) によって、異なるオーバーレイネットワークが区別されます。

### ポッド通信のシナリオ
<a name="_pod_communication_scenarios"></a>

 **同じハイブリッドノード上のポッド** 

同じハイブリッドノード上のポッドが通信する場合、通常はカプセル化は必要ありません。CNI では、ローカルルートが設定されます。これにより、ノードの内部仮想インターフェイスを介してポッド間のトラフィックを転送します。

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

パケットはノードから出ることはなく、カプセル化を必要としません。

 **異なるハイブリッドノード上のポッド** 

異なるハイブリッドノード上のポッド間で通信するには、カプセル化が必要です。

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

これにより、物理ネットワークでポッド IP のルーティングが認識されていなくても、ポッドトラフィックは、物理ネットワークインフラストラクチャを通過できます。