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 コントローラーのタイプについて説明しました。

クラスター内リバースプロキシによる Ingress コントローラーの実装

NGINX Ingress Controller の実装は、上記のアーキテクチャに沿っています。

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 EKSeksctl のドキュメントを参照してください。

クラスターが稼働していることを確認します。

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

アプリケーションの Namespace にある Pod と Service のリストのスクリーンショット

シンプルな 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

以下のような出力が得られます。

アプリケーションの Namespace にある Ingress オブジェクトのリストのスクリーンショット

上記の ADDRESSPORT 列は、コントローラーの 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 リソースがは正しくデプロイされ、設定されています。

NGINX サーバーからの HTTP 404 レスポンスのスクリーンショット

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 ControllerIngressClass パラメータをサポートしていません。

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 の背後にある Service のレスポンスのスクリーンショット

複数の 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'

出力は次のようになります。

Ingress の背後にある Service のレスポンスのスクリーンショット

最後の 2 つのリクエストは、Default Backend にルーティングされることが期待されます。1 つはそのホストで定義されていないパスに送信されているため、もう 1 つは存在しないホストであるためです。

a.myapp.comb.myapp.com の DNS レコードを NGINX Ingress Controller の Service に向けることで、両方のホストを処理することができます。このタスクを完了するために、Service を外部トラフィック向けに公開します (外部ロードバランサー経由など) 。これについては、この記事の後半で詳しく説明します。

Ingress の pathType および正規表現とリライト

これまで、Ingress ルールの pathTypePrefix と定義してきました。pathTypeExact に設定し、パスで正規表現を使用したり、リライトルールを定義することもできます。

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

これは、次のような結果になります。

Service のレスポンスのスクリーンショット

ロードバランサー経由で 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

これによって、予想どおり、以前と同じ結果が得られます。

Service のレスポンスのスクリーンショット

既存のロードバランサーに NGINX Ingress Controller をアタッチする

前述の Service コントローラーを使用する方法に加えて、AWS CLI や Infrastructure as Code ツール (AWS CloudFormationAWS CDKTerraform など) を介して 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 リソースは、ingressClassNamenginx-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 コントローラーとともに、アプリケーションを外部トラフィックに公開するための豊富な選択肢を提供するはずです。

翻訳はプロフェッショナルサービスの杉田が担当しました。原文はこちらです。