このチュートリアルは、次の「Microservices March 2022: Kubernetes Networking」で紹介した概念を実践する4つのチュートリアルのうちの1つです。
- 自動スケーリングでKubernetesのレイテンシを低減
- レート制限でKubernetes APIを保護する(本記事)
- KubernetesアプリをSQLインジェクションから保護
- カナリアデプロイで稼働時間とレジリエンスを向上
NGINXをさらに多くのKubernetesネットワーキングのユースケースで活用するための詳しいガイダンスをお探しですか?無料のeBook「Managing Kubernetes Traffic with NGINX: A Practical Guide」をダウンロードしてください。
組織で初めてのアプリとAPIをKubernetes上にリリースしたばかりです。大量のトラフィックが予想されるため、NGINX Ingress Controllerがトラフィックを迅速にルーティングできるよう、すでに自動スケーリングを実装しています。しかし、APIが悪意のある攻撃の標的になる可能性が懸念されています。総当たりによるパスワード推測やDDoS攻撃などによってAPIが大量のHTTPリクエストを受信すると、APIとアプリの両方が過負荷に陥り、クラッシュするおそれもあります。
しかし、ご安心ください。トラフィック制御手法の一つである「レート制限」は、受信リクエストのレートを実際のユーザーに一般的な値に制限するAPIゲートウェイのユースケースです。NGINX Ingress Controllerにレート制限ポリシーを実装するよう設定することで、過剰なリクエストによってアプリとAPIが過負荷になるのを防ぎます。これで対策は完了です。
ラボとチュートリアルの概要
このブログは、Microservices March 2022のユニット2のラボ「KubernetesでAPIを公開する」に対応しています。複数のNGINX Ingress Controllerとレート制限を組み合わせ、アプリやAPIへの過剰な負荷を防ぐ方法を紹介します。
このチュートリアルを実行するには、以下の要件を満たすマシンが必要です。
- 2 CPU以上
- 2 GBの空きメモリ
- 20 GBの空きディスク容量
- インターネット接続
- Docker、Hyperkit、Hyper-V、KVM、Parallels、Podman、VirtualBox、VMware Fusion/Workstationなどのコンテナまたは仮想マシンマネージャー
- minikubeがインストール済み
- Helmがインストール済み
- ブラウザーウィンドウを起動できる構成。起動できない場合は、ブラウザーから該当するサービスにアクセスする方法を確認する必要があります。
ラボとチュートリアルを最大限に活用するため、開始前に以下の準備を行うことを推奨します。
- 視聴する:ライブ配信された概念概要の録画
- ご確認ください:背景情報を紹介するブログ、ウェビナー、動画
- ラボの内容をまとめた18分間の動画をご覧ください: 
このチュートリアルでは、次のテクノロジーを使用します。
各チャレンジの手順には、アプリの構成に使用するYAMLファイルの全文が記載されています。また、F5のGitHubリポジトリからテキストをコピーすることもできます。各YAMLファイルのテキストには、GitHubへのリンクも併記されています。
このチュートリアルには、3つの課題が含まれています。
課題1:クラスター、アプリ、API、Ingress Controllerをデプロイする
このチャレンジでは、minikubeクラスターをデプロイし、サンプルアプリおよびAPIとしてPodinfoをインストールします。次に、NGINX Ingress Controllerをデプロイし、トラフィックルーティングを設定して、Ingress構成をテストします。
Minikubeクラスターの作成
Create a minikubeクラスターを作成します。数秒後、デプロイが成功したことを示すメッセージが表示されます。
$ minikube start 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default Podinfo AppとPodinfo APIのインストール
Podinfoは、「Kubernetesでマイクロサービスを実行するためのベストプラクティスを紹介する、Goで作成されたWebアプリケーション」です。フットプリントが小さいため、サンプルのアプリとAPIとして使用します。
- 任意のテキストエディターを使用して、以下の内容を含むYAMLファイル「1-apps.yaml」を作成します(または、GitHubからコピーします)。このファイルでは、以下を含むDeploymentを定義します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: stefanprodan/podinfo
ports:
- containerPort: 9898
---
apiVersion: v1
kind: Service
metadata:
name: api
spec:
ports:
- port: 80
targetPort: 9898
nodePort: 30001
selector:
app: api
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: stefanprodan/podinfo
ports:
- containerPort: 9898
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
ports:
- port: 80
targetPort: 9898
nodePort: 30002
selector:
app: frontend
type: LoadBalancer- HTMLページをレンダリングするWebアプリ(ここでは「Podinfo Frontend」と呼びます)
- JSONペイロードを返すAPI(Podinfo API)
- アプリとAPIをデプロイします:
$ kubectl apply -f 1-apps.yamldeployment.apps/api created
service/api created
deployment.apps/frontend created
service/frontend created - Confirm that the pods for Podinfo APIとPodinfo FrontendのPodが正常にデプロイされ、
RunningがSTATUS列に表示されていることを確認します。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
api-7574cf7568-c6tr6 1/1 Running 0 87s
frontend-6688d86fc6-78qn7 1/1 Running 0 87s
NGINX Ingress Controllerのデプロイ
NGINX Ingress Controllerを最も迅速にインストールするには、Helmを使用します。
Helmを使用して、NGINX Ingress Controllerを別の名前空間(nginx)にインストールします。
- 名前空間を作成します:
$ kubectl create namespace nginx - NGINXリポジトリをHelmに追加します:
$ helm repo add nginx-stable https://helm.nginx.com/stable - クラスターにNGINX Ingress Controllerをダウンロードしてインストールします:
$ helm install main nginx-stable/nginx-ingress \ --set controller.watchIngressWithoutClass=true \ --set controller.ingressClass=nginx \ --set controller.service.type=NodePort \ --set controller.service.httpPort.nodePort=30010 \ --set controller.enablePreviewPolicies=true \ --namespace nginx - Confirm that the NGINX Ingress Controller pod deployed, as indicated by the value
STATUS列の値が「Running」になっていることを確認し、NGINX Ingress Controllerポッドがデプロイされたことを確認します(読みやすいように、出力は2行に分けて表示しています)。
$ kubectl get pods -namespace nginx NAME READY STATUS ...
main-nginx-ingress-779b74bb8b-d4qtc 1/1 Running ...
... RESTARTS AGE
... 0 92s アプリへのトラフィックのルーティング
- 任意のテキストエディターを使用して、次の内容でYAMLファイル「2-ingress.yaml」を作成します(または「GitHubからコピー」します)。このファイルでは、アプリとAPIにトラフィックをルーティングするために必要なIngressマニフェストを定義します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: first
spec:
ingressClassName: nginx
rules:
- host: "example.com"
http:
paths:
- backend:
service:
name: frontend
port:
number: 80
path: /
pathType: Prefix
- host: "api.example.com"
http:
paths:
- backend:
service:
name: api
port:
number: 80
path: /
pathType: Prefix- Ingressリソースをデプロイします:
$ kubectl apply -f 2-ingress.yaml ingress.networking.k8s.io/first created Ingress設定をテストする
- Ingress構成が想定どおりに動作していることを確認するため、一時的なPodを使用してテストします。クラスター内で使い捨てのBusyBox Podを起動します:
$ kubectl run -ti --rm=true busybox --image=busybox If you don't see a command prompt, try pressing enter.
/ #- テストします。Podinfo APIホスト名api.example.comを指定してNGINX Ingress Controllerポッドにリクエストを送信します。表示された出力は、APIがトラフィックを受信していることを示します。
/ # wget --header="Host: api.example.com" -qO- main-nginx-ingress.nginx {
"hostname": "api-687fd448f8-t7hqk",
"version": "6.0.3",
"revision": "",
"color": "#34577c",
"logo": "https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif",
"message": "greetings from podinfo v6.0.3",
"goos": "linux",
"goarch": "arm64",
"runtime": "go1.16.9",
"num_goroutine": "6",
"num_cpu": "4"
} - 同じBusyBox podで次のコマンドを実行し、WebブラウザをシミュレートしてWebページを取得することで、Podinfo Frontendをテストします。表示される出力は、Webページ冒頭部分のHTMLコードです。
/ # wget --header="Host: example.com" --header="User-Agent: Mozilla" -qO- main-nginx-ingress.nginx <!DOCTYPE html>
<html>
<head>
<title>frontend-596d5c9ff4-xkbdc</title>
# ...- 別のターミナルで、Podinfoをブラウザーで開きます。「greetings from podinfo」ページが表示されれば、Podinfoは実行中です。
$ minikube service podinfo- おめでとうございます。NGINX Ingress Controllerはリクエストを受信し、アプリとAPIに転送しています。

