Certified Kubernetes Application Developer (CKAD) #19 Ingress と NetworkPolicy

#18 では Service で Pod のまとまりに安定した入口を作りました。しかし Service だけでは 2 つ足りません。1 つは 複数のサービスを 1 つの外部入口からホストとパスで振り分ける L7 ルーティング で、もう 1 つは クラスター内の Pod 同士の通信を、誰が誰に話しかけられるかという単位で制御する ことです。前者が Ingress、後者が NetworkPolicy です。

この記事では、外部から入ってくるトラフィックをホストとパスを基準にルーティングする Ingress と、Pod 間通信をホワイトリストでロックする NetworkPolicy を実技の観点から扱います。どちらも CKAD の Services and Networking ドメインに属しており、マニフェストのフィールドを手に馴染ませることが核心です。ネットワーキングの基礎が曖昧なら K8s 中級 #3 を先に見ておくとよいです。

Ingress: 1 つの入口から L7 ルーティング #

Service の LoadBalancer タイプはサービスごとに外部 IP を 1 つずつ確保します。サービスが増えるほど外部 IP が増え、コストも増えます。Ingress は たった 1 つの入口で HTTP/HTTPS リクエストをホスト名と URL パスで振り分け て内部の Service に転送します。L4 で動作する Service と違い、Ingress は L7 で HTTP ヘッダーの Host とパスを見てルーティングします。

host と path ベースのルーティング #

Ingress の核心フィールドは rules です。各ルールは host (省略可能) と http.paths 配列を持ち、各 path は path 文字列、pathType、そしてトラフィックを受け取る backend を指定します。backend は対象 Service の名前とポートを指します。

rules:
- host: shop.example.com
  http:
    paths:
    - path: /api
      pathType: Prefix
      backend:
        service:
          name: api-svc
          port:
            number: 8080
    - path: /
      pathType: Prefix
      backend:
        service:
          name: web-svc
          port:
            number: 80

shop.example.com/api に入ってきたリクエストは api-svc へ、それ以外のパスは web-svc へ行きます。1 つのホストの中でパスごとに別のサービスにトラフィックを振り分ける構造です。

pathType: Prefix と Exact #

pathType はパスをどう照合するかを決めます。CKAD で空欄にしやすい必須フィールドです。

pathType照合方式
PrefixURL パス要素単位の接頭辞照合。/api/api/api/v1 に照合
Exactパスが正確に一致するときのみ照合。大文字小文字を区別
ImplementationSpecificコントローラの実装に委任

実務ではほとんど Prefix を使います。pathType を省略するとマニフェストが拒否されたり意図と違う動作をしたりするので、path ごとに必ず埋める必要があります。

IngressClass と defaultBackend #

クラスターには複数種類の Ingress Controller があり得るので、どのコントローラがこの Ingress を処理するかを spec.ingressClassName で指定します。以前は kubernetes.io/ingress.class アノテーションを使いましたが、今は ingressClassName フィールドが標準です。

spec:
  ingressClassName: nginx

どのルールにも照合しないリクエストを受ける既定のバックエンドは spec.defaultBackend で置けます。指定しなければ照合しないリクエストはコントローラの既定の 404 に落ちます。

TLS: secret 参照で HTTPS 終端 #

Ingress は spec.tls で HTTPS を終端します。証明書と鍵は kubernetes.io/tls タイプの Secret に入れ、Ingress はその Secret 名を参照します。

spec:
  tls:
  - hosts:
    - shop.example.com
    secretName: shop-tls

hosts に書いたホストに入ってくる HTTPS リクエストに shop-tls Secret の証明書を適用します。Secret 自体は kubectl create secret tls shop-tls --cert=tls.crt --key=tls.key であらかじめ作っておきます。

Ingress Controller がなければ動作しない #

Ingress は ルールを書いた仕様にすぎず、それ自体では何もしません。 実際にトラフィックを受け取ってルーティングするのは ingress-nginx や Traefik のような Ingress Controller です。コントローラは Ingress オブジェクトを監視し、そのルールどおりにリバースプロキシを構成します。したがってクラスターに Ingress Controller がインストールされていなければ、Ingress をいくら作っても外部トラフィックは入ってきません。CKAD 環境には通常コントローラがあらかじめインストールされているので、kubectl get ingressclass で利用可能なクラス名をまず確認し、その名前を ingressClassName に入れます。

NetworkPolicy: Pod 間通信をホワイトリストでロックする #

既定状態の Kubernetes クラスターは すべての Pod が互いに自由に通信 します。どの Pod でも他の Pod の IP に接続できる all-allow が既定値です。NetworkPolicy はこの開いた通信にファイアウォールルールをかけるリソースです。

核心の動作は 1 文で要約できます。NetworkPolicy が 1 つも選択しない Pod は依然として all-allow ですが、いずれかの NetworkPolicy が選択した Pod はそのポリシーに明記されたトラフィックのみ許可します。 つまりポリシーが付いた瞬間、その Pod はホワイトリスト方式に切り替わります。

podSelector と policyTypes #

NetworkPolicy の適用対象は spec.podSelector で選びます。このセレクターに照合する、同じネームスペースの Pod がポリシーの適用対象です。

spec.policyTypes は、このポリシーが入ってくるトラフィック (Ingress) を制御するか、出ていくトラフィック (Egress) を制御するか、両方かを決めます。

spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
  - Ingress
  - Egress

policyTypesIngress があれば、入ってくる接続は spec.ingress ルールに明記された出所だけが許可され、明記されていない出所はすべて遮断されます。Egress も同じ方式で出ていく接続を制御します。

ingress.from と egress.to #

許可する出所と宛先は 3 つのセレクターで表現します。

セレクター意味
podSelector同じネームスペース内で label で選んだ Pod
namespaceSelectorlabel で選んだネームスペース全体
ipBlockCIDR 範囲 (cidr) と例外 (except) で指定した IP 帯域

ingress.from の下にこれらのセレクターを並べると、その出所から来るトラフィックのみ許可されます。ここに YAML のインデント 1 つが意味を変える落とし穴 があります。1 つの from 項目の中に podSelectornamespaceSelector を一緒に置くと 両方を満たす (AND) 必要があり、- で項目を分けると 2 つのうちどちらか一方を満たす (OR) だけでよくなります。

# AND: 指定ネームスペース内の、指定 label の Pod のみ
ingress:
- from:
  - namespaceSelector:
      matchLabels:
        team: backend
    podSelector:
      matchLabels:
        app: web
# OR: 指定ネームスペース全体、または同じ ns の指定 label の Pod
ingress:
- from:
  - namespaceSelector:
      matchLabels:
        team: backend
  - podSelector:
      matchLabels:
        app: web

ポート制限 #

ingressegress ルールには ports を加えて許可ポートを狭められます。fromto で出所を選び、ports でその出所がアクセスできるポートまで制限する形です。

ingress:
- from:
  - podSelector:
      matchLabels:
        app: web
  ports:
  - protocol: TCP
    port: 5432

app: web Pod から来る TCP 5432 接続のみ許可し、残りは遮断します。

default deny パターン #

最もよく出題されるパターンが、ネームスペース全体に既定の遮断をかける default deny です。podSelector を空の値 ({}) にするとネームスペースの すべての Pod が対象になり、ingress ルールを空にすると入ってくるトラフィックが全部遮断されます。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: prod
spec:
  podSelector: {}
  policyTypes:
  - Ingress

policyTypesIngress だけがあり spec.ingress がないので、prod ネームスペースのすべての Pod へ入ってくる接続が全部遮断されます。ここに個別の許可ポリシーを追加で付けてホワイトリストを広げていくのが標準の運用方式です。出ていくトラフィックまで遮断するには policyTypesEgress を加え、egress を空にします。

CNI が NetworkPolicy をサポートする必要がある #

NetworkPolicy も Ingress と同じく仕様にすぎず、これを実際に施行するのはクラスターの CNI プラグイン です。Calico や Cilium のような CNI は NetworkPolicy をサポートしますが、一部の単純なネットワークプラグインはポリシーを無視します。つまり NetworkPolicy を作っても CNI がサポートしなければ通信がそのまま開いていることがあるので、試験環境ではポリシー適用後に実際に遮断されるか必ず確認します。

実戦 YAML 例 #

Ingress: host + path + TLS #

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-ingress
  namespace: shop
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - shop.example.com
    secretName: shop-tls
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-svc
            port:
              number: 8080
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-svc
            port:
              number: 80

shop.example.com に入ってくる HTTPS リクエストを shop-tls 証明書で終端し、/apiapi-svc:8080 へ、残りは web-svc:80 へルーティングします。

NetworkPolicy: 特定の label からのみ ingress 許可 + default deny #

