本記事には広告(アフィリエイトリンク)が含まれます。

Gateway APIでHTTPS公開|CKAD第18回

広告

新卒インフラエンジニア向け「Kubernetes 実践教科書① CKAD アプリケーション開発編」の第18回です。第17回で fanclub-api を Helm chart にまとめ、helm install で一括デプロイできるようにしました。ただし外部からの入口はまだ無く、動作確認は kubectl execport-forward に頼っていました。今回は Gateway APITraefikcert-managerhttps://fanclub.local を TLS 付きで公開し、ブラウザから CRUD を操作できる状態にします。CKAD ドメイン D5(Services and Networking)の 2026 年 NEW 必須項目です。

動作確認バージョン:Gateway API v1.4.0(standard channel)/ Traefik chart v39.0.9(Traefik v3.6.15)/ cert-manager v1.20.0 / Helm v4.1.4 / kind v0.31.0(K8s v1.35.0)/ fanclub-backend:0.3.2(据え置き)/ fanclub-frontend:0.1.0 → 1.0.0(2026-07-03 実機検証時点)

広告

今ここマップ(全 19 回中の現在地)

現在地は第 6 部「パッケージ管理と HTTPS 公開」の第 18 回です。chart 化した fanclub-api に、外部公開の入口(HTTPS)を付けます。

  • 第 1 部 コンテナと Docker(第 1〜4 回)
  • 第 2 部 Kubernetes 基礎(第 5〜6 回)
  • 第 3 部 アプリリソース(第 7〜11 回)
  • 第 4 部 ワークロード戦略(第 12〜14 回)
  • 第 5 部 セキュリティ基礎(第 15〜16 回)
  • 第 6 部 パッケージ管理と HTTPS 公開(第 17〜19 回)← 今ここ

この回のゴール

  • Ingress と Gateway API の違いを説明できる。
  • GatewayClass / Gateway / HTTPRoute の役割を理解し、Traefik を Gateway 実装として導入できる。
  • cert-manager の自己署名 CA で TLS 証明書を発行し、https://fanclub.local をブラウザから開ける。

この回の作業はすべて、作業端末 k8s-ops に一般ユーザー developer で SSH した状態で行います。設定ファイルは developer のホーム(~)に一般ユーザーとして作成し、kubectl / helm第6回から使ってきた同じ kubeconfig(~/.kube/config)で kind クラスタを操作します。root 権限は使いません。

Ingress との対比

Kubernetes で HTTP(S) を外部公開する従来の仕組みは Ingress でした。Ingress には次の弱点があります。

  • アノテーション依存:TLS のリダイレクトやパス書き換えなど、標準フィールドに無い設定はコントローラ独自のアノテーションで書く。実装ごとに書式が違い、移植性が低い。
  • 役割が 1 リソースに混在:インフラ担当・クラスタ運用・アプリ開発の関心事が 1 つの Ingress に同居し、権限分離しにくい。
  • 実装差異:同じ Ingress でもコントローラ実装で挙動が変わる。

Gateway API は Ingress の後継として設計された標準 API で、役割ごとにリソースを分けます。設定は標準フィールドで表現でき、アノテーション頼みが減ります。加えて、広く使われてきた ingress-nginx は 2026 年 3 月で EOL(開発終了) になり、本シリーズでは採用しません。これも Gateway API へ移る実務上の理由です。

Gateway API のリソース

Gateway API は、関心事を 3 つのリソースに分けます。誰が管理するかが明確になる設計です。

  • GatewayClass:どの実装(プロバイダ)を使うかを指定する。Traefik を入れると traefik という GatewayClass が用意される。インフラ担当の関心事。
  • Gateway:リスナー(待ち受けるポート・プロトコル・TLS 終端)を定義する。クラスタ運用者の関心事。
  • HTTPRoute:ホスト名やパスで、どの Service へ転送するかを定義する。アプリ開発者の関心事。
  • ReferenceGrant:Namespace をまたいで参照するとき(例: 別 Namespace の Service や Secret を指す)に、参照先側で許可を与える。本回は Gateway・HTTPRoute・Service・Secret をすべて同じ fanclub Namespace に置くので ReferenceGrant は不要です。