- 元のターミナルで、BusyBoxセッションを終了します:
/ # exit
$チャレンジ2:アプリとAPIに過剰な負荷をかける
この課題では、オープンソースの負荷生成ツールであるLocustをインストールし、それを使用してトラフィックの急増をシミュレートします。これによりAPIに過剰な負荷をかけ、アプリをクラッシュさせます。
Locustのインストール
- 任意のテキストエディターを使用して、以下の内容で「3-locust.yaml」というYAMLファイルを作成します(または、GitHubからコピーします)。
ConfigMapオブジェクトでは、「locustfile.py」というスクリプトを定義します。このスクリプトは、適切なヘッダーを付加してPodに送信するリクエストを生成します。トラフィックはアプリとAPIに均等に分散されず、「Podinfo API」に偏り、5件のリクエストのうち「Podinfo Frontend」に送信されるのは1件のみです。DeploymentオブジェクトとServiceオブジェクトでLocust Podを定義します。
apiVersion: v1
kind: ConfigMap
metadata:
name: locust-script
data:
locustfile.py: |-
from locust import HttpUser, task, between
class QuickstartUser(HttpUser):
wait_time = between(0.7, 1.3)
@task(1)
def visit_website(self):
with self.client.get("/", headers={"Host": "example.com", "User-Agent": "Mozilla"}, timeout=0.2, catch_response=True) as response:
if response.request_meta["response_time"] > 200:
response.failure("Frontend failed")
else:
response.success()
@task(5)
def visit_api(self):
with self.client.get("/", headers={"Host": "api.example.com"}, timeout=0.2) as response:
if response.request_meta["response_time"] > 200:
response.failure("API failed")
else:
response.success()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: locust
spec:
selector:
matchLabels:
app: locust
template:
metadata:
labels:
app: locust
spec:
containers:
- name: locust
image: locustio/locust
ports:
- containerPort: 8089
volumeMounts:
- mountPath: /home/locust
name: locust-script
volumes:
- name: locust-script
configMap:
name: locust-script
---
apiVersion: v1
kind: Service
metadata:
name: locust
spec:
ports:
- port: 8089
targetPort: 8089
nodePort: 30015
selector:
app: locust
type: LoadBalancer- Locustをデプロイします:
$ kubectl apply -f 3-locust.yaml configmap/locust-script created
deployment.apps/locust created
service/locust created - Locustのデプロイを確認します。次の出力例では、
kubectlapplyコマンドの実行からわずか数秒後に確認コマンドを実行したため、インストールはまだ進行中です。これは、Locust PodのSTATUSフィールドの値がContainerCreatingであることから確認できます。値がRunningになるまで待ってから、次のセクションに進んでください。(読みやすいように、出力は2行に分けています。)
$ kubectl get pods
NAME READY STATUS ... api-7574cf7568-c6tr6 1/1 Running ...
frontend-6688d86fc6-78qn7 1/1 Running ... locust-77c699c94d-hc76t 0/1 ContainerCreating ...
... RESTARTS AGE
... 0 33m
... 0 33m
... 0 4sトラフィック急増をシミュレーション
- ブラウザでLocustを開きます。
$ minikube service locust-

