NetworkACLでSubnet境界を制御する¶
JuneauのNetworkACLは、Subnetに入ってくる / Subnetから出ていくトラフィックを優先度付きのルールで制御します。 SecurityGroupが「Podごとの細かい許可リスト」だとすると、NetworkACLは「Subnetの入口・出口で守る粗いガード」と捉えるとわかりやすいです。両者は同時に有効化でき、両方を通過したトラフィックだけが通ります。
このガイドでは、
- backend用Subnetに対するingress NetworkACLで、許可するクライアントSubnetだけを通す
- priorityとdenyルールを組み合わせて例外を作る
- NetworkACLを外して挙動を元に戻す
までの流れを示します。
このガイドで構築するもの¶
- 専用Vpc (
app-vpc) と Subnet 2つapp-subnet(10.80.0.0/24): backend Pod配置先client-subnet(10.80.1.0/24): クライアントPod配置先
- backend Subnet向けNetworkACL (
web-acl) - backend Pod (nginx) と クライアントPod (curl)
client-subnetのCIDRからの通信のみが backend Subnet に届くこと
前提条件¶
- Juneauのcontroller/daemonが動作しているクラスター
- kubectlが利用可能なこと
手順¶
1. Vpcと2つのSubnetを作成¶
apiVersion: juneau.loutres.me/v1alpha1
kind: Vpc
metadata:
name: app-vpc
---
apiVersion: juneau.loutres.me/v1alpha1
kind: Subnet
metadata:
name: app-subnet
spec:
vpc: app-vpc
cidr: 10.80.0.0/24
---
apiVersion: juneau.loutres.me/v1alpha1
kind: Subnet
metadata:
name: client-subnet
spec:
vpc: app-vpc
cidr: 10.80.1.0/24
2. NetworkACLを作成¶
client-subnet (10.80.1.0/24) からのTCP/80だけを受け入れるルールを書きます。
apiVersion: juneau.loutres.me/v1alpha1
kind: NetworkACL
metadata:
name: web-acl
spec:
vpc: app-vpc
ingress:
- priority: 100
action: allow
protocol: tcp
cidr: 10.80.1.0/24
ports:
- port: 80
spec.ingress に1つ以上のルールを書くと、その方向はdeny-by-defaultになります。明示的に許可したCIDR以外からの通信は遮断されます。
$ kubectl get networkacl
NAME VPC ACLID INGRESS EGRESS READY
web-acl app-vpc 1 1 True
READY: True であれば反映済みです。詳細はNetworkACLを参照してください。
3. backend SubnetにNetworkACLを紐付ける¶
spec.networkACL でNetworkACLを指定します。
apiVersion: juneau.loutres.me/v1alpha1
kind: Subnet
metadata:
name: app-subnet
spec:
vpc: app-vpc
cidr: 10.80.0.0/24
networkACL: web-acl
$ kubectl get subnet app-subnet -o jsonpath='{.status.networkACL}'
{"name":"web-acl","aclID":1,"rulesetVersion":1}
status.networkACL.aclID が0でない値になればdaemon側に伝達されています。
4. backend Podをデプロイ¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
annotations:
juneau.loutres.me/subnet: app-subnet
spec:
containers:
- name: nginx
image: nginx:1.27
5. クライアントPodをデプロイ¶
client-subnet 上のクライアントPodと、別のSubnet上にもう1つPodを置いて挙動を比較します。今回は同じSubnetからの2つのPod (片方は許可、もう片方も同じCIDRなので許可される) ではなく、後段で priority と deny を使った例外を試すため、まずは標準的なクライアントPodを1つ用意します。
apiVersion: v1
kind: Pod
metadata:
name: curl-allowed
annotations:
juneau.loutres.me/subnet: client-subnet
spec:
containers:
- name: curl
image: curlimages/curl:8.7.1
command: ["sleep", "infinity"]
6. 通信を確認¶
$ BACKEND=$(kubectl get pod -l app=nginx -o jsonpath='{.items[0].status.podIP}')
$ kubectl exec curl-allowed -- curl -sS --max-time 5 http://$BACKEND/
<!DOCTYPE html>
...
<h1>Welcome to nginx!</h1>
client-subnet のCIDRが許可されているので、応答が返ります。
priorityとdenyを組み合わせる¶
NetworkACLの強みは「先に評価されたルールの結果が確定する」点にあります。たとえば「クライアントSubnetからは原則許可するが、10.80.1.42 だけは拒否したい」という要件は次のように書けます。
apiVersion: juneau.loutres.me/v1alpha1
kind: NetworkACL
metadata:
name: web-acl
spec:
vpc: app-vpc
ingress:
- priority: 50
action: deny
protocol: tcp
cidr: 10.80.1.42/32
ports:
- port: 80
- priority: 100
action: allow
protocol: tcp
cidr: 10.80.1.0/24
ports:
- port: 80
priority: 50 のdenyが先に評価されるため、後続のallowルールにかかわらず 10.80.1.42 からの通信は遮断されます。 このようにdenyを優先度の小さい値で先頭に置くと、広めの許可ルールに対する例外を簡潔に表現できます。
NetworkACLを外す¶
Subnetの spec.networkACL を空にすると、そのSubnetは再びdefault-allowに戻ります。
$ kubectl patch subnet app-subnet --type=merge -p '{"spec":{"networkACL":""}}'
status.networkACL が空になり、daemon側でも紐付けが解除されます。 NetworkACL自体を削除したい場合は、参照しているSubnetを先に外してから削除してください (Subnetが参照したまま削除しようとするとwebhookで拒否されます)。
SecurityGroupとの組み合わせ¶
NetworkACLとSecurityGroupは同時に有効化できます。
- NetworkACL: Subnetの境界で評価される (アドレスベース)
- SecurityGroup: Pod (NetworkInterface) の境界で評価される (CIDR / SecurityGroup参照)
両方が有効なときは、Subnetの入口・出口の両方で評価が走り、どちらかがdenyを返した時点でトラフィックは落ちます。 たとえば「Subnetに入る大きな通り道は NetworkACL で広めに許可しつつ、特定のbackend Pod群はさらに SecurityGroup で絞る」といった重ね合わせができます。
詳細はそれぞれのリファレンスを参照してください。
うまくいかないとき¶
- NetworkACLを付けた途端、想定外に通信が落ちる
spec.ingressまたはspec.egressを1つでも書くと、その方向はdeny-by-defaultになります。クラスタ内DNSや外部サービスに到達する必要がある場合は、それらに合致するallowルールも追加してください- 戻りトラフィックはステートフルにCTで通過するため、戻り方向のallowを書く必要はありません
status.aclIDが0のままkubectl describe networkacl <name>でConditionを確認してください。Ready=FalseでAllocating表示なら一時的な状態です
- Subnetに紐付けたのに反映されない
kubectl get subnet <name> -o jsonpath='{.status.networkACL}'でaclIDが0でないことを確認- NetworkACLとSubnetが同じVpcに属している必要があります
- NetworkACLを削除できない
- 参照しているSubnetが残っているとwebhookで拒否されます。
kubectl get subnet -o jsonpath='{range .items[?(@.spec.networkACL=="<name>")]}{.metadata.name}{"\n"}{end}'で参照Subnetを洗い出し、spec.networkACLを空にしてから削除してください
- 参照しているSubnetが残っているとwebhookで拒否されます。
- priorityで意図しないルールがマッチする
- 同じ方向内でpriorityが重複しているとwebhookで拒否されます。マッチ順を厳密に固定したい場合は十分に間隔を空けた priority (10, 20, 30 など) を使うと後から間に挿入しやすくなります