NGINX Ingress Controllerの柔軟なアプリケーション制御、具体的なユースケースと設定方法を理解する

2021年9月22日(水)
松本 央

はじめに

本連載では、NGINX(エンジンエックス)が求められている時代背景と、そのユースケース、具体的な設定手順について複数回に分けて解説致します。第2回目となる本稿では、NGINX Ingress Controllerの柔軟なアプリケーション制御について、具体的なユースケースと設定方法を交えて詳しく解説します。

本番環境でアプリケーションを動作させるにあたって
Kubernetes環境に求められる要件

第1回でもご紹介した通り、近年、皆様を取り巻くビジネス環境の競争は激化しており、その中でデジタル技術を最大限活用し顧客のニーズに合わせたサービスを実現することが業界問わず必須要件となっています。そのアプリケーションを、コンテナ技術を用いてデプロイし、サービスの伸縮性と継続的なアプリケーション開発を実現するために注目されているのがKubernetesです。

各社より様々なKubernetes プラットフォームが提供されており、それらをアプリケーションの要件に合わせて自由に選択することが可能です。これらを用いて本番環境でアプリケーションをデプロイし、顧客へのサービスを開始すると開発環境では見られなかった問題が発生します。今回はNGINX Plusを用いたNGINX Ingress Controllerによりそれらの問題を解決する方法をご紹介いたします。

本題に入る前にそれぞれのフェーズで発生する課題について見ていきましょう(図1)。

図1:アプリケーションコンテナ化における課題

コンテナ・Kubernetesの利用を開始した際には、まずそれぞれのテクノロジーが持つ特徴を理解し、組織やチームが求めるゴールに向かってアプリケーションの開発を進めていきます。その後、本番環境でのアプリケーションの稼働が開始します。外部との接続により様々な通信が流れ始めますので、プラットフォームを安定させるための制御やセキュリティの実装、可視化が必要になり、これらの制御を各チームが並行して柔軟に実施する必要があります。そしてサービスが拡大すると、デプロイするアプリケーションが増え様々な要件に対応しながらも、プラットフォーム全体として高い安定性とセキュリティを実現する必要があります。

Kubernetesを使いこなすことが企業の原動力となり、高い競争力を得られることは間違いありませんが、これらの課題を解決するためには今までの手法ではなくKubernetesに最適な形で実装することが求められます。運用負荷を大きく増やすことなくこれらの課題に柔軟に対応する方法が、NGINX Ingress Controllerを用いた通信制御なのです。

NGINX Ingress Controllerが提供する
高度なトラフィック制御と権限管理

旧来のモノリシックな環境ではネットワーク機器や負荷分散機器の運用を熟達した担当者が行い、チームでのミスを事前に防ぐ作業レビューにより障害を未然に回避していました。Kubernetes環境においても、アプリケーションを外部公開する際には同等の品質を担保しなければいけません。例えば、負荷分散の役割を持つIngress Controllerが想定外の設定となりアプリケーションの通信が停止する場合があります。

しかし、アジャイル開発では複数のチームが有機的に連携しながら高速なアプリケーションリリースを実現することが必要不可欠です。旧来のモノリシックな環境で行っていた、開発とインフラチームが別れ分業化された運用では、柔軟なアプリケーションリリースができず、競合他社に出し抜かれる可能性があります。Ingress Controllerが本来持つ通信制御の役割をそのままに、これらの問題を解決するにはどうしたらいいのでしょうか?

それを解決するのが、NGINX PlusによるNGINX Ingress Controllerです。VirtualServer / VirtualServerRouteという新たに拡張したリソース(CRD)により、Ingress ControllerのManifestで必要となっていた多くのAnnotationを用いること無く、比較的NGINX Configurationに近い方式で柔軟な設定を行うことが可能です。また、VirtualServer、VirtualServerRoute、Policyというリソースで表現される設定をKubernetesのRBACと組み合わせる事により、NGINX Ingress Controllerの設定をそれぞれ適切なユーザ・チームでの変更・管理を行うことにより、変更による影響範囲を適切に管理し、想定外の動作による影響をサービス全体に波及させることのないようにすることが可能です。また、設定を変更する人にとっては、自分の設定行為で影響する範囲が限定されているので、自身が担当するアプリケーションデプロイに関わる機能に集中することができ、自由に設定の反映や更新を行うことが可能となります。

拡張したリソース(CRD)を用いた設定のイメージが以下となります(図2)。

図2:NGINXが提供するCRDと各チームによる管理