- 各フィールドに次の値を入力します。
- ユーザー数 – 1000
- 生成レート – 30
- ホスト – http://main-nginx-ingress
- Click the Start swarmingをクリックして、Podinfo APIとPodinfo Frontendにトラフィックを送信します。Locustの[Charts]タブと[Failures]タブでトラフィックパターンを確認します:
- グラフ – APIリクエスト数が増加するにつれて、Podinfo APIの応答時間が長くなります。
- 障害 – Podinfo APIとPodinfo FrontendはIngressコントローラーを共有しているため、APIリクエスト数が増加すると、ほどなくしてWebアプリがエラーを返し始めます。
これは問題です。APIを使用する悪意のある攻撃者が1人いるだけで、APIだけでなく、NGINX Ingress Controllerが配信するすべてのアプリが停止する可能性があるためです。
課題3:2つのIngress Controllerとレート制限でアプリとAPIを保護
最後の課題では、前回のデプロイにおける制約を解消するため、2つのNGINX Ingress Controllerをデプロイします。具体的には、それぞれに対して個別の名前空間を作成し、Podinfo FrontendとPodinfo APIに個別のNGINX Ingress Controllerインスタンスをインストールします。さらに、アプリとAPIのトラフィックがそれぞれのNGINX Ingress Controllerに送信されるようにLocustを再構成し、レート制限が有効であることを確認します。まず、アーキテクチャ上の問題への対処方法を見ていきましょう。前回の課題では、NGINX Ingress Controllerに大量のAPIリクエストを送信したため、アプリにも影響が及びました。これは、1つのIngress ControllerがWebアプリ(Podinfo Frontend)とAPI(Podinfo API)の両方へのトラフィックのルーティングを担っていたためです。

