Amazon Web Services ブログ
Kubernetes アプリケーションの公開 Part 3: NGINX Ingress Controller
この記事は Exposing Kubernetes Applications, Part 3: NGINX Ingress Controller (記事公開日: 2022 年 11 月 22 日) を翻訳したものです。
はじめに
連載「Kubernetes アプリケーションの公開」では、Kubernetes クラスターで実行されているアプリケーションを、外部からのアクセスのために公開する方法に焦点を当てます。
Part 1 では、Kubernetes クラスターでインバウンドトラフィックの制御を定義する 2 つの方法である Service と Ingress リソースタイプについて探りました。Service と Ingress コントローラーによるこれらのリソースタイプの処理について説明し、その後、いくつかのコントローラーの実装バリエーションの利点と欠点について概要を説明しました。
Part 2 では、Ingress コントローラーの AWS のオープンソース実装である AWS Load Balancer Controller について、セットアップ、設定、想定されるユースケース、制限事項をウォークスルーしました。
今回、Part 3 では、Ingress コントローラーのまた別のオープンソース実装である NGINX Ingress Controller に注目します。その機能の一部や、AWS Load Balancer Controller との違いについてウォークスルーします。
NGINX Ingress Controller のアーキテクチャ
Part 1 では、下図に示すようなクラスター内レイヤー 7 リバースプロキシーを使用する Ingress コントローラーのタイプについて説明しました。
NGINX Ingress Controller の実装は、上記のアーキテクチャに沿っています。
コントローラーは、人気のあるオープンソースの HTTP およびリバースプロキシサーバーである nginx のインスタンスを含む Pod をデプロイ、設定、および管理します。これらの Pod は 、コントローラーの Service リソースを介して公開され、Ingress およびバックエンドの Service リソースで表される関連アプリケーションを対象としたすべてのトラフィックを受信します。コントローラーは、Ingress と Service の設定を、静的に提供される追加パラメータと組み合わせて、標準的な nginx の設定に変換します。そして、この設定を nginx の Pod に注入し、トラフィックをアプリケーションの Pod にルーティングします。
NGINX Ingress Controller の Service は、ロードバランサーを介して外部トラフィック向けに公開されています。この Service は、通常の <service-name>.<namespace-name>.svc.cluster.local
クラスター DNS 名を介してクラスター内部からも利用できます。
ウォークスルー
NGINX Ingress Controller がどのように動作するかを理解したので、実際に動作させてみましょう。
前提条件
1. AWS アカウントへのアクセスの取得
AWS アカウントと、AWS コマンドラインインターフェイス (AWS CLI) や類似のツールを使用して、ターミナルから AWS と通信できる必要があります。
以下のコード例では、AWS アカウント ID やリージョンなど、置き換えることを前提とした文字列がいくつかが含まれています。これらは、あなたの環境に合った値で置き換えてください。
2. クラスターの作成
eksctl を使用して Amazon EKS クラスターをプロビジョニングします。eksctl はクラスター自体の作成に加えて、VPC、サブネット、セキュリティグループといった必要なネットワークリソースもプロビジョニングおよび設定します。
以下の eksctl
設定ファイルは、Amazon EKS クラスターとその設定を定義します。
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: nginx-ingress-controller-walkthrough
region: ${AWS_REGION}
version: '1.27'
iam:
withOIDC: true
managedNodeGroups:
- name: main-ng
instanceType: m5.large
desiredCapacity: 1
privateNetworking: true
上記のコードを config.yml
ファイルに記述してください。
AWS_REGION
および AWS_ACCOUNT
環境変数を定義してください。その後、クラスターを作成します。
envsubst < config.yml | eksctl create cluster -f -
このウォークスルーでは、Kubernetes バージョン 1.23 の Amazon EKS プラットフォームバージョン eks.3
を使用しています。(訳注: 翻訳時には、Kubernetes バージョン 1.27 の Amazon EKS プラットフォームバージョン eks.4
を使用して動作を確認しています。)
シンプルにするため、上記の構成では、セキュリティやモニタリングなど、Kubernetes クラスターのプロビジョニングと管理の多くの側面は考慮していません。より詳細な情報とベストプラクティスについては、Amazon EKS と eksctl のドキュメントを参照してください。
クラスターが稼働していることを確認します。
kubectl get nodes
kubectl get pods -A
上記のコマンドは、1 つの Amazon EKS ノードと 4 つの実行中の Pod を返すはずです。
3. Helm のインストール
コントローラーのインストールと設定には、Kubernetes において一般的なパッケージマネージャーである Helm を使用します。こちらの手順に従って Helm をインストールしてください。
NGINX Ingress Controller のインストール
1. Helm を使用したコントローラーのインストール
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx \
--version 4.2.3 \
--namespace kube-system \
--set controller.service.type=ClusterIP
kubectl -n kube-system rollout status deployment ingress-nginx-controller
kubectl get deployment -n kube-system ingress-nginx-controller
コントローラーの Service を ClusterIP
に設定しています。これは、ウォークスルー中にコントローラーの様々な設定パラメータを変更した際に、ロードバランサーが再作成されるのを回避するためです。ロードバランサーの作成については、記事の後半で説明します。
テスト用 Service のデプロイ
1. Namespace の作成
kubectl create namespace apps
2. Service のマニフェストファイルの作成
以下のコードを service.yml
ファイルに記述します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${SERVICE_NAME}
namespace: ${NS}
labels:
app.kubernetes.io/name: ${SERVICE_NAME}
spec:
selector:
matchLabels:
app.kubernetes.io/name: ${SERVICE_NAME}
replicas: 1
template:
metadata:
labels:
app.kubernetes.io/name: ${SERVICE_NAME}
spec:
terminationGracePeriodSeconds: 0
containers:
- name: ${SERVICE_NAME}
image: hashicorp/http-echo
imagePullPolicy: IfNotPresent
args:
- -listen=:3000
- -text=${SERVICE_NAME}
ports:
- name: app-port
containerPort: 3000
resources:
requests:
cpu: 0.125
memory: 50Mi
---
apiVersion: v1
kind: Service
metadata:
name: ${SERVICE_NAME}
namespace: ${NS}
labels:
app.kubernetes.io/name: ${SERVICE_NAME}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: ${SERVICE_NAME}
ports:
- name: svc-port
port: 80
targetPort: app-port
protocol: TCP
http-echo イメージを使用する上記の Service は、${SERVICE_NAME}
変数で定義した Service 名でリクエストに応答します。シンプルにするため、レプリカは 1 つとしています。
3. サービスのデプロイと検証
以下のコマンドを実行します (この記事を通して、これらの Service を使用します) 。
SERVICE_NAME=first NS=apps envsubst < service.yml | kubectl apply -f -
SERVICE_NAME=second NS=apps envsubst < service.yml | kubectl apply -f -
SERVICE_NAME=third NS=apps envsubst < service.yml | kubectl apply -f -
SERVICE_NAME=fourth NS=apps envsubst < service.yml | kubectl apply -f -
SERVICE_NAME=error NS=apps envsubst < service.yml | kubectl apply -f -
SERVICE_NAME=another-error NS=apps envsubst < service.yml | kubectl apply -f -
すべてのリソースがデプロイされていることを確認しましょう。
kubectl get pod,svc -n apps
シンプルな Ingress のデプロイ
1. Ingress のマニフェストファイルの作成と Ingress のデプロイ
以下のコードを ingress.yml
ファイルにコピーしてください。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${NS}-ingress
namespace: ${NS}
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /first
pathType: Prefix
backend:
service:
name: first
port:
name: svc-port
- path: /second
pathType: Prefix
backend:
service:
name: second
port:
name: svc-port
AWS Load Balancer Controller の場合に見たのと同じように、ingressClassName
プロパティを nginx
に設定することで、NGINX Ingress Controller をターゲットにします。nginx
はコントローラーとともにインストールされるデフォルトの IngressClass の名前です。
以下のコマンドを実行して Ingress をデプロイします。
NS=apps envsubst < ingress.yml | kubectl apply -f -
しばらくすると、Ingress リソースの状態を確認できるようになります (IP アドレスのバインドには少し時間がかかる場合があります) 。
kubectl get ingress -n apps
以下のような出力が得られます。
上記の ADDRESS
と PORT
列は、コントローラーの Service のものが設定されています。
ClusterIP
タイプで Service を作成するようにコントローラーを設定したため、Service と通信する方法として、Service に対するポートフォワーディングを設定する必要があります。
kubectl port-forward -n kube-system svc/ingress-nginx-controller 8080:80
2. Ingress のテスト
これで、コントローラーの Service にリクエストを送信できるようになりました。
curl -sS localhost:8080/first
curl -sS localhost:8080/second
curl -sS localhost:8080/third
次の結果が得られれば、Ingress リソースがは正しくデプロイされ、設定されています。
IngressClass に関する考察
前述したように、nginx
という名前のデフォルトの IngressClass がコントローラーと一緒にインストールされています。以下のようなリソースです。
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx
labels:
app.kubernetes.io/name: ingress-nginx
...
spec:
controller: k8s.io/ingress-nginx
AWS Load Balancer Controller とは異なり、NGINX Ingress Controller は IngressClass パラメータをサポートしていません。
IngressClass をデフォルトにするためには、ingressclass.kubernetes.io/is-default-class: "true"
アノテーションを追加するか、コントローラーのインストール時にデフォルトにするように設定します。
helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx \
--namespace kube-system \
--set controller.ingressClassResource.default=true \
...
Default Backend とエラーハンドリング
Ingress リソースのいずれかによって処理されないパスにリクエストを送信すると、nginx が 404
で応答することを確認しました。このレスポンスは、コントローラーにインストールされている Default Backend から返されます。これをカスタマイズする 1 つの方法は、たとえば Helm の values.yml ファイルを介して、controller.defaultBackend
プロパティを設定することです。これについては、この記事で後ほど説明します。もう 1 つの方法は、Ingress リソースに nginx.ingress.kubernetes.io/default-backend
アノテーションを設定することです。
最後の方法として、次に示すような Ingress の仕様で設定することができます。
1. Ingress の更新とデプロイ
ingress.yml
ファイルを以下のように更新します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${NS}-ingress
namespace: ${NS}
spec:
ingressClassName: nginx
defaultBackend:
service:
name: error
port:
name: svc-port
rules:
- http:
paths:
- path: /first
pathType: Prefix
backend:
service:
name: first
port:
name: svc-port
- path: /second
pathType: Prefix
backend:
service:
name: second
port:
name: svc-port
デプロイします。
NS=apps envsubst < ingress.yml | kubectl apply -f -
2. Ingress のテスト
これで、再びリクエストを送信してみましょう。
curl -sS localhost:8080/first
curl -sS localhost:8080/second
curl -sS localhost:8080/third
これは、Default Backend を使用して、期待どおりに機能します。
複数の Ingress リソース
複数の Ingress リソースがあり、それらが異なるチームに属していたり、より大きなアプリケーションの一部であったりすることがよくあります。これらは別々に開発されデプロイされる必要がありますが、別々の構成は必要なく、1 つのコントローラーのインストールで処理できます。
NGINX Ingress Controller は Ingress リソースのマージをサポートしていますが、AWS Load Balancer Controller のようにリソースの順序やグルーピングを明示的に定義することはできません。
ホストベースのルーティング
これまでのすべての例では、すべてのリクエストは同じドメインにルーティングされ、Ingress リソースが同じ *.*
ホストにマージされることを前提としていました。どの Service がどのドメインで提供されるかを明示的に定義し、Ingress のホスト設定でそれらをセグメント化したりマージしたりすることもできます。
1. Ingress の更新とデプロイ
ingress.yml
を更新します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${NS}-ingress
namespace: ${NS}
spec:
ingressClassName: nginx
defaultBackend:
service:
name: error
port:
name: svc-port
rules:
- host: a.example.com
http:
paths:
- path: /first
pathType: Prefix
backend:
service:
name: first
port:
name: svc-port
- host: b.example.com
http:
paths:
- path: /second
pathType: Prefix
backend:
service:
name: second
port:
name: svc-port
以下のコマンドを実行します。
NS=apps envsubst < ingress.yml | kubectl apply -f -
2. Ingress のテスト
curl
を使用して、さまざまなドメインへのリクエストをシミュレートできます。
curl localhost:8080/first -H 'Host: a.example.com'
curl localhost:8080/second -H 'Host: b.example.com'
curl localhost:8080/first -H 'Host: b.example.com'
curl localhost:8080/first -H 'Host: b.example.net'
出力は次のようになります。
最後の 2 つのリクエストは、Default Backend にルーティングされることが期待されます。1 つはそのホストで定義されていないパスに送信されているため、もう 1 つは存在しないホストであるためです。
a.myapp.com
と b.myapp.com
の DNS レコードを NGINX Ingress Controller の Service に向けることで、両方のホストを処理することができます。このタスクを完了するために、Service を外部トラフィック向けに公開します (外部ロードバランサー経由など) 。これについては、この記事の後半で詳しく説明します。
Ingress の pathType および正規表現とリライト
これまで、Ingress ルールの pathType
を Prefix
と定義してきました。pathType
を Exact
に設定し、パスで正規表現を使用したり、リライトルールを定義することもできます。
1. Ingress の更新とデプロイ
ingress.yml
ファイルの Ingress 定義を変更し、再デプロイしましょう。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${NS}-ingress
namespace: ${NS}
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
ingressClassName: nginx
defaultBackend:
service:
name: error
port:
name: svc-port
rules:
- http:
paths:
- path: /first/(.*)/foo
pathType: Prefix
backend:
service:
name: first
port:
name: svc-port
nginx.ingress.kubernetes.io/rewrite-target
アノテーションは、ルールのパスで定義されたキャプチャグループのうち、どれを Service に送信するかを定義しています。すなわち、/$1
の場合は、1 番目のキャプチャグループの内容をリクエストのパスとして Service に送信します。
Ingress をデプロイしましょう。
NS=apps envsubst < ingress.yml | kubectl apply -f -
2. Ingress のテスト
テストを実行します。
curl -sS localhost:8080/first
curl -sS localhost:8080/first/foo
curl -sS localhost:8080/first/bar
curl -sS localhost:8080/first/bar/foo
これは、次のような結果になります。
ロードバランサー経由で NGINX Ingress Controller を公開する
in-tree Service コントローラーを使用する
NGINX Ingress Controller を外部トラフィック向けに公開する最もシンプルな方法は、Part 1 で説明した in-tree コントローラーに Service を処理させることです。そのためには、Service のタイプを LoadBalancer
に設定します。これによって、AWS Classic Load Balancer (CLB) がプロビジョニングされます。
helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx \
--namespace kube-system \
--set controller.service.type=LoadBalancer \
...
AWS Classic Load Balancer の代わりに、より新しく、推奨される AWS Network Load Balancer を指定することもできます。
helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx \
--namespace kube-system \
--set controller.service.type=LoadBalancer \
--set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-type"="nlb" \
...
また、Helm の values.yml
ファイルを使用して、これらの設定パラメータをより快適な方法で提供することもでき、同じ効果を得られます。次のセクションでそのような使用例を見ていきます。
AWS Load Balancer Controller を使用する方法
Service のために作成しプロビジョニングされる Network Load Balancer をより詳細に制御したい場合は、Service コントローラーをインストールします。AWS Load Balancer Controller が推奨される選択肢です。
AWS Load Balancer Controller も Ingress リソースを処理しますが、処理対象の IngressClass が alb
であり NGINX Ingress Controller とは異なるため、衝突は発生しません。
NGINX Ingress Controller 用に AWS NLB をプロビジョニングする
AWS Load Balancer Controller のインストールについては、すでに連載の Part 2 で説明しましたので、これについてはおなじみのはずです。
1. AWS Load Balancer Controller 用の AWS IAM ポリシーの作成
この手順のステップ 2 と3 のみを実行して、AWSLoadBalancerControllerIAMPolicy
を作成します。IRSA (IAM Roles for Service Accounts) を使用して、AWS Load Balancer Controller に IAM アクセス許可を提供します。
なお、OIDC IAM プロバイダーの登録は、上述のクラスター定義によって eksctl
が自動的に行うため、明示的に行う必要はありません。
2. AWS Load Balancer Controller 用の Service Account の作成
連載の Part 2 では、eksctl
によるクラスターの作成時に、AWS Load Balancer Controller の Service Account を作成しましたが、今回は個別に作成します。
eksctl create iamserviceaccount \
--cluster=nginx-ingress-controller-walkthrough \
--name=aws-load-balancer-controller \
--namespace=kube-system \
--attach-policy-arn=arn:aws:iam::${AWS_ACCOUNT}:policy/AWSLoadBalancerControllerIAMPolicy \
--approve
3. CRD のインストール
以下のコマンドは、AWS Load Balancer Controller が機能するために必要な CustomResourceDefinition
をインストールします。
kubectl apply -k \
"github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"
4. Helm を使用した AWS Load Balancer Controller のインストール
helm repo add eks https://aws.github.io/eks-charts
helm upgrade -i aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system \
--set clusterName=nginx-ingress-controller-walkthrough \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller
kubectl -n kube-system rollout status deployment aws-load-balancer-controller
kubectl get deployment -n kube-system aws-load-balancer-controller
5. NGINX Ingress Controller の再デプロイ
NGINX Ingress Controller の Helm チャートに設定パラメータを渡す方法を変更します。values.yml
ファイルを作成します。
controller:
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-name: apps-ingress
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: http
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /healthz
service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: 10254
ここでは、Service のタイプを変更し、ロードバランサー (Network Load Balancer) の名前を定義し、アクセスできるように internet-facing
としています。さらに、ターゲットタイプを ip
とし、NGINX サーバーのヘルスチェックを設定しています。
AWS Load Balancer Controller の Service のアノテーションの詳細については、ここを参照してください。
NGINX Ingress Controller を再デプロイします。
helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx \
--version 4.2.3 \
--namespace kube-system \
--values values.yml
kubectl -n kube-system rollout status deployment ingress-nginx-controller
kubectl get deployment -n kube-system ingress-nginx-controller
6. Ingress のテスト
pathType やコントローラーのリライト機能を説明するために使用したのと同じ Ingress 定義を使っていることに注意してください。
Network Load Balancer の URL を環境変数に保存します。
export NLB_URL=$(kubectl get -n kube-system service/ingress-nginx-controller \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
数分後、ロードバランサーのプロビジョニングが完了すると、リクエストを送信できるようになります。
curl ${NLB_URL}/first
curl ${NLB_URL}/first/foo
curl ${NLB_URL}/first/bar
curl ${NLB_URL}/first/bar/foo
これによって、予想どおり、以前と同じ結果が得られます。
既存のロードバランサーに NGINX Ingress Controller をアタッチする
前述の Service コントローラーを使用する方法に加えて、AWS CLI や Infrastructure as Code ツール (AWS CloudFormation、AWS CDK、Terraform など) を介して Application Load Balancer や Network Load Balancer をプロビジョニングすることも可能です。
そのような場合、上記でインストールしたカスタムリソース定義の一部である TargetGroupBinding
を使用することができます。このリソースは、Service の Pod の IP アドレスをターゲットグループのターゲットとして登録することにより、Service (名前と Namespace で選択) をロードバランサーのターゲットグループ (ARN で選択) にバインドします。
これは、ロードバランサーが他のコンピュートリソースで使用されている場合に有用な場合があります。まれに、クラスター内レイヤー 7 プロキシの上に、Application Load Balancer の独自機能の 1 つを利用するように設定する必要がある場合にも有用です。
複数の Ingress コントローラー
場合によっては、クラスター内に NGINX Ingress Controller の複数のインスタンスを構成して、別々の Ingress リソースを処理させることができます。これは、コントローラーに異なる設定を提供することで実現します。AWS Load Balancer Controller とは異なり、NGINX Ingress Controller はこのような構成をサポートしています。
次の例は 2 番目のコントローラーの例です。ユニークな名前、IngressClass 名、コントローラー値の設定が IngressClass に反映されます。
helm upgrade -i ingress-nginx-one ingress-nginx/ingress-nginx \
--namespace kube-system \
--set controller.ingressClassResource.controllerValue=k8s.io/ingress-nginx-one \
--set controller.ingressClassResource.name=nginx-one \
...
これで、Ingress リソースは、ingressClassName
を nginx-one
に設定することで、このコントローラをターゲットにできるようになります。
クリーンアップ
これでウォークスルーは終了です。ウォークスルー中に作成したリソースを削除するには、次のコマンドを実行します。
helm uninstall -n kube-system ingress-nginx
helm uninstall -n kube-system aws-load-balancer-controller
envsubst < config.yml | eksctl delete cluster -f -
aws iam delete-policy --policy-arn arn:aws:iam::${AWS_ACCOUNT}:policy/AWSLoadBalancerControllerIAMPolicy
まとめ
このシリーズでは、いくつかの Ingress コントローラーを紹介し、それぞれがどのように異なる動作をするのかを強調しながら説明しました。
NGINX Ingress Controller は、nginx のパワーを利用します。これはより柔軟なコントローラーですが、リクエストのデータパス上に必要なコンポーネントのメンテナンス、パッチ適用、スケーリングが必要になるという欠点があります。
これに対し、AWS Load Balancer Controller は、その負担を、可用性が高くスケーラブルで実績のあるマネージドサービスである Elastic Load Balancing にアウトソーシングし、その機能セットに依存して必要な設定オプションを提供します。
極めて高い柔軟性と運用の簡便性のどちらを選択するかは、導入されるアプリケーションの要件に基づいて決まります。この連載では取り上げていない他の Service コントローラーや Ingress コントローラーとともに、アプリケーションを外部トラフィックに公開するための豊富な選択肢を提供するはずです。
翻訳はプロフェッショナルサービスの杉田が担当しました。原文はこちらです。