Gateway API の役割分担図。GatewayClass(実装プロバイダ traefik)を土台に、Gateway(リスナー・TLS 終端)が乗り、その上に HTTPRoute(ホスト名・パスで Service へ転送)が結び付く。それぞれインフラ担当・クラスタ運用・アプリ開発の関心事として分離される。
図1:Gateway API のリソース分割(GatewayClass / Gateway / HTTPRoute の役割分担)

Gateway API の CRD を入れる

Gateway API のリソース(GatewayClass など)は Kubernetes 標準ではなく CRD(カスタムリソース定義)で提供されます。まず v1.4.0 の standard channel を適用します。実行コマンド:

$ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml

実行結果(例):GatewayClass / Gateway / HTTPRoute / ReferenceGrant など 6 種の CRD が作られます。

customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/grpcroutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/backendtlspolicies.gateway.networking.k8s.io created

続けて、この後使う Helm リポジトリを 2 つ登録しておきます。実行コマンド:

$ helm repo add traefik https://traefik.github.io/charts
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update

Traefik を Helm で入れる(Gateway 実装)

Gateway API は「仕様」なので、実際にトラフィックを捌く実装が要ります。本シリーズは Traefik を使います。kind 特有のはまりどころが 2 つあるので、先に値ファイルで対策を入れておきます。developer のホームに traefik-values.yaml を作成します。

providers:
  kubernetesGateway:
    enabled: true
  kubernetesIngress:
    enabled: false
gateway:
  enabled: false
gatewayClass:
  enabled: true
ports:
  web:
    port: 80
    hostPort: 80
  websecure:
    port: 443
    hostPort: 443
service:
  type: ClusterIP
updateStrategy:
  type: Recreate
  rollingUpdate: null
securityContext:
  capabilities:
    add:
    - NET_BIND_SERVICE
    drop:
    - ALL
  runAsGroup: 65532
  runAsNonRoot: true
  runAsUser: 65532

値の意味と、2 つのはまりどころを説明します。

  • providers.kubernetesGateway.enabled: true:Traefik を Gateway API のプロバイダとして動かす。gatewayClass.enabled: true と合わせて traefik という GatewayClass が作られる。Ingress は使わないので kubernetesIngress.enabled: false
  • gateway.enabled: false:Traefik chart 自身が作る既定の Gateway は不要(Gateway は後で chart の fanclub-gateway を自分で作る)なので無効にする。
  • はまりどころ①・ポート一致:Traefik の待ち受け口(entryPoint)は既定で websecure=:8443 です。しかし後で作る Gateway のリスナーは 443 です。Gateway のリスナー port は Traefik の entryPoint と一致していないと有効になりませんPortUnavailable になる)。そこで ports.web.port / ports.websecure.port80 / 443 に変え、非 root で低番ポートを開けるよう securityContextNET_BIND_SERVICE を足します。
  • hostPort: 80 / 443第5回の kind 構成で「ホストの 80/443 → kind ノードの 80/443」をマッピング済みです。Traefik がノードの 80/443 に hostPort で結び付くことで、ホストからの HTTPS が Traefik に届きます。
  • はまりどころ②・更新の詰まりhostPort はノードのポートを 1 Pod が占有します。既定のローリング更新だと、新旧 2 つの Pod が同時に 80/443 を掴もうとして新 Pod が起動できません。updateStrategy.type: Recreate で「古い Pod を消してから新 Pod を作る」に変えて回避します。

この値で Traefik を install します。実行コマンド:

$ helm install traefik traefik/traefik --version 39.0.9 \
    -n traefik --create-namespace -f ~/traefik-values.yaml

GatewayClass が使える状態(ACCEPTED=True)になったか確認します。実行コマンド:

$ kubectl get gatewayclass

実行結果(例):

NAME      CONTROLLER                      ACCEPTED   AGE
traefik   traefik.io/gateway-controller   True       20s

cert-manager で TLS 証明書を用意する

HTTPS には TLS 証明書が要ります。cert-manager を入れ、自己署名 CA(社内 CA を模した学習用の認証局)を作って、そこから fanclub.local のサーバ証明書を発行します。まず cert-manager 本体を install します(CRD も同時に入れます)。実行コマンド:

$ helm install cert-manager jetstack/cert-manager --version v1.20.0 \
    -n cert-manager --create-namespace --set crds.enabled=true