サービスごとに個別のNGINX Ingress Controllerポッドを実行することで、過剰なAPIリクエストによるアプリへの影響を防げます。すべてのユースケースで必須とは限りませんが、このシミュレーションでは、複数のNGINX Ingress Controllerを実行するメリットを容易に確認できます。

ソリューションの2つ目は、Podinfo APIへの過剰な負荷を防ぐため、NGINX Ingress ControllerをAPIゲートウェイとして使用してレート制限を実装することです。
レート制限とは
レート制限は、一定期間内にユーザーが実行できるリクエスト数を制限します。たとえば、DDoS攻撃を緩和するために、レート制限を使用して受信リクエストのレートを実際のユーザーに一般的な値に制限できます。NGINXでレート制限を実装すると、過剰なリクエストを送信したクライアントはエラーページにリダイレクトされるため、APIへの悪影響を防ぐことができます。仕組みについては、NGINX Ingress Controllerドキュメントをご覧ください。
APIゲートウェイとは
APIゲートウェイは、クライアントからのAPIリクエストを適切なサービスにルーティングします。この単純な定義についてよくある大きな誤解は、APIゲートウェイが固有のテクノロジーであるというものです。しかし、そうではありません。「APIゲートウェイ」とは、さまざまな種類のプロキシを介して実装できる一連のユースケースを指します。最も一般的なのはADCまたはロードバランサーとリバースプロキシですが、Ingressコントローラーやサービスメッシュも増えています。レート制限は、APIゲートウェイを導入する一般的なユースケースです。KubernetesにおけるAPIゲートウェイのユースケースについて詳しくは、F5ブログの「どう選ぶ?APIゲートウェイ、Ingressコントローラー、サービスメッシュの比較」をご覧ください。
クラスターの準備
新しいアーキテクチャとレート制限を実装する前に、既存のNGINX Ingress Controller構成を削除する必要があります。
- NGINX Ingress Controllerの設定を削除します:
$ kubectl delete -f 2-ingress.yaml
ingress.networking.k8s.io "first" deleted
- 「nginx‑web」という名前の名前空間をPodinfo Frontend用に作成します:
$ kubectl create namespace nginx-web
namespace/nginx-web created
- 次の名前空間を作成します:nginx‑api(対象:Podinfo API):
$ kubectl create namespace nginx-api
namespace/nginx-api created
Podinfoフロントエンド用NGINX Ingress Controllerのインストール
- NGINX Ingress Controllerをインストールします:
$ helm install web nginx-stable/nginx-ingress --set controller.ingressClass=nginx-web \ --set controller.service.type=NodePort \ --set controller.service.httpPort.nodePort=30020 \ --namespace nginx-web
- 「4-ingress-web.yaml」という名前のIngressマニフェストをPodinfo Frontend用に作成します(またはGitHubからコピーします)。
apiVersion: k8s.nginx.org/v1 kind: Policy metadata: name: rate-limit-policy spec: rateLimit: rate: 10r/s key: ${binary_remote_addr} zoneSize: 10M --- apiVersion: k8s.nginx.org/v1 kind: VirtualServer metadata: name: api-vs spec: ingressClassName: nginx-api host: api.example.com policies: - name: rate-limit-policy upstreams: - name: api service: api port: 80 routes: - path: / action: pass: api - 新しいマニフェストをデプロイします:
$ kubectl apply -f 4-ingress-web.yaml
ingress.networking.k8s.io/frontend created
Locustを再構成
次に、Locustを再構成し、以下を確認します。
- Podinfo APIが過負荷にならないこと。
- 送信されるリクエストの数にかかわらず、Podinfo APIがPodinfo Frontendに影響を及ぼすことはありません。
次の手順を実行します。
- Locustスクリプトを次のように変更します。LocustのダッシュボードではURLを1つしか指定できないため、次の内容のYAMLファイル「6-locust.yaml」を使用して、Pythonスクリプトに値をハードコードします(または「GitHubからコピー」します)。各「
task」のURLを確認してください。
apiVersion: v1
kind: ConfigMap
metadata:
name: locust-script
data:
locustfile.py: |-
from locust import HttpUser, task, between
class QuickstartUser(HttpUser):
wait_time = between(0.7, 1.3)
@task(1)
def visit_website(self):
with self.client.get("http://web-nginx-ingress.nginx-web/", headers={"Host": "example.com", "User-Agent": "Mozilla"}, timeout=0.2, catch_response=True) as response:
if response.request_meta["response_time"] > 200:
response.failure("Frontend failed")
else:
response.success()
@task(5)
def visit_api(self):
with self.client.get("http://api-nginx-ingress.nginx-api/", headers={"Host": "api.example.com"}, timeout=0.2) as response:
if response.request_meta["response_time"] > 200:
response.failure("API failed")
else:
response.success()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: locust
spec:
selector:
matchLabels:
app: locust
template:
metadata:
labels:
app: locust
spec:
containers:
- name: locust
image: locustio/locust
ports:
- containerPort: 8089
volumeMounts:
- mountPath: /home/locust
name: locust-script
volumes:
- name: locust-script
configMap:
name: locust-script
---
apiVersion: v1
kind: Service
metadata:
name: locust
spec:
ports:
- port: 8089
targetPort: 8089
nodePort: 30015
selector:
app: locust
type: LoadBalancer
- All requests to Podinfo Frontendへのすべてのリクエストは、nginx‑web NGINX Ingress Controller(http://web-nginx-ingress.nginx-web)に転送されます
- All requests to Podinfo APIへのすべてのリクエストは、nginx‑api NGINX Ingress Controller(http://api-nginx-ingress.nginx-api)に転送されます
- 新しいLocust構成をデプロイします。出力から、スクリプトは変更され、その他の要素は変更されていないことを確認できます。
$ kubectl apply -f 6-locust.yaml
configmap/locust-script configured
deployment.apps/locust unchanged
service/locust unchanged
- 新しいConfigMapを強制的に再読み込みするには、Locust Podを削除します。削除するPodを特定するため、
kubectldeletepodコマンドの引数には、すべてのPodのリストからLocust Podを選択するパイプコマンドを指定します。
$ kubectl delete pod `kubectl get pods | grep locust | awk {'print $1'}`
- Locustが再読み込みされたことを確認します(Locustポッドの
AGE列の値が数秒になっていることを確認してください)。
$ kubectl get pods
NAME READY STATUS ... api-7574cf7568-jrlvd 1/1 Running ...
frontend-6688d86fc6-vd856 1/1 Running ... locust-77c699c94d-6chsg 0/1 Running ...
... RESTARTS AGE
... 0 9m57s
... 0 9m57s
... 0 6s
レート制限の確認
- Locustに戻り、次のフィールドのパラメーターを変更します:
- ユーザー数 – 400
- 生成レート – 10
- ホスト – http://main-nginx-ingress
- Click the Start swarmingボタンをクリックして、Podinfo APIとPodinfo Frontendにトラフィックを送信します。左上にあるLocustのタイトルバーで、STATUS列のユーザー数が増えるにつれて、FAILURES列の値も増えることを確認します。ただし、エラーの発生元はPodinfo FrontendではなくPodinfo APIになっています。これは、APIに設定されたレート制限により、過剰なリクエストが拒否されるためです。右下のトレースでは、NGINXが
503ServiceTemporarilyUnavailableというメッセージを返していることを確認できます。このメッセージはレート制限機能の一部であり、カスタマイズできます。APIにはレート制限が適用されますが、Webアプリケーションは常に利用できます。これで完了です。
次のステップ
実際の環境では、レート制限だけでアプリやAPIを悪意のある攻撃者から保護することはできません。Kubernetesのアプリ、API、インフラストラクチャを保護するには、以下の方法を少なくとも1つまたは2つ実装する必要があります。
- 認証と認可
- WebアプリケーションファイアウォールとDDoS対策
- エンドツーエンド暗号化とゼロトラスト
- 業界規制への準拠
Microservices March 2022のユニット3「Kubernetesにおけるマイクロサービスのセキュリティパターン」では、これらをはじめとするトピックを取り上げています。NGINX PlusおよびNGINX App ProtectとともにNGINX Ingress Controller for Kubernetesをお試しいただくには、今すぐ30日間の無料トライアルを開始するか、ユースケースについてご相談ください。NGINX Open SourceとともにNGINX Ingress Controllerをお試しいただくには、リリース版のソースコードを入手するか、DockerHubからビルド済みコンテナをダウンロードできます。
関連するブログ記事
チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。
AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。
JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。
F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。
NGINX PlusによるAWS Auto Scalingグループの負荷分散
新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります