目的
KubernetesやOpenShiftクラスターが2つあり、クラスタをまたがってPodどうしが通信したい場合、通常はそれぞれのクラスターでRouterやIngressを使ってServiceを公開し、それを利用してアクセスをすることになります。Submarinerを使うと、クラスタ間でIPsec VPNが張られ、あたかもクラスター内でServiceやPodにアクセスしているかのように、リモートのクラスター内のServiceやPodにアクセスが可能になります。
今回はRed Hat OpenShift on IBM Cloud(ROKS)を東京リージョンと大阪リージョンそれぞれに建てて、Submarinerを検証してみます。
手順
ROKSクラスターのオーダー(東京・大阪)
東京と大阪でROKSクラスターをオーダーします。このときの注意点として、次の3点があります。
- VPCサブネットが重複しないこと
- Podサブネットが重複しないこと
- Serviceサブネットが重複しないこと
VPCサブネットは設定時に東京・大阪のVPCで重複しないようにしてください。
PodサブネットとServiceサブネットは、IBM Cloudコンソールからオーダーするとデフォルト値が適用されて重複する可能性があります。CLIからオーダーするとレンジを明示できますので、少なくとも片方のクラスターはCLIでレンジを指定して重複しないようにしてください。
東京にすでにクラスターが存在している場合、次のコマンドでPodサブネットとServiceサブネットを確認することができます。
$ oc get networks.config.openshift.io cluster -o yaml
...
spec:
clusterNetwork:
- cidr: 172.17.128.0/18 # Podサブネット
hostPrefix: 23
networkType: Calico
serviceNetwork:
- 172.21.0.0/16 # Serviceサブネット
...
例えば、大阪のクラスターは次のように作成します。
$ ibmcloud oc cluster create vpc-gen2 --flavor bx2.16x64 --name ${大阪クラスタ名} --subnet-id ******** --vpc-id ******** --zone jp-osa-1 --cos-instance ******** --pod-subnet 172.17.192.0/18 --service-subnet 172.22.0.0/16 --version 4.8
.11_openshift --workers 2
$ ibmcloud oc zone add vpc-gen2 -c ${大阪クラスタ名} --subnet-id ******** --worker-pool default --zone jp-osa-2
$ ibmcloud oc zone add vpc-gen2 -c ${大阪クラスタ名} --subnet-id ******** --worker-pool default --zone jp-osa-3
$ ibmcloud oc worker-pool resize -c ${大阪クラスタ名} --size-per-zone 1 --worker-pool default
ROKSをCLIでオーダーする場合、各ゾーン最低2台を指定しないとエラーになります。今回は各ゾーン1台3ゾーンにしたかったので、上記のように一旦23=6台構成にした後で1*3=3台構成に縮小しています。
私の環境では次のようになります。
|東京|大阪
---|---|---
VPCサブネット|10.244.2.0/24|10.248.0.0/24
Podサブネット|172.17.128.0/18|172.17.196.0/18
Serviceサブネット|172.21.0.0/16|172.22.0.0/16
東京・大阪間Transit Gatewayの有効化
東京・大阪間のVPCがIBM Cloudのプライベートネットワークで通信できるよう、Transit Gatewayを有効化します。
作業用kubeconfigの作成(作業PC)
ここから先は東京・大阪でそれぞれ作業をしますので、自分がどちらのクラスタで作業をしているか間違わないようにする必要があります。それぞれkubeconfigを生成して都度指定しながら作業をすると間違いにくいです。
# !/usr/bin/env bash
export IBMCLOUD_API_KEY=********
TOK_RG=*****
TOK_CLUSTER=********
OSA_RG=*****
OSA_CLUSTER=********
function get-cluster-id {
ibmcloud oc cluster get -c ${1} --output json | jq -r .id
}
ibmcloud login -r jp-tok -g ${TOK_RG}
KUBECONFIG=./tok.config ibmcloud oc cluster config -c ${TOK_CLUSTER}
KUBECONFIG=./tok.config oc login -u apikey -p ${IBMCLOUD_API_KEY}
KUBECONFIG=./tok.config oc config delete-context ${TOK_CLUSTER}/$(get-cluster-id ${TOK_CLUSTER})
ibmcloud login -r jp-osa -g ${OSA_RG}
KUBECONFIG=./osa.config ibmcloud oc cluster config -c ${OSA_CLUSTER}
KUBECONFIG=./osa.config oc login -u apikey -p ${IBMCLOUD_API_KEY}
KUBECONFIG=./osa.config oc config delete-context ${OSA_CLUSTER}/$(get-cluster-id ${OSA_CLUSTER})
Submarinerのインストールと構成
Submariner CLIのインストール(作業PC)
作業PCにCLIをインストールします。
$ curl -Ls https://get.submariner.io | bash
Installing subctl version latest
OS detected: linux
Architecture detected: amd64
Download URL: https://github.com/submariner-io/releases/releases/download/v0.10.1/subctl-v0.10.1-linux-amd64.tar.xz
Downloading...
subctl-v0.10.1-linux-amd64 has been installed as /home/teruq/.local/bin/subctl
This provides subctl version: v0.10.1
$ subctl completion bash | sudo tee /etc/bash_completion.d/subctl >/dev/null
$ source /etc/bash_completion.d/subctl
Calico CLIのインストール(作業PC)
$ curl -o calicoctl -O -L "https://github.com/projectcalico/calicoctl/releases/download/v3.20.2/calicoctl"
$ sudo cp calicoctl /usr/local/bin/
$ sudo chmod 755 /usr/local/bin/calicoctl
Calico
IPPoolの作成
東京と大阪それぞれのIPレンジを下記のように記述します。
---
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: service-tok
spec:
cidr: 172.21.0.0/16
natOutgoing: false
disabled: true
---
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: pod-tok
spec:
cidr: 172.17.128.0/18
natOutgoing: false
disabled: true
---
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: service-osa
spec:
cidr: 172.22.0.0/16
natOutgoing: false
disabled: true
---
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: pod-osa
spec:
cidr: 172.17.192.0/18
natOutgoing: false
disabled: true
それぞれのクラスタに相手のIPPool
を登録します。つまり、東京のクラスタには大阪のレンジを、大阪のクラスタには東京のレンジを登録します。
$ DATASTORE_TYPE=kubernetes KUBECONFIG=./tok.config calicoctl create -f ippool-osa.yaml --allow-version-mismatch
Successfully created 2 'IPPool' resource(s)
$ DATASTORE_TYPE=kubernetes KUBECONFIG=./osa.config calicoctl create -f ippool-tok.yaml --allow-version-mismatch
Successfully created 2 'IPPool' resource(s)
ノードにラベル付与
競合が発生するとsubmariner-operatorに次のログが出力されます。
E1014 15:09:03.590563 1 submariner_controller.go:204] controller_submariner "msg"="failed to update the Submariner status" "error"="Operation cannot be fulfilled on submariners.submariner.io "submariner": the object has been modified; please apply your changes to the latest version and try again" "Request.Name"="submariner" "Request.Namespace"="submariner-operator"
$ KUBECONFIG=./tok.config oc label nodes --all submariner.io/gateway=true
node/10.244.130.28 labeled
node/10.244.2.36 labeled
node/10.244.66.34 labeled
$ KUBECONFIG=./osa.config oc label nodes --all submariner.io/gateway=true
node/10.248.0.10 labeled
node/10.248.128.10 labeled
node/10.248.64.11 labeled
Submariner Brokerのインストール(東京)
東京クラスターにSubmariner Brokerをインストールします。
$ subctl deploy-broker --kubeconfig ./tok.config
✓ Setting up broker RBAC
✓ Deploying the Submariner operator
✓ Created operator CRDs
✓ Created operator namespace: submariner-operator
✓ Created operator service account and role
✓ Updated the privileged SCC
✓ Created lighthouse service account and role
✓ Updated the privileged SCC
✓ Created Lighthouse service accounts and roles
✓ Deployed the operator successfully
✓ Deploying the broker
✓ The broker has been deployed
✓ Creating broker-info.subm file
✓ A new IPsec PSK will be generated for broker-info.subm
Podが起動したことを確認します。
$ KUBECONFIG=./tok.config oc get pods -n submariner-operator
NAME READY STATUS RESTARTS AGE
submariner-operator-7987f7ccfb-8fdml 1/1 Running 0 49s
[teruz@t14s20 odf]$
クラスターの参加(東京)
東京クラスターをSubmarinerに参加させます。--clusterid
はtok
とします。
$ subctl join --clusterid tok broker-info.subm --cable-driver vxlan --kubeconfig ./tok.config
* broker-info.subm says broker is at: https://c100-e.jp-tok.containers.cloud.ibm.com:*****
* There are 3 labeled nodes in the cluster:
- 10.244.130.28
- 10.244.2.36
- 10.244.66.34
Network plugin: Calico
Service CIDRs: [172.21.0.0/16]
Cluster CIDRs: [172.17.128.0/18]
✓ Discovering network details
✓ Validating Globalnet configurations
✓ Discovering multi cluster details
✓ Deploying the Submariner operator
✓ Created Lighthouse service accounts and roles
✓ Creating SA for cluster
✓ Deploying Submariner
✓ Submariner is up and runnin
$ subctl join --clusterid tok broker-info.subm --kubeconfig ./tok.config
* broker-info.subm says broker is at: https://c100-e.jp-tok.containers.cloud.ibm.com:30677
? Which node should be used as the gateway? 10.244.2.36
Network plugin: Calico
Service CIDRs: [172.21.0.0/16]
Cluster CIDRs: [172.17.128.0/18]
✓ Discovering network details
✓ Validating Globalnet configurations
✓ Discovering multi cluster details
✓ Deploying the Submariner operator
✓ Created Lighthouse service accounts and roles
✓ Creating SA for cluster
✓ Deploying Submariner
✓ Submariner is up and running
Podを確認すると、submariner-lighthouse-agentがErrorになっています。
$ KUBECONFIG=./tok.config oc get pods -n submariner-operator
NAME READY STATUS RESTARTS AGE
submariner-gateway-29qtp 1/1 Running 0 26s
submariner-gateway-9l52q 1/1 Running 0 26s
submariner-gateway-hcgr5 1/1 Running 0 26s
submariner-lighthouse-agent-6898c46575-5bssg 0/1 Error 2 22s
submariner-lighthouse-coredns-74bfcb6f65-99njs 1/1 Running 0 19s
submariner-lighthouse-coredns-74bfcb6f65-m2x5p 1/1 Running 0 19s
submariner-operator-7987f7ccfb-2dfb6 1/1 Running 0 74m
submariner-routeagent-4js8k 1/1 Running 0 22s
submariner-routeagent-9lzg4 1/1 Running 0 22s
submariner-routeagent-p6fmf 1/1 Running 0 22s
ログを確認すると、APIサーバのTLS証明書の検証に失敗しているようです。
$ KUBECONFIG=./tok.config oc logs submariner-lighthouse-agent-6898c46575-5bssg -n submariner-operator
I1013 01:31:57.083392 1 main.go:70] Arguments: [/usr/local/bin/lighthouse-agent -alsologtostderr -v=2]
I1013 01:31:57.083442 1 main.go:71] AgentSpec: {tok submariner-operator false}
W1013 01:31:57.083499 1 client_config.go:608] Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.
I1013 01:31:57.442060 1 main.go:98] Starting submariner-lighthouse-agent {tok submariner-operator false}
F1013 01:31:57.505801 1 main.go:115] Failed to create lighthouse agent: cannot access the API server "https://c100-e.jp-tok.containers.cloud.ibm.com:******": Get "https://c100-e.jp-tok.containers.cloud.ibm.com:30677/apis/multicluster.x-k8s.io/v1alpha1/namespaces/submariner-k8s-broker/serviceimports/any": x509: certificate signed by unknown authority
恐らくは2021年10月にLet's Encryptのルート証明書が変更になったことが原因な気がします。仕方がないので今回はPodのCA証明書を手動で差し替えます。
APIサーバの証明書を調べます。issuerがDigiCertのようです。
$ openssl s_client -connect c100-e.jp-tok.containers.cloud.ibm.com:****** -showcerts
...
issuer=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
...
DigiCertのサイトでDigiCert TLS RSA SHA256 2020 CA1
を調べると、ルート証明書が公開されていますので、PEMをダウンロードします。
https://www.digicert.com/kb/digicert-root-certificates.htm#roots
$ wget https://cacerts.digicert.com/DigiCertTLSRSASHA2562020CA1-1.crt.pem
Base64エンコードします。
$ cat DigiCertTLSRSASHA2562020CA1-1.crt.pem | base64 -w 0
LS0tLS1CRUdJTiBDRVJUSUZJ.....
CustomResourceのキーbrokerK8sCA
をエンコードした値に書き換えます。
$ KUBECONFIG=./tok.config oc edit submariners.submariner.io submariner -n submariner-operator
...
spec:
broker: k8s
brokerK8sApiServer: c100-e.jp-tok.containers.cloud.ibm.com:******
brokerK8sApiServerToken: ********
brokerK8sCA: LS0tLS1CRUdJTiBDRVJUSUZJ.....(先ほどエンコードしたルート証明書)
...
保存すると、Podが無事起動しました。
$ KUBECONFIG=./tok.config oc get pods -n submariner-operator | grep lighthouse-agent
submariner-lighthouse-agent-85b9c48df8-rgjbl 1/1 Running 0 33s
クラスターの参加(大阪)
大阪クラスターをSubmarinerに参加させます。--clusterid
はosa
とします。
$ subctl join --clusterid osa broker-info.subm --kubeconfig ./osa.config
* broker-info.subm says broker is at: https://c100-e.jp-tok.containers.cloud.ibm.com:*****
* There are 3 labeled nodes in the cluster:
- 10.248.0.10
- 10.248.128.10
- 10.248.64.11
Network plugin: Calico
Service CIDRs: [172.22.0.0/16]
Cluster CIDRs: [172.17.192.0/18]
✓ Discovering network details
✓ Validating Globalnet configurations
✓ Discovering multi cluster details
✓ Deploying the Submariner operator
✓ Created operator CRDs
✓ Created operator namespace: submariner-operator
✓ Created operator service account and role
✓ Updated the privileged SCC
✓ Created lighthouse service account and role
✓ Updated the privileged SCC
✓ Created Lighthouse service accounts and roles
✓ Deployed the operator successfully
✓ Creating SA for cluster
✓ Deploying Submariner
✓ Submariner is up and running
or
$ subctl join --clusterid osa broker-info.subm --kubeconfig ./osa.config
* broker-info.subm says broker is at: https://c100-e.jp-tok.containers.cloud.ibm.com:30677
? Which node should be used as the gateway? 10.248.0.10
Network plugin: Calico
Service CIDRs: [172.22.0.0/16]
Cluster CIDRs: [172.17.192.0/18]
✓ Discovering network details
✓ Validating Globalnet configurations
✓ Discovering multi cluster details
✓ Deploying the Submariner operator
✓ Created operator namespace: submariner-operator
✓ Created operator service account and role
✓ Created lighthouse service account and role
✓ Created Lighthouse service accounts and roles
✓ Deployed the operator successfully
✓ Creating SA for cluster
✓ Deploying Submariner
✓ Submariner is up and running
こちらもlighthouse-agentが起動に失敗するので、CustomResourceを書き換えます。
$ KUBECONFIG=./osa.config oc get pods -n submariner-operator | grep lighthouse-agent
submariner-lighthouse-agent-66fc4dfd47-rxswt 0/1 CrashLoopBackOff 1 34s
$ KUBECONFIG=./osa.config oc edit submariners.submariner.io submariner -n submariner-operator
→brokerK8sCAを東京と同じ値に変更
$ KUBECONFIG=./osa.config oc get pods -n submariner-operator | grep lighthouse-agent
submariner-lighthouse-agent-6749795b6f-rw6j5 1/1 Running 0 28s
接続確認
oc edit cm rook-ceph-operator-config -n openshift-storage
CSI_ENABLE_OMAP_GENERATOR", "value": "true"
apiVersion: v1
data:
ROOK_CSI_KUBELET_DIR_PATH: /var/data/kubelet
CSI_ENABLE_OMAP_GENERATOR: "true"
kind: ConfigMap
metadata:
creationTimestamp: "2021-10-12T14:09:39Z"
name: rook-ceph-operator-config
namespace: openshift-storage
resourceVersion: "22743"
uid: 4ca20351-9f4f-4be3-b0f6-dea95c37a107
$ oc get pods -n openshift-storage -l app=csi-rbdplugin-provisioner -o jsonpath={.items[*].spec.containers[*].name}
csi-provisioner csi-resizer csi-attacher csi-snapshotter csi-omap-generator csi-rbdplugin liveness-prometheus csi-provisioner csi-resizer csi-attacher csi-snapshotter csi-omap-generator csi-rbdplugin liveness-prometheus
oc get pods -n openshift-storage -l app=csi-rbdplugin-provisioner -o jsonpath={.items[].spec.containers[].name}
東京・大阪それぞれでSTATUS
がconnected
になっていることを確認します。
$ subctl show connections --kubeconfig ./tok.config
Cluster "c100-e-jp-tok-containers-cloud-ibm-com:30677"
✓ Showing Connections
GATEWAY CLUSTER REMOTE IP NAT CABLE DRIVER SUBNETS STATUS RTT avg.
kube-c5j0dfuo0fmosdq1d1pg-roksd osa 10.248.128.10 no vxlan 172.22.0.0/16, 172.17.192.0/18 connected 8.455507ms
$ subctl show connections --kubeconfig ./osa.config
Cluster "c101-e-jp-osa-containers-cloud-ibm-com:31014"
✓ Showing Connections
GATEWAY CLUSTER REMOTE IP NAT CABLE DRIVER SUBNETS STATUS RTT avg.
kube-c5g3vset0jaip0r1pmkg-rokso tok 10.244.2.36 no vxlan 172.21.0.0/16, 172.17.128.0/18 connected 8.395445ms
動作確認
サービス公開の仕組み
ServiceExport
ここまでの手順で東京・大阪のクラスタは接続されていますが、まだお互いのサービスにアクセスすることはできません。公開したいサービスを登録する必要があります。それをServiceExportと呼びます。
ServiceExportはマニフェストで登録します。
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceExport
metadata:
name: hello # Service名
namespace: qiita # Serviceがあるネームスペース名
subctlで登録もできます。
$ subctl export service hello -n qiita --kubeconfig ./xxx.config
公開したサービスは、${サービス名}.${ネームスペース名}.svc.clusterset.local
という特殊なFQFNで解決できるようになります。
サービス名重複時の動作
もし同じ名称同じネームスペースのサービスを複数クラスターが同時に公開した場合は、すこし特殊な動きをします。この状態で例えばhello.qiita.svc.clusterset.local
にアクセスしようとした場合、次の動きになります。
- 自クラスターにサービスが存在していればそのIPを返す
- 自クラスターにサービスが存在していなければ、他の存在しているクラスターからラウンドロビン的にIPを返す(今回は2クラスタなのでこれはありえないですが、Submarinerは本来は3つ以上のクラスターにも対応しています)
1点目がポイントです。リモートのクラスターにアクセスしているつもりが、実はローカルのクラスターにアクセスしていたという間違いを犯しやすくなります。ですので、明示的に相手のクラスターのサービスにアクセスしたい場合は、サービス名またはネームスペース名を別にする必要があります。
大阪のサービスを東京からアクセス
大阪にHelloアプリをデプロイします。
$ KUBECONFIG=./osa.config oc new-project qiita
$ KUBECONFIG=./osa.config oc new-app --name hello-osa --docker-image docker.io/ibmcom/hello
$ KUBECONFIG=./osa.config oc get svc hello-osa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-osa ClusterIP 172.22.70.152 <none> 8080/TCP 24s
サービスを公開します。
$ subctl export service hello-osa -n qiita --kubeconfig ./osa.config
Service exported successfully
東京から大阪にアクセスします。サービスのIPを指定することも、FQDN名を指定することもできます。
- Submarinerでは、相手方のサービスを
${サービス名}.${ネームスペース}.svc.clusterset.local
で解決することができます。- ServiceのClusterIPで直接アクセスする場合はServiceExportは不要です。が、実運用上現実的ではないでしょう。
- もし公開側でServiceを作成してClusterIPが変わった場合でも、自動的にServiceImportは新しいIPに変更されます。
$ KUBECONFIG=./tok.config oc run centos8 --image centos:8 --command -- tail -f /dev/null
$ KUBECONFIG=./tok.config oc rsh centos8 curl http://hello-osa.qiita.svc.clusterset.local:8080/
Hello World
東京のサービスを大阪からアクセス
逆も同様です。東京にHelloアプリをデプロイします。
$ KUBECONFIG=./tok.config oc new-project qiita
$ KUBECONFIG=./tok.config oc new-app --name hello-tok --docker-image docker.io/ibmcom/hello
$ KUBECONFIG=./tok.config oc get svc hello-tok
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello ClusterIP 172.21.57.229 <none> 8080/TCP 5s
東京のHelloを公開します。subctlで公開することもできます。
$ subctl export service hello -n qiita --kubeconfig ./tok.config
Service exported successfully
$ oc get serviceexport hello -n qiita -o yaml
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ServiceExport
metadata:
name: hello
namespace: qiita
...
大阪から東京にアクセスします。
$ KUBECONFIG=./osa.config oc run centos8 --image centos:8 --command -- tail -f /dev/null
# IP直接
$ KUBECONFIG=./osa.config oc rsh centos8 curl http://172.21.57.229:8080/
Hello World
# サービス名
$ KUBECONFIG=./osa.config oc rsh centos8 curl http://hello.qiita.svc.clusterset.local:8080/
Hello World
クリーンアップ
$ KUBECONFIG=./tok.config oc delete ns submariner-k8s-broker submariner-operator
$ KUBECONFIG=./tok.config oc label nodes -l submariner.io/gateway=true submariner.io/gateway-
$ KUBECONFIG=./osa.config oc delete ns submariner-operator
$ KUBECONFIG=./osa.config oc label nodes -l submariner.io/gateway=true submariner.io/gateway-
残作業
- Transit Gateway断時の挙動
- Gatewayの冗長化検証
- Submarinerのバージョンアップ
- ノードのバージョンアップ、リプレイス時の挙動
- ROKSのエンドポイントのプライベート化