インフラ担当者がVirtualServerというリソースを書式に則って記述します。この例では、2つのサービスがありますが、それぞれに該当するURIを各サービスのVirtualServerRouteに紐付けます。また、これらのサービスが動作する環境は本番環境となりますので、別途セキュリティ担当者が定義したセキュリティポリシーをインフラ担当者が管理するVirtualServerで参照しています。それぞれの担当は担当外の設定を意識する必要は無く、またセキュリティポリシーなどは別のチームが作成したポリシーを要件に応じて選択・参照することで要件を満たすことが可能となります。

Manifestを用いた
NGINX Ingress Controller環境のセットアップ

それではまず環境のセットアップを行います。こちらの記事は執筆時点に記載されている手順で行っております。最新の機能や新たなKubernetes Versionへの対応が必要な場合には、最新の操作手順(Installation with Manifests)の内容をご確認ください。

NGINX Ingress ControllerのDocker Imageを作成します。手順はこちらの記事(Building the Ingress Controller Image)を参照してください。

事前作業

今回はNGINX Plusの機能をご紹介いたしますので、予めNGINX Ingress Controllerの無料トラアイルよりライセンスファイルを取得してください。また、作成したDockerイメージをコンテナレジストリに登録して利用する場合、対象のコンテナレジストリにログインが可能であることを確認してください。

NGINX Plus/NGINX App Protect Ingress Controllerの
Dockerイメージ作成

それでは早速セットアップを進めていきましょう。Dockerイメージ作成に必要な各種ファイルやKubernetes環境セットアップに用いるファイル群をGitHubより取得します。

# cd ~/
# git clone https://github.com/nginxinc/kubernetes-ingress/
# cd ~/kubernetes-ingress
# git checkout v1.12.0

Dockerイメージを作成します。今回はNGINX PlusとF5が提供するWAFモジュールであるApp ProtectがインストールされたDockerイメージを作成するため「debian-image-nap-plus」を選択します。

# cp ~/nginx-repo* .
# ls nginx-repo.*
nginx-repo.crt  nginx-repo.key 
# make debian-image-nap-plus PREFIX=myregistry.example.com/nginxplus-ingress-nap TARGET=container TAG=1.12.0
# docker images | grep nginxplus-ingress-nap
myregistry.example.com/nginxplus-ingress-nap   1.12.0                    c99dfd32b43b        19 seconds ago      576MB

正しくDockerイメージが作成できたことを確認し、皆様の環境で利用するコンテナレジストリへDockerイメージをアップロードしてください。

NGINX Ingress Controller環境の
セットアップ

Kubernetes環境のセットアップを行います。この記事ではkubeadmで構築した環境でコマンドを実行しています。各コマンドの詳細については手順を確認してください。

先程の手順で取得したGitHubのフォルダへ移動し、必要となるManifestをデプロイします。

# cd ~/kubernetes-ingress/deployments
# kubectl apply -f common/ns-and-sa.yaml
# kubectl apply -f rbac/rbac.yaml
# kubectl apply -f rbac/ap-rbac.yaml
# kubectl apply -f common/default-server-secret.yaml
# kubectl apply -f common/nginx-config.yaml
# kubectl apply -f common/ingress-class.yaml
# kubectl apply -f common/crds/k8s.nginx.org_virtualservers.yaml
# kubectl apply -f common/crds/k8s.nginx.org_virtualserverroutes.yaml
# kubectl apply -f common/crds/k8s.nginx.org_transportservers.yaml
# kubectl apply -f common/crds/k8s.nginx.org_policies.yaml
# kubectl apply -f common/crds/k8s.nginx.org_globalconfigurations.yaml
# kubectl apply -f common/crds/appprotect.f5.com_aplogconfs.yaml
# kubectl apply -f common/crds/appprotect.f5.com_appolicies.yaml
# kubectl apply -f common/crds/appprotect.f5.com_apusersigs.yaml

NGINX Ingress Controllerの実行

NGINX Ingress Controllerのpodを実行します。DeploymentとDaemonSetによる実行が可能ですが、のこの記事ではDeploymentで実行します。DaemonSetで実行したい場合にはマニュアルを参照して適切に読み替えて進めてください。

Deploymentのマニフェストを修正します。7、15、21、22行目を環境に合わせて修正してください。argsで指定するパラメータの詳細はCommand-line Argumentsを参照してください。

# vi deployment/nginx-plus-ingress.yaml

** 省略 **
    spec:
      serviceAccountName: nginx-ingress
      containers:
      - image: myregistry.example.com/nginxplus-ingress-nap:1.12.0     # 対象のレジストリを指定してください
        imagePullPolicy: IfNotPresent
        name: nginx-plus-ingress
** 省略 **
        args:
          - -nginx-plus
          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
          - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
          - -enable-app-protect                     # App Protectを有効にします
         #- -v=3 # Enables extensive logging. Useful for troubleshooting.
         #- -report-ingress-status
         #- -external-service=nginx-ingress
         #- -enable-prometheus-metrics
         #- -global-configuration=$(POD_NAMESPACE)/nginx-configuration
          - -enable-preview-policies           # OIDCに必要となるArgsを有効にします
          - -enable-snippets                  # OIDCで一部設定を追加するためsnippetsを有効にします

