Red Hatでコンサルタントをしている織です。こちらの記事では、upstreamのKubernetesとMultusを使ってKVM環境上でMultusを検証しました。本記事では、IPI方式でAWS上に構築したOCP (OpenShift Container Platform) v4.3を使ってMultusを動かしてみます。
いきなりIPIと書きましたが、OpenShift v4では、環境構築の方法が大きく2種類あり、IPIはそのうちのひとつです。
IPI(Installer Provisioned Infrastructure)方式のインストール - ネットワーク、ロードバランサ、DNSレコード、仮想マシンインスタンス等、OpenShiftのインストールに必要なインフラのコンポーネントをインストーラが自動的に作成します。workerノードのオートスケールができるようになったりします。インフラをAPIで制御できる環境(例えばIaaS)上にOpenShiftクラスターを構築するときはこちらが便利です。
UPI(User Provisioned Infrastructure)方式のインストール - ネットワーク、ロードバランサ、DNSレコード、仮想マシンインスタンス等、OpenShiftのインストールに必要なインフラのコンポーネントは、あらかじめユーザーが自身で用意しておく必要があります。インストーラはその上にOpenShiftをインストールします。インフラをAPIで制御できない環境(例えばオンプレのベアメタル環境)や、IPIが作る構成だと要件に合わない場合、workerノードに(Red Hat CoreOSではなく)RHELを使いたい場合等はUPIを使います。
IPIでAWS上にOpenShiftクラスターを構築すると、
- 作成されるEC2インスタンスはNIC(ENI)を1個しか持たない
- ノードは異なるAZにまたがって分散配置
という構成になります。AWS上のOpenShiftでMultusを使う場合は、おそらくUPIで環境を作ることになるかと思うのですが、今回はIPIインストールで構築した環境に対して、少しカスタマイズしてMultusを使えるようにしました。特に深い理由はありません。
実際に行ったカスタマイズは以下です。
- 各ノードに対してNICを追加し、追加NICで通信するためのVPCサブネットを作成する
- 追加NICを使って異なるAZにいるworkerノード上のPodと通信できるようにstatic routeを設定する
OpenShift v4では、クラスターに対する設定は全てOperator経由で行う作りになっています。例えばネットワーク周りの設定はCluster Network Operator(CNO)が司ります。Multusの設定をする際、前の記事ではNetworkAttachmentDefinitionのCustom Resourceを定義するmanifestを書いてkubectl create
しましたが、OpenShiftではCNO経由でNetworkAttachmentDefinitionを作るよう、CNOに対して指示します。この辺りが本記事で一番言いたかったことなのですが、いろいろ余計なことを書いたので長くなってしまいました。ご容赦ください...
OpenShiftクラスターの構築
AWS上にOpenShift v4.3.0[1]の環境を構築します。今回構築に使ったIPIという方式では、VPC、ELB、Route53のゾーン登録等、必要なAWSリソースは全て自動で作成し、その上でRHCOS(Red Hat CoreOS)を使ったEC2インスタンスを起動してOpenShiftクラスターを構成する、という一連の作業をコマンド一撃でやってくれます。
$ openshift-install create cluster
AWS上でIPI方式でOpenShiftをインストールする手順の詳細は下記をご参照ください。
また、本検証ではやりませんでしたが、もしUPIを使ってインストールする場合は下記をご参照ください。
Multusのために環境をカスタマイズ
IPIインストールでAWSにOpenShiftクラスターを構築すると、下図のような構成になります(EC2インスタンスとVPCのプライベートネットワークのみ記載しています)。
MultusでもうひとつNICを使うので、これをカスタマイズして、最終的に下図のようにしたいです。
そのために、
- 各AZにサブネットを追加し
- 追加サブネットごとに接続するENIを作成し
- 作成したENIを各workerノードにアタッチする
というカスタマイズをします。
サブネットの追加
Multusで追加接続するためのサブネットを各リージョンに作成します。IPv4のアドレスブロックは、VPCのアドレスブロックから適当に切り出します。本環境では
リージョン | サブネット名 | アドレスブロック |
---|---|---|
ap-northeast-1a | ocp43-multus-1a | 10.0.201.0/24 |
ap-northeast-1c | ocp43-multus-1c | 10.0.202.0/24 |
ap-northeast-1d | ocp43-multus-1d | 10.0.203.0/24 |
を作成しました。AWS CLIでやるなら
$ aws ec2 create-subnet --availability-zone ${az} --cidr-block ${cidr} --vpc-id ${vpc_id} $ aws ec2 create-tags --resources ${multus_subnet_id} --tags Key=Name,Value=${multus_subnet_name}
みたいな感じです。
ENIの作成
各リージョンのworkerノードを追加したサブネットに接続するため、追加サブネットごとにENIを作成します。 今回は下記のIPアドレスを使うことにします。
サブネット | プライマリのIPv4アドレス | セカンダリのIPv4アドレス |
---|---|---|
ocp43-multus-1a | 10.0.201.11 | 10.0.201.21 〜 10.0.201.29 |
ocp43-multus-1c | 10.0.202.11 | 10.0.202.21 〜 10.0.202.29 |
ocp43-multus-1d | 10.0.203.11 | 10.0.203.21 〜 10.0.203.29 |
ipvlanでPodに付与するIPアドレスを、あらかじめENIのセカンダリプライベートIPv4アドレスとして列挙しておきます。
$ aws ec2 create-network-interface --subnet-id ${multus_subnet_id} \ --description "for multus, subnet: ${multu_subnet_name}, az:${az}" \ --private-ip-addresses Primary=true,PrivateIpAddress=${prefix}.11 \ Primary=false,PrivateIpAddress=${prefix}.21 \ Primary=false,PrivateIpAddress=${prefix}.22 \ Primary=false,PrivateIpAddress=${prefix}.23 \ Primary=false,PrivateIpAddress=${prefix}.24 \ Primary=false,PrivateIpAddress=${prefix}.25 \ Primary=false,PrivateIpAddress=${prefix}.26 \ Primary=false,PrivateIpAddress=${prefix}.27 \ Primary=false,PrivateIpAddress=${prefix}.28 \ Primary=false,PrivateIpAddress=${prefix}.29 $ aws ec2 create-tags --resources ${interface_id} --tags Key=Name,Value=eni-${multus_subnet_name}
ENIをworkerノードへアタッチ
作成したENIを、各workerノードにアタッチします。
$ aws ec2 attach-network-interface --device-index 1 --instance-id ${instance_id} --network-interface-id ${interface_id}
workerノードの確認
ENIをworkerノードにアタッチすると、workerノードのRHCOS(カーネル、ユーザーランドのコードはRHEL8と全く同じ[4])のカーネルが動的に追加インターフェースをens4
として認識してくれます。workerノードでそのことを確認してみます。
OpenShift v4.xでmaster/workerノードに入ってOSコマンドを実行する方法は2つあります。
- sshログインする - インストーラ(
openshift-installer
コマンド)実行ノードのssh-agentに鍵が登録されていれば、それをRHCOSのユーザーcore
のauthorized_keysに登録してくれます。 oc debug node
を使用する -oc get node
でノード名を調べ、oc debug node/NODENAME
を実行します。例:oc debug node/ip-10-0-133-117.ap-northeast-1.compute.internal
どちらの方法で確認しても構いませんが、1.を実施するには、IPIインストールで作成したVPCサブネット上に外部からアクセスするための踏み台サーバが必要になります。
どちらかの方法でworkerノードに入って、インスタンスのインターフェースを確認します。
# ip addr show dev ens3 2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000 link/ether 06:41:25:41:65:46 brd ff:ff:ff:ff:ff:ff inet 10.0.133.117/20 brd 10.0.143.255 scope global dynamic noprefixroute ens3 valid_lft 3255sec preferred_lft 3255sec inet6 fe80::b6d2:f22:84d1:e350/64 scope link noprefixroute valid_lft forever preferred_lft forever # ip addr show dev ens4 31: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000 link/ether 06:e5:2e:bf:58:3a brd ff:ff:ff:ff:ff:ff inet 10.0.201.11/24 brd 10.0.201.255 scope global dynamic noprefixroute ens4 valid_lft 2308sec preferred_lft 2308sec inet6 fe80::2af9:7855:12d6:f03d/64 scope link noprefixroute valid_lft forever preferred_lft forever
ens3
が元々インスタンスが持っていたENIで、ens4
が先ほど追加したENIです。
ルーティングテーブルを見てみます。
# ip route show default via 10.0.128.1 dev ens3 proto dhcp metric 100 default via 10.0.201.1 dev ens4 proto dhcp metric 101 10.0.128.0/20 dev ens3 proto kernel scope link src 10.0.140.137 metric 100 10.0.201.0/24 dev ens4 proto kernel scope link src 10.0.201.11 metric 101 10.128.0.0/14 dev tun0 scope link 172.30.0.0/16 dev tun0
ens4にもDHCPからもらったデフォルトゲートウェイが設定されていて気持ち悪いので、ens4ではDHCPでIPアドレスだけをつける(DHCPによるデフォルトゲートウェイとDNS設定をしない)ようにします。
# nmcli c s NAME UUID TYPE DEVICE Wired connection 1 e2fe62b6-e972-3d43-8023-320f467fec57 ethernet ens3 Wired connection 2 76577e5a-43d2-3f3d-b2d9-a93a067cf42c ethernet ens4
ens4のConnection nameであるWired connection 2
に対して、ipv4.ignore-auto-dns
とipv4.ignore-auto-routes
を有効化します。
# nmcli c m 'Wired connection 2' ipv4.ignore-auto-dns yes ipv4.ignore-auto-routes yes # sudo nmcli c d 'Wired connection 2' Connection 'Wired connection 2' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/2) # sudo nmcli c u 'Wired connection 2' Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/3)
これで、余分なデフォルトゲートウェイの設定が消えました。
# ip r default via 10.0.128.1 dev ens3 proto dhcp metric 100 10.0.128.0/20 dev ens3 proto kernel scope link src 10.0.140.137 metric 100 10.0.201.0/24 dev ens4 proto kernel scope link src 10.0.201.11 metric 101 10.128.0.0/14 dev tun0 scope link 172.30.0.0/16 dev tun0
各workerノードに対してこの操作を実施しておきます。
Multusのセットアップ
次にMultusの設定を行います。基本的にこちらの手順に従います。
まずおさらいですが、upstreamのKubernetes+Multusを使った前の記事では、
--- apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: ipvlan-conf spec: config: '{ "cniVersion": "0.3.1", "plugins": [ { "type": "ipvlan", "capabilities": { "ips": true }, "master": "eth1", "ipam": { "type": "static" } }, { "capabilities": { "mac": true }, "type": "tuning" } ] }'
のようなmanifestを使ってNewtorkAttachmentDefinitionのCustom Resourceを作成しました。
一方OpenShift v4.xでは、CNIの設定は全てCNO (Cluster Network Operator)[5]が司る仕組みになっています。したがいまして、CNOに対してNetworkAttachmentDefinitionを作成してもらうよう指示する必要があります。具体的には、
$ oc edit networks.operator.openshift.io cluster
を実行し、spec.additionalNetworks
の中に、NetworkAttachmentDefinitionのCustom Resourceで設定する内容を記載します。本記事の検証では
spec: additionalNetworks: - name: network-ipvlan type: Raw rawCNIConfig: '{ "cniVersion": "0.3.1", "type": "ipvlan", "mode": "l2", "capabilities": {"ips": true}, "master": "ens4", "ipam": { "type": "static", "routes": [ {"dst": "10.0.202.0/24", "gw": "10.0.201.1"}, {"dst": "10.0.203.0/24", "gw": "10.0.201.1"} ] } }'
のような内容を記載しました[6]。
この内容は、ap-northeast-1a
リージョン(追加サブネットのIPv4アドレスブロックが10.0.201.0/24
)のworkerノードにPodをデプロイしたい場合の設定です。static routeの設定は、デプロイ先のworkerノードがいるリージョンごとに変える必要がありますのでご注意ください。
上記oc edit networks.operator.openshift.io cluster
で編集した内容は、oc patch
を使って
oc patch networks.operator.openshift.io cluster --type merge -p '{"spec": {"additionalNetworks": [{"name": "network-ipvlan", "type": "Raw", "rawCNIConfig": "{\"cniVersion\": \"0.3.1\", \"type\": \"ipvlan\", \"mode\": \"l2\", \"capabilities\": { \"ips\": true }, \"master\": \"ens4\", \"mode\": \"l2\", \"capabilities\": { \"ips\": true }, \"ipam\": {\"type\": \"static\", \"routes\": [{ \"dst\": \"10.0.202.0/24\", \"gw\": \"10.0.201.1\" }, { \"dst\": \"10.0.203.0/24\", \"gw\": \"10.0.201.1\" }]}}"}]}}'
もしくは
oc patch networks.operator.openshift.io cluster --type json -p ' [{ "op": "add", "path": "/spec/additionalNetworks", "value": [] },{ "op": "add", "path": "/spec/additionalNetworks/0", "value": { name: "network-ipvlan", type: "Raw", rawCNIConfig: "{ \"cniVersion\": \"0.3.1\", \"type\": \"ipvlan\", \"mode\": \"l2\", \"capabilities\": { \"ips\": true }, \"master\": \"ens4\", \"ipam\": { \"type\": \"static\", \"routes\": [ { \"dst\": \"10.0.202.0/24\", \"gw\": \"10.0.201.1\" }, { \"dst\": \"10.0.203.0/24\", \"gw\": \"10.0.201.1\" } ] } }" } } ]'
のように反映させることもできます。
CNOの設定変更が終わったら、oc get net-attach-def
を実行し、作成したNetworkAttachmentDefinitionの内容を確認します。
$ oc get net-attach-def network-ipvlan -o yaml apiVersion: k8s.cni.cncf.io/v1 kind: NetworkAttachmentDefinition metadata: creationTimestamp: "2020-01-24T14:57:36Z" generation: 1 name: network-ipvlan <snip> spec: config: '{"cniVersion": "0.3.1", "type": "ipvlan", "mode": "l2", "capabilities": { "ips": true }, "master": "ens4", "mode": "l2", "capabilities": { "ips": true }, "ipam": {"type": "static", "routes": [{ "dst": "10.0.202.0/24", "gw": "10.0.201.1" }, { "dst": "10.0.203.0/24", "gw": "10.0.201.1" }]}}'
Podのデプロイ
1つ目のPodのデプロイ
本検証では、下記のような設定のPodをデプロイします。
- ipvlanでホストのens4経由で追加サブネットに接続する
- Podのデフォルトゲートウェイは、デフォルトCNIプラグインであるOpenShift-SDN側(ens3で接続している、IPIインストーラが作成したサブネット)にする
- ipvlanで接続した追加サブネット経由でPod間通信ができるよう、static routeを設定する
まず、ap-northeast-1aのAZにいるworkerノードにデプロイすることを考えます。準備として、各ノードがどのAZにいるかを確認します。
$ aws ec2 describe-instances --filter Name=vpc-id,Values=${vpc_id} | jq -r '.Reservations[] | .Instances[] | [.PrivateDnsName, (.Tags[] | select(.Key == "Name").Value)] | @text' ["ip-10-0-158-197.ap-northeast-1.compute.internal","ocp43-9ld5l-worker-ap-northeast-1c-cphxt"] ["ip-10-0-149-79.ap-northeast-1.compute.internal","ocp43-9ld5l-master-1"] ["ip-10-0-143-118.ap-northeast-1.compute.internal","ocp43-9ld5l-master-0"] ["ip-10-0-133-117.ap-northeast-1.compute.internal","ocp43-9ld5l-worker-ap-northeast-1a-vcnxn"] ["ip-10-0-167-226.ap-northeast-1.compute.internal","ocp43-9ld5l-master-2"] ["ip-10-0-162-156.ap-northeast-1.compute.internal","ocp43-9ld5l-worker-ap-northeast-1d-x62nl"] ["ip-10-0-46-214.ap-northeast-1.compute.internal","bastion-ocp43"]
ap-northeast-1aにいるworkerノードはip-10-0-133-117.ap-northeast-1.compute.internal
であることがわかりました。
次にそのノードのラベルを確認します。
$ oc get node ip-10-0-133-117.ap-northeast-1.compute.internal --show-labels NAME STATUS ROLES AGE VERSION LABELS ip-10-0-133-117.ap-northeast-1.compute.internal Ready worker 3d12h v1.16.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=m4.large,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=ap-northeast-1,failure-domain.beta.kubernetes.io/zone=ap-northeast-1a,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-10-0-133-117,kubernetes.io/os=linux,node-role.kubernetes.io/worker=,node.openshift.io/os_id=rhcos
ノードのセレクタとして、ラベルkubernetes.io/hostname=ip-10-0-133-117
を使うことにします。
このラベルをnodeSelector
で指定し、ipvlanの追加インターフェースに対して10.0.201.27/24
を付与するPodipvlsan-1
のmanifestは次のようになります。
--- apiVersion: v1 kind: Pod metadata: name: ipvlan-1 labels: app: multus-ipvlan annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "network-ipvlan", "ips": [ "10.0.201.27/24" ] } ]' spec: containers: - name: centos-tools image: docker.io/centos/tools:latest command: - /sbin/init securityContext: privileged: true nodeSelector: kubernetes.io/hostname: ip-10-0-133-117
このファイルをipvlan-ocp-27-1.yaml
に保存して、下記コマンドでPodをデプロイします。
$ oc create -f ipvlan-ocp-27-1.yaml
Podが起動したら、期待する設定が反映されているか確認します。
$ oc rsh ipvlan-1 ip -d 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 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 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 3: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue state UP group default link/ether 0a:58:0a:80:02:1d brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0 veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 inet 10.128.2.29/23 brd 10.128.3.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::58e8:eeff:fe0a:45bf/64 scope link valid_lft forever preferred_lft forever 4: net1@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UNKNOWN group default link/ether 06:e5:2e:bf:58:3a brd ff:ff:ff:ff:ff:ff promiscuity 0 ipvlan mode l2 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 inet 10.0.201.27/24 brd 10.0.201.255 scope global net1 valid_lft forever preferred_lft forever inet6 fe80::6e5:2e00:1bf:583a/64 scope link valid_lft forever preferred_lft forever
Multusによるipvlanの追加インターフェースがnet1
として、指定したIPアドレスが付与した状態で存在することが確認できました。次にPodのルーティングテーブルを確認します。
$ oc rsh ipvlan-1 ip route show default via 10.128.2.1 dev eth0 10.0.201.0/24 dev net1 proto kernel scope link src 10.0.201.27 10.0.202.0/24 via 10.0.201.1 dev net1 10.0.203.0/24 via 10.0.201.1 dev net1 10.128.0.0/14 dev eth0 10.128.2.0/23 dev eth0 proto kernel scope link src 10.128.2.29 172.30.0.0/16 via 10.128.2.1 dev eth0 224.0.0.0/4 dev eth0
無事、NetworkAttachmentDefinitionで仕込んでおいた10.0.202.0/24
および10.0.203.0/24
宛てのstatic routeが設定されています。
このstatic routeにより、ipvlanの追加インターフェースを使った通信をリージョン(workerノード)をまたいで行うことができるようになります。
2つ目のPodのデプロイ
次は、ap-northeast-1cのAZにいるworkerノードにPodをデプロイします。
このAZの追加サブネットは10.0.202.0/24
であるため、他リージョンの追加サブネットと通信するためには
宛先 | nexthop |
---|---|
10.0.201.0/24 | 10.0.202.1 |
10.0.203.0/24 | 10.0.202.1 |
のようなstatic routeが必要になります。これを反映するように、NetworkAttachmentDefinitionnetwork-ipvlan
に書いたstatic route設定の内容を次のように変更します。
spec: additionalNetworks: - name: network-ipvlan type: Raw rawCNIConfig: '{ "cniVersion": "0.3.1", "type": "ipvlan", "mode": "l2", "capabilities": {"ips": true}, "master": "ens4", "ipam": { "type": "static", "routes": [ {"dst": "10.0.201.0/24", "gw": "10.0.202.1"}, {"dst": "10.0.203.0/24", "gw": "10.0.202.1"} ] } }'
前述のoc edit networks.operator.openshift.io cluster
もしくは下記のoc patch
で変更を実施します。
$ oc patch networks.operator.openshift.io cluster --type merge -p '{"spec": {"additionalNetworks": [{"name": "network-ipvlan", "type": "Raw", "rawCNIConfig": "{\"cniVersion\": \"0.3.1\", \"type\": \"ipvlan\", \"mode\": \"l2\", \"capabilities\": { \"ips\": true }, \"master\": \"ens4\", \"mode\": \"l2\", \"capabilities\": { \"ips\": true }, \"ipam\": {\"type\": \"static\", \"routes\": [{ \"dst\": \"10.0.201.0/24\", \"gw\": \"10.0.202.1\" }, { \"dst\": \"10.0.203.0/24\", \"gw\": \"10.0.202.1\" }]}}"}]}}'
network-ipvlan
が期待する内容に変わったことが確認できたら、次のようなmanifestでPodを起動します。
--- apiVersion: v1 kind: Pod metadata: name: ipvlan-2 labels: app: multus-ipvlan annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "network-ipvlan", "ips": [ "10.0.202.27/24" ] } ]' spec: containers: - name: centos-tools image: docker.io/centos/tools:latest command: - /sbin/init securityContext: privileged: true nodeSelector: kubernetes.io/hostname: ip-10-0-158-197
nodeSelectorとしてデプロイ先のAZにいるworkerノードについたラベルを指定し、annotationでPodのIPアドレスを指定しています。 Podのデプロイができたら、Podに入ってIPアドレスを確認します。
$ oc rsh ipvlan-2 ip -d 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 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 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 3: eth0@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue state UP group default link/ether 0a:58:0a:83:00:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0 veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 inet 10.131.0.14/23 brd 10.131.1.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::9010:bfff:fefb:aad7/64 scope link valid_lft forever preferred_lft forever 4: net1@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UNKNOWN group default link/ether 0a:cd:aa:50:a0:b8 brd ff:ff:ff:ff:ff:ff promiscuity 0 ipvlan mode l2 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 inet 10.0.202.27/24 brd 10.0.202.255 scope global net1 valid_lft forever preferred_lft forever inet6 fe80::acd:aa00:150:a0b8/64 scope link valid_lft forever preferred_lft forever
ルーティングテーブルも確認します。
$ oc rsh ipvlan-2 ip route show default via 10.131.0.1 dev eth0 10.0.201.0/24 via 10.0.202.1 dev net1 10.0.202.0/24 dev net1 proto kernel scope link src 10.0.202.27 10.0.203.0/24 via 10.0.202.1 dev net1 10.128.0.0/14 dev eth0 10.131.0.0/23 dev eth0 proto kernel scope link src 10.131.0.14 172.30.0.0/16 via 10.131.0.1 dev eth0 224.0.0.0/4 dev eth0
このAZ用のstatic route設定が無事反映されていることが確認できました。さらにPodipvlan-1
と疎通できることも確認しておきます。
$ oc rsh ipvlan-2 ping -c 3 10.0.201.27 PING 10.0.201.27 (10.0.201.27) 56(84) bytes of data. 64 bytes from 10.0.201.27: icmp_seq=1 ttl=64 time=2.35 ms 64 bytes from 10.0.201.27: icmp_seq=2 ttl=64 time=2.34 ms 64 bytes from 10.0.201.27: icmp_seq=3 ttl=64 time=2.43 ms --- 10.0.201.27 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2002ms rtt min/avg/max/mdev = 2.345/2.378/2.432/0.038 ms
コンテナホストでのtcpdumpの実行
実際に追加サブネットを通って通信できているかを、ホスト上でtcpdumpして確認したくなるかもしれません。コンテナホストのRHCOSはコンテナ実行に特化しているため、tcpdump等のツールは入っていないのですが、support-tools
というコンテナを使うとtcpdumpできるようになります。
まず、1つ目のPodをデプロイしたノードに入ります。
$ oc debug node/ip-10-0-133-117.ap-northeast-1.compute.internal Starting pod/ip-10-0-133-117ap-northeast-1computeinternal-debug ... To use host binaries, run `chroot /host` Pod IP: 10.0.133.117 If you don't see a command prompt, try pressing enter. sh-4.2# chroot /host sh-4.4#
次にtoolbox
というコマンドを実行します[7]。このコマンドを実行すると、support-tools
コンテナを取得し、privilegedかつホストネットワークを掴んだ状態でデプロイし、その中に入ります。これで、ホストのインターフェースに対してtcpdumpが使えるようになります[8]。
sh-4.4# toolbox Trying to pull registry.redhat.io/rhel8/support-tools... Getting image source signatures Copying blob 1457434f891b done Copying blob cb3c77f9bdd8 done Copying blob fd8daf2668d1 done Copying config 517597590f done Writing manifest to image destination Storing signatures 517597590ff4236b0e5e3efce75d88b2b238c19a58903f59a018fc4a40cd6cce Spawning a container 'toolbox-' with image 'registry.redhat.io/rhel8/support-tools' Detected RUN label in the container image. Using that as the default... command: podman run -it --name toolbox- --privileged --ipc=host --net=host --pid=host -e HOST=/host -e NAME=toolbox- -e IMAGE=registry.redhat.io/rhel8/support-tools:latest -v /run:/run -v /var/log:/var/log -v /etc/machine-id:/etc/machine-id -v /etc/localtime:/etc/localtime -v /:/host registry.redhat.io/rhel8/support-tools:latest [root@ip-10-0-133-117 /]#
裏でipvlan-2
からipvlan-1
に対してpingを打ち続けながら、このsupport-tools
コンテナの中でens4
に対してtcpdumpを実行してみます。
[root@ip-10-0-133-117 /]# tcpdump -enni ens4 icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on ens4, link-type EN10MB (Ethernet), capture size 262144 bytes 15:59:21.983883 0a:cd:aa:50:a0:b8 > 0a:65:d7:a5:30:62, ethertype IPv4 (0x0800), length 98: 10.0.202.27 > 10.0.201.27: ICMP echo request, id 16088, seq 4, length 64 15:59:21.986235 0a:65:d7:a5:30:62 > 0a:cd:aa:50:a0:b8, ethertype IPv4 (0x0800), length 98: 10.0.201.27 > 10.0.202.27: ICMP echo reply, id 16088, seq 4, length 64 15:59:22.985390 0a:cd:aa:50:a0:b8 > 0a:65:d7:a5:30:62, ethertype IPv4 (0x0800), length 98: 10.0.202.27 > 10.0.201.27: ICMP echo request, id 16088, seq 5, length 64 15:59:22.987738 0a:65:d7:a5:30:62 > 0a:cd:aa:50:a0:b8, ethertype IPv4 (0x0800), length 98: 10.0.201.27 > 10.0.202.27: ICMP echo reply, id 16088, seq 5, length 64 15:59:23.985892 0a:cd:aa:50:a0:b8 > 0a:65:d7:a5:30:62, ethertype IPv4 (0x0800), length 98: 10.0.202.27 > 10.0.201.27: ICMP echo request, id 16088, seq 6, length 64 15:59:23.988242 0a:65:d7:a5:30:62 > 0a:cd:aa:50:a0:b8, ethertype IPv4 (0x0800), length 98: 10.0.201.27 > 10.0.202.27: ICMP echo reply, id 16088, seq 6, length 64
ipvlan-1
、ipvlan-2
のPod間で、ホストのens4を使ってICMP echo request/replyのやり取りをしていることが確認できました。
問題判別のヒント
NetworkAttachmentDefinitionが期待どおり設定されない場合
NetworkAttachmentDefinitionが期待どおり設定されない場合はCNO Podのログを確認してください。
まずCNOのPod名を確認します。
$ oc -n openshift-network-operator get pod NAME READY STATUS RESTARTS AGE network-operator-5c7c7dc988-vlw6v 1/1 Running 0 25h
このPodに対して、oc logs
を実行します。
$ oc -n openshift-network-operator logs -f network-operator-5c7c7dc988-vlw6v
Podの起動に失敗したり、Pod manifestの設定がうまくPodに反映されない場合
Podの起動に失敗したりPod manifestの設定がうまくPodに反映されない場合は、まずoc describe pod
の出力を確認してください。それで当たりがつかない場合は、デプロイ先のworkerノードのログを確認します。crioサービスのログにエラーメッセージが出ていないかを最初に見るのがよいと思います。
具体的には、workerノードにsshもしくはoc debug node
でログインし、journalctlで確認します[9]。
# journalctl -u crio -f
最後に
IPIインストールで構築したOpenShiftクラスタに対してサブネットとNICを追加し、Multusを使って2つのネットワークに接続するPodをデプロイし、追加サブネット経由でPod間通信ができることを確認しました。
ネットワーク構成上、PodをデプロイするAZごとに異なるstatic routeを入れる必要があり、そのためデプロイ先のAZごとにNetworkAttachmentDefinitionのCustom Resourceを作り直しました。これはとても煩雑で、できればやりたくありません。対応策はいくつか考えられます。
対応策1: コンテナホスト側でstatic routeを設定する
Podにはstatic routeを書かず、コンテナホスト上でstatic routeを設定しても、同じようにパケットを流すことができます。
具体的には、コンテナホストにログインし、追加サブネット側のインターフェースens4
に対して
# ip route add to 10.0.202.0/24 via 10.0.201.1
もしくは
# nmcli con mod ens4 +ipv4.routes "10.0.202.0/24 10.0.201.1"
のようにstatic routeを設定します (前者は再起動すると設定が消えますが、後者は再起動してもstatic route設定が反映されます)。
対応策2: Pod のmanifestに書く
例えばstaticにIPアドレスを振るIPAMの設定をPod manifestのannotationに埋め込んだように、Podに注入するstatic routeもPod manifestに埋め込むことができれば、デプロイ先のAZが変わるたびにNetworkAttachmentDefinitionのstatic route設定を変更する必要がなくなります。また、対応策1の場合は、コンテナホストで一律にstatic routeが決まってしまいますので、Podごとにstatic routeの設定を切り替えたい場合はこちらの方法が使えるとうれしそうです。この方法の実現方法についてはupstreamで議論中で[10]、OpenShift v4.3ではまだ使えません。今後のMultusの進化に期待しましょう。
謝辞
今回も、Red HatでNFVPEをしている林さんにいろいろ教えていただきました。ありがとうございました!
-
つい最近出たばかりの新しいバージョンです!↩
-
RHCOS(Red Hat CoreOS)のOSイメージは、RHEL8と同じrpmパッケージを使いつつ(つまり入っているバイナリはRHEL8と同じ)、rpm-ostreeを使ってOSイメージを作成しています。rpm-ostreeはProject Atomic由来のツールで、ユーザーデータ(/etcや/var)への影響なしにOSイメージをアトミックにロールバックできる仕組みを提供します。RHCOSは、RHELのrpmパッケージ、rpm-ostree等のProject Atomic由来のツール、CoreOS社のContainer Linuxで使っていたIgnition等の仕組みを悪魔合体させたディストリビューションと言えます。RHCOS自体は(今のところ)、OCP(OpenShiftの製品版)のノードを動かすOSとしての使い方しかサポートされません(つまり汎用OSとして使うユースケースはサポートされません)。一方、Red Hat CoreOSのupstreamであるFedora CoreOSは、コンテナホストに特化した汎用OSとして使われることを想定して開発が進められています。↩
-
前の記事では、NetworkAttachmentDefinitionの内容として、
plugins
配列の中にtype: ipvlan
の設定とtype: tuning
の設定を書いていましたが、OpenShift v4.3のMultusだとこの書き方がCNOにはじかれてしまうので、少し古い書き方をしています。↩ -
toolboxはシェルスクリプトで、中で (1)
podman login registry.redhat.io
(2)podman pull registry.redhat.io/rhel8/support-tools
(3)podman container runlabel RUN registry.redhat.io/rhel8/support-tools
みたいな感じのことをやっています。↩ -
Red Hat製品で問題判別時の情報収集に使うsosreportコマンドも、
support-tools
コンテナ経由で実行できるようになります。↩ -
余談ですが、
export SYSTEMD_LESS=FRXMK
を実行しておくと、journalctlコマンドのページャが横スクロールせずにターミナルの右端で折り返してくれるので、ちょっとだけ幸せになれます。↩ -
対応策2を実現する実装として、cni-route-overrideが提案されています。Pod manifestのannotationに書いたstatic routeの設定を、
cni-args
から差し込みます。↩