APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

【AKS】 Ingress NGINXからNGINX Gateway Fabricへの乗り換え記

はじめに

みなさんこんにちは。エーピーコミュニケーションズ ACS事業部亀崎です。

みなさんもIngress NGINXのRetirementについてのニュースはご存知かとおもいます。 retireとなるのは2026年3月末。そう、まもなくです。

www.kubernetes.dev

以降は他のIngress Controllerか、Gateway APIに乗り換えを求められています。 私の手元の環境でも、AKS上に(OSS版の)Ingress NGINXをインストールして利用していました。 このため、どこかに移行する必要がありました。 では、どこに移行するか? 今後はInbound Trafficの管理はGateway APIが中心になると思いますので、Gateway APIを利用することにしました。 Gateway APIに対応したものも多数登場していますが、いろいろ考えた末に NGINX Gateway Fabric を採用することにしました。

docs.nginx.com

理由としては以下のようなものです。

  • 今の利用状況を考えると、Service Meshは機能過剰に感じること。Ingress NGINX同様単純なN-S TrafficのGateway機能だけで十分。
  • これまでIngress NGINX を使っていたため、同じNGINXを利用したもののほうが移行が簡単ではないかと考えたこと

こうした選定理由もあって、移行はそれほど苦労はないだろうとおもっていました。このため当初はこのブログも書くほどのものではないだろうとも考えていました。

しかし、実際はいろいろと苦労した部分がありました。せっかくなので、皆さんにも共有しようと考え本ブログを書いています。

ということで、今回は Ingress to Gateway の移行記録、になります。

インストール作業記録

まずあらためて、今回の対象について記載します。

これまでの Ingress NGINX環境は Ingress NGINX と TLS証明書管理にcert-managerを利用し、Let's Encryptの証明書を発行していました。今回はIngress NGINXに換えてNGINX Gateway Fabricを利用する形にします。

移行対象のアプリケーションについては、登録されているingress オブジェクトを移行対象にしてしまうと混乱も大きく、時間もかかるとおもったので、まずは対象を絞り、最初のターゲットは argocd-serverを選択しました。選定理由としては、通常のWebアクセス(HTTP)に加えて、 argocd CLIからの gRPC アクセスも存在するため、今後移行する際のパターンの大半をカバーしていると考えたことです。

まずはインストールの内容です。この部分は比較的順調でした。

NGINX Gateway Fabric

今回の作業実施時点(2026年2月)でNGINX Gateway Fabric(NGF)のバージョンはv2.4.2。 手順は以下のページを参考に実施します。

https://docs.nginx.com/nginx-gateway-fabric/install/helm/

1) Gateway API CRDのインストール

(少なくとも作業実施時点では)AKSにはGateway APIのCRDはインストールされていません。このため最初にCRDをインストールする必要があります。

kubectl kustomize "https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/standard?ref=v2.4.2" | kubectl apply -f -
2) Helm Chartからインストール

続いて NGINX Gateway Fabric本体のインストールです。 values.yamlの指定は特に必要ありません。といいつつ実際のインストールでは以下の指定を明示的に行っています。

certGenerator:
  enable: true

そしてコマンド実行です。

helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric --version 2.4.2 --create-namespace -n nginx-gateway

さきほど、Gateway APIのCRDをインストールしましたが、実はこれ以外にNGINX Gateway Fabric固有のCRDのインストールも必要になります。 helm v3以降の場合はinstallCRDsフラグを付けなくても自動的にCRDがインストールされますが、もしkustomizeなどを利用している場合はこの点にもご注意ください。

なお、ClusterRoleなどいくつかのリソースがCluster Scopeにインストールされますので、権限にもご注意ください。

難しいところはないと思います。

cert-manager

cert-managerもGateway API対応に変更しなければなりません。 修正の方法についてはNGFのドキュメントにも記載されています。

docs.nginx.com

手元の環境ではcert-managerもhelmを使ってインストールしています。そのvalues.yamlは以下のようなものになります。

crds:
   enabled: true
config:
  apiVersion: controller.config.cert-manager.io/v1alpha1   
  kind: ControllerConfiguration
  enableGatewayAPI: true

Argo CD

