ポリシーを書いたことと、ポリシーが守られることは違う
一言でいうと
kubectl applyが成功したというのは、ポリシーが保存されたという意味にすぎません。データプレーンがなければ、何もブロックされません。
なぜ本物のCiliumでなければならないのか
前のモジュールでは、CiliumNetworkPolicyをいくつも書きました。しかし、それらのラボが動いているのはCRDだけがロードされたクラスターなので、kubectl applyが成功したということ以外は、何も確認できていませんでした。
このモジュールでは、VMの中に本物のCiliumを立ち上げて、同じポリシーを書き直します。すると、3つのことが新しく見えてきます。
1. アイデンティティは実際の番号
kubectl get ciliumendpointで、アイデンティティの番号とセキュリティ関連のラベルの集合をあわせて確認します。appラベルが1つ同じでも、namespaceなどほかのセキュリティラベルが違えば、アイデンティティも違うことがあります。同じセキュリティラベルの集合のエンドポイントは、アイデンティティを共有します。ポリシーには、変わるPodのIPや、永続性が保証されない数値のIDを直接固定するのではなく、許可したいラベルの意味を込めます。
基本のKubernetes NetworkPolicyも、Podとネームスペースのセレクターをサポートしています。Ciliumだけがラベルのポリシーを提供するのではなく、APIが表現した意図をどのデータプレーンで強制するか、そしてFQDNやL7のような範囲をどう拡張するかが、比較すべき点です。
2. L7の拒否は403
CiliumがHTTPを見るには、パケットをEnvoyに渡す必要があります。そのため、L7でブロックされるときは、接続が切れるのではなく、403が返ってきます。
これは、L3のブロックよりも優れています。タイムアウトでは、「ネットワークがおかしいのか、サーバーが落ちたのか、ファイアウォールなのか」を区別できませんが、403は明確な拒否です。クライアントがリトライするかどうかをすぐに判断でき、ログにも原因が残ります。
3. Hubbleは「なぜ」を教えてくれる
hubble observe --type policy-verdictは、各フローがどの方向でどんな判定を受けたのかを示します。これがなければ、ポリシー20個を1つずつ消しながら犯人を探すことになります。
ポリシーが効かないときに見る順序
ポリシーを書いたのにブロックされない、あるいはブロックされてはいけないものがブロックされる、ということがあります。順番に見ていきます。
1. ポリシーがそのPodを選択しているか。
kubectl get cep -n <ns> <pod> -o jsonpath='{.status.identity.labels}'
kubectl describe cnp <정책> | sed -n '/Endpoint Selector/,/Ingress/p'
セレクターのラベルとエンドポイントのラベルが、1文字まで同じである必要があります。app: apiとapp: api は別物です。
2. 方向は正しくかけたか。1つの通信には、出ていく側と入ってくる側のポリシーが両方あります。送信元にegressを、宛先にingressを、どちらも許可して初めて通じます。片方だけを開けて、「ポリシーを書いたのに通らない」と迷うことが、最もよくあります。
3. デフォルトのブロックが有効になっているか。Ciliumは、そのエンドポイントを選択するポリシーが1つでもできた瞬間に、その方向のデフォルトがブロックに切り替わります。ingressルールだけを書いたポリシーを付けても、egressはそのまま開いていますが、egressルールを1つ書いた瞬間に、残りのegressがすべてブロックされます。DNS(ポート53)を抜かして、名前解決から失敗するのが代表的です。
egress:
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s:k8s-app: kube-dns
toPorts:
- ports: [{port: "53", protocol: UDP}]
rules:
dns: [{matchPattern: "*"}]
4. Hubbleで判定を見る。
hubble observe --type policy-verdict --verdict DROPPED --last 20
DROPPEDの行に、どのポリシーがどの方向でブロックしたのかが出ます。ここまで来れば、たいていは1–3のどれかに戻ります。
L7ポリシーの代償
L7ルール(rules.http)を付けると、そのトラフィックはEnvoyを経由します。得るものと失うものがはっきりしています。
| L3/L4ポリシー | L7ポリシー | |
|---|---|---|
| 処理する場所 | eBPF(カーネル) | Envoy(ユーザー空間) |
| 追加される遅延 | ほとんどなし | 数百µs–数ms |
| 拒否の方式 | パケットのドロップ(タイムアウト) | 403応答 |
| 見えるもの | IP・ポート | メソッド・パス・ヘッダー |
そのため、すべての通信にL7をかけるわけではありません。外部から入ってくる境界や、機密性の高いServiceの前にだけ置き、内部のService間はL3/L4のままにします。
実務で本当に大切なこと
ポリシーは、IPではなくアイデンティティで判断します。そのため、Podを再起動してIPが変わってもポリシーが揺らがず、ルールの数はPodの数ではなく、アイデンティティの数に従います。ラベルの設計が、そのままポリシーの規模の設計です。
L7の拒否が403で返ってくることは、運用上、大きな利点です。タイムアウトでは、ネットワーク・サーバー・ファイアウォールを区別できませんが、403は明確な拒否なので、クライアントがリトライするかどうかをすぐに判断でき、ログにも原因が残ります。
ブロックされたときは、ポリシーを1つずつ消すのではなく、Hubbleに尋ねます。hubble observe --type policy-verdictが、どの方向でどんな判定が出たのかを教えてくれます。これがなければ、20個のポリシーの束から、犯人を二分探索することになります。
次のラボで、この3つを自分で確認します。