ROKSクラスター外の仮想マシンにCalicoを導入してPodに直接アクセスする

IBM CloudのManaged OpenShift（略してROKS、Red hat Openshift on ibm cloud Kubernetes Service）は、SDNドライバーにCalicoを使っているのが特徴の一つとして挙げられるが（通常はVXLANのOpenShift SDN）、Calicoの機能としてクラスター外のホストからKubernetes SDNのネットワークに参加する方法があるのを知り、試してみた。

Calico - About non-cluster hosts
https://docs.projectcalico.org/getting-started/bare-metal/about

初めに書いておくと、これが出来たところで構成上何かがうれしいという事は特にない。任意のPodにクラスター外から通信したければNodePort使えよという事である故。

# ROKSクラスター
せっかくなのでVPC gen2上に作成。2ノード。
https://cloud.ibm.com/kubernetes/catalog/create?platformType=openshift

やれ標準プランのICOSが必要だの、SecurityGroupに穴を空けなきゃいかんだのをクリアしてクラスターを作成。結局image-registryのPodが証明書が違うとかエラーを吐いて起動してこない（p11-kit: couldn't complete writing file: /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt: Operation not permitted）。
安定の不安定ぶりだが、今回の検証には関係ないのでそこはスキップ。

# 仮想サーバー
ROKSクラスターを作った同じサブネットにVPC仮想サーバーを１台作成。CentOS7の一番小さいの。
仮想サーバー名は、例として「neighbor」。
なお、CentOS8はiptablesがなくなっているため、Calicoとは相性悪いかもと思ってやめた。今ざっとググったら動くことは動くらしいが。

# OpenShiftにCluster AdminのSAを作る
仮想サーバーのCalicoがアクセスできるよう、OpenShiftクラスターにCluster Adminロールを持ったSAを作って、そのトークンを入手する。

１．ocコマンドを入手する。
https://mirror.openshift.com/pub/openshift-v4/clients/oc/

２．OpenShiftクラスターのWeb管理画面にログインし、右上の「アカウント名」>「Copy Login Command」からCLIでのログインの文字列を入手する。

３．ocコマンドでクラスターにログインする。

```
# oc login --token=Y4PsrZ4LSgWHMd7gnB0G-ZXuHv9Vlyi6q0P67GyAcgw --server=https://c100-e.jp-tok.containers.cloud.ibm.com:31248
```

４．SAを作り、cluster-adminロールを与える。

```
# oc create sa admin
# oc adm policy add-cluster-role-to-user cluster-admin -z admin
```

５．SAのトークンを入手し、kubeconfigに仕込む。

```
# oc get sa admin -o jsonpath='{.secrets}'
　→ 「admin-token-*」のシークレット名を取得する。
# token=$(oc get secret ＜シークレット名＞  -o jsonpath='{.data.token}' | base64 -d)
# echo $token
　→ eyJhbGciOiJSUzI1NiIsIm...みたいな、１０行ぐらいのランダム文字列が表示
# server=$(grep -o 'https://.*$' .kube/config | head -n 1)
# cat > .kube/config << EOF
apiVersion: v1
kind: Config
preferences: {}
clusters:
- name: cluster
  cluster:
    server: ${server}
contexts:
- name: default
  context:
    cluster: cluster
    namespace: default
    user: admin
current-context: default
users:
- name: admin
  user:
    token: ${token}
EOF

```

作ったら、kubeconfigが有効なことを確かめる。

```
# oc get svc

（実行結果）
[root@neighbor ~]# oc get svc
NAME                  TYPE           CLUSTER-IP       EXTERNAL-IP                            PORT(S)    AGE
kubernetes            ClusterIP      172.21.0.1       <none>                                 443/TCP    6h55m
openshift             ExternalName   <none>           kubernetes.default.svc.cluster.local   <none>     6h27m
openshift-apiserver   ClusterIP      172.21.34.217    <none>                                 443/TCP    6h55m
```

# Calico Felixをインストールする
コンテナ版を導入する必要がある（記事作成時点では）。

１．Dockerをインストールする。

```
# sudo yum install -y yum-utils
# sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
# sudo yum install docker-ce docker-ce-cli containerd.io
# sudo systemctl start docker
```

