MultusによるKubernetesクラスタ外からのパケット転送実験
Multus
最近になってMultusというKubernetes CNIの一つを知った。Kubernetes環境ではPodは通常1つのNICのみを持つが、Multusを使うことでPodに複数のNICを持たせることが可能になる。
- GitHub - intel/multus-cni
- Multusで遊ぶ - 赤帽エンジニアブログ
- Multus CNI pluginをKubernetesで試した - ntoofu
- Adv Network Features in Kubernetes App Note
上記情報源より簡単にその特徴を挙げる。詳細な説明は各記事を参照のこと。(2つの日本語記事はとても分かりやすくまとめられていて助かります。)
- intel社のOSSであり、コンテナ環境でNFVを実現するための課題 (複数NIC、通信の分離や高速化など) を解決する一手法
- Kubernetesで管理されるコンテナがホストサーバーのNICを介して通信高速化機能 (DPDKやSR-IOVなど) を利用することができる
- Multus自体は「Delegating CNIプラグイン」であり、別途CNIプラグインの存在を前提とし、それらに対してネットワーク設定をdelegateする形でNICを設定する
- Multusが作成するNICはKubernetes側では感知されない
- Multusが付与する複数NICの設定はNetworkAttachmentDefinitionというCRDに準拠する
- Multusと同じ「Delegating CNIプラグイン」としてはCNI-Genieがある
- Contrail CNIでも複数のNIC追加ができるがこちらは非Delegatingプラグインらしい
Podに複数のNICを付与する標準はKubernetes Network Plumbing Working Groupで定義されているらしい。
実験
Multusを用いてルーティングソフトウェア (FRR) が動作するPodにNICを追加し、そのNICを経由してKubernetesクラスタの外からのパケットをルーティング・フォワーディングするという実験を行う。本当は色々なNetwork FunctionをPodとして追加したりするともっと面白そう。
環境と構成
下図の通り、macOS上のminikubeにFRRとMultusをデプロイする。
- macOS 10.15.7
- VirtualBox 6.1.16
- minikube v1.17.1
- Kubernetes v1.20.2
- CNI 0.3.1
- CNI plugin v0.8.5
- Multus v3.6
- FRRouting (frr) 7.7-dev_git
1.基盤環境構築
macOSに2つのbridgeを用意する。以下のApple公式リンクに従いシステム環境設定からbridgeを作成する。
Macで仮想ネットワークインターフェイスのブリッジを設定する - Apple サポート
実際にやってみたところ、bridge作成後、適当なIPアドレスをアサインするまでは ipconfig
で認識されなかった。また、作成時に指定したブリッジ名は ipconfig
上では適用されていなかった。(既にbridge0が存在していたことから、自動的に連番になるものと推測。)
## macOS $ ifconfig bridge1 bridge1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500 options=63<RXCSUM,TXCSUM,TSO4,TSO6> ether f2:18:98:45:d8:01 Configuration: id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0 maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200 root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0 ipfilter disabled flags 0x0 Address cache: nd6 options=201<PERFORMNUD,DAD> media: <unknown type> status: inactive $ ifconfig bridge2 bridge2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500 options=63<RXCSUM,TXCSUM,TSO4,TSO6> ether f2:18:98:45:d8:02 Configuration: id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0 maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200 root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0 ipfilter disabled flags 0x0 Address cache: nd6 options=201<PERFORMNUD,DAD> media: <unknown type> status: inactive
続いてminikubeを用意する。今回はVirtualBoxのVMで扱いたいため --driver='virtualbox'
を指定。またCNIプラグインとしてciliumを採用する。
## macOS $ minikube start -p minikube-vbox --driver='virtualbox' --cni='cilium' \ --container-runtime=containerd --kubernetes-version='stable'
kubectlの接続を確認したら一度minikube VMを落とす。minikube VMに作成したbridge 2つをNICとして設定したいが、起動時にそれをするオプションはないためVM完成後にVirtualBoxのGUIから実施する。
$ kubectl version Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:28:09Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"darwin/amd64"} Server Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:20:00Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"} $ minikube -p minikube-vbox stop
minikube VMにsshしNICを確認する。NICのプロミスキャスモードを有効化し、NICのMACアドレスがVirtualBox GUIで表示されていたMACアドレスと一致していることを確認する。
## macOS $ minikube -p minikube-vbox start $ minikube -p minikube-vbox ssh
## minikube VM $ sudo ip link set dev eth2 promisc on $ sudo ip link set dev eth3 promisc on $ ip addr show eth2 4: eth2: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:b8:f0:85 brd ff:ff:ff:ff:ff:ff $ ip addr show eth3 5: eth3: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:13:a1:e9 brd ff:ff:ff:ff:ff:ff
2.Multusのデプロイ
Multusのquickstartに従う。MultusのPodとCRD (NetworkAttachmentDefinition) がデプロイされている。
## macOS $ git clone https://github.com/intel/multus-cni.git && cd multus-cni $ cat ./images/multus-daemonset.yml | kubectl apply -f - $ kubectl get pods -A -l app=multus NAMESPACE NAME READY STATUS RESTARTS AGE kube-system kube-multus-ds-amd64-dpr7z 1/1 Running 0 19s $ kubectl get crd network-attachment-definitions.k8s.cni.cncf.io NAME CREATED AT network-attachment-definitions.k8s.cni.cncf.io 2021-02-06T07:45:31Z
Nodeであるminikube VMにはMultus用の設定が保存されている。
## minikube VM $ sudo jq . /etc/cni/net.d/00-multus.conf { "cniVersion": "0.3.1", "name": "multus-cni-network", "type": "multus", "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", "delegates": [ { "cniVersion": "0.3.1", "name": "cilium", "type": "cilium-cni", "enable-debug": false } ] }
MultusがデプロイされるとNodeの /opt/cni/bin
ディレクトリにプラグインのバイナリが配置される。今回IPアドレスを静的に設定したいと考えたが、使用したい staticより同バージョンのバイナリをダウンロードして直接配置することにした。
## minikube VM $ ls /opt/cni/bin bandwidth bridge cilium-cni cnitool dhcp firewall flannel host-local ipvlan loopback macvlan multus portmap ptp tuning vlan $ ls /opt/cni/bin/static ls: cannot access '/opt/cni/bin/static': No such file or directory
## macOS $ mkdir tmp $ wget -P ./tmp https://github.com/containernetworking/plugins/releases/download/v0.8.5/cni-plugins-linux-amd64-v0.8.5.tgz $ cd tmp && tar zxvf cni-plugins-linux-amd64-v0.8.5.tgz # minikube VMにバイナリを転送するためmountする (他にもっと良い方法あるかも) $ minikube -p minikube-vbox mount ./:/mnt ## minikube VM $ sudo cp /mnt/static /opt/cni/bin/ $ /opt/cni/bin/static version CNI static plugin v0.8.5 ## macOS # minikube mountを停止しておく
3. Kubernetesリソースのデプロイ
今回はmacvlanプラグインを使用してFRRのPodにL2のネットワークを提供する。
下記のマニフェストにより2つのNetworkAttachmentDefinitionリソースをデプロイする。
- ホスト (ここではminikube VM) に接続したNICを指定するために
type: macvlan
でmode: bridge
指定 - NICの指定は
master
で行う promiscMode: true
を指定- IPアドレスのアサインはPod側のannotationで任意に指定することにしたため、
ipam
設定にてtype: static
を指定
## macOS $ cat <<EOF | kubectl apply -f - apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: bridge-conf-1 spec: config: '{ "cniVersion": "0.3.1", "type": "macvlan", "master": "eth2", "mode": "bridge", "promiscMode": true, "ipam": { "type": "static" } }' --- apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: bridge-conf-2 spec: config: '{ "cniVersion": "0.3.1", "type": "macvlan", "master": "eth3", "mode": "bridge", "promiscMode": true, "ipam": { "type": "static" } }' EOF $ kubectl get network-attachment-definition NAME AGE bridge-conf-1 15s bridge-conf-2 15s
FRRのPodをデプロイする。FRRはZebraを有効化するために securityContext
にて privileged: true
を指定する必要がある。
(今回は実験用なのでこれでよいが、実際にはNodeを守るための手段を講じる必要がある。特にKubernetesが管理しないNICが追加される今回のようなPodではなおさら必要性が増すと考える。)
Podのannotationでは上記で作成したNetworkAttachmentDefinitionリソース (bridge-conf-1
, bridge-conf-2
) とそのbridgeで使用するIPアドレスをそれぞれ指定している。
## macOS $ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: frr annotations: k8s.v1.cni.cncf.io/networks: | [ { "name": "bridge-conf-1", "ips": ["10.0.10.1/24", "2001:db8:10::1/64"] }, { "name": "bridge-conf-2", "ips": ["10.0.20.1/24", "2001:db8:20::1/64"] } ] spec: containers: - name: frr image: frrouting/frr:latest securityContext: privileged: true EOF $ kubectl get pod frr NAME READY STATUS RESTARTS AGE frr 1/1 Running 0 80s
FRR Podでネットワークを確認すると net1
, net2
としてインターフェースが作成されていることが分かる。なお eth0
はKubernetesネットワークに接続するための通常のNICである。
## macOS $ kubectl exec frr -- ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000 link/sit 0.0.0.0 brd 0.0.0.0 3: net1@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 76:97:54:32:af:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.10.1/24 brd 10.0.10.255 scope global net1 valid_lft forever preferred_lft forever inet6 2001:db8:10::1/64 scope global valid_lft forever preferred_lft forever inet6 fe80::7497:54ff:fe32:af03/64 scope link valid_lft forever preferred_lft forever 4: net2@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 7a:e6:74:b5:3f:8f brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.20.1/24 brd 10.0.20.255 scope global net2 valid_lft forever preferred_lft forever inet6 2001:db8:20::1/64 scope global valid_lft forever preferred_lft forever inet6 fe80::78e6:74ff:feb5:3f8f/64 scope link valid_lft forever preferred_lft forever 43: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 4a:c4:21:45:63:91 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.0.24/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::48c4:21ff:fe45:6391/64 scope link valid_lft forever preferred_lft forever
4.疎通試験用VMの用意
bridgeに接続された適当なVMを2つ用意する。
(本当はコンテナで実施したかったが、docker for macでは host
タイプのネットワークを1つしか作成できないらしく断念)
## macOS $ cat Vagrantfile Vagrant.configure("2") do |config| config.vm.hostname = "vm1" config.vm.box = "ubuntu/xenial64" config.vm.network "public_network", bridge: "bridge1", ip: "10.0.10.10" end $ vagrant up $ vagrant ssh ## VM1 $ ip addr show (snip) 3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:da:b2:d8 brd ff:ff:ff:ff:ff:ff inet 10.0.10.10/24 brd 10.0.10.255 scope global enp0s8 valid_lft forever preferred_lft forever inet6 fe80::a00:27ff:feda:b2d8/64 scope link valid_lft forever preferred_lft forever # この時点でFRRコンテナと通信が可能 $ ping 10.0.10.1 -c 3 PING 10.0.10.1 (10.0.10.1) 56(84) bytes of data. 64 bytes from 10.0.10.1: icmp_seq=1 ttl=64 time=1.26 ms 64 bytes from 10.0.10.1: icmp_seq=2 ttl=64 time=0.263 ms 64 bytes from 10.0.10.1: icmp_seq=3 ttl=64 time=0.279 ms --- 10.0.10.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2001ms rtt min/avg/max/mdev = 0.263/0.601/1.262/0.467 ms # せっかくなのでIPv6アドレスの設定も $ sudo ip address add 2001:db8:10::10/64 dev enp0s8 # 疎通試験のためにstatic routeを追加 $ sudo ip route add 10.0.20.0/24 via 10.0.10.1 $ sudo ip route add 2001:db8:20::/64 via 2001:db8:10::1
VM2も同様に設定する。
## macOS $ cat Vagrantfile Vagrant.configure("2") do |config| config.vm.hostname = "vm2" config.vm.box = "ubuntu/xenial64" config.vm.network "public_network", bridge: "bridge2", ip: "10.0.20.20" end $ vagrant up $ vagrant ssh ## VM2 $ sudo ip address add 2001:db8:20::20/64 dev enp0s8 $ sudo ip route add 10.0.10.0/24 via 10.0.20.1 $ sudo ip route add 2001:db8:10::/64 via 2001:db8:20::1
5.疎通試験
FRRを設定する。
## macOS $ kubectl exec -it frr bash ## FRR Pod $ vtysh frr# conf t frr(config)# interface net1 frr(config-if)# ip address 10.0.10.1/24 frr(config-if)# ipv6 address 2001:db8:10::1/64 frr(config-if)# no shutdown frr(config-if)# exit frr(config)# interface net2 frr(config-if)# ip address 10.0.20.1/24 frr(config-if)# ipv6 address 2001:db8:20::1/64 frr(config-if)# no shutdown frr(config-if)# exit frr(config)# ip forwarding frr(config)# ipv6 forwarding frr(config)# end
疎通確認を行う。想定通りVM間で通信できていることが分かる。
## VM1 $ ping 10.0.20.20 -c 3 PING 10.0.20.20 (10.0.20.20) 56(84) bytes of data. 64 bytes from 10.0.20.20: icmp_seq=1 ttl=63 time=0.536 ms 64 bytes from 10.0.20.20: icmp_seq=2 ttl=63 time=1.34 ms 64 bytes from 10.0.20.20: icmp_seq=3 ttl=63 time=1.09 ms --- 10.0.20.20 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 0.536/0.989/1.342/0.337 ms $ ping6 2001:db8:20::20 -c 3 PING 2001:db8:20::20(2001:db8:20::20) 56 data bytes 64 bytes from 2001:db8:20::20: icmp_seq=1 ttl=63 time=0.531 ms 64 bytes from 2001:db8:20::20: icmp_seq=2 ttl=63 time=0.609 ms 64 bytes from 2001:db8:20::20: icmp_seq=3 ttl=63 time=1.24 ms --- 2001:db8:20::20 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2000ms rtt min/avg/max/mdev = 0.531/0.794/1.242/0.318 ms
ちなみに仮想環境なのであまり意味はないが、通信帯域としては300Mbps程度であった。
## VM1 $ sudo iperf3 -c 10.0.20.20 (snip) - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bandwidth Retr [ 4] 0.00-10.00 sec 365 MBytes 306 Mbits/sec 258 sender [ 4] 0.00-10.00 sec 363 MBytes 304 Mbits/sec receiver
所感
上記の通りKubernetesで管理されているコンテナでクラスタ外からのパケットルーティング・フォワーディングを簡単に行うことができた。Webアプリの動作など通常のKubernetesの使い方からするとかなりイレギュラーなことをやっている気持ちになった。今後コンテナベースのNetwork Function (CNF) が流行るのかどうか分からないが、一つ興味深いOSSだった。
一つのユースケースとしておうちKubernetesでご家庭のネットワーク機能を実現できたら面白そう。
一方でセキュリティについては意識する必要があると思う。Multusによって生えたNICはKubernetes側に感知されないため、Kuberneteが提供するセキュリティ機能を利用することができないため、Firewallなどネットワークプレイヤーでのセキュリティを十分に担保する必要があると感じた。その辺りもVirtual Function的にPodで提供して対応するべきか。
kind 製 Kubernetes クラスタ内サービスへのアクセス (for macOS)
はじめに
kind を使うと複数 Node のクラスタが簡単に作成できて便利ですが、ホスト (macOS) 側から kind 上で提供されているサービスにどうアクセスするのか少し悩みました。
kind では docker コンテナ1つを1 Nodeとして Kubernetes クラスタを構築できますが、 Docker Desktop for Mac の制約 によりそもそもホストからコンテナへ IP で到達することができません。そのため kind で作成されたクラスタに type: NodePort
の Service をデプロイしたとてホスト側からその Service にアクセスできないのです。
kind の Issue にも関連がありそうな話題が。
やってみよう
kind で作ったクラスタ内のサービスにアクセスするいくつかの方法を試します。
環境は以下の通り。
- macOS Mojave 10.14.6
- Docker Desktop for Mac 2.1.0.7
- Kubernetes v1.15.3
- kind v0.5.1
まずは kind でクラスタを作成します。
❯ cat <<EOF | kind create cluster --config - kind: Cluster apiVersion: kind.sigs.k8s.io/v1alpha3 nodes: - role: control-plane - role: worker - role: worker - role: worker EOF ❯ export KUBECONFIG="$(kind get kubeconfig-path --name="kind")" ❯ kubectl get nodes NAME STATUS ROLES AGE VERSION kind-control-plane Ready master 84s v1.15.3 kind-worker Ready <none> 50s v1.15.3 kind-worker2 Ready <none> 50s v1.15.3 kind-worker3 Ready <none> 50s v1.15.3
テスト用のアプリケーションをデプロイ
❯ cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 EOF ❯ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-68c7f5464c-8b58k 1/1 Running 0 2m16s 10.244.2.6 kind-worker2 <none> <none> nginx-68c7f5464c-m6tz5 1/1 Running 0 2m16s 10.244.1.5 kind-worker3 <none> <none> nginx-68c7f5464c-tcnhc 1/1 Running 0 2m16s 10.244.3.7 kind-worker <none> <none> ❯ for PODNAME in `kubectl get pods -o jsonpath='{.items[*].metadata.name}'`; do kubectl exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html; done
HTTP でアクセスすると Pod 名が表示されます。
ホスト (macOS) から如何にしてクラスタ内で展開されるサービスを見ることができるでしょうか。思いついたものを試してみます。
kubectl port-forward
デバッグでよく使うやつ。ただし Pod に直接接続するため今回のようなケースではロードバランスされません。
❯ kubectl port-forward deploy/nginx 8080:80 ## 別ターミナルにて ❯ curl 127.0.0.1:8080 nginx-68c7f5464c-8b58k ❯ curl 127.0.0.1:8080 nginx-68c7f5464c-8b58k ❯ curl 127.0.0.1:8080 nginx-68c7f5464c-8b58k
kind の extraPortMappings
kindの公式ドキュメント にある extraPortMappings
を使うと hostPort
に割当が可能なようです。
❯ cat <<EOF | kind create cluster --config - kind: Cluster apiVersion: kind.sigs.k8s.io/v1alpha3 nodes: - role: control-plane - role: worker - role: worker - role: worker extraPortMappings: - containerPort: 30000 hostPort: 8080 EOF ## Deployment 作成とアプリの設定は前述したものと同様のコマンドを実行 ❯ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: nginx spec: type: NodePort ports: - name: "http-port" protocol: TCP port: 80 targetPort: 80 nodePort: 30000 selector: app: nginx EOF ❯ curl 0.0.0.0:8080 nginx-68c7f5464c-w4h7v ❯ curl 0.0.0.0:8080 nginx-68c7f5464c-rd6hs ❯ curl 0.0.0.0:8080 nginx-68c7f5464c-vl2r6
kind クラスタの config として3つ目の Node に extraPortMappings
を設定し立ち上げました。
ホストのポート (extraPortMappings[*].hostPort: 8080
) へのアクセスが Node のコンテナのポート (extraPortMappings[*].containerPort: 30000
) に転送されます。そのため nodePort: 30000
で指定した NodePort タイプの Service を用意することでロードバランスも可能です。
❯ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 81b82a6df489 kindest/node:v1.15.3 "/usr/local/bin/entr…" 8 minutes ago Up 8 minutes 53372/tcp, 127.0.0.1:53372->6443/tcp kind-control-plane 6f9bf2c0662b kindest/node:v1.15.3 "/usr/local/bin/entr…" 8 minutes ago Up 8 minutes kind-worker 0d5c32d65b11 kindest/node:v1.15.3 "/usr/local/bin/entr…" 8 minutes ago Up 8 minutes kind-worker2 8b955ef61737 kindest/node:v1.15.3 "/usr/local/bin/entr…" 8 minutes ago Up 8 minutes 0.0.0.0:8080->30000/tcp kind-worker3
実際に docker コンテナの状態を確認すると kind-worker3
の PORTS
の項目にて 0.0.0.0:8080->30000/tcp
されていることが分かります。
そのため以下のような記述をしても、1つ目の Node をデプロイした時点でホスト側の 8080 ポートは使用されてしまい、複数の Node に同じ extraPortMappings[*].hostPort
を割り当てることはできません。
❯ cat <<EOF | kind create cluster --config - kind: Cluster apiVersion: kind.sigs.k8s.io/v1alpha3 nodes: - role: control-plane - role: worker extraPortMappings: - containerPort: 30000 hostPort: 8080 - role: worker extraPortMappings: - containerPort: 30000 hostPort: 8080 - role: worker extraPortMappings: - containerPort: 30000 hostPort: 8080 EOF ## エラーが発生 ERRO[00:46:47] docker: Error response from daemon: driver failed programming external connectivity on endpoint kind-worker2 (17e1142d52d7f985f716cf49a2fc77a2af235bb8eca3918218b1d900f64831aa): Bind for 0.0.0.0:8080 failed: port is already allocated.
つまりは NodePort と言いつつも実際にユーザからのトラフィックを受け取るのは1つの Node のみということになります。
kubectl-open-svc-plugin
@superbrothers さんが作成された kubectl-open-svc-plugin というプラグインを試します。
kubectl port-forward
の ClusterIP 版だと理解しています。
これは、クラスタ外からアクセスできない ClusterIP タイプの Service にクラスタの外から簡単にアクセスするためのプラグインです。
kubectl plugin マネージャである krew
を使えば簡単に導入可能でした。
❯ kubectl krew install open-svc ❯ cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: nginx spec: type: ClusterIP ports: - name: "http-port" protocol: "TCP" port: 8080 targetPort: 80 selector: app: nginx EOF ❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/ nginx-68c7f5464c-m6tz5 ❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/ nginx-68c7f5464c-tcnhc ❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/ nginx-68c7f5464c-tcnhc ❯ curl http://127.0.0.1:8080/api/v1/namespaces/default/services/nginx:http-port/proxy/ nginx-68c7f5464c-8b58k
生成された URL にアクセスすると ClusterIP によるロードバランスが確認できます。
なんて便利!色々と使い所がありそうです。
おわり
kind や minikube など手元で試せる Kubernetes にデプロイしたサービスにアクセスする方法はどれもそのツールの実装方法に依存しており、なかなかに直感的でないように感じています。今回の話だと macOS ではなく Linux を使えばシンプルに解決するとも言えますが。
kubectl-open-svc-plugin
のような補助ツールがあることでマルチクラスタにおけるロードバランスを手軽に試すことができるのは非常に有り難いと感じました。
Go 力養成のため自分も何かプラグインを作ってみたいと思いました。
CKA 取れた
You scored 78% for this Exam. A score of at least 74% was required to pass.
少し前ですが CKA (Certified Kubernetes Administrator) ギリギリ受かりました。予定では88%取れているはずだったのに。肝が冷えます。
規約で大したことは書けないけれど記録のために書いておきます。
モチベーション
私は物事を体系的に学ぶのが好きな教科書人間です。 Kubernetes は業務でもぼちぼちと触っていたのですが、如何せん付け焼き刃の歯抜け知識でした。またチームメンバに情報を展開するにあたっても普段触らない背景知識の必要性を感じていました。そこで今年のGW10連休、暇なので*1 一から勉強し直そうと思い立ち、せっかく資格があるとのことなので連休明けにそれ取ることを目標にしました。
事前状態
GW初日時点での私の戦闘力はこのような感じでした。
- バイブル Kubernetes 完全ガイド 全編のうち70%程度を数ヶ月前にさらっと読んでいる。
- 業務を通して基本的な Resource (Pod, Deployment, 各種 Service 等) について概念や基本的な使い方を把握している。一方で Scheduling, Storage, Security, Installation 等の内容は残念なヨクワカラナイ。
- Kubernetes The Hard Way を一巡。分からないところを調べながら割と時間をかけて取り組みついでに 記事数を稼ぐ。これにより Kubernetes の各コンポーネントと相互接続方法を大まかに理解している。
公式の試験カリキュラム と照らし合わせてみるとこの段階でのカバー率は15%程度だったと思います。
やったこと
試験の情報収集
公式ハンドブック を流し読み。ググると沢山出てくる合格者体験記で試験の Tips や戦略を先人から学びます。問題の内容は受けてみるまで分かりませんが、試験の雰囲気を掴むことはできました。
Cloud Native Certified Kubernetes Administrator (CKA)
Linux Academy のコースで、 公式の試験カリキュラム に沿った内容で作られているため今回の教科書として採用。Yearly で Subscribe をすると1週間の無料トライアル期間が得られるため、GW期間中にlab含めて一巡して解約しました。試験分野を効率的になぞることができるため Kubernetes にまだ自信のない方にはおすすめ。
Kubernetes The Hard Way
直前にもう一周しました。多くの合格者ブログで三周以上やっておくべしと書いてありましたがそこまでの時間はありませんでした。
Kubernetes 完全ガイド
弱点である Scheduling, Storage あたりを中心に yaml を写経デプロイしながら学習。
問題演習
予想問題 (あくまで個人の予想です) を探して解きました。これとかこれ。時間を図りながらやると緊張感があってよいかもしれない。
実際に受けた雑感
- 後半の難しい問題2問を取り組む時点で試験時間の半分 (1.5時間) が経過。結局その2問は歯が立たなかったので、それ以外の問題の見直しをしました。捨てる問題を決めればそこまで時間に追われることもなかったです。(点数はギリギリでしたが...)
- 他の方も多く書かれているように
kubectl run
やkubectl expose
を息をするようにタイプできると捗ります。--dry-run -o yaml
だいじ。 - 試験時間の中盤移行、与えられたクラスタの kube-apiserver へのアクセスが徐々に重くなっていきました。試験官に相談したところ試験Webの機能でセッションを一度クリアせよ、と指示を受けましたがあまり効果はなし。是非改善して欲しい。
- 試験時間の後半で突然 PC (MacBook Pro 13-inch 2018, macOS 10.14.4) が再起動しました。かなり焦りましたが再起動後に試験のWebにアクセスしチャットで試験官に事情を説明したところ問題なく再開することができました。どこかのブログでも似たようなことが書かれていたような気がします。
所感
つい先日、 CKA の有効期限が2年から3年の延びました。CKADは2年のままのようですね。運良く受かりましたが再受験までの期間が延びたことに油断せず継続的に知識をアップデートしていきたいです。
*1:GWにやったことはこれと Avengers EndGame を2回観たことくらいです。充実です。
Kubernetes The Hard Way をやって知ったこと
巷ではケーサンショー、もとい k3s が盛り上がっていることとは存じますが、初級修行僧の身として今更ながら Kubernetes The Hard Way をやりました。知らなかったところと調べたことをメモしておきます。
- 実施した commit
- bf2850974e19c118d04fdc0809ce2ae8a0026a27
- 注記
- 気になった点や調べたこと、感想はこの色で書いてます
01~03
真顔でコピペ。
04 Provisioning a CA and Generating TLS Certificates
- 各コンポーネントがそれぞれに (主に API Server との) 通信を行う際に使用する認証を生成している。
- Kubernetes admin user
- Node (Kubelet)
- kube-controller-manager
- kube-proxy
- kube-scheduler
- kube-apiserver
- Service Account
- GCPの Service Account ではない
- Kubernetes では Node の Kubelet から生成される API リクエストの認証を行うのに Node Authorizer という認証方式を使う
- Using Node Authorization - Kubernetes
- Kubelet が Node Authorizer に認証されるためには
system:nodes:NodeName
の形式で自身を識別させる認証情報を持つ必要がある
- 各コンポーネントの中で中心的にリクエストを受け付ける API Server の Certificate には、そのIPアドレス (
kubernetes-the-hard-way
という名前で取得した外部 IP アドレス) を含めることで remote client との間で認証が可能になる - Kubernetes Controller Manager は鍵ペアを使用して Service Account tokens を生成/認証する
- 参考の要約
- Service Account は クラスタ外部で管理される通常のユーザとは異なる Kubernetes 管理下のユーザであり Pod が kube-apiserver と通信を行うための認証に用いられる
- Service Account の情報は Secret として Pod 起動時にマウントされている (
/var/run/secrets/kubernetes.io/serviceaccount
配下)
05 Generating Kubernetes Configuration Files for Authentication
- 04章で作成した認証情報を使って Kubernetes clients (各コンポーネント) が kube-apiserver を認識し認証されるための kubeconfig を生成する
- API Server の IP アドレスは冗長性のため通常ロードバランサに付与して負荷分散するのがベター
- Kubelet の kubeconfig を生成する際には、 Kubelet が動作する Node の名前
system:nodes:NodeName
にマッチする Certificate を使う必要がある。これにより Node Authorizer による認証が行われる
06 Generating the Data Encryption Config and Key
- Kubernetes は cluster state, application config, secrets といったデータを暗号化する仕組みを持つ
- 暗号鍵と Secret を暗号化するための
kind: EncryptionConfig
の manifest を生成し Controller Node にばら撒く
07 Bootstrapping the etcd Cluster
- Kubernetes のコンポーネントはステートレスであり、 ステートに関する情報は etcd に格納される
- etcd サーバはインスタンスの内部IPアドレス (03章で作成した kubernetes-the-hard-way VPC の kubernetes subnet 範囲内のIPアドレス) を使ってクライアントからのリクエストを受け付けたり、 etcd クラスタ内の他の peer とコミュニケーションを行う
- etcd は API Version を指定しないと v2 で起動する
etcdctl -h
で表示される実行可能なサブコマンド一覧も v2 と v3 で異なる- 今回のように認証情報を渡して起動すると、それなしではクラスタに関する情報を閲覧することができない
controller-2:~$ sudo ETCDCTL_API=3 etcdctl member list --endpoints=https://127.0.0.1:2379 --cacert=/etc/etcd/ca.pem --cert=/etc/etcd/kubernetes.pem --key=/etc/etcd/kubernetes-key.pem 3a57933972cb5131, started, controller-2, https://10.240.0.12:2380, https://10.240.0.12:2379 f98dc20bce6225a0, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379 ffed16798470cab5, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379 controller-2:~$ sudo ETCDCTL_API=3 etcdctl member list Error: context deadline exceeded
08 Bootstrapping the Kubernetes Control Plane
- kube-apiserver, kube-controller-manager, kube-scheduler を Systemd で Service 化して起動する
- ちなみに kubeadm は上記コンポーネントを Pod として動かしているらしい。他には GKE とかはどうだろうか。
- kube-scheduler には
kind: KubeSchedulerConfiguration
の manifest を引数に与える - オプションより
--cluster-cidr
は kube-controller-manager で指定--service-cluster-ip-range
は kube-apiserver, kube-controller-manager の両方で指定--leader-elect=true
は kube-controller-manager でのみ指定- Kubernetes完全ガイドの第19章を見ると kube-scheduler も leader 制をとることができるという
- /healthz エンドポイントはデフォルトでは認証不要になっている
kind: ClusterRole
の manifest を作製してよく使う一般的な Kubelet API を定義し、kind: ClusterRoleBinding
manifest でkubernetes
ユーザに紐づけを行う
09 Bootstrapping the Kubernetes Worker Nodes
- Worker に必要なコンポーネントをインストールする
- runc
- 低レイヤコンテナランタイム
- 高レイヤコンテナランタイムである containerd から OCI 経由で命令を受け取りコンテナを作成する
- gVisor
- runc と同じく低レイヤコンテナランタイム
- 比較的セキュアなランタイム
- container networking plugins
- CNI プラグイン
- コンテナのネットワークインターフェースに関する仕様とライブラリから成る
- containerd
- 高レイヤコンテナランタイム
- Kubelet から CRI という Unix Socket 経由の gRPC API で命令を受けコンテナ作成に必要な環境を整備し、低レイヤランタイムに命令を送る
- ランタイムに関しては非常に分かりやすいこちらの資料を参照した 今話題のいろいろなコンテナランタイムを比較してみた [Docker Meetup Tokyo #26発表レポート]
- kubelet
- kube-proxy
- runc
- cni の設定では type として
bridge
を指定し、03章で定義したpod-cidr
を記述しているtype: bridge
は cni にデフォルトで備わる プラグインの一つ 。type は他にflannel
等サードパーティのプラグイン名を指定するtype: loopback
が必要な理由は Kubernetes CNI loopback by によると以下の通り
The loopback CNI is automatically used by Kubernetes to do low-level networking (like for the Kubelet to talk to the containers local to the node) and Kubernetes will fail if it's not installed or running,
- containerd の設定では低レイヤコンテナランタイムである runc 及び runsc (gVisor) を指定
- kubelet の設定では
kind: KubeletConfiguration
を作成、その中でclusterDNS
、podCIDR
、認証情報などを指定 - kubelet の unit ファイルを作成。
--container-runtime
で指定可能な値はdocker, remote, rkt(deprecated)
であり、 containerd を使う場合にはremote
を設定の後、--container-runtime-endpoint
で使用する Unix Socket を指定する
- kube-proxy の設定では
kind: KubeProxyConfiguration
を作成、その中で動作モードをiptables
と指定。
10 Configuring kubectl for Remote Access
- 前半で作製した admin ユーザで kubectl を実行できるようにする設定する
- ロードバランスされている API Server のIPアドレスと admin ユーザの認証情報を使用する
- これを全て手で設定することを思うと
gcloud container clusters get-credentials
は偉大である
11 Provisioning Pod Network Routes
- Pod が Node にスケジューリングされると Pod は Node の Pod Cidr からIPアドレスを受け取って設定するが、09章で見たように Pod Cidr は Node 毎に異なるため、通常であれば別 Node にある Pod とは通信することができない
- そこで今回はそれを GCP のルートを用いて Pod Cidr を Node の Internal IP アドレスに解決する経路を生成する
- この lab では敢えて原始的な方法を用いているが Calico や Flannel 等の CNI プラグインを用いる方法が一般的ですよね
12 Deploying the DNS Cluster Add-on
- サービスディスカバリのための DNS として CoreDNS を Add-on 的にデプロイする
- 作成されている Service に対して名前解決ができることを確認する
- が、やけに遅い
$ time kubectl exec -ti $POD_NAME -- nslookup kubernetes Server: 10.32.0.10 Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local Name: kubernetes Address 1: 10.32.0.1 kubectl exec -ti $POD_NAME -- nslookup kubernetes 0.09s user 0.03s system 0% cpu 45.347 total
- Service に対して問い合わせる場合と Endpoint に問い合わせる場合で差があるようだ。 Service に問い合わせをする場合、実際の DNS 応答は Endpoint のIPアドレスから返ってくるため、そこらへんが nslookup の機嫌を損ねているのではと推測。
## dns の Service と Endpoint を確認しておく $ kubectl -n kube-system get svc kube-dns NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.32.0.10 <none> 53/UDP,53/TCP 12d $ kubectl -n kube-system describe ep kube-dns (snip) Subsets: Addresses: 10.200.0.9,10.200.2.7 NotReadyAddresses: <none> Ports: Name Port Protocol ---- ---- -------- dns 53 UDP dns-tcp 53 TCP $ kubectl exec -it $POD_NAME /bin/sh ## 結果が得られることに変わりはないが Service を介した場合だけやけに遅い / # time nslookup kubernetes 10.32.0.10 1> /dev/null real 0m 10.01s user 0m 0.00s sys 0m 0.00s / # time nslookup kubernetes 10.200.0.9 1> /dev/null real 0m 0.00s user 0m 0.00s sys 0m 0.00s / # time nslookup kubernetes 10.200.2.7 1> /dev/null real 0m 0.00s user 0m 0.00s sys 0m 0.00s
12 Smoke Test
crictl
コマンドを使用して CRI の Unix Socket 経由で実際に動いているコンテナの一覧を取得可能
root@worker-0:~# sudo crictl -r unix:///var/run/containerd/containerd.sock ps CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT POD ID c67595b3b33c2 8c811b4aec35f 32 minutes ago Running busybox 24 a58da8259ee26 a15e2d786eb05 367cdc8433a45 About an hour ago Running coredns 0 c3a0d3c772526 66f1bbe5ed396 881bd08c0b082 5 hours ago Running nginx 2 e8a5bbdb3733a
まとめ
Hard とは名前だけで優しさに溢れた素晴らしい教材でした。またやろう。
BGP Scanner を試す
BGP Scanner という MRT レンダリングツールを教えてもらったので試してみました。
BGP Scanner?
バイナリである MRT のデータをテキストで見せるレンダリングツールとしては bgpdump が有名だと思います。 よくお世話になっていますが、特定の subnet や AS 番号でフィルタしたいなんて時に grep 等で頑張らないといけないところに辛さがあります。
BGP Scanner は Isolarioプロジェクト で開発されているC言語製の MRT レンダリングツールで、既存の MRT レンダリングツール群よりイケているとのことです。 情報は少ないですが以下参考にしました。
- ITNOGでの発表資料
- APNICのブログ (及び有り難いことにそれを日本語化しているポスト)
要約すると次のような利点があるそうです。
- パフォーマンスを重視した設計で速い、しかも低メモリ消費
- フィルタリング機能が充実
- 高水準言語でラップ可能なため開発が活発化する(と期待される)
bgpdump 含め様々な MRT レンダリングツールと比較したパフォーマンス評価の結果もあり、早いことは間違いなさそうです。
インストール
環境
- MacBook Pro (13-inch, 2018, Four Thunderbolt 3 Ports)
- macOS Mojave ver10.14.3
- Memory: 16GB
インストール方法
(1) Download bgpscanner-[version].tar.gz (2) ./configure && make && make install
ITNOGでの発表資料 によると上記コマンドでおっけーって書いてあるけど、私の Mac ではそのまんまだとビルドできませんでした。
% wget https://www.isolario.it/tools/bgpscanner-1.0-1.tar.gz % tar -zxvf bgpscanner-1.0-1.tar.gz % cd bgpscanner-1.0-1 % ./configure % make /Library/Developer/CommandLineTools/usr/bin/make all-am depbase=`echo src/main.o | sed 's|[^/]*$|.deps/&|;s|\.o$||'`;\ gcc -DHAVE_CONFIG_H -I. -Isubprojects/isocore/include/ -g -O2 -MT src/main.o -MD -MP -MF $depbase.Tpo -c -o src/main.o src/main.c &&\ mv -f $depbase.Tpo $depbase.Po src/main.c:942:13: warning: implicit declaration of function 'posix_fadvise' is invalid in C99 [-Wimplicit-function-declaration] posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); ^ src/main.c:942:37: error: use of undeclared identifier 'POSIX_FADV_SEQUENTIAL' posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); ^ 1 warning and 1 error generated. make[1]: *** [src/main.o] Error 1 make: *** [all] Error 2
Man 曰く、
POSIX_FADV_SEQUENTIAL アプリケーションは指定されたデータがシーケンシャルに (大きなオフセットの前に小さなオフセットのデータを読むように) アクセスされることを期待する。 Man page of POSIX_FADVISE
はて。
すみません、コメントアウトさせてください。(動きはしますが正しさは保障できません。。)
% emacs src/main.c (snip) 941 // if (fd != STDIN_FILENO) 942 // posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); (snip)
ビルドできました。-v
も -h
もないみたいですが、とりあえず動くようです。
% make % make install % which bgpscanner /usr/local/bin/bgpscanner % bgpscanner -v bgpscanner: illegal option -- v bgpscanner: The Isolario MRT data reader utility Usage: bgpscanner [-cdlL] [-mM COMMSTRING] [-pP PATHEXPR] [-i ADDR] [-I FILE] [-a AS] [-A FILE] [-e PREFIX] [-E FILE] [-t ATTR_CODE] [-T FILE] [-o FILE] [FILE...] bgpscanner [-cdlL] [-mM COMMSTRING] [-pP PATHEXPR] [-i ADDR] [-I FILE] [-a AS] [-A FILE] [-s PREFIX] [-S FILE] [-t ATTR_CODE] [-T FILE] [-o FILE] [FILE...] bgpscanner [-cdlL] [-mM COMMSTRING] [-pP PATHEXPR] [-i ADDR] [-I FILE] [-a AS] [-A FILE] [-u PREFIX] [-U FILE] [-t ATTR_CODE] [-T FILE] [-o FILE] [FILE...] bgpscanner [-cdlL] [-mM COMMSTRING] [-pP PATHEXPR] [-i ADDR] [-I FILE] [-a AS] [-A FILE] [-r PREFIX] [-R FILE] [-t ATTR_CODE] [-T FILE] [-o FILE] [FILE...] Available options: -a <feeder AS> Print only entries coming from the given feeder AS -A <file> Print only entries coming from the feeder ASes contained in file (snip)
試してみる
おなじみ オレゴン大学Route Viewsプロジェクト から適当な MRT ファイルをいただいて覗いてみます。
見え方
bgpdump
との比較。
個人的に現時点では bgpdump
の方が見やすい印象です。
例えば bgpscanner
は NEXT_HOP と FROM IP (= feeder IP) をどっちも出してたり。フィルタリングは色々できる (後述) けど、出力の内容をオプションで制御するのは現時点では改善の余地ありだと思います。
せめてUNIX時間は直させておくれよ。
% bgpdump -M rib.20190211.1400.bz2| head -n5 2019-02-12 23:39:33 [info] logging to syslog TABLE_DUMP2|02/11/19 14:00:00|B|94.156.252.18|34224|0.0.0.0/0|34224 3356|IGP TABLE_DUMP2|02/11/19 14:00:00|B|216.221.157.162|40191|0.0.0.0/0|40191 3257|IGP TABLE_DUMP2|02/11/19 14:00:00|B|202.73.40.45|18106|0.0.0.0/0|18106|IGP TABLE_DUMP2|02/11/19 14:00:00|B|195.22.216.188|6762|1.0.0.0/24|6762 13335|IGP TABLE_DUMP2|02/11/19 14:00:00|B|94.156.252.18|34224|1.0.0.0/24|34224 13335|IGP % bgpscanner rib.20190211.1400.bz2 | head -n5 =|0.0.0.0/0|34224 3356|94.156.252.18|i|||34224:333|94.156.252.18 34224|1549527724|1 =|0.0.0.0/0|40191 3257|216.221.157.162|i||||216.221.157.162 40191|1549493033|1 =|0.0.0.0/0|18106|202.73.40.45|i||||202.73.40.45 18106|1549493010|1 =|1.0.0.0/24|6762 13335|195.22.216.188|i||13335 173.245.63.1|6762:31 6762:40|195.22.216.188 6762|1549781054|1 =|1.0.0.0/24|34224 13335|94.156.252.18|i||13335 162.158.84.1|34224:333 34224:334 34224:2040|94.156.252.18 34224|1549873473|1
それにしても bgpdump
で -M (one-line output)
すると COMMUNITIES は出力されないんでしたかね。
早さ
試しに測ると早いです。私の環境では bgpscanner
のほうが30%程度高速でした。これは有り難い場面もありそう。
% ls -lh rib.20190211.1400.bz2 | awk '{print $5}' 113M % /usr/bin/time bgpdump -Hm -t dump rib.20190211.1400.bz2 > /dev/null 134.11 real 133.18 user 0.42 sys % /usr/bin/time bgpscanner rib.20190211.1400.bz2 > /dev/null 91.89 real 82.53 user 9.17 sys
完全に余談ですが Macのbashビルトインコマンドのtimeと/usr/bin/timeは別物 - StupidDog's blog ということを初めて知りました。
フィルタリング
せっかくなのでいくつかフィルタリングをやってみます。
-a/-A: feeder AS number
当該経路の feeder の AS 、つまり AS-PATH の1番左の AS でフィルタリングができます。
-p
で AS-PATH の正規表現が使えるのにも関わらずわざわざ別のオプションにしてるのは、Isolario プロジェクトが BGP コレクターをやっているからでしょうか。分かりません。
-A
ではファイル指定が可能。便利。
## 単一の feeder AS を渡す % bgpscanner -a 701 rib.20190211.1400.bz2 | head -n5 =|1.0.0.0/24|701 2914 13335|137.39.3.55|i||13335 4.14.97.106||137.39.3.55 701|1549494007|1 =|1.0.4.0/22|701 174 4826 38803 56203|137.39.3.55|i||||137.39.3.55 701|1549493600|1 =|1.0.4.0/24|701 174 4826 38803 56203|137.39.3.55|i||||137.39.3.55 701|1549493600|1 =|1.0.5.0/24|701 174 4826 38803 56203|137.39.3.55|i||||137.39.3.55 701|1549493600|1 =|1.0.6.0/24|701 174 4826 38803 56203|137.39.3.55|i||||137.39.3.55 701|1549493600|1 ## 複数の feeder AS を渡す % bgpscanner -a 701 -a 6939 rib.20190211.1400.bz2 | head -n5 =|1.0.0.0/24|6939 13335|64.71.137.241|i||13335 172.68.188.1||64.71.137.241 6939|1549494051|1 =|1.0.0.0/24|701 2914 13335|137.39.3.55|i||13335 4.14.97.106||137.39.3.55 701|1549494007|1 =|1.0.4.0/22|6939 4826 38803 56203|64.71.137.241|i||||64.71.137.241 6939|1549494433|1 =|1.0.4.0/22|701 174 4826 38803 56203|137.39.3.55|i||||137.39.3.55 701|1549493600|1 =|1.0.4.0/24|6939 4826 38803 56203|64.71.137.241|i||||64.71.137.241 6939|1549494433|1 ## 複数の feeder AS をファイルで渡す % echo '701\n6939' > feeder_as.txt % bgpscanner -A feeder_as.txt rib.20190211.1400.bz2 | head -n5 =|1.0.0.0/24|6939 13335|64.71.137.241|i||13335 172.68.188.1||64.71.137.241 6939|1549494051|1 =|1.0.0.0/24|701 2914 13335|137.39.3.55|i||13335 4.14.97.106||137.39.3.55 701|1549494007|1 =|1.0.4.0/22|6939 4826 38803 56203|64.71.137.241|i||||64.71.137.241 6939|1549494433|1 =|1.0.4.0/22|701 174 4826 38803 56203|137.39.3.55|i||||137.39.3.55 701|1549493600|1 =|1.0.4.0/24|6939 4826 38803 56203|64.71.137.241|i||||64.71.137.241 6939|1549494433|1
-f: feeder IP
feeder の IP アドレスを sort | uniq
した結果が見られます。
% bgpscanner -f rib.20190211.1400.bz2 | head -n5 12.0.1.63 7018|1 37.139.139.0 57866|1 45.61.0.85 22652|1 64.57.28.241 11537|1 64.71.137.241 6939|1
-l/-L: with(out) AS-PATH loop
-l
では AS-PATH がループしているエントリを、 -L
ではループしていないエントリを表示。
% bgpscanner -l rib.20190211.1400.bz2 | head -n5 =|5.144.132.0/22|7018 6762 12880 12880 12880 12880 12880 12880 1288 12880 12880 12880 59441|12.0.1.63|i|||7018:5000 7018:37232|12.0.1.63 7018|1549889812|1 =|5.144.132.0/22|6762 12880 12880 12880 12880 12880 12880 1288 12880 12880 12880 59441|195.22.216.188|i|||6762:30 6762:40|195.22.216.188 6762|1549889835|1 =|5.144.132.0/22|286 6762 12880 12880 12880 12880 12880 12880 1288 12880 12880 12880 59441|134.222.87.1|i|||286:18 286:19 286:28 286:29 286:49 286:800 286:888 286:3031 286:4517 286:4990 286:28610 6762:1 6762:92 6762:14900|134.222.87.1 286|1549889812|1 =|5.144.132.0/22|3303 6762 12880 12880 12880 12880 12880 12880 1288 12880 12880 12880 59441|217.192.89.50|i|||3303:1004 3303:1006 3303:3056 3303:8199 6762:1 6762:92 6762:14900|217.192.89.50 3303|1549892534|1 =|5.144.132.0/22|57463 6762 12880 12880 12880 12880 12880 12880 1288 12880 12880 12880 59441|87.121.64.4|i|||0:5394 0:43561 0:60501 1:1089 6762:1 6762:30 6762:40 6762:14900 64700:6762 65400:1 65400:65400|87.121.64.4 57463|1549889838|1
-m/-M: COMMUNITIES
BGP COMMUNITIES によるフィルタリング。複数指定した時に OR じゃなくて AND でフィルタして欲しいケースの方が多いような気がします。。subnet などは複数指定で OR になるのは分かりますが果たして。
-M
は反対に指定された BGP COMMUNITIES を含まない経路を表示。
% bgpscanner -m 6762:31 rib.20190211.1400.bz2 | head -n3 =|1.0.0.0/24|6762 13335|195.22.216.188|i||13335 173.245.63.1|6762:31 6762:40|195.22.216.188 6762|1549781054|1 =|1.0.4.0/22|6762 174 4826 38803 56203|195.22.216.188|i|||6762:31|195.22.216.188 6762|1549494497|1 =|1.0.4.0/24|6762 174 4826 38803 56203|195.22.216.188|i|||6762:31|195.22.216.188 6762|1549494497|1 % bgpscanner -m 6762:31 -m 6762:40 rib.20190211.1400.bz2 | head -n3 =|1.0.0.0/24|6762 13335|195.22.216.188|i||13335 173.245.63.1|6762:31 6762:40|195.22.216.188 6762|1549781054|1 =|1.0.4.0/22|6762 174 4826 38803 56203|195.22.216.188|i|||6762:31|195.22.216.188 6762|1549494497|1 =|1.0.4.0/24|6762 174 4826 38803 56203|195.22.216.188|i|||6762:31|195.22.216.188 6762|1549494497|1
-p/-P: AS-PATH Expression
-p "^701"
や -p "701 15169"
ができるようです。
が、割と Segmentation fault
が出がちです。今後の改善に期待です。(PR を出そう)
% bgpscanner -p "15169$" rib.20190211.1400.bz2 | head -n3 =|8.8.4.0/24|2905 15169|196.7.106.245|i||||196.7.106.245 2905|1549799014|1 =|8.8.4.0/24|6762 15169|195.22.216.188|i|||6762:31|195.22.216.188 6762|1549893400|1 =|8.8.4.0/24|34224 15169|94.156.252.18|i|||34224:333 34224:334 34224:2090|94.156.252.18 34224|1549527763|1
-e/-E他: subnet
-e subnet
で引数で与えた subnet に Exact Match する prefix のみを表示。
% bgpscanner -e 1.4.252.0/22 rib.20190211.1400.bz2 | head -n3 =|1.4.252.0/22|37100 38040 23969|105.16.0.247|?|||NO_EXPORT|105.16.0.247 37100|1549758853|1 =|1.4.252.0/22|34224 38040 23969|94.156.252.18|?|||34224:333 34224:334 34224:2040|94.156.252.18 34224|1549709323|1 =|1.4.252.0/22|2914 38040 23969|129.250.1.71|?|||2914:410 2914:1405 2914:2406 2914:3400|129.250.1.71 2914|1549494890|1
subnet でフィルタリング可能なオプションがいくつか用意されています。大文字はファイル指定。
オプション | フィルタリング効果 |
---|---|
-e (-E) | 引数で与えられた subnet に Exact Match するエントリを表示 |
-s (-S) | 引数で与えられた subnet に Or Longer Match するエントリを表示 |
-r (-R) | 引数で与えられた subnet に Related な(?) エントリを表示 |
-u (-U) | 引数で与えられた subnet の Supernet となるエントリを表示 |
Related とは何かというところですが、やってみると -e
, -s
, -u
を合わせたような結果になります。 "ような" というのは、試した限りでは完全に各オプションの出力が合算される訳ではなさそうという自信の無さから。
codeを深くまで追ってはいないのです。
所感
フィルタリングはまだ不安定な部分もありますが、高機能でかゆいところに手が届くため様々な場面で活用できそうな有り難いツールです。気になるところがあったら PR しよう。
蛇足
コンテナ化しました。nstgt/bgpscanner
こういった類のツールをコンテナ化する利点はあまり思い付きませんが、インストールの過程で寄り道した副産物なのでいずれ何かで使える日が来ることを切に願います。
JANOG43 セルフフォローアップ
JANOG43に参加しました。2,3日目のみの参加でしたが、新しい技術・事例や各社の運用の工夫など、非常に勉強になるプログラムばかりでした。 その場では理解が追いつかないところが多々あったので、特に気になったプログラムを2件だけピックアップして自分のためにフォローアップしたいと思います。
Note
- (本来と使い方は違いますが) 引用の部分に私の所感や調べたことを記載しています。
- 理解の誤りや記述の間違いにお気づきの場合には、お手数ですがコメント等でご指摘いただけると幸いです。
- 後日アーカイブ動画が配信されたら再度見直して加筆修正する部分もあるかと思います。
Day2: LINEのネットワークをゼロから再設計した話
LINEのネットワークをゼロから再設計した話
LINE株式会社の小林さんによる発表。個人的には今回のJANOG全体を通して一番衝撃と感銘を受けたプログラムでした。
背景と方針
- それまでのLINEのネットワークの課題は、East-Westトラフィックの増加によるキャパシティ圧迫とトラディショナルな2N構成に起因する運用の複雑化にあった。
- 過去のLINEのネットワーク構成については JANOG39にて同社の三枝さんによる発表でも語られており、2015年以降から顕在化した様々な課題に対して、アーキテクチャレベルでの見直しを図ることで、スケーラビリティの高いネットワークを最小数のオープンなプロトコルのみで構築することで、根本解決を目指す。
(筆者メモ)
一つのキーワードとして、可能な限り "シンプル" に設計・構築するという思想が全体に散りばめられていたと感じました。
新しいアーキテクチャ
- Externalを除いて事実上Spineが1番上になる3階層のCLOSアーキテクチャをホワイトボックススイッチを利用して実現。
- 挙げられていた事前資料も踏まえると、ToRより上位では全て100G Linkを利用しサーバ間通信のボトルネックを排除、また各階層毎にN+1スケールが可能なため、障害に強く拡張性の高いネットワークを構築できる。
- 物理構築が完了している前提で、ZTPとAnsibleを用いた設定を行い、1,000台以上の ホワイトボックススイッチを2,3時間で構築可能。
(筆者メモ)
CLOSネットワークとはOTTを中心に積極的に採用されるDCネットワーク構成で、サーバ群が接続されるLeafとそれを束ねるSpineから成り、East-Westトラフィックに対し高いスケーラビリティを持ちます。改めて復習しましたが、以下の発表が参考になりました。
資料:JANOG38 :: ヤフーのIP CLOS ネットワーク
(筆者注記: 発表では新アーキテクチャに関する様々な技術要素が説明されていましたが、ポイントを掻い摘んで復習します。全貌は是非発表資料を御覧ください。)
L2-LESS
- 通常のCLOSネットワークではToRより上がL3、下がL2となるが、サーバから一方のToRに向けたトラフィックをもう一方のToRに振り替える際に、サーバ管理者に作業を依頼する必要があるのみならず、パケットロスが発生してしまう。 それを防ぐためサーバにBGPをしゃべらせることでToR <-> サーバ間をL3で接続。
- 実装としては (筆者補足:質疑で説明あり。LINE社のPrivate CloudはOpenStackで構築されている。) Compute Node上でFRRを動作させ、ハイパーバイザ上のRouting Tableを監視して、ハイパーバイザに接続されたVMの/32のホスト経路をToRにBGPで広報する。
- (筆者補足:質疑で説明あり) アプリケーションの要求によってはどうしてもL2が必要なケースがあり、ホワイトボックススイッチでMC-LAGを利用している箇所も存在する。
(筆者メモ)
パケロスの仕組みが分からず推測です。
BondingされたサーバのAct-SbyインターフェースとMC-LAG ToR間の接続で、例えばToR Aを切り離してメンテナンスする際に、サーバのActのLinkを落としてSbyをActive化しようとするとMC-LAGの切り替わりが完了するまでの数百msecの間サーバ宛のトラフィックは既に切れている旧Actインターフェースに流れ込もうとしロスしてしまう、といった感じなのでしょうか。
サーバから見た経路情報
- ハイパーバイザ上のFRRでBGPの経路を見ると2台のToR AS4208258575とAS4208258576のうち前者から受信した経路がalways-compare-medによりBest Pathに選ばれてる。
- 選ばれた経路のNext Hopには、IPv4の経路であるがRFC5549によりIPv6のリンクローカルアドレスが表示される。
- ただしLinux KernelではIPv6のNext Hopを登録することができないため、実際にはIPv4のリンクローカルアドレスによってルーティングされる。
(筆者メモ)
RFC5549はIPv4のNLRIにIPv6のNext Hopを設定して広報する技術。日本語だとIRS26で土屋さんが発表された資料が参考になりました。また、FRRのRFC5549の動作については以下の記事が参考になりました。
FRRはRFC5549な経路をLinuxカーネルのルーティングテーブルへどうやってインストールするのか - yunazuno.log
ホワイトボックススイッチの採用理由
- BGP UnnumberedとHostname Capability for BGPを使いたく、両実装を満たすFRRを動作させるため、LinuxベースNOSで動くCumulusを採用。
- (補足:質疑で説明あり) 機器選定では調達コストより運用コストの低下を重視。また、実際には各社の製品を比較しショートパケットの転送に優れた製品を選択した。
(筆者メモ)
BGP Unnumbered
RFC5549の拡張で、インターフェースにIPアドレスを設定することなくBGPピアを上げることができる技術。Cumulus社の動画は分かりやすかったです。
Hostname Capability for BGP
Open MessageにFQDNを入れて交換することでBGPピアを張るだけで対向のHostnameが分かるようにする技術。
データセンター間ネットワーク
- DC内のみならずDC間接続においてもL2オーバーレイは作らないポリシーのもと、シンプルなSR-MPLSを採用。
- 元々の帯域に余裕をもたせているため、帯域制御も不要。
(筆者メモ)
残念ながらSegment Routingを十分に理解していないため中途半端に書かずに別記事か何かでまとめようと思います。
今後の展望
- 様々なビジネスニーズに迅速に対応するため、一つのアンダーレイネットワーク上に複数のオーバーレイテナントを構築すること検討中。
- 実現手法としてSRv6に注目している。
Day3: オンプレミスKubernetesのネットワーク
オンプレミスKubernetesのネットワーク
株式会社Jストリームの城田さんによる発表。私はKubernetes勉強中の身で各CNI実装についてよく知らなかったため、エッセンスやメリット/デメリットがまとまった発表は大変有難かったです。
Kubernetesをオンプレミスで動かす理由
Node間ネットワーク
- Kubernetes本体ではネットワーキング機能は提供しておらず、Pod間通信、Serviceと他NodeのPod間通信にはNode同士を接続するネットワークが必要となる。
- 抽象化されたCNI (Container Network Interface) に従ってネットワークを実装することで初めてClusterとして機能する。
- 以降代表的なCNI実装を紹介。
Flannel
- Nodeに渡りユニキャストなVXLANを構築する。VXLAN以外にもIP-IPやIPSec(実験的)にも対応している。最近更新頻度が下がってきたのが懸念。
- 各Nodeにflanneldが立ち上がり、VXLANインターフェースである
flannel.1
とBridgeインターフェースであるcni.0
を作製。Node内のPod間通信はcni.0
を経由し、Node間通信ではPodからNodeへ転送後、NodeのRIBに従って到達したflannel.1
でVXLANのカプセリング化を行って宛先のNodeへ転送される。
(筆者メモ)
以下の記事も参考になりました。Node間通信でNodeのRIB, ARPテーブル, FDBに書かれている情報はflanneldが書き込みをしているようです。
Kubernetes Network Deep Dive (NodePort, ClusterIP, Flannel) - Qiita
Project Calico
- Bridgeやオーバーレイを作らずにBGPを用いたL3で処理を完結する。Network Policyに対応している。
- Calicoのconfig情報を保有するetcdを除く複数のプロセスが一つのPodにまとめられ、その中の一つであるBIRDが(デフォルトでは)フルメッシュでBGP接続を行う。
- Bridgeは作製されないのでNode内通信はNodeでルーティングされ、Node間通信はBGPで学習した経路情報によって行われる。
(筆者メモ)
FlannelとCalicoのさらに詳しい動作は JAPAN CONTAINER DAYS V18.12 でも発表があり、こちらも分かりやすいです。
資料:コンテナネットワーキング(CNI)最前線
アーカイブ動画:[1BL] コンテナネットワーキング(CNI)最前線 - YouTube
kube-router
- FlannelとCalicoをあわせたような動作で、Node内通信はBridge経由、Node間通信はBGPによるルーティングを提供する。
- BGPの設定はNodeのAnnotationsに記載する。
- CalicoはRoute Reflectorを設ける時に専用のコンテナを起動する必要があるが、kube-routerではCluster内からRoute Reflectorを選択することができる。
質疑の時間にコメントがあり、日本国内で自前でKubernetesを建てている人に聞くとFlannelとCalicoがほとんどでkube-routerを使っているケースは稀とのこと。
Node外ネットワーク
- クラウド上でマネージドなKubernetesを使う場合には各社が提供するロードバランサを使い、LoadBalancerタイプのServiceを利用することでCluster外からPodへの通信を通すとができるが、オンプレではそれがない。NodePortタイプやIngressタイプのServiceを利用するにしても、なんらかこれらを束ねるロードバランサーが必要になる。
- 以降、Cluster外部からアクセスを可能とするロードバランサを紹介。
MetalLB
- Kubernetes Node上で動くロードバランサ。L2モード/BGPモードがある。
- L2モードではService毎に代表Nodeを選びProxy ARPすることで当該Service宛の通信を全て一つのNodeで受け内部でロードバランスされる。トラフィックが一つのNodeに集中することが難点。
- BGPモードではCluster外部のルータとBGPを張りECMPを行う。外部のルータと、Cluster内のServiceで二重にロードバランスするのが難点。設定によってServiceでのロードバランスをしないようにすることができるが、外部のルータはCluster内のPodの配置を認識できないため、トラフィックに偏りが生じてしまう。
F5 Container Connector
- F5社のBIG-IPという製品をKubernetesから設定することができる。NodePortモード/Clusterモードがある。
- NodePortモードではMetalLBのBGPモードと同じくBIG-IPとServiceとで二重にロードバランスする。
- ClusterモードではBIG-IPをKubernetes内に組み込むことで、FlannelまたはCalicoを利用してPodに向けたロードバランスを行う。
- 商用環境でBIG-IPを利用している実績がある。
自社の構成
- FlannelとCalicoはBIG-IPとの連携に課題があると感じ、kube-router + F5 Container Connector (Clusterモード) で構築を行っている。
(筆者メモ)
以下の発表ではCNI含めたKubernetesネットワークの"すべて"について語られており、圧巻です。
発表資料: Kubernetes ネットワーキングのすべて
アーカイブ動画:[1B1] Kubernetes ネットワーキングのすべて - YouTube
おまけ
上記の発表に比べると大変お恥ずかしいのですが、私自身もLight Lighting Talk大会で発表させていただきました。 speakerdeck.com
普段の運用で感じている課題を、最近流行りのクラウドネイティブ関連技術で何とかできないかと思い勢いでやってみた、という内容です。
発表の意図としては、やったことそのものを伝えたいというよりネットワークとクラウドネイティブな技術との関わりがますます活発化するであろうことを、JANOGerの皆さんと共有できればと思った次第です。今回のJANOGでは他にもそれ系の発表が多々あり、私自身改めてその重要性を感じました。
試作機の実装は趣味で進行中、課題は山積み前途多難ですがぼちぼちやってます。
おわり
他にも勉強になったプログラムは沢山ありましたが、取り急ぎ勢いで書けるところまでを書きました。 後ほどアーカイブ動画で復習しようと思います。発表者の皆さん、スタッフの皆さんありがとうございました。
EVE-NGスタートアップのメモ
以前はGNS3を使っていたけれど社外の方に教えてもらってこっちのほうが使いやすそうだったのでお試ししました。公式ドキュメント(とビデオ)様様ですが、気になったところをメモしておきます。
はじめに
モチベーション
多数のネットワークベンダーに対応し、かつポータブルな仮想ネットワーク環境が欲しい。
環境
- macOS Mojave ver10.14.2
- Memory: 16GB
- VMware Fusion ver11.0.2
- EVE-NG Community Edition 2.0.3-92
やったこと
EVE-NGのインストール
公式ページから辿れる動画のまま。
動画ではVMware Workstationを使っていますがほぼ同じ流れをVMware Fusionで踏襲しました。
パラメータ等
- 割当メモリ: 8GB
- "この仮想マシンでハイパーバイザアプリケーションを有効にする" にチェック
- プロセッサ: 4個
- ネットワークアダプタ: NAT
- Static IPを付与。私の環境では
172.16.216.10/24
- Static IPを付与。私の環境では
このあたりも参考にさせていただきました。
各種OSイメージのインストール
今回はインターネットで入手可能なOSイメージを入れてみました。
Cisco CSR1000V
感謝しつつCisco社のダウンロードサイトから入手。私のCiscoアカウントでダウンロードできた最新のバージョンは 3.15.0S
でした。
入手したISOイメージをEVE-NGのVMに転送し、公式のインストールガイド に倣ってコマンドを打つだけ。
# Host macOS $ scp ~/Downloads/csr1000v-universalk9.03.15.00.S.155-2.S-std.iso root@172.16.216.10:/root/csr1000v $ ssh root@172.16.216.10 # EVE-NG VM $ cd csr1000v $ /opt/qemu/bin/qemu-img create -f qcow2 virtioa.qcow2 8G $ /opt/qemu-2.2.0/bin/qemu-system-x86_64 -nographic -drive file=virtioa.qcow2,if=virtio,bus=0,unit=0,cache=none -machine type=pc-1.0,accel=kvm -serial mon:stdio -nographic -nodefconfig -nodefaults -rtc base=utc -cdrom /tmp/csr1000v-universalk9.03.15.00.S.155-2.S-std.iso -boot order=dc -m 3072 # Press any key to continue では Ctl+c を押下 (1回目,2回目共に) # インストールガイドにもあるように、2回目のGNU GRUBでSerial consoleにカーソルをあわせた後、Enterを押さないように注意 $ mkdir /opt/unetlab/addons/qemu/csr1000v-universalk9.03.15.00.S.155-2.S-std/ $ mv virtioa.qcow2 /opt/unetlab/addons/qemu/csr1000v-universalk9.03.15.00.S.155-2.S-std/ $ /opt/unetlab/wrappers/unl_wrapper -a fixpermissions
実は最初に昔ダウンロードしていたバージョン 3.12.0S
を使おうと試みたのですが、2回目の Press any key to continue
が表示されずにイメージが起動し、インストールできませんでした。その後、バージョン 3.15.0S
で試した際に、既に存在するHDD virtioa.qcow2
を削除しなかったためにインストールプロセスで 3.12.0S
が起動してしまいました。インストールプロセスを再実行する際には virtioa.qcow2
を作り直すのが無難かも。
Juniper vSRX
vMXを試したかったところですがダウンロードできなかったので、それでも有り難くJuniper社のダウンロードサイトからvSRXの 17.3R2
をいただきました。
公式インストールガイドも有り難い限りです。
# Host macOS $ scp ~/Downloads/media-vsrx-vmdisk-17.3R2.10.qcow2 root@172.16.216.10:/root/vsrx $ ssh root@172.16.216.10 # EVE-NG VM $ cd vsrx $ mkdir /opt/unetlab/addons/qemu/vsrxng-17.3R2.10/ $ cp media-vsrx-vmdisk-17.3R2.10.qcow2 /opt/unetlab/addons/qemu/vsrxng-17.3R2.10/ $ cd /opt/unetlab/addons/qemu/vsrxng-17.3R2.10/ $ mv media-vsrx-vmdisk-17.3R2.10.qcow2 virtioa.qcow2 $ ls virtioa.qcow2 $ /opt/unetlab/wrappers/unl_wrapper -a fixpermissions
Arista vEOS
Arista社のダウンロードサイトからバージョン 4.21.1.1F
をいただく。感謝とISOも忘れずに。
公式のインストールガイド に従うのみ。
# Host macOS $ scp ~/Downloads/ $ scp Aboot-veos-serial-8.0.0.iso vEOS-lab-4.21.1.1F.vmdk root@172.16.216.10:/root/veos $ ssh root@172.16.216.10 # EVE-NG VM $ cd veos $ /opt/qemu/bin/qemu-img convert -f vmdk -O qcow2 vEOS-lab-4.21.1.1F.vmdk hda.qcow2 $ mkdir -p /opt/unetlab/addons/qemu/veos-4.21.1.1F $ mv hda.qcow2 /opt/unetlab/addons/qemu/veos-4.21.1.1F/ $ mv Aboot-veos-serial-8.0.0.iso /opt/unetlab/addons/qemu/veos-4.21.1.1F/cdrom.iso $ /opt/unetlab/wrappers/unl_wrapper -a fixpermissions
三位一体で起動&疎通確認
EVE-NGのWebコンソール (私の環境では http://172.16.216.10/#/login
) にアクセスしログイン。適当なlabを開始します。
先程インストールした3種のOSを選択しクソみたいな簡単なトポロジで相互接続確認。各ルータを起動するとMACが鬼の形相でファンをぶん回します。
適当にconfigしてping通っておしまい。いろいろ遊べそうです。
csr1000v#show ip interface brief Interface IP-Address OK? Method Status Protocol GigabitEthernet1 10.0.1.1 YES manual up up GigabitEthernet2 10.0.2.1 YES manual up up GigabitEthernet3 unassigned YES NVRAM administratively down down GigabitEthernet4 unassigned YES NVRAM administratively down down csr1000v#ping 10.0.1.2 Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 10.0.1.2, timeout is 2 seconds: .!!!! Success rate is 80 percent (4/5), round-trip min/avg/max = 1/2/3 ms csr1000v#ping 10.0.2.2 Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 10.0.2.2, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 8/13/30 ms veos#ping 10.0.3.1 repeat 1 PING 10.0.3.1 (10.0.3.1) 72(100) bytes of data. 80 bytes from 10.0.3.1: icmp_seq=1 ttl=64 time=24.0 ms --- 10.0.3.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 24.002/24.002/24.002/0.000 ms
ちなみに1: ターミナルでtelnetできない問題
labのトポロジ図でルータのアイコンをクリックするとターミナルが起動してtelnet接続できる訳ですが、macOS 10.13 High Sierra移行telnetコマンドは消滅してしまったため、 /usr/bin/telnet
が無くそのままでは接続できませんでした。対処法は こちらを参考 。
要はbrew等でtelnetのバイナリを入手し、リカバリーモードで起動後にSIP (System Integrity Protection) を無効化した状態で /usr/bin
配下にtelnetのバイナリを移せばOK。
SIPの解除はこちらを参考に。自己責任で、戻し忘れの無きように。
# brewでtelnetをインストール $ brew install telnet $ which telnet /usr/local/bin/telnet $ csrutil status System Integrity Protection status: enabled. # MACをリカバリーモードで起動してターミナルを開く -bash-3.2# csrutil disable # 再起動で通常起動してターミナルを起動 $ csrutil status System Integrity Protection status: disabled. $ sudo cp /usr/local/bin/telnet /usr/bin $ /usr/bin/telnet telnet> ^C # SIPを戻すために再度リカバリーモードで起動 -bash-3.2# csrutil enable
あと通常ではターミナルを起動すると telnet://
が紐付けられたMACのデフォルトのターミナルアプリが開きます。私はiTerm2を使っているので、この記事の Change the default ssh:// handler in iTerm2 itself
の項を参考に telnet://
の紐付けをiTerm2に変えました。
ちなみに2: 環境のポータビリティ
EVE-NGのlabは簡単にimport/exportすることが可能なようです。自分の手元で作ったlab環境を、同じOSイメージがインストールされた別のEVE-NG環境に持ち込むことが簡単にできるのは助かります。チーム内での検証の同期や研修等でも使えそう。
ちなみに3: パフォーマンスチューニング
はまだやってません。Nodeに対してCPU Limitを設定できるチェックボックスがありますが、有効にしてみてもあんまり変わってないような。
余談ですが各ルータへの割当メモリをケチって削ってみたところちゃんと動きませんでした。そりゃそうだ。
- Cisco CSR1000V
- メモリをデフォルトの3072MBから2048MBに変更 → インターフェースが認識されない
- Juniper vSRX
- メモリをデフォルトの4096MBから2048MBに変更 → そもそも起動しない
おわりに
OSイメージさえあれば簡単にインストール可能だし、(あまり触れていませんが)Webコンソールも扱いやすく、個人的にはGNS3より使いやすいと思います。スペック盛り盛りのサーバを用意すれば検証が捗りそうです。