修正したマニフェストを指定しPodを作成します。

# kubectl apply -f deployment/nginx-plus-ingress.yaml
deployment.apps/nginx-ingress created

# kubectl get pods --namespace=nginx-ingress
NAME                             READY   STATUS    RESTARTS   AGE
nginx-ingress-5787f47959-4vrwb   1/1     Running   0          5s

STATUSが正しく「Running」になっていることが確認できます。正しいSTATUSとならない場合には、「kubectl describe pod <pod name> (-n <namespace>)」コマンド、「kubectl logs <pod name> (-n <namespace>)」コマンドの結果を参考に原因の調査を行ってください。

NGINX Ingress Controller の外部への公開

NGINX Ingress Controller PodをNode Portで外部へ公開します。各Public CloudのKubernetesを利用している場合にはマニュアルの各手順を参照してください。

以下のnodeport.yamlの内容を適用します。本稿では外部からHTTP、HTTPSの接続を待ち受ける設定とします。

apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress
  namespace: nginx-ingress
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  - port: 443
    targetPort: 443
    protocol: TCP
    name: https
  selector:
    app: nginx-ingress

NodePortをデプロイします。

# kubectl apply -f service/nodeport.yaml
service/nginx-ingress created

# kubectl get svc -n nginx-ingress
NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
nginx-ingress   NodePort   10.101.189.31           80:30286/TCP,443:32353/TCP   7s

この手順を完了し、現在の状態は以下の図となります(図3)。

図3:NodePortデプロイ後の構成

最後に、この環境へクライアントからアクセスするため、HTTP(TCP/80)、HTTPS(TCP/443)を待ち受け、それぞれNodePortで公開するポート番号へ転送するLBを用意します。

今回のラボ環境では同Linux Host上にNGINX Plusをインストールし以下nginx.confとしました。NGINX OSSでも同様の設定で問題ありません。NGINX PlusのInstall手順はInstalling NGINX Plusを参照してください。

# cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf-
# vi /etc/nginx/nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


# TCP/UDP load balancing
#
stream {
    upstream tcp80_backend {
        server localhost:30286;
    }
    upstream tcp443_backend {
        server localhost:32353;
    }

    server {
        listen 80;
        proxy_pass tcp80_backend;
    }
    server {
        listen 443;
        proxy_pass tcp443_backend;
    }
}

# nginx -s reload

現在の状態は以下となり、サービスを外部に公開する準備が完了しました(図4)。

図4:NGINX LBデプロイ後の構成

CRD(Custom Resource Definition)を
利用した通信制御と権限管理

それではアプリケーションをデプロイしてみましょう。まず1つ目はNGINX Ingress Controllerの柔軟な設定と権限管理を実現するVirtualServer/VirtualServerRouteを利用します。

対象となるGitHubのフォルダへ移動します。ブラウザでGitHub上の手順ご覧になられる場合にはCross-Namespace Configurationを参照してください。

# cd ~/kubernetes-ingress/examples-of-custom-resources/cross-namespace-configuration/

このアプリケーションは「cafe」というアプリケーション配下に「tea」と「coffee」というそれぞれのサービスがあり、その制御をVirtualServer/VirtualServerRouteで実施する構成となります。まずcafe-virtual-server.yamlの内容を見てみましょう。

apiVersion: k8s.nginx.org/v1
kind: VirtualServer         # 作成するオブジェクトとしてVirtualServerを指定
metadata:
  name: cafe
  namespace: cafe         # cafe VirtualServerをcafe namespaceに作成
spec:
  host: cafe.example.com  # hostnameを指定
  tls:
    secret: cafe-secret
  routes:
  - path: /tea              # /tea 宛の通信を、tea namespaceのteaにルーティング
    route: tea/tea
  - path: /coffee           # /coffee 宛の通信を、coffee namespaceのcoffeeにルーティング
    route: coffee/coffee

2行目でkind: VirtualServerを指定しています。こちらは、環境のセットアップでデプロイしたCRDとなります。VirtualServerを利用する事により、より直感的にIngress Controllerの設定を行うことが可能となります。

今回の例では、通信を待ち受けるVirtualServerと各サービス、tea、 coffeeでそれぞれ別のVirtualServerRouteを設定し、各チームがそれぞれの権限で設定を管理することを想定したシナリオとなります。このため、VirtualServerは別途作成するcafeというnamespaceを指定します。

cafe.example.com宛のHTTP/HTTPSリクエストを受け取った後、routesのpathの内容に従って、/teaの場合にはtea namespaceのteaサービスへ、/coffeeの場合にはcoffee namespaceのcoffeeサービスへルーティングがされます。