Ingress NGINX利用時は、TLS証明書をpass throughモードで利用していました。ArgoCD serverでHTTPSとGRPCの両方を公開するためです。 このため、argocd-serverの設定もinsecureフラグはfalseにしていました。

参考リンク

argo-cd.readthedocs.io

これに対しGatewayではTLSをTerminateしても問題ないのではないかと考えました。このため argocd-serverの設定もinsecure=trueに変更しました。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
data:
  application.namespaces: "*"
  server.insecure: "true"

必要となるインストール作業は以上です。 ここまでは順調でした。ここからが、試行錯誤のはじまりです。

TLS証明書設定 & Gateway設定記録

ここからArgo CD ServerのGatewayオブジェクトやHTTPRoute、GRPCRouteの設定を行います。

移行前のIngressオブジェクトは以下のようなものとなります。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server-ingress
  annotations:
    ingress.kubernetes.io/proxy-body-size: 100M
    ingress.kubernetes.io/app-root: "/"
    cert-manager.io/issuer: argo-cd-issuer
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  ingressClassName: nginx
  rules:
  - host: argocd-server.your-domain
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: argocd-server
            port:
              name: https
  tls:
  - hosts:
    - argocd-server.your-domain
    secretName: argocd-server-tls # as expected by argocd-server

これをGateway APIで定義していくのですが、その内容は以下のものを参考にしました。

argo-cd.readthedocs.io

こちらはNGINX Gateway Fabric用のものではありませんが、同じGateway API対応のものですので大きく変わらないだろうという判断です。

しかしそう簡単にいきませんでした。 発生した問題は以下のとおりです。

  • Gatewayから作成されるLoadBalancerにPublic IPが割り当てられない
  • HTTPとGRPCを同じホスト名で公開するとブラウザからアクセスした場合にもgRPCの応答を返してしまう

これらの課題を解決していくなかでcert-managerのIssuer関連の問題も出てきたのですが、これは結果的に途中の設定に問題があったためと思われるためここでは省略します。

最終的な設定内容を示しながらポイントを記載します。

まずはGatewayオブジェクトです。

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: argocd-gateway
  annotations:
    cert-manager.io/issuer: argocd-gateway-issuer
spec:
  gatewayClassName: nginx
  infrastructure:
    annotations:
      service.beta.kubernetes.io/azure-load-balancer-resource-group: "azure-resource-name"
      service.beta.kubernetes.io/azure-pip-name: "azure-public-ip-resource-name"
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      hostname: argocd-server.your-domain
      tls:
        mode: Terminate
        certificateRefs:
          - name: argocd-server-cert
            kind: Secret
    - name: grpc
      protocol: HTTPS
      port: 443
      hostname: argocd-grpc.your-domain
      tls:
        mode: Terminate
        certificateRefs:
          - name: argocd-grpc-cert
            kind: Secret

まず最初のポイントがPublic IPの割り当てです。これを解決するための設定が以下の部分です。

spec:
  ...
  infrastructure:
    annotations:
      service.beta.kubernetes.io/azure-load-balancer-resource-group: "azure-resource-name"
      service.beta.kubernetes.io/azure-pip-name: "azure-public-ip-resource-name"

実はIngress NGINXのインストールでも同じような設定があったため、こうした設定を組み込む必要があることは認識していたので、おおよその見当は当初から付いていました。 その設定をどこで行うべきかをいろいろ調べた結果、Gatewayの spec.infrastructure.annotations に設定すべき、ということが判明しこのようにしています。

つづいて、HTTPRoute / GRPCRouteの設定です。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: argocd-server
spec:
  parentRefs:
    - name: argocd-gateway
  hostnames:
    - argocd-server.your-domain
  rules:
    - backendRefs:
        - name: argocd-server
          port: 80
      matches:
        - path:
            type: PathPrefix
            value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: argocd-grpc
spec:
  parentRefs:
    - name: argocd-gateway
  hostnames:
    - argocd-grpc.your-domain
  rules:
    - backendRefs:
        - name: argocd-server
          port: 443
      # matches:
      #   - headers:
      #       - name: content-type
      #         value: application/grpc

お気づきでしょうか。Gatewayの指定ではポート443の指定が2つあります。実はHTTPRouteのhostnameの設定では argocd-server.your-domain 、GRPCRouteのhostnameは argocd-grpc.your-domain と異なった名称になっています。これにあわせて、Gatewayのほうも 以下のような指定となっていたのです。

    # argocd-server(HTTPSアクセス)用の指定
    - name: https
      protocol: HTTPS
      port: 443
      hostname: argocd-server.your-domain
      tls:
        mode: Terminate
        certificateRefs:
          - name: argocd-server-cert
            kind: Secret
    # argocd-grpc(GRPCアクセス)用の設定
    - name: grpc
      protocol: HTTPS
      port: 443
      hostname: argocd-grpc.your-domain 
      tls:
        mode: Terminate
        certificateRefs:
          - name: argocd-grpc-cert
            kind: Secret

最初はHTTP(Web)アクセスもGRPCアクセスも同じhostnameで受け付ける予定でした。GRPCRouteの以下の指定があれば、GRPCプロトコルのアクセスとHTTP(Web)アクセスが区別できると考えたからです。

      matches:
        - headers:
            - name: content-type
              value: application/grpc

この指定はArgoCDのCiliumの利用例でも記載されていました。しかし、実際にやってみるとこのmatchesの指定が機能していない。 最近のWebブラウザはHTTP/2.0でアクセスすることもあるのですが、ブラウザからのアクセスも(matchesでcontent-typeの判定をしているにもかかわらず)GRPCと認識され、HTTPRouteが適用されない。

Geminiを使ってこの問題を調べたところ、Content-Typeといった一部のヘッダでは機能しないことがある、という回答が返ってきました。 これはNGF+NGINXの組み合わせ特有の挙動ではないかと思われます。このためHTTPS用のhostnameとgRPC用のhostnameを分けて受け付けることにしました。 これでWebブラウザからのアクセスも argocd CLIコマンドからのGRPCアクセスも利用できるようになりました。

なお、最終的に必要になるのかわかりませんが、argocd-server側のService指定も少し変更しています。 変更箇所は443ポートのところのnameです。もともとはhttpsでしたが、grpcに変更しました。

これでなにか変わるのだろうか?とは思いますが、さまざまなトライ&エラーの作業の中では確かに動作が違う部分がありました。

apiVersion: v1
kind: Service
metadata:
  name: argocd-server
spec:
  type: ClusterIP
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
    - name: grpc    # もともとの名前はhttps
      protocol: TCP
      port: 443
      targetPort: 8080

cert-managerとの連携の部分は記載の内容でうまく動作しています。今回のトライ&エラーにおいて、Certificateとそこで指定しているSecretの名称を異なったものとしていたことが原因なのか、作成した覚えのないCertificate(名称がSecretの名称と同じもの)が作成されてしまい、うまくCertificateが取得できないという事象が発生しました。このためCertificateとSecretの名称を同じものにしています。

Issuerについては共通ですが、CertificateについてもWeb用とgRPC用の2つを用意しています。以下はgRPC用です(Web用Certificateも同様の内容です)

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: argocd-gateway-issuer
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: 'you@our-mail-address'
    privateKeySecretRef:
      name: gateway-issuer-account-key
    solvers:
      - selector: {}
        http01:
          gatewayHTTPRoute:
            parentRefs:
              - name: argocd-gateway
                namespace: argocd
                kind: Gateway

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: argocd-grpc-cert
spec:
  secretName: argocd-grpc-cert
  issuerRef:
    name: argocd-gateway-issuer
    kind: Issuer
  dnsNames:
    - argocd-grpc.your-domain

設定としては以上です。

おわりに

当初はIngressからGatewayの移行は簡単だと考え、1日程度の作業で完了させる予定でした。しかし結果として3日くらいの時間がかかってしまいました。 少し甘く見すぎていた結果ですが、色々勉強にはなりました。

まとめると今回の移行のポイントは以下のとおりです。

  1. Azure固有の設定: 公開IPの固定は Gateway オブジェクトの infrastructure.annotations で行う。
  2. gRPCとHTTPSの両立: 同一ホスト名での自動判定(Content-Type matches)に頼らず、ホスト名を分けるのが確実。
  3. cert-managerの対応: ControllerConfiguration で enableGatewayAPI: true を忘れずに。

Ingress NGINXのRetirementに伴い同じような作業を考えている方もいらっしゃると思います。そうした方に今回の導入記が参考になると幸いです。