Independent Service Vendor (ISV) のユーザーは、コストと運用管理を削減するために、マルチテナントアーキテクチャでホストされたエンドユーザーソリューションを提供することが多くあります。しかし、このアプローチでは、Kubernetes クラスターでリソース枯渇やネットワーク帯域の枯渇の問題が発生し、隣接するワークロードに影響を与える可能性があります。Kubernetes はデフォルトで、CPU とメモリなどのリソース可用性を制限する機能を提供し、コンピューティングリソースの枯渇を防いでいます。しかし、ワークロードは急速に進化しており、パフォーマンスを向上させるためにネットワーク帯域幅などの他のリソースを使用するようになっています。例えば、pod はレスポンスタイムを改善するために大量のトラフィックをダウンロードすることを選択し、帯域幅の枯渇と隣接する pod への影響を引き起こす可能性があります。
この記事では、Amazon Virtual Private Cloud (Amazon VPC) CNI プラグインを使用して、この Kubernetes の課題をどのように解決するかを見ていきます。Amazon VPC CNI プラグインを使用して、pod の入力と出力の帯域幅使用を制限することでネットワーク帯域の枯渇を防ぎ、ネットワークの安定性と QoS を確保する方法を示します。
Amazon VPC CNI とは
利用可能な CNI プラグイン は複数ありますが、AWS が Amazon Elastic Kubernetes Service (EKS) 向けに開発した Amazon VPC CNI プラグインは、コンテナネットワーキングが Amazon VPC のネットワーキングとセキュリティ機能を直接利用できるようにします。この CNI プラグインは、ノード上の Elastic Network Interface (ENI) を管理し、Amazon Elastic Compute Cloud (Amazon EC2) と AWS Fargate の両方に対応しています。
ノードをプロビジョニングすると、プラグインは自動的にプライマリ ENI のノードのサブネットからスロット (IP またはプレフィックス) のプールを割り当てます。これにより、Amazon EKS にデプロイされたアプリケーションの Kubernetes Pod ネットワーキングと接続を可能にし、Amazon VPC ネットワーキング機能が直接 Kubernetes pod に統合されます。例えば、pod には VPC から独自のプライベート IP アドレスが割り当てられ、セキュリティグループを pod に直接適用できます。
帯域幅制限機能を有効にする場合、Amazon VPC CNI プラグインは bandwidth プラグイン に依存し、`tc` (Traffic Control) などの Linux トラフィック制御ユーティリティを使用して、個々のコンテナまたは pod の入力および出力帯域幅の制限を制御します。
ウォークスルー
CNI プラグインを使用して、入力と出力のトラフィックシェーピングを有効にする方法を見ていきましょう。
前提条件
このウォークスルーを行うには、以下の前提条件を満たしている必要があります:
- Amazon EKS クラスターバージョン 1.24 以上
- Amazon VPC CNI バージョン 1.15.0 以上
- kubectl バージョン 24 以上
- eksctl バージョン 175.0 以上
Step0: EKS クラスターを eksctl で作成 (オプション)
この構成は、EKS クラスターバージョン 1.28 をプロビジョニングするために `eksctl` で使用されます。すでに独自の EKS クラスターがある場合は、このステップをスキップできます。
この EKS クラスターを v1.28 でプロビジョニングする場合は、`kubectl` も v1.28 であるか、クラスターとのマイナーバージョンの差が 1 以内であることを確認してください。例えば、v1.28 のクライアントは、v1.27、v1.28、v1.29 のコントロールプレーンと通信できます。
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: dev-cluster
region: ap-southeast-1
version: 1.28
managedNodeGroups:
- name: ng-1-workers
labels: { role: workers }
instanceType: m6a.large
desiredCapacity: 2
volumeSize: 30
privateNetworking: true
Step1: EC2 インスタンスの CNI bandwidth プラグインを有効化
pod の帯域幅制限を設定する前に、Amazon EC2 にアクセスして CNI プラグインの帯域幅機能を有効にする必要があります。AWS System Manager Session Manager を使用して Amazon EC2 に接続することをお勧めします。Session Manager は、インバウンドポートを開く必要がなく、踏み台ホストを維持したり、Secure Shell (SSH) キーを管理する必要がないため、セキュアで監査可能なノード管理を提供します。したがって、セキュリティを強化し、攻撃対象領域を減らすことができます。
上記の `eksctl` で EKS クラスターをプロビジョニングすると、デフォルトで Amazon Linux Amazon Machine Images (AMIs) の Amazon EKS 最適化版が使用されます。この AMI を使うと、EC2 インスタンスが必要な要件で構成されるため、Session Manager を使ってインスタンスに接続できるようになり、さらなるセットアップは不要です。
Amazon EC2 コンソールを使用して Session Manager で Linux インスタンスに接続する方法
- Amazon EC2 コンソールを開きます。
- ナビゲーションペインで、Instances を選択します。
- インスタンスを選択し、Connect を選択します。
- Session Manager を選択します。
- Connect を選択します。
Session Manager のセットアップ方法と詳細については、Session Manager のセットアップをご覧ください。インスタンスに接続したら、次のコマンドを実行してください。
sudo su
cd /etc/cni/net.d
echo "$(cat 10-aws.conflist | jq '.plugins += [{"type": "bandwidth", "capabilities": {"bandwidth": true}}]')" > 10-aws.conflist
次の JSON オブジェクトが `plugins` キーの下に追加されます。
{
...
"plugins": [
...
{
"type": "bandwidth",
"capabilities": {"bandwidth": true}
}
]
}
Step2: iperf と tc CLI の EC2 インスタンスへインストール
この手順では、帯域幅制限をチェックおよびテストするために使用される必要な CLI ツール `iperf` と `tc` をインストールします。
- `iperf` はネットワークパフォーマンスを測定するためによく使われるコマンドラインツールです。クライアントとサーバー間、またはサーバー間の帯域幅を測定するのに使えます。
- `tc` (Traffic control) は、Linux のユーザースペースユーティリティコマンドで、ネットワークインターフェースのトラフィック制御設定を構成・管理できます。ネットワークトラフィックシェーピング、スケジューリング、ポリシー付け、優先順位付けなど、強力なツールセットを提供します。
# If you are using Amazon Linux 2, need to enable epel before getting iperf package
# https://repost.aws/knowledge-center/ec2-enable-epel
sudo amazon-linux-extras install epel -y
sudo yum install iperf -y
sudo yum install iproute-tc -y
Amazon Linux 2023 は Extra Packages for Enterprise Linux (EPEL) をサポートしておらず、`yum` を通して `iperf` をインストールすることはできません。ただし、この AWS Knowledge Center の記事の手順に従えば、AL2023 に iperf を手動でダウンロードしてインストールできます。
Step 3: 帯域幅制限なしで pod をデプロイ
まず、標準のアプリケーションである `nginx` を EKS クラスターにデプロイします。後で 2 つの設定を比較しやすくするため、現時点では帯域幅制限は設定されていません。
`nginx-deployment.yaml` という新しいファイルを次の定義で作成してください:
cat < nginx-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
securityContext:
runAsNonRoot: true
containers:
- name: nginx
image: nginxinc/nginx-unprivileged
ports:
- containerPort: 8080
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumeMounts:
- mountPath: /tmp
name: tmp
volumes:
- emptyDir: {}
name: tmp
EOF
次のコマンドを実行してデプロイします:
kubectl apply -f nginx-deployment.yaml
pod の IP とその pod が存在するノードの IP を確認するには、次のコマンドを実行します:
Step4: 入力/出力制限に関するテスト pod
アプリケーションをデプロイする際に帯域幅の制限を指定しなかった後、EC2 インスタンス内で `tc` コマンドを使用して現在の `qdisc` を確認します。
- `qdisc` (キューイング規律) は、ネットワークインターフェースでパケットがキューイングされ、送信されるための方法を管理するアルゴリズムです。カーネルのパケットキューからネットワークインターフェースカードへパケットが送出される順序を決定します。
`qdisc` を確認するには、次のコマンドを実行してください:
出力:
`qdisc pfifo_fast` は単純な先入れ先出し (FIFO) キューを使用し、トラフィックシェーピングや優先順位付けは行いません。
qdisc noqueue 0: dev lo root refcnt 2
qdisc mq 0: dev eth0 root
qdisc pfifo_fast 0: dev eth0 parent :4 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth0 parent :3 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth0 parent :2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth0 parent :1 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc noqueue 0: dev eniaafbc306ee3 root refcnt 2
qdisc noqueue 0: dev eni8abfd912174 root refcnt 2
qdisc mq 0: dev eth1 root
qdisc pfifo_fast 0: dev eth1 parent :4 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth1 parent :3 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth1 parent :2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth1 parent :1 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc noqueue 0: dev pod-id-link0 root refcnt 2
qdisc noqueue 0: dev enifb88ae1f006 root refcnt 2
次に、`iperf` を使用して帯域幅の測定を行います。最大の帯域幅を測定するには、次のコマンドを実行します。
Step3 で取得した IP で {POD_IP} を置き換えてください。
iperf -c {POD_IP} -p 8080 -i 1
出力:
------------------------------------------------------------
Client connecting to 192.168.113.250, TCP port 80
TCP window size: 3.90 MByte (default)
------------------------------------------------------------
[ 3] local 192.168.137.88 port 51026 connected with 192.168.113.250 port 80
[ ID] Interval Transfer Bandwidth
[ 3] 0.0- 1.0 sec 594 MBytes 4.98 Gbits/sec
[ 3] 1.0- 2.0 sec 592 MBytes 4.97 Gbits/sec
[ 3] 2.0- 3.0 sec 592 MBytes 4.97 Gbits/sec
[ 3] 3.0- 4.0 sec 592 MBytes 4.96 Gbits/sec
[ 3] 4.0- 5.0 sec 592 MBytes 4.96 Gbits/sec
[ 3] 5.0- 6.0 sec 592 MBytes 4.96 Gbits/sec
[ 3] 6.0- 7.0 sec 592 MBytes 4.97 Gbits/sec
[ 3] 7.0- 8.0 sec 592 MBytes 4.96 Gbits/sec
[ 3] 8.0- 9.0 sec 592 MBytes 4.96 Gbits/sec
[ 3] 9.0-10.0 sec 591 MBytes 4.96 Gbits/sec
[ 3] 0.0-10.0 sec 5.78 GBytes 4.97 Gbits/sec
Step5: 帯域幅制限付きで pod を再デプロイ
pod の出力入力制限なしでの帯域幅をテストした後、出力入力の帯域幅制限を指定するために、次のアノテーションをデプロイメントに追加します:
- `kubernetes.io/ingress-bandwidth` – 受信帯域幅を制御するため
- `kubernetes.io/egress-bandwidth` – 送信帯域幅を制御するため
`nginx-deployment.yaml` のマニフェストを更新し、同じコマンドで再デプロイしてください:
cat < nginx-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
annotations:
kubernetes.io/ingress-bandwidth: 1G
kubernetes.io/egress-bandwidth: 1G
spec:
securityContext:
runAsNonRoot: true
containers:
- name: nginx
image: nginxinc/nginx-unprivileged
ports:
- containerPort: 8080
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumeMounts:
- mountPath: /tmp
name: tmp
volumes:
- emptyDir: {}
name: tmp
EOF
アプリケーションを再デプロイしてください:
kubectl apply -f nginx-deployment.yaml
Step6: 入力/出力制限に関するテスト pod
マニフェストを更新して、入力と出力の帯域幅制限を設定した後、Step4 を繰り返して、新しい構成が有効になっていることを確認します。
`qdisc` を確認するには、次のコマンドを実行してください:
出力:
qdisc noqueue 0: dev lo root refcnt 2
qdisc mq 0: dev eth0 root
qdisc pfifo_fast 0: dev eth0 parent :4 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth0 parent :3 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth0 parent :2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth0 parent :1 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc noqueue 0: dev eniaafbc306ee3 root refcnt 2
qdisc noqueue 0: dev eni8abfd912174 root refcnt 2
qdisc mq 0: dev eth1 root
qdisc pfifo_fast 0: dev eth1 parent :4 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth1 parent :3 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth1 parent :2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc pfifo_fast 0: dev eth1 parent :1 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
qdisc noqueue 0: dev pod-id-link0 root refcnt 2
qdisc tbf 1: dev enif603d342b24 root refcnt 2 rate 1Gbit burst 512Mb lat 25ms
qdisc ingress ffff: dev enif603d342b24 parent ffff:fff1 ----------------
qdisc tbf 1: dev bwp3163ee8c94ce root refcnt 2 rate 1Gbit burst 512Mb lat 25ms
出力から分かるように、`qdisc` は `tbf` (Token Bucket Filter) を使用しています。これはトラフィックコントロールで利用可能な階層構造を持ったキューイング規律です。
最大の実現可能な帯域幅を測定するには、次のコマンドを実行してください。
iperf -c {POD_IP} -p 8080 -i 1
出力:
------------------------------------------------------------
Client connecting to 192.168.125.111, TCP port 80
TCP window size: 1.68 MByte (default)
------------------------------------------------------------
[ 3] local 192.168.137.88 port 43566 connected with 192.168.125.111 port 80
[ ID] Interval Transfer Bandwidth
[ 3] 0.0- 1.0 sec 594 MBytes 4.98 Gbits/sec
[ 3] 1.0- 2.0 sec 149 MBytes 1.25 Gbits/sec
[ 3] 2.0- 3.0 sec 118 MBytes 989 Mbits/sec
[ 3] 3.0- 4.0 sec 118 MBytes 990 Mbits/sec
[ 3] 4.0- 5.0 sec 119 MBytes 998 Mbits/sec
[ 3] 5.0- 6.0 sec 118 MBytes 988 Mbits/sec
[ 3] 6.0- 7.0 sec 119 MBytes 998 Mbits/sec
[ 3] 7.0- 8.0 sec 118 MBytes 989 Mbits/sec
[ 3] 8.0- 9.0 sec 118 MBytes 987 Mbits/sec
[ 3] 9.0-10.0 sec 119 MBytes 1.00 Gbits/sec
[ 3] 0.0-10.0 sec 1.65 GBytes 1.42 Gbits/sec
帯域制御の前後:
次の視覚化は、帯域幅を Gbits/sec で示しています。オレンジ線は、デプロイメントに帯域幅のアノテーションを追加する「前」を表しています。青線は、帯域幅のアノテーションを設定した「後」を表しています。
クリーンアップ
以下のコマンドでデプロイメントを削除します:
kubectl delete -f nginx-deployment.yaml
以下のコマンドで Step0 でプロビジョニングした EKS クラスターを削除するには:
eksctl delete cluster --name dev-cluster
考慮事項
bandwidth プラグインは、この記事を書いている時点では、Amazon VPC CNI ベースのネットワークポリシーと互換性がありません。ネットワークポリシーエージェントは、Traffic Classifier (tc) システムを使用して、pod に対して設定されたネットワークポリシーを適用します。bandwidth プラグインが有効になっていると、bandwidth プラグインと Network Policy エージェントの tc 設定が競合するため、ポリシーの適用に失敗します。私たちは、ネットワークポリシー機能と併せて bandwidth プラグインをサポートする方法を検討しており、この問題はAWS GitHub の Issueで追跡されています。
おわりに
この記事では、Amazon VPC CNI プラグインとその機能を使用して、Amazon EKS 上で pod として実行されているアプリケーションの入力および出力帯域幅を制限する方法を示しました。これを使用することで、ユーザーはネットワーク上の pod の使用を制限し、Kubernetes クラスター内の他の pod による大量のネットワーク消費によるネットワーク帯域の枯渇を防ぐ機能を実装できます。
翻訳はソリューションアーキテクト祖父江が担当しました。原文はこちらです。