kind(Kubernetes in docker)クラスタでROS2アプリを動かす

## はじめに
この記事は、[箱庭もくもく会 \#1 \- connpass](https://hakoniwa.connpass.com/event/234843/)で「2022年の年明け」から手を動かした内容を記録したものです。

表題のkind(Kubernetes in docker)は、公式によると
> kind is a tool for running local Kubernetes clusters using Docker container “nodes”. kind was primarily designed for testing Kubernetes itself, but may be used for local development or CI.

だそうです。  
手元の環境を汚さず、Dockerコンテナを使ってローカルでKubernetesクラスタを実行するためのツールです。基本的にはローカル開発とかCIで使うことを想定して開発されているようです。Kubernetesを勉強するのにはうってつけとして、界隈では注目を集めています。

そんなクラスタを使って、ROS2を動かしてみようというのが今回の企画。

## セットアップ
Docker、Kubectlはインストール済み、セットアップ済み前提で記載します。

### kindのインストール
[kind – Quick Start](https://kind.sigs.k8s.io/docs/user/quick-start/)
公式のドキュメントによると、Windows/Mac/LinuxなんでもOK！っていう感じ。


```bash
# mac
brew install kind

# windows
choco install kind
```

### コマンド補完（Mac）

`kind`コマンドに対して補完を有効化したい場合は、以下を追加しよう。

```bash
[[ /usr/local/bin/kubectl ]] && source <(kind completion zsh)
```

### 設定ファイル
ROS2の肝？である、UDPマルチキャストを利用するためにKubenetestのCNIとして[Weave Net](https://www.weave.works/docs/net/latest/kubernetes/kube-addon/)を使いたい。ゆえに、デフォルトのネットワークを無効化した上で使用する。`disableDefaultCNI: true`

とりあえず、`kind-example-config.yaml`で保存する。

```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true
nodes:
- role: control-plane
- role: control-plane
- role: control-plane
- role: worker
- role: worker
- role: worker
```

### クラスタの生成

```bash
kind create cluster --config kind-example-config.yaml --name rdbox-hakoniwa
```

### kubectlのcontext

```bash
kubectl cluster-info --context kind-rdbox-hakoniwa
```

### Weave Netのapply

```bash
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
```

しばらくしてから`kubectl get node`を実行。全てのノードがReadyになっていることを確認する。

```bash
❯ kubectl get node
NAME                            STATUS   ROLES                  AGE     VERSION
rdbox-hakoniwa-control-plane    Ready    control-plane,master   7m27s   v1.21.1
rdbox-hakoniwa-control-plane2   Ready    control-plane,master   7m      v1.21.1
rdbox-hakoniwa-control-plane3   Ready    control-plane,master   6m7s    v1.21.1
rdbox-hakoniwa-worker           Ready    <none>                 5m56s   v1.21.1
rdbox-hakoniwa-worker2          Ready    <none>                 5m56s   v1.21.1
rdbox-hakoniwa-worker3          Ready    <none>                 5m55s   v1.21.1
```

## ROS2アプリを動かしてみる

### フォルダ構成

```bash
.
└── ros2
    ├── ros2_listener.yaml
    └── ros2_talker.yaml
```

### ros2_talker.yaml

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simulation-talker
  labels:
    app: simulation-talker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simulation-talker
  template:
    metadata:
      labels:
        app: simulation-talker
        role: my-role
    spec:
      containers:
        - name: simulation-talker
          image: osrf/ros:foxy-desktop
          tty: true
```

### ros2_listener.yaml

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simulation-listener
  labels:
    app: simulation-listener
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simulation-listener
  template:
    metadata:
      labels:
        app: simulation-listener
        role: my-role
    spec:
      containers:
        - name: simulation-listener
          image: osrf/ros:foxy-desktop
          tty: true
```


### マニフェストを展開する

```bash
kubectl apply -f ros2
```

しばらくしてから`kubectl get pod`を実行。全てのpodがRunningになっていることを確認する。

```bash
❯ kubectl get pod
NAME                                   READY   STATUS    RESTARTS   AGE
simulation-listener-8699f4f798-b4rrf   1/1     Running   0          2m42s
simulation-talker-8595bdb886-xst67     1/1     Running   0          2m42s
```

### 中に入ってROSコマンドを動かす

PodNameは、上記で実行し取得したNameを使用する。①からトピックをPubして、②で受け取る感じ。

端末①

```bash
$ kubectl exec -it simulation-talker-8595bdb886-xst67 -- /bin/bash
$ source ros_entrypoint.sh
$ ros2 run demo_nodes_cpp talker
[INFO] [1641090170.460439541] [talker]: Publishing: 'Hello World: 1'
[INFO] [1641090171.460306556] [talker]: Publishing: 'Hello World: 2'
[INFO] [1641090172.460231589] [talker]: Publishing: 'Hello World: 3'
[INFO] [1641090173.460365240] [talker]: Publishing: 'Hello World: 4'
[INFO] [1641090174.460597760] [talker]: Publishing: 'Hello World: 5'
[INFO] [1641090175.460255840] [talker]: Publishing: 'Hello World: 6'
```

端末②

```bash
$ kubectl exec -it simulation-listener-8699f4f798-b4rrf -- /bin/bash
$ source ros_entrypoint.sh
$ ros2 run demo_nodes_cpp listener
[INFO] [1641090170.461078099] [listener]: I heard: [Hello World: 1]
[INFO] [1641090171.460873884] [listener]: I heard: [Hello World: 2]
[INFO] [1641090172.460738264] [listener]: I heard: [Hello World: 3]
[INFO] [1641090173.460742628] [listener]: I heard: [Hello World: 4]
[INFO] [1641090174.460995658] [listener]: I heard: [Hello World: 5]
[INFO] [1641090175.460751746] [listener]: I heard: [Hello World: 6]
```


## 外から見ると、、、
このような状態で、`docker ps`してみると以下のような感じになる。Kubernetesのノード1つにつき、Dockerコンテナが一つ割り当てられるようなイメージ。
その辺の設計について[kind – Initial design](https://kind.sigs.k8s.io/docs/design/initial/)を見て私自身ももうちょっと深堀りしていきたいと思います。

```bash
❯ docker ps
CONTAINER ID   IMAGE                                COMMAND                  CREATED          STATUS          PORTS                       NAMES
ec8c214a4bf7   kindest/haproxy:v20200708-548e36db   "/docker-entrypoint.…"   29 minutes ago   Up 29 minutes   127.0.0.1:52302->6443/tcp   rdbox-hakoniwa-external-load-balancer
25c273b79e7d   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   29 minutes ago   Up 29 minutes                               rdbox-hakoniwa-worker3
15505559f31c   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   29 minutes ago   Up 29 minutes                               rdbox-hakoniwa-worker
e12cd9839935   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   29 minutes ago   Up 29 minutes   127.0.0.1:52303->6443/tcp   rdbox-hakoniwa-control-plane2
63bd1bbca037   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   29 minutes ago   Up 29 minutes                               rdbox-hakoniwa-worker2
09cede1487ab   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   29 minutes ago   Up 29 minutes   127.0.0.1:52305->6443/tcp   rdbox-hakoniwa-control-plane
405188ac6d8f   kindest/node:v1.21.1                 "/usr/local/bin/entr…"   29 minutes ago   Up 29 minutes   127.0.0.1:52304->6443/tcp   rdbox-hakoniwa-control-plane3
```

## 後片付け
検証が終わったら以下を実行します。

```bash
# ROS2アプリの削除
$ kubectl delete -f ros2

# クラスタの削除
$ kind delete cluster --name rdbox-hakoniwa
```

## おわりに
今回はもくもく会の1.5時間という時間内でやれる範囲で、簡単な動作確認をしました。結構普通に使えることが分かったので
- [箱庭](https://toppers.github.io/hakoniwa)との連携
- [RDBOX](https://github.com/rdbox-intec/rdbox)との連携
- 簡易版のCI/CD環境の提供

この辺りをもう少し深堀りしていって展開させて頂ければと思います。

また、2022年は、箱庭のもくもく会や勉強会を定期的に開催予定です。ぜひ[箱庭 \- connpass](https://hakoniwa.connpass.com/)もご注目下さい。

それにしても、手を動かすのって楽しいですね！！！！
（最近、全然手を動かさせてもらえない男の叫び）