まずネームスペース全体を遮断する default deny を敷き、その上に app: web Pod から来る DB アクセスのみ開けるポリシーを載せます。

# 1) 既定の遮断
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: shop
spec:
  podSelector: {}
  policyTypes:
  - Ingress
---
# 2) web から db への 5432 のみ許可
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-db
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: web
    ports:
    - protocol: TCP
      port: 5432

app: db Pod は両方のポリシーに選択されますが、NetworkPolicy は和集合で動作するので、結果的に app: web Pod から来る TCP 5432 のみ許可 され、それ以外のすべての ingress は遮断されます。

デバッグ: ポリシーが実際に遮断するか確認する #

NetworkPolicy は適用しても結果がすぐ目に見えないので、自分で接続を試みて遮断されるかを確認する必要があります。一時的な Pod を立ち上げて対象の Service や Pod へ接続をかける方式が最も速いです。

# ポリシー適用前: 接続が成功すれば正常
k run probe --image=busybox --rm -it --restart=Never -- \
  wget -qO- --timeout=3 db-svc:5432

# ポリシー適用後: 許可されていない出所ならタイムアウトで遮断される
k run probe --image=busybox --rm -it --restart=Never -- \
  wget -qO- --timeout=3 db-svc:5432

許可された label を持つ Pod から送れば通り、そうでない Pod から送るとタイムアウトになります。ポリシーが意図どおりに動作しないとき確認する箇所は次のとおりです。

  • kubectl get networkpolicy -n <ns> でポリシーが正しいネームスペースにあるか確認
  • kubectl describe networkpolicy <name>podSelector が意図した Pod を選ぶか確認
  • 対象 Pod と出所 Pod の label がセレクターと正確に一致するか確認
  • from 項目のインデントで AND/OR が入れ替わっていないか確認
  • 通信が最後まで遮断されないなら CNI が NetworkPolicy をサポートするか確認

Ingress 側が動作しないときは kubectl describe ingress <name> でルールと backend Service が正しく接続されているか見て、kubectl get ingressclass で指定したクラスが実際に存在するか確認します。backend Service の名前やポートのタイプミスが最もよくある原因です。

試験ポイント #

  • Ingress は L7 ルーティング です。rules.hosthttp.paths でホストとパスごとに backend Service へトラフィックを振り分けます。
  • pathType は必須 です。ほとんど Prefix、正確な照合が必要なときだけ Exact を使います。
  • ingressClassName でコントローラを指定 します。kubectl get ingressclass で名前をまず確認します。
  • TLS は kubernetes.io/tls Secret を spec.tls.secretName で参照 します。Secret は kubectl create secret tls で作ります。
  • Ingress は Ingress Controller がなければ動作 しません。仕様だけではトラフィックは流れません。
  • NetworkPolicy が選択した Pod はホワイトリストに切り替わり ます。ポリシーがなければ all-allow が既定です。
  • default deny は podSelector: {} + policyTypes で作ります。ingress/egress を空にして全部遮断します。
  • from 項目の中のセレクターは AND、- で分けた項目は OR です。インデント 1 つが意味を変えます。
  • NetworkPolicy は CNI がサポートしてはじめて施行 されます。適用後に実際に遮断されるか確認します。

まとめ #

この記事で押さえたこと:

  • Ingress 。host/path ルーティング、pathType (Prefix/Exact)、rules/backend (service+port)、ingressClassNamedefaultBackend、TLS Secret 参照、そして Ingress Controller への依存
  • NetworkPolicy 。all-allow 既定値、ポリシーが付いた Pod のホワイトリストへの切り替え、podSelector/policyTypesingress.from/egress.to の 3 つのセレクター、ポート制限
  • default deny パターン とその上に許可ポリシーを積む運用方式、CNI への依存とデバッグ手順

Service から始めて、Ingress で外部入口を、NetworkPolicy で内部通信制御を仕上げました。2 つのリソースのセレクター構文をもう一度固めたいなら K8s 中級 #7 で label とセレクターの動作を復習するとよいです。

次へ: 試験のコツと時間管理 #

ドメイン別の知識は #18 と #19 で仕上がりました。残るは その知識を 2 時間以内に点数に変える運用力 です。

#20 試験のコツと時間管理、よく間違えるパターン では、作業ごとの時間配分、部分点を最大化する解答順序、kubectl explain と dry-run を活用した速度戦略、そして context 切り替えの漏れや pathType の漏れのように受験者が繰り返し間違えるパターンをまとめて整理します。

X