では、次にteaサービスで利用するtea-virtual-server-route.yamlを見てみましょう。

apiVersion: k8s.nginx.org/v1
kind: VirtualServerRoute     # 作成するオブジェクトとしてVirtualServerRouteを指定
metadata:
  name: tea
  namespace: tea           # tea VirtualServerRouteをtea namespaceに作成
spec:
  host: cafe.example.com
  upstreams:
  - name: tea
    service: tea-svc
    port: 80
  subroutes:
  - path: /tea               # /tea 宛の通信を、upstream teaに転送
    action:
      pass: tea

tea VirtualServerRouteと同じ構成となりますが、coffee-virtual-server-route.yamlを見てみましょう。

apiVersion: k8s.nginx.org/v1
kind: VirtualServerRoute
metadata:
  name: coffee
  namespace: coffee        # coffee VirtualServerRouteをcoffee namespaceに作成
spec:
  host: cafe.example.com
  upstreams:
  - name: coffee
    service: coffee-svc
    port: 80
  subroutes:
  - path: /coffee
    action:
      pass: coffee

このように、VirtualServer、VirtualServerRouteを適切なNamespaceやKubernetesのRBACで組み合わせる事により、サービスで対象とすべきURL Pathとサービスの管理権限を分けることができ、相互に誤った変更をすることが無い、適切な管理を行うことができるようになります。

それでは、実際に設定を反映し、結果を確認します。

Namespaceを作成します。新たに、cafe、coffee、teaが作成されていることが確認できます。

# kubectl apply -f namespaces.yaml
namespace/cafe created
namespace/tea created
namespace/coffee created

# kubectl get ns
NAME              STATUS   AGE
cafe              Active   3s
coffee            Active   3s
default           Active   3d22h
kube-node-lease   Active   3d22h
kube-public       Active   3d22h
kube-system       Active   3d22h
nginx-ingress     Active   3d22h
tea               Active   3s

アプリケーションをデプロイします。

# kubectl apply -f tea.yaml
# kubectl apply -f tea-virtual-server-route.yaml
# kubectl apply -f coffee.yaml
# kubectl apply -f coffee-virtual-server-route.yaml
# kubectl apply -f cafe-secret.yaml
# kubectl apply -f cafe-virtual-server.yaml

デプロイしたPodの情報の確認、及びコマンドラインで疎通を確認した結果を示します。5行目を環境に合わせて修正してください。

# kubectl get pods -n coffee -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
coffee-6f4b79b975-2mv47   1/1     Running   0          60m   10.244.0.8   ubuntu              

# curl -H "Host: cafe.example.com" http://localhost/coffee
Server address: 10.244.0.8:8080
Server name: coffee-6f4b79b975-2mv47
Date: 17/Aug/2021:08:27:34 +0000
URI: /coffee
Request ID: 5ec6bd7996060d9d0b3e74e3207e0818

PATH /coffeeを指定し、cafe.example.comにアクセスすると、対象のサーバのIPアドレスを含む情報が応答されたことを確認できました。PATH /teaもデプロイされておりますので合わせてご確認ください。

現在の状態は以下の図となります(図5)。

図5:Cross Name Spaceサンプルデプロイ後の構成

NGINX Ingress ControllerでListenするポートを設定情報では明示していませんが、IngressやVirtualServerで設定を行った場合は、記述内容に合わせてHTTP(TCP/80)、HTTPS(TCP/443)が処理される状態となります。

この例ではNGINX Ingress Controllerの設定となるVirtualServerやVirtualServerRouteのリソースはそれぞれ別のNamespaceで管理しております。このようにアプリケーションデプロイとIngress Controllerで管理する設定範囲を適切に管理する事により、各チームで自由にデプロイすることが可能となり、Kubernetes環境でよりスムーズなアプリケーションの公開が可能となります。

それでは、最後に作成したアプリケーションを削除します。

# kubectl delete -f tea.yaml
# kubectl delete -f tea-virtual-server-route.yaml
# kubectl delete -f coffee.yaml
# kubectl delete -f coffee-virtual-server-route.yaml
# kubectl delete -f cafe-secret.yaml
# kubectl delete -f cafe-virtual-server.yaml
NGINX テクニカルソリューションズアーキテクト
ISPでネットワークエンジニアとしてキャリアをスタート。ネットワークのコアとして様々なプロトコルを扱うことに楽しみを感じF5ネットワークスジャパンでBIG-IPのコンサルタントやソリューションエンジニアとしての経験を経て、NGINX テクニカルソリューションズアーキテクトに。NGINXの紹介・技術支援を通じてクラウドネイティブで安定したインフラ環境の実現のため日々奮闘中。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています