新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第11回です。動作確認バージョン: AlmaLinux 10.1 / K8s v1.35.5 / Gateway API v1.4.0 / Traefik chart v39.0.0(Traefik v3.6.x)/ cert-manager v1.20.0 / MetalLB v0.16.0(2026-05-24 時点)
- 今ここマップ(第11回 / 全16回 / 第3部完走)
- 第11回のスコープと設計 — 4 機構の依存関係を把握する
- Gateway API とは — GatewayClass / Gateway / HTTPRoute の設計思想
- Traefik chart v39.x を Gateway API 実装として導入する
- cert-manager v1.20.0 + self-signed CA + ClusterIssuer
- CoreDNS — hosts プラグインで fanclub.local を解決する
- やってみよう①: Gateway API CRD + Traefik インストール
- やってみよう②: cert-manager + Certificate + TLS Secret 確認
- やってみよう③: HTTPRoute + CoreDNS hosts + https://fanclub.local 確認
- Step 0: fanclub-frontend をデプロイする
- Step 1: Gateway を TLS リスナー付きに上書きする
- Step 2: ReferenceGrant で Namespace 間 Secret 参照を許可する
- Step 3: fanclub-httproute HTTPRoute を作成する
- Step 4: HTTPRoute の Accepted / ResolvedRefs を確認する
- Step 5: CoreDNS ConfigMap に hosts ブロックを追加する
- Step 6: CoreDNS を rollout restart して即時反映する
- Step 7: クラスタ内 Pod から nslookup で名前解決を確認する
- Step 8: k8s-ops から curl で完成形を確認する
- まとめ・現場ヒヤリハット・理解度チェック
- シリーズ一覧
今ここマップ(第11回 / 全16回 / 第3部完走)
今ここ: 第11回 / 全16回(第3部:ネットワーク)
▓▓▓▓▓▓▓▓▓▓▓░░░░░ 69%
第1部(クラスタ構築): ■■■■■ 5/5 回(完了)
第2部(ワークロード管理): ■■■ 3/3 回(完了)
第3部(ネットワーク): ■■■ 3/3 回(完了)← 今ここ
第4部(ストレージ): □ 0/1 回
第5部(監視・運用): □□ 0/2 回
第6部(トラブルシュート): □□ 0/2 回
第10回では NetworkPolicy の default-deny-all から段階許可、CoreDNS Egress 遮断のトラブルシュートを実施しました。第11回では、より高度なトラフィック制御として Gateway API を導入します。ingress-nginx が EOL を迎えた今、Gateway API が Kubernetes の新標準です。
第11回のキャッチコピー: 「Gateway API + Traefik + cert-manager + CoreDNS の 4 機構連携で https://fanclub.local を実現する」
第11回は第3部「ネットワーク」の完走回です。第9回 Service + MetalLB、第10回 NetworkPolicy + Calico の知識を統合し、外部ブラウザから HTTPS で fanclub-api に到達する完成形を構築します。
第11回終了時の達成状態:
- Gateway API v1.4.0 の CRD(GatewayClass / Gateway / HTTPRoute)がインストール済み
- Traefik chart v39.0.0 が
traefikNamespace で稼働し、MetalLB EXT IP192.168.1.200を保持 - cert-manager v1.20.0 が稼働し、self-signed CA 経由で
fanclub-tlsCertificate が READY: True - HTTPRoute
fanclub-httprouteがATTACHED: Trueで Traefik Gateway にバインド済み - CoreDNS の hosts プラグインで
fanclub.localが192.168.1.200に解決される curl -k https://fanclub.localで HTTP 200 OK が返り fanclub-api のレスポンスが取得できる
第11回のスコープと設計 — 4 機構の依存関係を把握する
本セクションでは、第11回で扱うこと・扱わないこと、4 機構の依存関係、演習 Namespace 設計、そして本番運用での 3 つの警告を整理します。
第11回で「やること」と「やらないこと」
| やること | やらないこと |
|---|---|
| Gateway API v1.4.0 standard channel CRD のインストール | experimental channel(TCPRoute / TLSRoute / UDPRoute) |
| Traefik chart v39.0.0 を Gateway API 実装として導入 | ingress-nginx(2026-03 EOL のため本シリーズ不採用) |
| cert-manager v1.20.0 + self-signed CA 2 段階方式 | Let’s Encrypt / ACME(公開ドメインが必要・第3巻外) |
CoreDNS hosts プラグインで fanclub.local 名前解決 | ExternalDNS による自動 DNS 管理(第3巻 SRE 編で扱う) |
curl -k https://fanclub.local 200 OK 確認 | HTTP → HTTPS 自動リダイレクト(第11回スコープ外) |
| HTTPRoute による hostname ベースルーティング | HTTPRoute の重み付けトラフィック分割(第3巻で扱う) |
4 機構の依存関係図

4 機構は順序依存です。CRD なしに Traefik は GatewayClass を作れず、cert-manager の Secret なしに Gateway は TLS を終端できず、HTTPRoute なしに hostname ルーティングは成立せず、CoreDNS hosts なしに fanclub.local が IP に解決されません。
演習 Namespace の設計
| Namespace | 役割 | 削除タイミング |
|---|---|---|
traefik(新規) | Traefik Pod / GatewayClass / Gateway / LoadBalancer Service | 削除しない(以降の回で使用) |
cert-manager(新規) | cert-manager コンポーネント + fanclub-ca Secret | 削除しない(以降の回で使用) |
fanclub(既存) | fanclub-api 稼働中 + Certificate + HTTPRoute | 変更しない |
kube-system(既存) | CoreDNS ConfigMap 編集(hosts プラグイン追加) | 変更しない |
設計判断 5 件
判断① Gateway API CRD は v1.4.0 standard channel を --server-side で適用する
CRD サイズが client-side apply のアノテーション上限(256 KiB)を超える場合があるため、Gateway API 公式でも --server-side を推奨しています。standard channel は GatewayClass / Gateway / HTTPRoute / GRPCRoute / ReferenceGrant / BackendTLSPolicy の 6 リソースを含み、v1.4.0 で BackendTLSPolicy が standard 昇格しました。
判断② Traefik chart v39.0.0 は Gateway API 専用設定・kubernetesIngress は無効化する
providers.kubernetesIngress.enabled: false で旧 Ingress リソースを無効化し、providers.kubernetesGateway.enabled: true + gateway.enabled: true で Helm が GatewayClass と Gateway を自動生成します。gateway.namespacePolicy: All により fanclub Namespace の HTTPRoute も受け入れます。
判断③ cert-manager は self-signed CA 2 段階方式を採用する
selfsigned ClusterIssuer でルート CA Certificate を発行し、その Secret を参照する CA ClusterIssuer(fanclub-ca-issuer)を作成、最後に fanclub.local 用 TLS Certificate を発行します。「selfsigned のみ」では CA 証明書の配布・信頼追加が難しいため、2 段階方式が cert-manager 公式の推奨パターンです。
判断④ CoreDNS hosts プラグインで fanclub.local → 192.168.1.200 の A レコードを追加する
MetalLB が Traefik Service に払い出す EXT IP は 192.168.1.200 です。CoreDNS の hosts プラグインに静的 A レコードを追加することで、全 Pod から fanclub.local を名前解決できるようになります。fallthrough を必ず記述します。省略するとマッチしなかった全クエリが SERVFAIL になり、クラスタ全体の名前解決が壊れます。
判断⑤ やってみよう演習は 3 本に分割する
4 機構を 1 本の演習に詰め込むと読者が迷子になるため、やってみよう①(Gateway API CRD + Traefik)→ ②(cert-manager + Certificate)→ ③(HTTPRoute + CoreDNS + curl 確認)の独立完結型 3 本構成にします。各演習が「完了状態を確認してから次へ進む」流れを徹底します。
本番運用警告 3 本
本番警告①: Self-signed 証明書は本番環境で使用しない
本記事では self-signed CA → ClusterIssuer → Certificate で fanclub.local の TLS を実現します。この方式はブラウザに「接続は安全ではありません」警告が表示され、信頼チェーンが構築されません。本番環境では Let’s Encrypt / ACME(cert-manager の ClusterIssuer: spec.acme)または企業内 PKI(中間 CA)を使用します。Let’s Encrypt は公開ドメインと外部向けエンドポイント(HTTP-01 / DNS-01 チャレンジ)が必要となるため、社内システムでは企業 PKI 連携が現実的な選択肢です。
本番警告②: MetalLB L2 モードはシングルノードボトルネック
L2 モードでは Gratuitous ARP により 1 ノードだけが VIP の ARP 応答を担当します。そのノードを経由してすべての LoadBalancer トラフィックが流れるため、ノード障害時は ARP キャッシュ失効と GARP 再送が完了するまで(数秒〜数十秒)断が発生します。本番環境では BGP モードを採用し、ECMP(Equal-Cost Multipath)で複数ノードに分散させてノード障害時の切り替えを数秒レベルに短縮します。本シリーズの Hyper-V 環境には家庭ルータしかなく BGP ピアが構成できないため、L2 モードは開発・学習環境向けの選択であることを理解しておきます。
本番警告③: CoreDNS hosts プラグインは ExternalDNS の代替ではない
本記事では CoreDNS ConfigMap に手動で A レコードを追記します。この方法は Gateway の External IP が変わるたびに手動更新が必要で、複数ドメインが増えると ConfigMap が肥大化し、誤った設定で全クラスタの名前解決が壊れるリスクがあります。本番では kubernetes-sigs/external-dns を導入し、Service / Gateway の External IP 変化を DNS プロバイダー(Route53 / Cloud DNS / PowerDNS 等)に自動反映します。本シリーズのオンプレ環境では第2巻スコープ外とし、第3巻 SRE 編で扱う予定です。
Gateway API とは — GatewayClass / Gateway / HTTPRoute の設計思想
なぜ Ingress から Gateway API への移行が必要か
Kubernetes の外部公開の標準だった Ingress リソース(networking.k8s.io/v1)には設計上の限界があります。TLS 終端の挙動、リライト、認証連携、トラフィック分割など、ほとんどの設定が「実装依存のアノテーション」に押し込まれており、Ingress Controller を別実装に切り替えた瞬間に YAML が動かなくなります。さらに、長年 Ingress の事実上の標準実装だった ingress-nginx は 2026 年 3 月で EOL を迎えました。
Gateway API(gateway.networking.k8s.io/v1)は SIG-Network が「Ingress の正式な後継」として開発した API です。CKA 2026 から D3 ドメインに明示追加され、ingress-nginx EOL と相まって 2026 年が Gateway API への本格移行年になります。
GatewayClass / Gateway / HTTPRoute の 3 層構造

Ingress では「ホスト名・パス・TLS・annotations」がすべて 1 リソースに集約されており、誰が何を変更したのかを追跡しづらい問題がありました。Gateway API は責務を 3 層に分け、それぞれ異なるロール(クラスタ管理者・インフラチーム・アプリチーム)が独立して管理できる Role-oriented design を採用しています。
Ingress と Gateway API の比較表
| 項目 | Ingress | Gateway API |
|---|---|---|
| API バージョン | networking.k8s.io/v1 | gateway.networking.k8s.io/v1 |
| TLS 終端設定 | annotations で実装依存 | Gateway spec で標準化 |
| トラフィック分割(重み付け) | 実装依存(annotations) | HTTPRoute backendRefs の weight で標準化 |
| 責務分離 | 1 リソースにすべて集約 | GatewayClass / Gateway / HTTPRoute に分離 |
| CKA 2026 出題 | 参考程度 | 必須(D3 明示) |
| ingress-nginx 状況 | 2026-03 EOL | 本シリーズで採用 |
standard channel と experimental channel
| channel | 含まれる CRD | 用途 |
|---|---|---|
| standard | GatewayClass / Gateway / HTTPRoute / GRPCRoute / ReferenceGrant / BackendTLSPolicy | 本番運用・CKA 試験範囲 |
| experimental | standard + TCPRoute / TLSRoute / UDPRoute | L4 や gRPC ルーティングを必要とする検証用途 |
第11回では standard channel のみを使用します。HTTP / HTTPS のルーティングが要件のため、experimental channel は不要です。CKA D3 試験も standard channel の範囲で出題されます。
Traefik chart v39.x を Gateway API 実装として導入する
Traefik chart v39.x の概要
| 項目 | 値 |
|---|---|
| Helm chart バージョン | v39.0.0 |
| Traefik 本体バージョン | Traefik v3.6.7(chart v39.0.0 同梱) |
| Gateway API サポート | 標準提供(providers.kubernetesGateway) |
| chart v38 からの破壊的変更 | ポート設定の http 明示ネスト要求 / Traefik Hub は v3.19.0 以降のみ |
| 本シリーズでの Namespace | traefik(新規作成) |
values.yaml の設計説明
第11回で使用する traefik-values.yaml の各セクションの意味を解説します。
providers.kubernetesIngress.enabled: false— 旧 Ingress リソースの監視を停止する。ingress-nginx EOL に伴い本シリーズでは Ingress を使わないproviders.kubernetesGateway.enabled: true— Gateway API リソース(Gateway / HTTPRoute)の監視を有効化するgateway.enabled: true— Helm が GatewayClass(traefik)と Gateway(traefik-gateway)を自動生成するgateway.namespacePolicy: All— 全 Namespace の HTTPRoute を受け入れる。fanclub Namespace の HTTPRoute をルーティング対象にするために必須service.type: LoadBalancer— MetalLB が EXT IP192.168.1.200を払い出すports.web.exposedPort: 80/ports.websecure.exposedPort: 443— HTTP :80 と HTTPS :443 の両エントリーポイントを公開する(HTTPS の TLS 終端は後述の Gateway listener のcertificateRefsが担うため、Traefik entrypoint にはtlsを書かない。chart v39 のスキーマでもports.websecure.tlsは許可されない)
traefik-values.yaml 全量
providers:
kubernetesIngress:
enabled: false
kubernetesGateway:
enabled: true
namespaces: []
experimentalChannel: false
gateway:
enabled: true
namespacePolicy: All
service:
type: LoadBalancer
ports:
web:
port: 8000
expose:
default: true
exposedPort: 80
websecure:
port: 8443
expose:
default: true
exposedPort: 443
Pod 内では Traefik プロセスが :8000 と :8443 で待ち受け、Kubernetes Service が :80 と :443 を exposedPort として外部公開する構成です。なぜ Pod 内で 80/443 を直接 LISTEN しないかというと、Traefik コンテナは非 root(UID 1000)で起動するため特権ポート(<1024)にはバインドできず、内部では非特権ポート(8000/8443)を LISTEN する必要があるからです。MetalLB が Service に EXT IP を割り当てるため、外部からは 192.168.1.200:443 でアクセスできます。
cert-manager v1.20.0 + self-signed CA + ClusterIssuer
cert-manager の役割
cert-manager は Kubernetes の Certificate リソースから TLS 証明書を発行し、結果を Secret として保存する Operator です。証明書の有効期限管理、自動更新、ACME チャレンジ(Let’s Encrypt)対応など、TLS 運用に必要な機能を提供します。第11回では ACME ではなく self-signed CA を発行元として使い、社内向けエンドポイント fanclub.local の TLS を実現します。
self-signed CA 2 段階方式の階層構造

2 段階方式の利点: ルート CA Certificate(fanclub-ca-secret)が独立した Secret として存在するため、その ca.crt をブラウザに信頼ルートとして追加すれば「接続は安全ではありません」警告を解消できます。selfsigned のみで TLS 証明書を直接発行する方式では、サーバー証明書ごとに信頼チェーンが切れるため、ルート CA を取り出して配布する運用ができません。
cert-manager v1.20.0 の K8s v1.35 互換性
cert-manager v1.20.0 は Kubernetes v1.31 以降を要件としており、本シリーズの v1.35.5 で問題なく動作します。Helm chart のインストール時に --set crds.enabled=true を指定することで CRD も同時インストールされます。v1.15.0 以降では旧来の installCRDs=true は deprecated となり、crds.enabled=true が公式の正式オプションです。
ClusterIssuer と Issuer の違い
| リソース | スコープ | 用途 |
|---|---|---|
| Issuer | Namespace スコープ | 特定 Namespace 内の Certificate のみ発行可能 |
| ClusterIssuer | クラスタスコープ | 全 Namespace の Certificate を発行可能 |
本記事では fanclub Namespace 以外でも証明書を発行できるよう ClusterIssuer を採用します。ClusterIssuer が参照する CA Secret は cert-manager Namespace に置きます(cert-manager のデフォルト動作)。
CoreDNS — hosts プラグインで fanclub.local を解決する
CoreDNS hosts プラグインの仕組み
CoreDNS の hosts プラグインは、/etc/hosts ファイルライクな静的レコードを CoreDNS の応答に組み込む機能です。クラスタ内 DNS(kube-dns Service / ClusterIP 10.96.0.10)を経由する全 Pod から、追加した A レコードが名前解決可能になります。
kubeadm デフォルトの Corefile 構造
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
hosts ブロック追加後の Corefile
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
hosts {
192.168.1.200 fanclub.local
fallthrough
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
絶対に守る 2 点
fallthroughを必ず記述する — 省略すると hosts ブロックでマッチしなかった全クエリが SERVFAIL になり、Pod 間の Service 名解決を含むクラスタ全体の名前解決が壊滅するreloadプラグインを既存 Corefile から削除しない — ConfigMap 変更後に Corefile を最大 2 分で自動再読み込みする機能。即時反映が必要な場合はkubectl rollout restart -n kube-system deployment/corednsを併用する
やってみよう①: Gateway API CRD + Traefik インストール
Gateway API v1.4.0 standard channel CRD を kubectl apply --server-side で適用し、Traefik chart v39.0.0 を Helm でインストールします。完了後、GatewayClass が ACCEPTED: True、Traefik Service が MetalLB EXT IP を保持していることを確認します。作業場所は k8s-ops(developer ユーザー)です。
Step 1: Gateway API v1.4.0 standard channel CRD を適用する
実行コマンド:
$ kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
実行結果:
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/grpcroutes.gateway.networking.k8s.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/backendtlspolicies.gateway.networking.k8s.io serverside-applied
実行コマンド:
$ kubectl get crds | grep gateway.networking.k8s.io
実行結果:
backendtlspolicies.gateway.networking.k8s.io 2026-05-24T08:01:00Z
gatewayclasses.gateway.networking.k8s.io 2026-05-24T08:01:00Z
gateways.gateway.networking.k8s.io 2026-05-24T08:01:00Z
grpcroutes.gateway.networking.k8s.io 2026-05-24T08:01:00Z
httproutes.gateway.networking.k8s.io 2026-05-24T08:01:00Z
referencegrants.gateway.networking.k8s.io 2026-05-24T08:01:00Z
Step 2: traefik-values.yaml を作成する
実行コマンド:
$ cat > traefik-values.yaml <<'EOF'
providers:
kubernetesIngress:
enabled: false
kubernetesGateway:
enabled: true
namespaces: []
experimentalChannel: false
gateway:
enabled: true
namespacePolicy: All
service:
type: LoadBalancer
ports:
web:
port: 8000
expose:
default: true
exposedPort: 80
websecure:
port: 8443
expose:
default: true
exposedPort: 443
EOF
Step 3: Traefik Helm リポジトリを追加して chart v39.0.0 をインストールする
実行コマンド:
$ helm repo add traefik https://traefik.github.io/charts
$ helm repo update
実行結果:
"traefik" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "traefik" chart repository
Update Complete. Happy Helming!
実行コマンド:
$ helm upgrade --install traefik traefik/traefik \
--namespace traefik \
--create-namespace \
--version 39.0.0 \
-f traefik-values.yaml
実行結果:
Release "traefik" does not exist. Installing it now.
NAME: traefik
LAST DEPLOYED: Sun May 24 17:05:12 2026
NAMESPACE: traefik
STATUS: deployed
REVISION: 1
TEST SUITE: None
Step 4: GatewayClass の ACCEPTED 状態を確認する
実行コマンド:
$ kubectl get gatewayclasses
実行結果:
NAME CONTROLLER ACCEPTED AGE
traefik traefik.io/gateway-controller True 2m
ACCEPTED: True は、Traefik コントローラーが GatewayClass を認識し管理対象として受け入れたことを意味します。False または空欄の場合は Traefik Pod の起動状態を kubectl get pods -n traefik で確認します。
Step 5: Traefik Pod と MetalLB EXT IP を確認する
実行コマンド:
$ kubectl get pods -n traefik
実行結果:
NAME READY STATUS RESTARTS AGE
traefik-7f5b8c8d6f-z2x4n 1/1 Running 0 2m
実行コマンド:
$ kubectl get service -n traefik
実行結果:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik LoadBalancer 10.107.215.117 192.168.1.200 80:31234/TCP,443:32567/TCP 2m
EXTERNAL-IP: 192.168.1.200 は MetalLB の IPAddressPool(第9回で設定済み)から払い出された VIP です。この IP に対して :80 と :443 の両ポートが Traefik Pod の :8000 / :8443 へ転送されます。
Step 6: 自動生成された Gateway を確認する
実行コマンド:
$ kubectl get gateway -n traefik
実行結果:
NAME CLASS ADDRESS PROGRAMMED AGE
traefik-gateway traefik 192.168.1.200 True 2m
Helm が gateway.enabled: true 設定に従い Gateway リソースを自動生成しています。PROGRAMMED: True は Gateway が Traefik コントローラーによって実体化(リスナーポート確保)されたことを意味します。自動生成される Gateway 名は chart の Release 名(traefik)に -gateway を付した traefik-gateway です。なお本回ではこの後、TLS リスナー(websecure)と証明書参照を明示的に定義した Gateway(名前 traefik)を別途 apply し、HTTPRoute はその明示 Gateway(traefik)を parentRefs で参照します。
やってみよう②: cert-manager + Certificate + TLS Secret 確認
cert-manager v1.20.0 を Helm でインストールし、self-signed CA 2 段階方式で fanclub-tls Certificate を発行します。完了後、fanclub Namespace に fanclub-tls Secret が生成され、READY: True 状態になることを確認します。
Step 1: Jetstack Helm リポジトリを追加して cert-manager をインストールする
実行コマンド:
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
実行結果:
"jetstack" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
Update Complete. Happy Helming!
実行コマンド:
$ helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.20.0 \
--set crds.enabled=true
実行結果:
Release "cert-manager" does not exist. Installing it now.
NAME: cert-manager
LAST DEPLOYED: Sun May 24 17:15:42 2026
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
NOTES:
cert-manager v1.20.0 has been deployed successfully!
Step 2: cert-manager Pod の起動を確認する
実行コマンド:
$ kubectl get pods -n cert-manager
実行結果:
NAME READY STATUS RESTARTS AGE
cert-manager-7c6dd44d8c-h5n8r 1/1 Running 0 90s
cert-manager-cainjector-5c5f6d4f9b-8z2qb 1/1 Running 0 90s
cert-manager-webhook-6f9c8d4b7c-jx5fp 1/1 Running 0 90s
3 Pod(controller / cainjector / webhook)が全て Running になるまで待ちます。webhook が Running になる前に ClusterIssuer を作成しようとすると admission webhook エラーで失敗します。
Step 3: selfsigned-bootstrap ClusterIssuer を作成する
実行コマンド:
$ kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-bootstrap
spec:
selfSigned: {}
EOF
実行結果:
clusterissuer.cert-manager.io/selfsigned-bootstrap created
Step 4: ルート CA Certificate(fanclub-ca)を作成する
実行コマンド:
$ kubectl apply -f - <<'EOF'
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
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-bootstrap
kind: ClusterIssuer
group: cert-manager.io
EOF
実行結果:
certificate.cert-manager.io/fanclub-ca created
実行コマンド:
$ kubectl get certificate -n cert-manager
実行結果:
NAME READY SECRET AGE
fanclub-ca True fanclub-ca-secret 15s
Step 5: fanclub-ca-issuer ClusterIssuer を作成する
実行コマンド:
$ kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: fanclub-ca-issuer
spec:
ca:
secretName: fanclub-ca-secret
EOF
実行結果:
clusterissuer.cert-manager.io/fanclub-ca-issuer created
実行コマンド:
$ kubectl get clusterissuers
実行結果:
NAME READY AGE
fanclub-ca-issuer True 10s
selfsigned-bootstrap True 3m
Step 6: fanclub-tls Certificate を作成する
実行コマンド:
$ kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: fanclub-tls
namespace: fanclub
spec:
secretName: fanclub-tls
duration: 2160h
renewBefore: 720h
privateKey:
algorithm: ECDSA
size: 256
dnsNames:
- fanclub.local
issuerRef:
name: fanclub-ca-issuer
kind: ClusterIssuer
group: cert-manager.io
EOF
実行結果:
certificate.cert-manager.io/fanclub-tls created
実行コマンド:
$ kubectl get certificate -n fanclub
実行結果:
NAME READY SECRET AGE
fanclub-tls True fanclub-tls 30s
Step 7: TLS Secret の中身を確認する
実行コマンド:
$ kubectl get secret fanclub-tls -n fanclub -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -issuer -dates
実行結果:
subject=CN = fanclub.local
issuer=CN = fanclub-local-ca
notBefore=May 24 08:15:00 2026 GMT
notAfter=Aug 22 08:15:00 2026 GMT
subject に fanclub.local、issuer に fanclub-local-ca(Step 4 で作成したルート CA)が表示され、有効期限が 90 日後(duration: 2160h = 90 日)に設定されていることを確認します。
やってみよう③: HTTPRoute + CoreDNS hosts + https://fanclub.local 確認
HTTPRoute を作成して fanclub-api への hostname ルーティングを設定し、CoreDNS hosts プラグインで fanclub.local の名前解決を追加します。最後に curl -k https://fanclub.local で HTTP 200 OK が返ることを確認します。これが第11回および第3部「ネットワーク」完走の達成状態です。
Step 0: fanclub-frontend をデプロイする
HTTPRoute のルーティング先となる frontend(Nginx + 静的 HTML)をデプロイします。第4回の fanclub-api chart は backend のみを含むため、frontend はここで Deployment + Service(fanclub-frontend-svc)として追加します。静的 HTML は ConfigMap で配信します。実行コマンド(k8s-ops):
$ kubectl create configmap fanclub-frontend-html -n fanclub \
--from-literal=index.html='<!DOCTYPE html><html><head><title>fanclub</title></head><body><h1>Welcome to fanclub</h1></body></html>'
$ kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: fanclub-frontend
namespace: fanclub
spec:
replicas: 1
selector:
matchLabels:
app: fanclub-frontend
template:
metadata:
labels:
app: fanclub-frontend
spec:
containers:
- name: nginx
image: 192.168.1.123:5000/nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
volumes:
- name: html
configMap:
name: fanclub-frontend-html
---
apiVersion: v1
kind: Service
metadata:
name: fanclub-frontend-svc
namespace: fanclub
spec:
selector:
app: fanclub-frontend
ports:
- port: 80
targetPort: 80
EOF
kubectl rollout status deploy/fanclub-frontend -n fanclub で Running を確認します。この fanclub-frontend-svc が後続の HTTPRoute の backendRefs ルーティング先になります。frontend は Nginx 1.27 + 静的 HTML 構成で、本番では CRUD UI(Bootstrap + Vanilla JS・第1巻で実装)を同梱した専用イメージを使います。なお backend の API は HTTPRoute で /api プレフィックスを別 backendRefs に振り分ける構成へ拡張できます。
Step 1: Gateway を TLS リスナー付きに上書きする
Helm が自動生成した Gateway(traefik-gateway)は HTTP リスナーのみを持つため、HTTPS リスナー(certificateRefs: fanclub-tls)を備えた Gateway を別途新規作成します。この明示 Gateway の名前は traefik(Namespace も traefik)で、自動生成された traefik-gateway とは別リソースとして並存します。後続の HTTPRoute はこの明示 traefik Gateway を parentRefs で参照します。
実行コマンド:
$ kubectl apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: traefik
namespace: traefik
spec:
gatewayClassName: traefik
listeners:
- name: web
port: 8000
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: websecure
port: 8443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: fanclub-tls
namespace: fanclub
allowedRoutes:
namespaces:
from: All
EOF
実行結果:
gateway.gateway.networking.k8s.io/traefik created
Gateway が異 Namespace(fanclub)の Secret を参照しているため、ReferenceGrant が必要です。次の Step で適用します。
Step 2: ReferenceGrant で Namespace 間 Secret 参照を許可する
実行コマンド:
$ kubectl apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: traefik-to-fanclub-tls
namespace: fanclub
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: traefik
to:
- group: ""
kind: Secret
name: fanclub-tls
EOF
実行結果:
referencegrant.gateway.networking.k8s.io/traefik-to-fanclub-tls created
実行コマンド:
$ kubectl get gateway traefik -n traefik
実行結果:
NAME CLASS ADDRESS PROGRAMMED AGE
traefik traefik 192.168.1.200 True 8m
Step 3: fanclub-httproute HTTPRoute を作成する
実行コマンド:
$ kubectl apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: fanclub-httproute
namespace: fanclub
spec:
parentRefs:
- name: traefik
namespace: traefik
sectionName: websecure
hostnames:
- fanclub.local
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: fanclub-frontend-svc
port: 80
EOF
実行結果:
httproute.gateway.networking.k8s.io/fanclub-httproute created
Step 4: HTTPRoute の Accepted / ResolvedRefs を確認する
実行コマンド:
$ kubectl describe httproute fanclub-httproute -n fanclub
実行結果(抜粋):
Name: fanclub-httproute
Namespace: fanclub
API Version: gateway.networking.k8s.io/v1
Kind: HTTPRoute
Spec:
Hostnames:
fanclub.local
Parent Refs:
Name: traefik
Namespace: traefik
Section Name: websecure
Status:
Parents:
Conditions:
Type: Accepted
Status: True
Reason: Accepted
Type: ResolvedRefs
Status: True
Reason: ResolvedRefs
Controller Name: traefik.io/gateway-controller
Accepted: True と ResolvedRefs: True の両方が表示されれば HTTPRoute は Gateway にバインドされ、backendRef(fanclub-frontend-svc)も正しく解決されています。Accepted: False の場合は Gateway の allowedRoutes.namespaces.from 設定(All でなければならない)を疑います。
Step 5: CoreDNS ConfigMap に hosts ブロックを追加する
kubectl edit configmap coredns -n kube-system で開き、kubernetes プラグインブロックの直後に hosts ブロックを追加します。
実行コマンド:
$ kubectl edit configmap coredns -n kube-system
追記する hosts ブロック:
hosts {
192.168.1.200 fanclub.local
fallthrough
}
編集後の Corefile 全体を確認します。
実行コマンド:
$ kubectl get configmap coredns -n kube-system -o jsonpath='{.data.Corefile}'
実行結果:
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
hosts {
192.168.1.200 fanclub.local
fallthrough
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
Step 6: CoreDNS を rollout restart して即時反映する
実行コマンド:
$ kubectl rollout restart -n kube-system deployment/coredns
$ kubectl rollout status -n kube-system deployment/coredns
実行結果:
deployment.apps/coredns restarted
Waiting for deployment "coredns" rollout to finish: 1 of 2 updated replicas are available...
deployment "coredns" successfully rolled out
reload プラグインが入っているため最大 2 分待てば自動反映されますが、確実な反映のため rollout restart を実行します。
Step 7: クラスタ内 Pod から nslookup で名前解決を確認する
実行コマンド:
$ kubectl run dns-test --image=192.168.1.123:5000/busybox:1.36 --rm -it --restart=Never -- nslookup fanclub.local
実行結果:
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: fanclub.local
Address 1: 192.168.1.200
pod "dns-test" deleted
fanclub.local が 192.168.1.200(Traefik LoadBalancer EXT IP)に解決されることを確認しました。これでクラスタ内 Pod から fanclub.local 経由で fanclub-api に到達できる状態です。
Step 8: k8s-ops から curl で完成形を確認する
k8s-ops では /etc/hosts に 192.168.1.200 fanclub.local を追記してから curl を実行します(k8s-ops は作業端末 VM であり、Pod ネットワーク経由でクラスタ内 CoreDNS を参照する手段がないため、ホスト側の名前解決を直接設定します)。
実行コマンド(k8s-ops 上・root で実行):
# echo "192.168.1.200 fanclub.local" >> /etc/hosts
実行コマンド(k8s-ops 上・developer で実行):
$ curl -k -I https://fanclub.local
実行結果:
HTTP/2 200
content-type: text/html
content-length: 103
date: Sun, 24 May 2026 08:25:13 GMT
実行コマンド:
$ curl -k https://fanclub.local
実行結果:
<!DOCTYPE html><html><head><title>fanclub</title></head><body><h1>Welcome to fanclub</h1></body></html>
HTTP/2 200 が返り、Step 0 で ConfigMap から配信した fanclub-frontend の HTML(Welcome to fanclub)が取得できました。第11回および第3部「ネットワーク」の達成状態に到達しました。-k オプションは self-signed CA の警告を無視するためで、本番では ca.crt をブラウザに信頼ルートとして追加すれば -k なしでも警告なくアクセスできます。本番の frontend では Step 0 で触れた CRUD UI(Bootstrap + Vanilla JS・第1巻実装)を同梱した専用イメージに差し替えます。
まとめ・現場ヒヤリハット・理解度チェック
第11回のまとめ
- Gateway API は
GatewayClass(コントローラー)→Gateway(リスナー)→HTTPRoute(ルール)の 3 層で責務を分離する。ingress-nginx の「1 リソースにアノテーション集約」より標準化された Role-oriented 設計 kubectl apply --server-sideは CRD サイズが大きい場合の事実上の必須オプション。省略するとアノテーションサイズ超過(256 KiB 上限)で apply 失敗する- cert-manager の
ClusterIssuerはクラスタスコープ、Issuerは Namespace スコープ。CA Certificate の Secret はcert-managerNamespace に置く - self-signed CA 2 段階方式(selfsigned-bootstrap → fanclub-ca → fanclub-ca-issuer → fanclub-tls)でルート CA Secret を取り出してブラウザに信頼追加できる
- CoreDNS
hostsプラグインにはfallthroughを必ず記述する。省略するとマッチしなかった全クエリが SERVFAIL になり、クラスタ全体の名前解決が壊滅する - Gateway が異 Namespace の Secret を参照する場合は ReferenceGrant が必要。Gateway 側ではなく Secret 側 Namespace に作成する
allowedRoutes.namespaces.from: Allの設定漏れは HTTPRoute がAccepted: falseになる最多の原因- MetalLB L2 モードはシングルノードボトルネック。本番では BGP モード + ECMP に移行する
次回予告
第12回では PV / PVC / StorageClass + Longhorn v1.11.1 構築 + fanclub-db ボリューム移行を扱います。第3部「ネットワーク」を完走した第11回までは、データ層は emptyDir または hostPath で動いていました。第12回では Longhorn 分散ストレージを導入し、Workload Node 障害時でもデータが失われない耐障害性を確保します。第4部「ストレージ」の単独回です。
現場ヒヤリハット①: kubectl apply で Gateway API CRD apply が「Too long」で失敗した
状況: Gateway API v1.4.0 の standard-install.yaml を新人エンジニアが kubectl apply -f で適用しようとしたところ、以下のエラーで失敗しました。
The CustomResourceDefinition "gateways.gateway.networking.k8s.io" is invalid:
metadata.annotations: Too long: must have at most 262144 bytes
原因:
- client-side apply は Last-Applied アノテーションに CRD YAML 全文を格納する
- Gateway API の CRD は 1 リソースあたり数百 KiB あり、Kubernetes のアノテーションサイズ上限(262144 バイト = 256 KiB)を超えた
- Gateway API 公式ドキュメントの最初のページに「
--server-side推奨」と書いてあるが、新人は「とりあえず apply」で進めてしまった
修正: --server-side フラグを追加して再実行します。
$ kubectl apply --server-side -f standard-install.yaml
教訓: CRD 適用は --server-side を標準とします。チームの kubeadm 環境ブートストラップ手順書には「Gateway API CRD / cert-manager CRD は --server-side 必須」と明記し、新人がハマる前にレビュー段階で気づける状態にしておきます。
現場ヒヤリハット②: HTTPRoute が Accepted: false でルーティングが機能しない
状況: HTTPRoute を作成して curl -k https://fanclub.local を実行したところ 404 Not Found が返ってきました。Traefik Pod は Running、Gateway は PROGRAMMED: True、HTTPRoute も作成されていました。
原因: kubectl describe httproute fanclub-httproute -n fanclub で確認すると Accepted: False / Reason NotAllowedByListeners が表示されました。Gateway の allowedRoutes.namespaces.from がデフォルトの Same のままで、Gateway(traefik Namespace)と異なる Namespace(fanclub)の HTTPRoute を拒否していました。
修正: Gateway の各 listener に allowedRoutes.namespaces.from: All を追加します。
listeners:
- name: websecure
port: 8443
protocol: HTTPS
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- name: fanclub-tls
namespace: fanclub
教訓: Gateway API では Namespace 跨ぎが頻出します。HTTPRoute が Accepted: false の症状を見たら、まず allowedRoutes.namespaces.from を確認します。デフォルトの Same は「同一 Namespace のみ」なので、マルチテナント運用や fanclub のような分離型構成では All または Selector(labels で許可 Namespace を絞る)が必要です。
現場ヒヤリハット③: CoreDNS hosts プラグインに fallthrough を書き忘れて全クラスタ DNS が崩壊した
状況: CoreDNS ConfigMap に hosts ブロックを追加した直後、全 Pod から「DNS が引けない」と障害連絡が殺到しました。Service 名による Pod 間通信、kube-apiserver への接続、外部ドメインの解決、すべてが失敗していました。
原因: hosts ブロックに fallthrough を書き忘れていました。
hosts {
192.168.1.200 fanclub.local
}
- hosts プラグインは「マッチしたら応答、マッチしなかったらクエリを終了する」が既定動作
fallthroughを書かないと、fanclub.local以外のクエリがすべて SERVFAIL になる- kubernetes プラグイン(cluster.local)や forward プラグイン(外部 DNS)に処理が引き渡されない
修正: hosts ブロックの末尾に fallthrough を追加します。
hosts {
192.168.1.200 fanclub.local
fallthrough
}
教訓: CoreDNS の hosts プラグインを使うときは fallthrough をテンプレート化します。「CoreDNS hosts プラグインの追加手順書」には fallthrough を必ず記述し、変更レビューでも fallthrough 行の有無を必須チェック項目にします。本番 ConfigMap の変更前には必ず kubectl get configmap coredns -n kube-system -o yaml > backup.yaml でバックアップを取り、障害時に即ロールバックできる体制を整えます。
理解度チェック(○×形式・7 問)
各問に対して○か×で答え、解説を確認してください。
| # | 問題文 | 正解 | 解説 |
|---|---|---|---|
| Q1 | GatewayClass は Namespace スコープのリソースである | × | GatewayClass はクラスタスコープ。Gateway がリスナーポートを持つ Namespace スコープのリソース |
| Q2 | Gateway API standard channel の CRD は kubectl apply --server-side で適用することが推奨される | ○ | CRD サイズが client-side apply のアノテーション上限を超えるため、server-side apply が公式推奨 |
| Q3 | allowedRoutes.namespaces.from: Same を設定した Gateway は、別の Namespace にある HTTPRoute もルーティング対象にする | × | Same は同一 Namespace のみ。別 Namespace からは All または Selector が必要 |
| Q4 | cert-manager の ClusterIssuer は特定の Namespace に属し、その Namespace 内の Certificate だけを発行できる | × | ClusterIssuer はクラスタスコープ。Namespace を問わず Certificate を発行可能 |
| Q5 | CoreDNS の hosts プラグインで fallthrough を省略すると、hosts ブロックにマッチしないすべてのクエリへの応答が失敗する | ○ | fallthrough を省略すると hosts でマッチしなかった場合に次のプラグインへ渡されず SERVFAIL になる |
| Q6 | MetalLB L2 モードでは VIP のトラフィックが複数のノードに ECMP で分散される | × | L2 モードは 1 ノードがすべてのトラフィックを受け取るシングルノードボトルネック。ECMP は BGP モードの特徴 |
| Q7 | ingress-nginx は Kubernetes 公式の標準 Ingress Controller として 2026 年現在も活発にメンテナンスされている | × | ingress-nginx は 2026 年 3 月に EOL。本シリーズ(2026 年)では Gateway API + Traefik を採用 |
シリーズ一覧
第1部:クラスタ構築
- 第1回 第2巻スコープ + CKA 試験形式紹介 + kubeadm 概要 + kubeadm vs RKE2/k0s/OKD 概観
- 第2回 kubeadm シングルノード起動(pkgs.k8s.io + kubeadm init + Calico CNI)+ alma-proxy whitelist 構築
- 第3回 kubeadm HA 設計 + HAProxy LB(API Server LB 構成)
- 第4回 kubeadm HA クラスタ構築(CP×3 + WL×2)+ fanclub-api HA 移行
- 第5回 etcd backup/restore(etcdutl)+ ノード drain/uncordon
第2部:ワークロード管理
- 第6回 kubeadm upgrade + ノード drain/cordon + ミニトラブルシュート演習
- 第7回 Pod スケジューリング(taint/toleration/affinity/anti-affinity/PriorityClass)
- 第8回 HPA + ResourceQuota + LimitRange
第3部:ネットワーク
- 第9回 Service 詳細 + MetalLB(LoadBalancer 動作確認)
- 第10回 NetworkPolicy 詳細 + Calico/Cilium 比較 + ミニトラブルシュート演習
- 第11回 Gateway API + Traefik + cert-manager + CoreDNS(HTTPS 公開完成) ← 今ここ
第4部:ストレージ
第5部:監視・運用
- 第13回 Prometheus + Grafana + Loki + Fluent Bit + fanclub-api 監視ダッシュボード
- 第14回 Helm + Kustomize でクラスタコンポーネント install + ArgoCD GitOps + Velero backup
