Calicoを使ってK8s PodネットワークからIP Masquarading無しで外部へ接続する
オンプレミスでIPアドレスを台帳管理しているようなレガシーなネットワーク運用をされている方から(最近は少なくなくなっていますが)既存ネットワークへKubernetesを繋ぐ際の懸念として言及されることが多い、ClusterからのOutbound通信について書きたいと思います。
Podからの通信のSource IPアドレスを固定したい
標準的なK8s Clusterの場合、Cluster内のPodから外部宛ての通信はすべてNodeのIPアドレスにSNATされるため、Podに振られているIPアドレスが外部に晒されることはありません。
大概のネットワークではルーティング可能な(経路広告やスタティックな経路設定する必要がある)IPアドレスの数は限定的です。このため、K8s ClusterはPodのスケーラビリティに対してその影響を受けないよう、外部のネットワークとは異なるCluster内部通信用のネットワークを構成してトポロジーハイディングを図っています。
こうすることで、Cluster内部のPodをいくつどう構成しようが外部のルーターやホストがそれを意識する必要がなくなるため、Cluster内のネットワーク・リソース管理の自由が確保されるというわけです。
一方で、通信のセキュリティを確保するために、ネットワークの境界にFirewallを配備したり、SIEM(Security information and event management)やDLP(Data Loss Prevention)などで通信の監視ソリューションを導入したい場合、やはりLayer3でアプリケーションを特定したい要件が出てきます。要するにパケットのSource IPアドレスによるアプリケーションの特定やパケット・フィルタリングが求められるわけです。
Source IPアドレスでアプリケーションを特定するには
では、上述したK8sの特性がありつつも、あるアプリケーションのPodからの外部宛てのパケットにを特定のSource IPアドレスを付与するためにはどういった方式があるでしょうか。(あくまでもK8s Nativeな方式として。)
- 外部通信を行う特定のアプリケーションPod(Egress Pod)を専用のNodeにAffinityしてやる
- Podに外部へルーティング可能なIPアドレスをアサインする
前者は柔軟かつ潤沢なリソースを備えたIaaS上に乗っかってるClusterであれば許容出来るでしょうが、スケーラビリティへのリソース関与が大きくなるため、あまり気が進みません。
後者については以前(だいぶ前…)紹介したMultusも実現可能な方式の一つではありますが、IPAM管理が面倒だったりします。
そこで今回はCalicoを使った実現をご紹介したいと思います。BGPルーターと連携する必要がありますが、CNIでCalicoを採用しているClusterであれば簡単に試すことが可能です。
それ以外の方法
Calico Egress Gateway
細かい機能は調べ切れていませんが、Calico Enterprise版のEgress Gatewayを使えば、この辺の管理を簡単に行うことができそうです。
プロダクション環境では導入を検討したいところですが、今回はK8s CNIのSNATをさせない方法を試します。
https://www.tigera.io/blog/calico-egress-gateway-universal-firewall-integration-for-kubernetes/
Cilium Egress IP Gateway
Ciliumでもつい最近リリースされたVersion 1.10で同じような機能が提供されています。ただEgress Gateway用のNodeを立てる仕組みのようです。(実際この構成が現実的かも。)
https://cilium.io/blog/2021/05/20/cilium-110#egress-ip-gateway
Ciliumは実績も増えて来てますし、今回のリリースでは今まで欲しかった機能も充実してきて本命になりつつあります。
試してみる
以下、公式ドキュメントを参考に自宅のLab環境で試してみます。K8s ClusterとBGPサーバーのマシンは事前に確保してください。(自分はミニマルなKVMにUbuntuを入れました。)
https://docs.projectcalico.org/about/about-kubernetes-egress#nat-outgoing
実施内容
CalicoのIP PoolとIPAMの設定を行うことにより、以下について試してみます。
- Podに任意のIP PoolおよびIPアドレスを付与
- PodのIPアドレスのままClusterの外部へ疎通(NodeのIPアドレスへのSNAT無し)
- Serviceリソース無しで外部からPodに通信することも可能(しないと思いますが)
- Cluster内のIP PoolのRoute情報をBGPルーターに広告することが可能
本Hands-onでは構築済みのClusterへ設定を行い、外部のBGPサーバーと連携してみます。
前提条件
こちらで紹介している方法はCalicoの機能を試してみる目的としておりますので、Best Practiceではないことにご留意ください。
実際にプロダクション環境に適用する場合は、ご自身による十分なFeasibility Checkのご実施を忘れずに。(念のため)
準備:BGPサーバーの配備
BGP DaemonのBIRDを建てます。BGPサーバーに見立てたマシンで以下を実行してBIRDと確認用のnginxをインストールします。
sudo apt install bird nginx
/etc/bird/bird.conf
をエディットします。
protcol kernel
のスタンザ内のexport all;
をコメントアウトし、各Calico NodeをBGP Peer追加します。(各protcol bgp
の名前は記号以外の英数字で設定して下さい。)
以下例ではMaster Node含めた5台のCalico Nodeを追加しています。(CalicoのディフォルトのASは64512
、BGP RouterやCalico NodeのIPアドレスは適宜環境に合わせ変更して下さい。)
# This is a minimal configuration file, which allows the bird daemon to start
# but will not cause anything else to happen.
#
# Please refer to the documentation in the bird-doc package or BIRD User's
# Guide on http://bird.network.cz/ for more information on configuring BIRD and
# adding routing protocols.
# Change this into your BIRD router ID. It's a world-wide unique identification
# of your router, usually one of router's IPv4 addresses.
# router id 198.51.100.1;
router id 192.168.29.4;
# The Kernel protocol is not a real routing protocol. Instead of communicating
# with other routers in the network, it performs synchronization of BIRD's
# routing tables with the OS kernel.
protocol kernel {
scan time 60;
import none;
export all; # Actually insert routes into the kernel routing table
}
# The Device protocol is not a real routing protocol. It doesn't generate any
# routes and it only serves as a module for getting information about network
# interfaces from the kernel.
protocol device {
scan time 60;
}
protocol bgp ubu0703 {
import all;
local as 65000;
neighbor 192.168.29.73 as 64512;
}
protocol bgp ubu0704 {
import all;
local as 65000;
neighbor 192.168.29.74 as 64512;
}
protocol bgp ubu0705 {
import all;
local as 65000;
neighbor 192.168.29.75 as 64512;
}
protocol bgp ubu0706 {
import all;
local as 65000;
neighbor 192.168.29.76 as 64512;
}
protocol bgp ubu0707 {
import all;
local as 65000;
neighbor 192.168.29.77 as 64512;
}
BGP Daemonを再起動します。
sudo systemctl restart bird
Calico IP Poolの設定
calicoctl
をセットアップしたホスト(この環境ではMaster Node)で、Podに割り当てる任意のIPアドレスPool(以下例では192.168.32.0/24
)とSNATせずに接続する外部のCIDR(ここでは192.168.29.0/26
)をCalicoに適用します。
cat << EOF > calico-ippools.yaml
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: ipam-ipv4-ippool
spec:
cidr: 192.168.32.0/24
natOutgoing: true
---
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: living-nw
spec:
cidr: 192.168.29.0/26
disabled: true
EOF
calicoctl apply -f calico-ippools.yaml
このIP Poolを割り当てたPodを生成します。(ここではあえてDeploymentにしていませんが、templateで適当なWorkloadでデプロイしてください。)
for i in {0..5} ; do kubectl apply -f - << EOF
apiVersion: v1
kind: Pod
metadata:
name: ipam-nginx-app${i}
labels:
ipam-app: ipam-app${i}
annotations:
"cni.projectcalico.org/ipv4pools": "[\"ipam-ipv4-ippool\"]"
spec:
containers:
- name: ipam-nginx-app${i}
image: nginx:latest
ports:
- containerPort: 80
EOF
done
PodにCalico IP Poolのアドレスが付与されていることを確認します。
$ kubectl get po -l ipam-app -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ipam-nginx-app0 1/1 Running 0 20h 192.168.32.0 ubu07-06 <none> <none>
ipam-nginx-app1 1/1 Running 0 20h 192.168.32.192 ubu07-07 <none> <none>
ipam-nginx-app2 1/1 Running 0 20h 192.168.32.1 ubu07-06 <none> <none>
ipam-nginx-app3 1/1 Running 0 20h 192.168.32.193 ubu07-07 <none> <none>
ipam-nginx-app4 1/1 Running 0 20h 192.168.32.2 ubu07-06 <none> <none>
ipam-nginx-app5 1/1 Running 0 20h 192.168.32.194 ubu07-07 <none> <none>
上記ではPodの
annotations
にIP Pool名を指定していますが個別のIPアドレスの設定やNamespace毎の割り当て、Nodeに限定した付与も可能です。詳細については以下のドキュメントを参照下さい。
https://docs.projectcalico.org/reference/cni-plugin/configuration#ipam-1
Calico BGP Peerの設定
Calico Cluster内のルート情報を広告するためのBGP Peerの設定を適用します。BGP Peerは先程のBGP Daemonを設定したサーバーの情報を設定します。
cat << EOF > calico-bgppeer.yaml
cat calico-bgppeer.yaml
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: bgp-bird
spec:
peerIP: 192.168.29.4
asNumber: 65000
EOF
calicoctl apply -f calico-bgppeer.yaml
Calico側のPeer状態を確認します。
$ sudo calicoctl node status
Calico process is running.
IPv4 BGP status
+---------------+-------------------+-------+------------+--------------------------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+---------------+-------------------+-------+------------+--------------------------------+
| 192.168.29.74 | node-to-node mesh | up | 2021-04-13 | Established |
| 192.168.29.75 | node-to-node mesh | up | 2021-04-13 | Established |
| 192.168.29.76 | node-to-node mesh | up | 2021-04-14 | Established |
| 192.168.29.77 | node-to-node mesh | up | 2021-04-14 | Established |
| 192.168.29.4 | global | up | 21:52:18 | Established |
+---------------+-------------------+-------+------------+--------------------------------+
IPv6 BGP status
No IPv6 peers found.
BGPルーター側で正しくルート情報が広告されたか確認します。
以下ではbirdc
によるDaemon内の情報とOSのルート情報を確認しています。
# birdc show route
BIRD 1.6.8 ready.
172.16.251.64/26 via 192.168.29.73 on cbr0 [ubu0703 06:52:18] * (100) [AS64512i]
via 192.168.29.75 on cbr0 [ubu0705 06:52:18] (100) [AS64512i]
via 192.168.29.74 on cbr0 [ubu0704 06:52:18] (100) [AS64512i]
via 192.168.29.76 on cbr0 [ubu0706 06:52:18] (100) [AS64512i]
via 192.168.29.77 on cbr0 [ubu0707 06:52:18] (100) [AS64512i]
192.168.32.192/26 via 192.168.29.73 on cbr0 [ubu0703 06:52:18] * (100) [AS64512i]
via 192.168.29.75 on cbr0 [ubu0705 06:52:18] (100) [AS64512i]
via 192.168.29.74 on cbr0 [ubu0704 06:52:18] (100) [AS64512i]
via 192.168.29.76 on cbr0 [ubu0706 06:52:18] (100) [AS64512i]
via 192.168.29.77 on cbr0 [ubu0707 06:52:18] (100) [AS64512i]
192.168.32.0/26 via 192.168.29.73 on cbr0 [ubu0703 06:52:18] * (100) [AS64512i]
via 192.168.29.75 on cbr0 [ubu0705 06:52:18] (100) [AS64512i]
via 192.168.29.74 on cbr0 [ubu0704 06:52:18] (100) [AS64512i]
via 192.168.29.76 on cbr0 [ubu0706 06:52:18] (100) [AS64512i]
via 192.168.29.77 on cbr0 [ubu0707 06:52:18] (100) [AS64512i]
172.16.109.64/26 via 192.168.29.73 on cbr0 [ubu0703 06:52:18] * (100) [AS64512i]
via 192.168.29.75 on cbr0 [ubu0705 06:52:18] (100) [AS64512i]
via 192.168.29.74 on cbr0 [ubu0704 06:52:18] (100) [AS64512i]
via 192.168.29.76 on cbr0 [ubu0706 06:52:18] (100) [AS64512i]
via 192.168.29.77 on cbr0 [ubu0707 06:52:18] (100) [AS64512i]
# ip r | grep 192.168.32
192.168.32.0/26 via 192.168.29.73 dev cbr0 proto bird
192.168.32.192/26 via 192.168.29.73 dev cbr0 proto bird
Workaround:BGPルーター側に各PodへのDirectなルートテーブルが構成されない
本Hands-onの環境ではBGPルーター内OSのルートテーブルがWorker NodeにDirectに接続されない状態(この例ではMaster Node#0が1Hop目)となってしまいました。(BGP周りの設定がおかしいのかも…)
WorkaroundとしてCluster内への転送を許容するよう各Nodeに以下のようにiptables
の設定を入れています。
# iptables -I KUBE-FORWARD -s 192.168.29.0/26 -j ACCEPT
確認
以下コマンドでPod内から要求を出します。
for i in {0..5} ; do kubectl exec ipam-nginx-app${i} -- curl 192.168.29.4 ; done
BGPルーターサーバー側のnginxのaccess.log
を確認しPod内のIPアドレスの192.168.32.0/24
で疎通が通っていることを確認します。
# tail /var/log/nginx/access.log
:
192.168.32.0 - - [15/Apr/2021:13:43:31 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0"
192.168.32.192 - - [15/Apr/2021:13:43:33 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0"
192.168.32.1 - - [15/Apr/2021:13:43:33 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0"
192.168.32.193 - - [15/Apr/2021:13:43:34 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0"
192.168.32.2 - - [15/Apr/2021:13:43:34 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0"
192.168.32.194 - - [15/Apr/2021:13:43:35 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.64.0"
おわりに
Calicoを使ったPodへのルーティング可能な静的IPアドレスの付与について書かせていただきました。
今後、前述したCiliumの新しい機能も確認したいと思いますので、その際にはまたご紹介させていただこうと思います。