次に自己署名 CA を作ります。cert-manager では、次の 3 リソースを鎖のようにつなぎます。developer のホームに ca-issuer.yaml を作成します。

  1. selfSignedClusterIssuer(自分で自分に署名する起点)
  2. その起点で発行する CA 証明書isCA: true・秘密鍵は cert-manager Namespace の Secret に入る)
  3. その CA を使う ClusterIssuerfanclub-ca-issuer・以降のサーバ証明書はここが発行する)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-bootstrap
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: fanclub-ca
  namespace: cert-manager
spec:
  isCA: true
  commonName: fanclub-local-ca
  secretName: fanclub-ca-secret
  duration: 87600h
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: selfsigned-bootstrap
    kind: ClusterIssuer
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: fanclub-ca-issuer
spec:
  ca:
    secretName: fanclub-ca-secret

適用し、fanclub-ca-issuer が使える状態(READY=True)になったか確認します。実行コマンド:

$ kubectl apply -f ~/ca-issuer.yaml
$ kubectl get clusterissuer

実行結果(例):

NAME                   READY   AGE
fanclub-ca-issuer      True    5s
selfsigned-bootstrap   True    5s

CA 秘密鍵の Secret(fanclub-ca-secret)を cert-manager Namespace に置いているのは、ClusterIssuer が CA を既定でこの Namespace から読むためです(別 Namespace に置くと fanclub-ca-issuer が CA を見つけられません)。サーバ証明書(fanclub-tls)そのものは、この後 chart 側の Certificate テンプレートが fanclub-ca-issuer に発行を依頼します。CA の準備はここまでです。

Frontend を最終形(v1.0.0)に仕上げる