２．OpenShiftクラスターに疑似ノードを作る。
（参考）https://github.com/projectcalico/calico/issues/3551

```
oc create -f - <<EOF
apiVersion: v1
kind: Node
metadata:
  name: neighbor
EOF
```

３．OpenShiftクラスターで動いているCalicoのバージョンを確認する。

```
# oc get ds -n calico-system calico-node -o jsonpath='{.spec.template.spec.containers[0].image}'
　→ ～calico/node:v3.16.5
```

４．Calicoを起動する。
起動前に、その時点のroute状態を確認してもいい。

```
# route -n

（実行結果）
[root@neighbor ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.244.64.1     0.0.0.0         UG    0      0        0 eth0
10.244.64.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
```

起動する。

```
# docker run -d --rm --net=host --privileged \
  --name="calico-node" \
  -e DATASTORE_TYPE="kubernetes" \
  -e KUBECONFIG="/tmp/kube_config" \
  -e CALICO_NODENAME="neighbor" \
  -e CALICO_NETWORKING_BACKEND="bird" \
  -v /var/log/calico:/var/log/calico \
  -v /run/docker/plugins:/run/docker/plugins \
  -v /lib/modules:/lib/modules \
  -v /var/run/calico:/var/run/calico \
  -v /etc/pki:/pki \
  -v /root/.kube/config:/tmp/kube_config \
  calico/node:v3.16.5
```

起動後、routeに以下「172.17.9.64 10.244.64.4」、「172.17.43.128 10.244.64.5」、「172.17.55.192 0.0.0.0」の３つの行が追加されていれば成功である。

```
[root@neighbor ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.244.64.1     0.0.0.0         UG    0      0        0 eth0
10.244.64.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.17.9.64     10.244.64.4     255.255.255.192 UG    0      0        0 eth0
172.17.43.128   10.244.64.5     255.255.255.192 UG    0      0        0 eth0
172.17.55.192   0.0.0.0         255.255.255.192 U     0      0        0 *

```

また、例えばクラスターのcalico-nodeに接続しても、仮想サーバーのアドレス（ここでは10.244.64.8）がルーターとしてテーブルに追加されているのを確認できるはずである。

```
# oc exec -it -n calico-system calico-node-g4dpg bash
# ip route
...
172.17.55.192/26 via 10.244.64.8 dev eth0 proto bird
...
```

# Podにアクセスしてみる
```oc get pod -o wide```コマンドでPodを確認すると、表示されるPodのIPアドレスは172.17.9.64、172.17.43.128のいずれかのサブネットに所属しているはずである。
それらPodのIPアドレスに、仮想サーバーから直接アクセスできることを確認する。

例えば、、Prometheusとか？

```
# ipaddr=$(oc get pod -n openshift-monitoring prometheus-k8s-0 -o jsonpath='{.status.podIP}')
# echo $ipaddr
　→ 172.17.9.98
# start=$(date -d yesterday +%s)
# end=$(date +%s)
# curl -k -H "Authorization:Bearer $token" \
https://${ipaddr}:9091/api/v1/query_range \
--data-urlencode "start=$start" \
--data-urlencode "end=$end" \
--data-urlencode "step=30" \
--data-urlencode 'query=sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) * 100'

```

```{"status":"success","data":...```といった、100行ぐらいのデータが出力されれば成功である。

# ついでに
最初に書いたが、これで何が嬉しいというのも実のところ特には無く、例えば上記のPrometheusはデフォルトでrouteが公開されているし、よしんば無かったとしても以下のようなNodePortを追加してそこからアクセスすれば良いのである。

```
# oc expose pod -n openshift-monitoring prometheus-k8s-0 --type=NodePort --port 9091
# oc get node
# ipaddr=＜適当なノードのIPアドレス＞
# nodeport=$(oc get svc -n openshift-monitoring prometheus-k8s-0 -o jsonpath='{.spec.ports[0].nodePort}')
# curl -k -H "Authorization:Bearer $token" \
https://${ipaddr}:${nodeport}/api/v1/query_range \
--data-urlencode "start=$start" \
--data-urlencode "end=$end" \
--data-urlencode "step=30" \
--data-urlencode 'query=sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) * 100'
```