- MetalLBとは?
- MetalLBを使った際のクラウド環境との違い
- MetalLBの構成
- MetalLBの仕組み(ARP/NDP/BGP)
- MetalLBの課題
- 【ハンズオン】MetalLBのL2 modeを使ってみる
- どうやってMetalLBは通信しているのか?
- 【ハンズオン】MetalLBのBGP modeを使ってみる
- おまけ
- 参考情報
この記事は MetalLB v0.9.3
のものです
MetalLBとは?
kubernetesでサービスを外部に公開するときにtype: LoadBalancer
を使います。しかしこれはAWSやGCP、Azureといったパブリッククラウドのみで現在は扱えるものでオンプレミスの環境では利用できません。
MetalLBはオンプレミスでもtype: LoadBalancer
を扱えるようにする、kubernetes上で動作するOSSのアプリケーションです。
2020/04/15現在MetalLBのプロジェクトはβ版となっており、商用での利用事例がすくないのが現状です。日本ではサイボウズで利用事例があります。
MetalLBを使った際のクラウド環境との違い
クラウドでtype: LoadBalancer
を使った場合は勝手にIPアドレスが払い出されますが、MetalLBの場合はあらかじめユーザが指定したIPアドレスプール(例:192.168.0.0/24など)からの払い出しになります。
そのため、MetalLBを利用したNWはLAN(WANではない)のIP払い出しが利用シーンとして想定されており、クラウドのロードバランサーのようにグローバルIPが割り当てられることはありません。
そのため、外部と接続させたい場合はMetalLBで払い出したプライベートIPアドレスをグローバルIPを変換する仕組みが必要になります。(NATなど)
MetalLBの構成
MetalLBを動かすとspeakerとcontrollerと呼ばれるpodが動きます。
speakerとは?
speakerはすべてのkubernetesノードで1台づつ動作するpodです。(DaemonSetによってコントロールされている)
speakerの役割は先ほど紹介したARP・NDP・BGP
を使いサービスの通信到達性を担保します。
controllerとは?
controllerはDeploymentによって管理されるpodで、MetalLBで利用するIPアドレスの管理や払い出しをkubernetesクラスタをまたがって行う機能を持っています。
MetalLBの仕組み(ARP/NDP/BGP)
podが通常もてるIPアドレスはClusterIP
のためk8sクラスタ外のサーバは認識できません。
そこでMetalLBでは以下のいずれかのプロトコルを利用して到達可能なIPアドレスを外部へと通知しています。
- ARP(L2)
- NDP(IPv6向け/L2)
- BGP(L3)
MetalLBではこれらのプロトコルを利用してL2 mode
とBGP mode
の2種類の設定が可能です。
L2 modeについて(ARP/NDP)
L2 modeの場合は各ノードがサービスを提供する責任を持ちます。ネットワーク的な観点では以下の図のようにサービスの数だけNICにIPアドレスが紐づいているように見えます。
ただし
ifconfig
などのコマンドを叩いてみても実際にはIPアドレスは存在しません。(IPアドレスはUPしていない)
ARPの場合では接続元からのARP要求に対して、接続先ノードのMetalLB speakerが192.168.0.10はkuberneteNodeのMACアドレスだよとARPリプライを返すことでARP解決を行います。
L2 modeでは払い出されたIPに対する全ての通信は1台のノードに集約されます。そしてそこからkube-proxy
によって設定されたiptables
のルールにしたがってトラフィックが各podに分散されます。
そのためL2 modeはロードバランサーではありません。どちらかというとIPをもったノードがダウンしたときに、別のノードに振り返るフェールオーバーをする冗長化機能と言えます。
実際に分散を行なっていいるのはiptables
となります。
iptables
はルール数が増えてくると性能が劣化します。これはiptables
はルールが増えれば増えただけすべてをチェックしようとする機構によるものです(計算量が O(n))。これを回避するためにipvs
と呼ばれる別のエンジンが存在します。こちらはルールが増えた場合でも計算量が増えません(計算量がO(1))MetalLBで
ipvs
を使う場合はこちらを参考にしてください。
ただしipvs
は以下の通り動作するよきっとというレベルです。
MetalLB might work with IPVS mode in kube-proxy, in Kubernetes 1.13 or later. However, it is not explicitly tested yet, so it’s at your own risk. See our tracking bug for details.
L2 modeでのspeakerは払い出されたIPをハンドリングするリーダーが用意されます。以下の図では合計で3つのIPが2つのノードに払い出されており、それぞれのノード上のspeakerがそれぞれのリーダーとなっています。
L2 modeの冗長性
L2 modeでは何らかの理由でリーダーノード(対象のIPが存在するノード)がダウンした場合に自動でフェールオーバーが行われます。割り当てられたIPアドレスの解放は10秒後かかり、その後別のノードがリーダーとなります。
ただしノードのダウン自体はkubernetes自体が検知するものとなります。node-monitor-grace-period
で設定された時間内(デフォルトでは40秒)がまずかかりますし、さらに到達不能になったノード上で稼働しているpodのタイムアウトはデフォルトで5分かかります。
そのため合計で切り替わりに「5分超」かかることになりますのでご注意ください。
L2 mode利用時の制限
気をつける必要がある2つの制限があります。
- 特定のノードがボトルネックとなる
- フェールオーバーの遅さ
先ほど示した通りL2 modeの場合は特定のIPへの通信が全て1つのノードに集中します。つまり1つのノードの受信帯域の上限が通信の限界だということです。(ノードのNICが1Gbpsならそれが限界)
また現在の実装ではMetalLBはフェールオーバー時にGARPを使って通信元にIPアドレスとMACアドレスの紐付けが変更になったことを通知します。
ほとんどのシステムではGARPを正しく扱えキャッシュを更新されフェールオーバーは数秒で終わりますが、キャッシュの更新が遅れるバグのある実装となっているシステムも存在しています。
※Windows/Mac/Linuxは大丈夫だが古いOSでは問題になることがある
ワークアラウンドとしては計画的にフェールオーバーする際は、古いリーダシップノードをしばらく稼働し続け古いOSのクライアントがキャッシュの更新する時間を稼ぐことです。
※計画外のフェールオーバーでは古いOSのクライアントはキャッシュを更新するまで接続できません。
BGP modeについて
BGP modeではそれぞれのノードがネットワーク上のルーターとBGPピアリングを行います。BGPプロトコルをつかいMetalLBが192.168.0.10はkubernetes Nodeにいるよ!という経路広告をルータに行います。
BGPルーターでマルチパスの設定を行うことでルータを利用したロードバランシングを実現することができます。
BGP modeにおけるロードバランシングの動き
負荷分散(ロードバランシング)の正確な動作はルータによって異なりますが、基本的にはパケットハッシュによって通信ごと(TCP/UDPセッションごと)に特定のノードに通信をします。
パケットハッシュとはパケットのフィールドの一部を「シード」として使用して接続先を決定する方法のことです。パケットのフィールドが一緒であれば同じ接続先が常に選択されます。
BGPルータにてECMPを使うことによって負荷分散がなされます。
この図ように、BGPルータが宛先のIPに対してネクストホップをそれぞれのNodeにすることで負荷分散を実現しています。ただしデフォルトの設定のままだと「BGPルーターで負荷分散」のあとに「iptablesで負荷分散」となってしまい無駄が大きいです。
そのためexternalTrafficPolicy
をlocal
に変更することでノードをまたぐiptablesによる負荷分散を無効化することができます。
BGP modeの制限
BGPを選択すると標準のルーターを使用できる利点がありますが、接続先のノードやpodが停止した際にすべての接続が切断されるデメリットがあります。
BGPルータはハッシュをつかってステートレスな負荷分散を行います。しかしkubernetes上のNodeを削除したり、Nodeのアップデートなどのたびに再起動を行なったりするとハッシュが更新されます。すると既存の接続がすべて一度途切れてしまうことになります。
これの対応するにはいくつかの方法があります。
- BGPルータで安定したECMPハッシュアルゴリズム(resilient ECMPまたはresilientLAG)を使う。利用すると接続への影響を軽減することができる
- podの展開を特定のノードに固定する
- ノードを停止する場合は夜間に行う
- 起動するkubernetesの
Servie
とPod
を別のIPで起動しDNSを用いて向き先を変更します。その後、流入がなくなったノードを停止する Service
をIngressコントローラ
の後ろに配置します。ルータ→Ingress→Serviceとすることで、Ingressさえ変更がなければ通信断が発生しません
MetalLBの課題
送信元IPについて
externalTrafficPolicy
がCluster
となっているデフォルト設定の場合はiptablesによって別のノードに通信がNATされる場合があります。
この時に問題になるのが接続元のIPアドレスがわからなくなってしまう点です。そこが大事なアプリを作る際は注意してください。
対応策としてはexternalTrafficPolicy: Local
を入れることで接続元IPを保持することができます。ただしこの設定を使うとL2 modeの場合は1つのノードで全てのトラフィックを処理しなければならないため性能的に問題が発生する場合があります。(BGP modeの場合は事前に負荷分散されていますので問題ありません)
そのためL2 modeであればIngressを利用しNginx Ingress Controllerにてx-Fowarded-fowヘッダ
に接続元IPアドレスを格納する構成をとることで対応できます。
【ハンズオン】MetalLBのL2 modeを使ってみる
今回はCNIとしてcalicoを使っている環境でL2 modeで動作させてみます。
使ったコードはここにおいています。
k8s-sample/metallb at master · lirlia/k8s-sample · GitHub
環境
名前 | バージョン |
---|---|
OS | CentOS Linux release 7.6.1810 (Core) |
kubelet | v1.18.1 |
kubeadm | v1.18.1 |
docker | v1.13.1 |
calico | v3.8.8-1 |
metallb | v0.9.3 |
kubernetesの導入はこちらを参考にしてください
事前準備
- kubernetes(kubeadm/kubelet/kubectl/docker)がインストールされている
- calicoが導入されている
MetalLBのインストール
以下のコマンドを実行します。
mkdir metallb ; cd metallb kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml # On first install only kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
きちんと起動しているか確認しましょう。Running
であればOKです。
[root@master metallb]# kubectl get pods -n metallb-system NAME READY STATUS RESTARTS AGE controller-57f648cb96-vqm4v 1/1 Running 0 83s speaker-qgspg 1/1 Running 0 84s [root@master metallb]# [root@master metallb]# kubectl get daemonsets -n metallb-system NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE speaker 1 1 1 1 1 beta.kubernetes.io/os=linux 114s [root@master metallb]# [root@master metallb]# kubectl get deploy -n metallb-system NAME READY UP-TO-DATE AVAILABLE AGE controller 1/1 1 1 2m16s
つづいて設定を入れていきます。以下の設定をmetallb-config.yaml
として保存してください。
ただし- 192.168.10.10-192.168.10.30
のところはお使いのマシンが所属するIPアドレス空間に合わせて変更してください。
apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.10.10-192.168.10.30
そうしたら適用します。
[root@master metallb]# kubectl apply -f metallb-config.yaml configmap/config created
Nginxの起動
では実際にMetalLBを利用してNginxを起動してみましょう。
こちらのyamlをnginx.yaml
という名前で保存してください。
apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-metallb name: nginx-metallb spec: selector: matchLabels: app: nginx-metallb template: metadata: labels: app: nginx-metallb spec: containers: - image: nginx name: nginx-metallb ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-metallb spec: type: LoadBalancer selector: app: nginx-metallb ports: - name: http port: 80 targetPort: 80
そうしたら適用します。
[root@master metallb]# kubectl apply -f nginx.yaml deployment.apps/nginx-metallb created service/nginx-metallb created
このようにLoadBalancer
が表示されていれば成功です。今回は192.168.10.10
というIPで起動していますね。
[root@master metallb]# kubectl get svc nginx-metallb NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-metallb LoadBalancer 10.107.163.236 192.168.10.10 80:31550/TCP 4m59s
curl
を叩いてアクセスしてみましょう。きちんとアクセスできますね。
c[root@master metallb]# curl 192.168.10.10 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
ちなみに上で説明したIPアドレスはUPしていない件を確認しましょう。以下のコマンドを実行しても何も表示されません。
[root@master metallb]# ip a | grep 192.168.10.10
じゃあどうやって通信しているんだ?という話ですね。
どうやってMetalLBは通信しているのか?
先ほどのNginxへのアクセスを例に考えます。
まず最初に接続元クライアントは192.168.10.10
のMACアドレスを解決する必要があります。ということでARPリクエストを行います。これに対して対象のNode上で動作するspeaker podがARPリプライを返却します。
speaker podのログを見てみるとARPリクエストの受付をしたログが出ていますね。
[root@master metallb]# kubectl logs -n metallb-system speaker-npr4w {"caller":"arp.go:102","interface":"eth1","ip":"192.168.10.10","msg":"got ARP request for service IP, sending response","responseMAC":"xx:xx:xx:xx:xx:xx","senderIP":"192.168.10.1","senderMAC":"xx:xx:xx:xx:xx:xx","ts":"2020-04-15T09:35:18.661085983Z"}
続いてTCP通信です。対象のNodeへのアクセスはMACアドレスとIPアドレスがわかれば到達できます。しかし今回はIPアドレスがNode上ではUPしていません。一体どうすれば良いのでしょうか?
ここで使われるのがiptables
です。宛先IPアドレス192.168.10.10
が来た時に実行するルールが書かれています。それがこちら。
-A KUBE-SERVICES -d 192.168.10.10/32 -p tcp -m comment --comment "default/nginx-metallb:http loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-EUUHFJHBJM2VLLOI
このルールは192.168.10.10/32のポート80に通信が来たら「KUBE-FW-EUUHFJHBJM2VLLOI」を実行してくれというものです。呼び出された「KUBE-FW-EUUHFJHBJM2VLLOI」はNginxのServiceへ通信をNATしてくれというルールになっていますので、これらを使って通信を実現しているのです。
そのためiptables
のルールでIPアドレスが使われるだけなので、ifconfig
を実行してもMetalLBによって払い出されたIPアドレスは存在しないのです。
【ハンズオン】MetalLBのBGP modeを使ってみる
こういう構成をつくって緑色の矢印で通信をしたいと思います。
※ルータにはVyos
を使います
使ったコードはここにおいています。
k8s-sample/metallb at master · lirlia/k8s-sample · GitHub
仮想マシンの準備
まずは`Vagrantfileを作ります。
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.define "master" do |machine| config.vm.box = "centos/7" config.vm.define "master" machine.vm.hostname = "master" machine.vm.network "private_network", ip: "192.168.10.2" machine.vm.network "private_network", ip: "192.168.100.2", virtualbox__intnet: "intnet1" machine.vm.provider "virtualbox" do |vb| vb.gui = false vb.memory = "4096" vb.cpus = 2 end machine.vm.provision "shell", inline: <<-SHELL yum -y install git vim wget net-tools tcpdump echo "192.168.10.2 master" >> /etc/hosts SHELL end config.vm.define "router" do |router| config.vm.box = "higebu/vyos" router.vm.hostname = "router" router.vm.network "private_network", ip: "192.168.10.3" router.vm.network "private_network", ip: "192.168.100.3", virtualbox__intnet: "intnet1" router.vm.provider "virtualbox" do |vb| vb.gui = false vb.memory = "128" vb.cpus = 1 end end end
そして起動しログインします。
vagrant plugin install vagrant-vyos vagrant up vagrant ssh router
※kubernetes側はできている前提なのでここでは割愛します
VyosでのBGPの設定
今回は以下の設定で行います。
名前 | 設定内容 |
---|---|
BGPルータのIP | 192.168.100.3 |
BGPルータのAS番号 | 64001 |
BGPルータのID | 1.1.1.1 |
MetalLBのAS番号 | 64500 |
vagrant@router:~$ configure [edit] vagrant@router# edit protocols bgp 64001 vagrant@router# set parameters router-id 1.1.1.1 vagrant@router# set neighbor 192.168.100.2 remote-as 64500 vagrant@router# set neighbor 192.168.100.2 soft-reconfiguration inbound vagrant@router# set network 192.168.10.0/24 vagrant@router# commit vagrant@router# save Saving configuration to '/config/config.boot'... Done vagrant@router# exit vagrant@router:~$ vagrant@router:~$ show ip bgp BGP table version is 0, local router ID is 1.1.1.1 Status codes: s suppressed, d damped, h history, * valid, > best, i - internal, r RIB-failure, S Stale, R Removed Origin codes: i - IGP, e - EGP, ? - incomplete Network Next Hop Metric LocPrf Weight Path *> 192.168.10.0 0.0.0.0 1 32768 i Total number of prefixes 1
k8sノードでのルーティング設定
k8sノードとクライアントPCは192.168.10.0/24
で接続されているので、クライアントからpodへの通信(curl 192.168.100.10
の通信)がクライアント→BGPルータ→k8s node→クライアント
と行き帰りで経路が変わり通信ができなくなります。
ですのでk8s nodeにて戻りのルーティングをいじってあげましょう。
[root@master metallb]# route add -host 192.168.10.1 gw 192.168.100.3
以下のようになっていればOKです。
[root@master metallb]# netstat -nvr |grep 192.168.10.1 192.168.10.1 192.168.100.3 255.255.255.255 UGH 0 0 0 eth2
クライアントPCのルーティングを追加する
クライアントPCは192.168.100.0/24
のネットワークを知りません。その場合は初めから登録されているデフォルトゲートウェイに通信を行います。
しかしデフォルトゲートウェイにいってもVirtualBoxの中で設定されているネットワークは理解できないので、明示的に静的なルーティングを追加してあげる必要があります。(今回はMacOSでやっているのでMacのコマンドになりますが、Windowsでも同じようなコマンドを叩く必要があります)
$ sudo route add -net 192.168.100.0/24 192.168.10.3 Password: add net 192.168.100.0: gateway 192.168.10.3
MetalLBへの設定適用
※L2 modeの手順をみてMetalLBの適用(Config除く)まで済ませてください。
metallb-config-bgp.yaml
という名前でファイルを作ります。BGP用のコンフィグファイルです。
apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | peers: - peer-address: 192.168.100.3 peer-asn: 64001 my-asn: 64500 address-pools: - name: default protocol: bgp addresses: - 192.168.100.0/24 avoid-buggy-ips: true
適用します。
kubectl apply -f metallb-config-bgp.yaml
そうしたらnginxのpodをデプロイしましょう。今回はわかりやすくするためloadBalancerIP: 192.168.100.10
でMetalLBによって払い出されるIPを固定しています。
apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-metallb name: nginx-metallb spec: selector: matchLabels: app: nginx-metallb template: metadata: labels: app: nginx-metallb spec: containers: - image: nginx name: nginx-metallb ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: nginx-metallb spec: loadBalancerIP: 192.168.100.10 type: LoadBalancer selector: app: nginx-metallb ports: - name: http port: 80 targetPort: 80
そうしたら確認してみましょう!ありますね!
[root@master metallb]# kubectl get svc nginx-metallb NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-metallb LoadBalancer 10.104.4.192 192.168.100.10 80:32519/TCP 53m
そしてVyosもみてみます。
show ip bgp
を叩いてB>
で始まるB>* 192.168.100.10/32 [20/0] via 192.168.100.2, eth2, 00:15:58
が表示されていれば成功です。
vagrant@router:~$ show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - ISIS, B - BGP, > - selected route, * - FIB route S>* 0.0.0.0/0 [210/0] via 10.0.2.2, eth0 C>* 10.0.2.0/24 is directly connected, eth0 C>* 127.0.0.0/8 is directly connected, lo C>* 192.168.10.0/24 is directly connected, eth1 C>* 192.168.100.0/24 is directly connected, eth2 B>* 192.168.100.10/32 [20/0] via 192.168.100.2, eth2, 00:15:58 vagrant@router:~$
そうしたらクライアントからhttp://192.168.100.10にアクセスしてみましょう。Nginxの画面が見れるはずです。
おまけ(たくさん起動してみる)
いっぱい作ってkubectl apply
してみました。
[root@master metallb]# ls nginx-* nginx-1.yaml nginx-4.yaml nginx-7.yaml nginx-2.yaml nginx-5.yaml nginx-8.yaml nginx-3.yaml nginx-6.yaml nginx-9.yaml
たくさん起動していますね。
[root@master metallb]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d4h mysql-test ClusterIP 10.106.114.120 <none> 3306/TCP 20h nginx NodePort 10.101.154.51 <none> 80:30092/TCP 2d10h nginx-metallb LoadBalancer 10.104.4.192 192.168.100.10 80:32519/TCP 22m nginx-metallb-1 LoadBalancer 10.109.125.158 192.168.100.0 80:32157/TCP 21s nginx-metallb-2 LoadBalancer 10.102.97.71 192.168.100.1 80:30021/TCP 20s nginx-metallb-3 LoadBalancer 10.107.81.122 192.168.100.2 80:32220/TCP 18s nginx-metallb-4 LoadBalancer 10.100.54.217 192.168.100.3 80:30868/TCP 17s nginx-metallb-5 LoadBalancer 10.106.146.116 192.168.100.4 80:32493/TCP 15s nginx-metallb-6 LoadBalancer 10.107.75.82 192.168.100.5 80:32303/TCP 13s nginx-metallb-7 LoadBalancer 10.111.185.191 192.168.100.6 80:32647/TCP 10s nginx-metallb-8 LoadBalancer 10.109.162.157 192.168.100.7 80:32210/TCP 7s nginx-metallb-9 LoadBalancer 10.96.93.108 192.168.100.8 80:31430/TCP 5s sample-chart-from-myrepo NodePort 10.104.137.39 <none> 80:30088/TCP 19h
Vyosもこの通り
vagrant@router:~$ show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - ISIS, B - BGP, > - selected route, * - FIB route S>* 0.0.0.0/0 [210/0] via 10.0.2.2, eth0 C>* 10.0.2.0/24 is directly connected, eth0 C>* 127.0.0.0/8 is directly connected, lo C>* 192.168.10.0/24 is directly connected, eth1 C>* 192.168.100.0/24 is directly connected, eth2 B>* 192.168.100.0/32 [20/0] via 192.168.100.2, eth2, 00:00:41 B>* 192.168.100.1/32 [20/0] via 192.168.100.2, eth2, 00:00:38 B>* 192.168.100.2/32 [20/0] via 192.168.100.2 eth2, 00:00:32 B>* 192.168.100.3/32 [20/0] via 192.168.100.2, eth2, 00:00:27 B>* 192.168.100.4/32 [20/0] via 192.168.100.2, eth2, 00:00:20 B>* 192.168.100.5/32 [20/0] via 192.168.100.2, eth2, 00:00:14 B>* 192.168.100.6/32 [20/0] via 192.168.100.2, eth2, 00:00:08 B>* 192.168.100.10/32 [20/0] via 192.168.100.2, eth2, 00:20:57
以上です。
おまけ
ArgoCDでみたMetalLBの全体像
どんなkubernetesのリソースがあるか一目でわかります。みやすい!(拡大してください)
参考情報
Kubernetesの関連書籍