ブラウザ公開に合わせて、Frontend を最終形にします。第8回で作った Frontend は削除操作をブラウザの確認ダイアログで行っていましたが、これを専用の削除確認画面(delete.htmlに置き換え、6 画面(ホーム / 登録 / 詳細 / 編集 / 削除確認 / 検索)そろえて v1.0.0 とします。delete.html は他の画面と同じく Bootstrap と app.jsdeleteMember() を呼ぶ静的ページで、削除ボタンからこの画面へ遷移するよう app.jsdetail.html も更新します(差分は本シリーズ配布のアプリソースに含めています)。Frontend のソース一式は developer のホームの ~/fanclub-frontend にあり、これを更新したうえで第4回で構築した k8s-registry へビルドして push します。実行コマンド:

$ cd ~/fanclub-frontend
$ docker build -t k8s-registry:5000/fanclub-frontend:1.0.0 .
$ docker push k8s-registry:5000/fanclub-frontend:1.0.0

実行結果(例):registry のタグ一覧に 1.0.0 が増えます。

{"name":"fanclub-frontend","tags":["0.1.0","1.0.0"]}

なお、Frontend の Nginx は第8回から /api を Backend へリバースプロキシしています。この回でもその構成を維持します(後述のルーティング設計と関係します)。

chart の Gateway を有効化する

第17回の chart には、Gateway / HTTPRoute / Certificate のテンプレートを gateway.enabled のトグルで無効にした状態で仕込んであります。CRD と Traefik・cert-manager がそろった今、これを有効化します。まず 3 つのテンプレートの中身を確認します(~/fanclub-chart/templates/ にあります)。

gateway.yaml:443 で HTTPS を待ち受け、fanclub-tls Secret で TLS を終端します。

{{- if .Values.gateway.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: fanclub-gateway
  namespace: {{ .Values.namespace }}
spec:
  gatewayClassName: traefik
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    hostname: {{ .Values.gateway.host | quote }}
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: fanclub-tls
{{- end }}

certificate.yaml:先ほど作った fanclub-ca-issuer に、fanclub.local のサーバ証明書を fanclub-tls Secret として発行させます。

{{- if .Values.gateway.enabled }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: fanclub-tls
  namespace: {{ .Values.namespace }}
spec:
  secretName: fanclub-tls
  dnsNames:
  - {{ .Values.gateway.host | quote }}
  issuerRef:
    name: fanclub-ca-issuer
    kind: ClusterIssuer
{{- end }}

httproute.yaml:ここがルーティング設計の要です。本アプリは /(すべてのパス)を Frontend へ通す単一ルートにします。/api は Frontend の Nginx が Backend へリバースプロキシするので、通信は Gateway → Frontend → Backend → DB の一本道になります。

{{- if .Values.gateway.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: fanclub-route
  namespace: {{ .Values.namespace }}
spec:
  parentRefs:
  - name: fanclub-gateway
  hostnames:
  - {{ .Values.gateway.host | quote }}
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: fanclub-frontend
      port: 80
{{- end }}

この「一本道」にする設計は、第16回の NetworkPolicy 設計と揃えるためです。第16回では Gateway → Frontend → Backend → DB の隣接層だけを許可する 4 層のホワイトリストを敷き、外部→Frontend の許可(allow-frontend-from-gateway)は本回で足すと予告しました。もし HTTPRoute で /api を Backend へ直接振り分けると、Gateway が Frontend と Backend の両方に通信することになり、Backend 向けの許可も別途必要になってこの設計から外れます。そこで /api は Frontend の Nginx に任せ、Gateway は Frontend だけを見る形にします。

その allow-frontend-from-gateway を、chart の ~/fanclub-chart/templates/networkpolicy.yaml第16回の 6 枚と第17回のバッチ用 1 枚、計 7 枚がすでに入っているファイル)に追記します(gateway.enabled のときだけ有効になるよう {{- if .Values.gateway.enabled }} で囲みます)。Traefik は traefik Namespace にいるので、その Namespace(自動で付く kubernetes.io/metadata.name: traefik ラベルで指定)からの ingress を許可します。

{{- if .Values.gateway.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-from-gateway
  namespace: {{ .Values.namespace }}
spec:
  podSelector:
    matchLabels:
      app: fanclub-frontend
  policyTypes: ["Ingress"]
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: traefik
    ports: [{protocol: TCP, port: 80}]
{{- end }}

準備ができたら、gateway.enabled=true と Frontend の新タグ 1.0.0 を指定して helm upgrade します。helm コマンドは chart を ./fanclub-chart の相対パスで指すので、developer のホーム(~)で実行します。実行コマンド:

$ helm upgrade fanclub ./fanclub-chart \
    --set gateway.enabled=true --set frontend.image.tag=1.0.0

Gateway とルート、証明書ができたか確認します。実行コマンド:

$ kubectl get gateway,httproute,certificate -n fanclub

実行結果(例):Gateway が PROGRAMMED=True、Certificate が READY=True になれば、TLS 終端と経路が有効です。

NAME                                                CLASS     PROGRAMMED   AGE
gateway.gateway.networking.k8s.io/fanclub-gateway   traefik   True         30s

NAME                                                HOSTNAMES           AGE
httproute.gateway.networking.k8s.io/fanclub-route   ["fanclub.local"]   30s

NAME                                      READY   SECRET        AGE
certificate.cert-manager.io/fanclub-tls   True    fanclub-tls   30s
ブラウザから https でアクセスした通信の全体像。ブラウザ→ホスト OS(hosts で fanclub.local を 127.0.0.1 に解決)→ kind の 443 マッピング→ Traefik(Gateway・TLS 終端)→ Frontend(Nginx が静的配信と /api のリバースプロキシ)→ Backend → DB へと一本道でつながる。
図2:ブラウザ → HTTPS → Gateway → Frontend → Backend → DB の通信全体像

ブラウザから HTTPS でアクセスする

最後に、ブラウザを動かす端末で名前解決を通します。hosts ファイル(Linux/macOS は /etc/hosts、Windows は C:\Windows\System32\drivers\etc\hosts)に、管理者権限で次の 1 行を追記します。kind と同じ端末のブラウザで開くなら 127.0.0.1 を、別の端末(例: VM 上の kind に対して手元の PC のブラウザ)から開くなら kind ホストの IP アドレスを指します(あるいは SSH ポートフォワードで手元の 443 を kind ホストの 443 へ転送します)。ここでは kind と同じ端末で開く前提で 127.0.0.1 を使います。

127.0.0.1 fanclub.local

ブラウザで https://fanclub.local を開くと、会員一覧が表示されます。証明書は自己署名 CA が発行したものなので、CA をブラウザ/OS の信頼ストアに取り込んでいない場合は警告が出ます。学習環境では警告を承認して進めるか、fanclub-ca-secret の CA 証明書を信頼ストアに取り込みます。コマンドラインで疎通だけ確かめる場合は、次のように確認できます(自己署名なので -k、社内プロキシを迂回するため --noproxy を付けます)。実行コマンド:

$ curl -k --noproxy '*' --resolve fanclub.local:443:127.0.0.1 \
    https://fanclub.local/api/members

実行結果(例):会員一覧の JSON が返ります。ブラウザ →(HTTPS)→ Gateway → Frontend →(Nginx が /api を中継)→ Backend → DB の一本道が通っています。

[{"createdAt":"2026-07-03T07:36:53.911406","email":"hanako@example.com","id":1,"name":"鈴木花子","plan":"standard"},{"createdAt":"2026-07-03T07:36:53.911406","email":"ichiro@example.com","id":2,"name":"佐藤一郎","plan":"free"}]

ブラウザから会員の登録・編集・削除まで一通り操作できれば、fanclub-api は HTTPS で公開できました。

やってみよう

  1. Gateway API の CRD を適用し、Traefik を ~/traefik-values.yaml で install して kubectl get gatewayclassACCEPTED=True になることを確認する。
  2. cert-manager を install し、ca-issuer.yaml を適用して fanclub-ca-issuerREADY=True になることを確認する。
  3. Frontend を 1.0.0 でビルドして push し、helm upgrade fanclub ./fanclub-chart --set gateway.enabled=true --set frontend.image.tag=1.0.0 を実行する。
  4. kubectl get gateway,certificate -n fanclubPROGRAMMED=True / READY=True を確認する。
  5. ホストの hosts127.0.0.1 fanclub.local を足し、ブラウザで https://fanclub.local を開いて会員の登録・削除を操作する。

理解度チェック

次の各文が正しいか(○)誤りか(×)を判断してください。解答は下にまとめています。

  1. Gateway API は GatewayClass / Gateway / HTTPRoute に役割を分け、Ingress よりアノテーション依存が少ない。
  2. GatewayClass はどの実装(プロバイダ)を使うかを指定するリソースである。
  3. Gateway のリスナー port は、Gateway 実装(Traefik)の待ち受け口(entryPoint)と一致していなくてもよい。
  4. cert-manager の自己署名 CA は、selfSigned の Issuer → CA 証明書 → CA の ClusterIssuer の順に構成する。
  5. 同じ Namespace 内の Gateway・HTTPRoute・Service・Secret だけを参照する場合、ReferenceGrant は不要である。
  6. Gateway の TLS 終端で使う証明書は、Gateway と同じ Namespace の Secret で参照する。
  7. ingress-nginx は 2026 年 3 月で EOL となり、本シリーズでは採用しない。

解答

  • 1. ○:役割分離と標準フィールドが Gateway API の要点。
  • 2. ○:GatewayClass は実装プロバイダ(本回は traefik)を指す。
  • 3. ×:一致が必須。ずれると PortUnavailable でリスナーが無効になる(本回は Traefik を 80/443 に合わせた)。
  • 4. ○:selfSigned を起点に CA 証明書を発行し、その CA を使う ClusterIssuer を作る。
  • 5. ○:同一 Namespace 内の参照なら ReferenceGrant は要らない。
  • 6. ○:本回は fanclub-tls Secret を Gateway と同じ fanclub Namespace に置いた。
  • 7. ○:EOL のため新シリーズでは Gateway API へ移行する。

まとめ

本記事では、Gateway API で fanclub-api を HTTPS 公開しました。Ingress との違い(役割分離・標準 API・ingress-nginx の EOL)を押さえ、GatewayClass / Gateway / HTTPRoute の役割分担を確認しました。実装の Traefik は Helm で導入し、kind 特有の 2 つのはまりどころ(Gateway リスナーと entryPoint のポート一致・hostPort と Recreate 更新)を対策しました。cert-manager の自己署名 CA で fanclub-tls を発行し、Gateway で TLS を終端しました。ルーティングは / をすべて Frontend へ通し、/api は Nginx が Backend へ中継する一本道にして、第16回の NetworkPolicy 設計(allow-frontend-from-gateway で 4 層完成)と揃えました。これで https://fanclub.local をブラウザから操作できます。

次回予告

次回(第19回・最終回)は、ここまで積み上げた全 14 機構を、クリーンな kind クラスタにゼロから 120 分で通し構築する総合演習です。Namespace 分離から HTTPS 公開まで、Helm 一発デプロイを軸に第1巻を完走します。ブラウザから https://fanclub.local で CRUD 操作ができれば、第1巻の完走マイルストーン達成です。

シリーズ一覧

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

第4部:ワークロード戦略

第5部:セキュリティ基礎

第6部:パッケージ管理 + HTTPS 公開

広告
kubernetes
スポンサーリンク