【備忘録】ラズパイをAnsibleでクラスタ化する

#サンプルコード
https://github.com/akihiro0000/raspi-k8s

#目次
[1.アーキテクチャ、開発フロー](#1はじめに)
[2.Ansibleにとは](#2-内容い)
[2.Ansibleによる諸々設定](#2-内容い)
[3.Ansibleによるクラスタ作成](#3-内容ろ)
#アーキテクチャ、開発フロー

###必要備品
ラズパイ2つをルーターに繋げます

- raspi×２（master、worker）
- ルーター×1
- SIM×1
- ハブ×1
- 有線LANケーブル×3
- SDカード×2


#Ansibleとは
- Simple、Powerful、Agentlessという3つの特徴を掲げるPython製のインフラ構築自動化ツール
- 先行製品として有名なPuppetやChef等で実現できることを踏襲
- 構成管理ツールとも呼ばれ、「機器のあるべき姿（構成）をファイルで管理する」ためのものである
- 冪等性（同じ操作を何度繰り返しても、同じ結果が得られる性質）あり

###特徴
- Simple
    - Playbookと呼ばれるファイルに環境構築に関する設定情報をYAMLにより定義します。
- Powerful
    - OSSコミュニティの活動が活発であり、日々開発がされている、様々な機器を操作するためのモジュールを使用することができます。
- Agentless
    - 構築対象となるサーバ、機器に対してAgentをインストールする必要がないため、余計な管理負荷、セキュリティリスクを負わなくて済みます。

###Chef,Puppetとの違い

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/908963/2e51e511-7a67-1b13-d6c4-84675a66b5e1.png)
※以下サイトの図を参考にさせていただきました。
https://www.scsk.jp/product/oss/tec_guide/ansible/1_ansible1_1.html



#Ansibleによる諸々設定
###本記事向けファイル構造
本来はrole内にdefault等様々な設定方法があるそうですが、今回はroleの身を用います。

```
.
├── ansible.cfg
├── inventory
│   └── inventory.ini
├── k8s_provisioning.yml
├── raspbian_provisioning.yml
└── roles
    ├── apt
    │   └── tasks
    │       └── main.yml
    ├── k8s
    │   └── provisioning
    │       ├── defaults
    │       │   └── main.yml
    │       └── tasks
    │           ├── install_k8s_packages.yml
    │           ├── main.yml
    │           ├── master-provisioning.yml
    │           ├── memory_setup.yml
    │           ├── store_info_to_join.yml
    │           └── workers-provisioning.yml
    └── ras
        └── tasks
            └── main.yml
```


###疎通確認

```
ansible -i inventory/inventory.ini workers  -m ping
```

###実行
```
ansible-playbook  -i inventory/inventory.ini raspbian_provisioning.yml
ansible-playbook  -i inventory/inventory.ini k8s_provisioning.yml
```

結果として以下のようにクラスタができていればok

```
>$$sudo kubectl get nodes

NAME     STATUS   ROLES                  AGE     VERSION
ras000   Ready    control-plane,master   8m31s   v1.21.2
ras001   Ready    <none>                 7m2s    v1.21.2
```

```
>$$sudo kubectl get pods --all-namespaces

NAMESPACE     NAME                             READY   STATUS    RESTARTS   AGE
kube-system   coredns-558bd4d5db-t6d6m         1/1     Running   0          9m18s
kube-system   coredns-558bd4d5db-wfxm8         1/1     Running   0          9m18s
kube-system   etcd-ras000                      1/1     Running   0          9m23s
kube-system   kube-apiserver-ras000            1/1     Running   0          9m23s
kube-system   kube-controller-manager-ras000   1/1     Running   0          9m23s
kube-system   kube-flannel-ds-arm-nq7ps        1/1     Running   0          8m8s
kube-system   kube-flannel-ds-arm-qm4dp        1/1     Running   0          9m18s
kube-system   kube-proxy-rzspj                 1/1     Running   0          9m18s
kube-system   kube-proxy-vp598                 1/1     Running   0          8m8s
kube-system   kube-scheduler-ras000            1/1     Running   0          9m23s
```

###playbook作成の大まかなフロー
- フォルダ階層をイメージし、大枠を作成する
    - 今回の場合は「RasberryPiの初期設定」「kubernetesのプロビジョニング」の2つコンポーネントで構築する。凝縮度＆結合度を考慮する。
    - 参考：https://www.affordd.jp/koha_hp/KeyWords/KW.Coupling.html
- InventoryにプロビジョニングしたいIPを記載
- ansibleの設定はansible.cfgに記載
- Rolesでplaybookを整理する
    - role内のtaskにメイン実行関数、defaultsに定数を入れる





###参考資料
- SDカードをターミナルでフォーマット  
https://qiita.com/ikemura23/items/f3dfc0311d50bc34aa25
- Raspberry PiのプロビジョニングをAnsibleで行う
https://hassiweb-programming.blogspot.com/2019/08/kubernetes-install-on-raspberrypi.html

- MACにansibleをinstallする
https://weblabo.oscasierra.net/ansible-homebrew-install-1/

- Ansibleの疎通確認にはsshpassのインストールが必須らしい
https://qiita.com/suganum/items/59bbf0972c65b4b682c0
https://qiita.com/rysk_lunch/items/bb817964fc3fbe6d177c

- NTPとは
https://www.nic.ad.jp/ja/basics/terms/ntp.html

- ansible.cfgのwarningを消す
https://dev.classmethod.jp/articles/ansible-interpreter-warning/

- ansibleのトラブルシューティング
https://docs.ansible.com/ansible-tower/3.1.4/html_ja/administration/troubleshooting.html

- playbookの書き方
https://kurokawanushi.hatenablog.com/entry/2019/03/03/190000

- playbookに「become：yes」をつけるとroot権限で実行できる
https://dekitakotono.blogspot.com/2019/04/become-sudo-su.html

- ラズパイプロビジョニング例１
https://github.com/hassiweb/k8s-install-on-rpi/tree/master/ansible/roles/k8s

- ラズパイプロビジョニング例2
https://github.com/giuaig/ansible-raspi-config/blob/master/raspi-config.yml

- kubenetesのクラスタには同じホストネームを持つnodeが存在してはいけない
    - raspi-configでそれぞれのエッジデバイスが異なるホストネームになるように設定する

- ラズパイが起動した時にスクリプトを自動実行させる
https://raspberrypi-japan.com/run-startup-program-3/



#scd30センサーデータ取得ファイル、Dockerfile作成
```
from scd30_i2c import SCD30
import time
import pytz
from datetime import datetime
import os


#set scd30 sensor
try:
    scd30 = SCD30()

    scd30.set_measurement_interval(10)
    scd30.start_periodic_measurement()

    time.sleep(2)
except TimeoutError as e:
    print(e)
    os.system('exit')
    pass


while True:
    # if scd30 isn't ready, wait 0.2sec
    try:
        if scd30.get_data_ready():
            m = scd30.read_measurement()
            if m is not None:
                tim = '"timestamp":"'+datetime.now(pytz.timezone('Asia/Tokyo')).strftime('%Y-%m-%d %H:%M:%S.%f')+'"'
                temp = '"' + "temp(degree)" + '"' + ":" + '"' + str(round(m[1]-3.5,3)) + '"'
                hum = '"' + "humid(%)" + '"' + ":" + '"' + str(round(m[2],3)) + '"'
                co2 = '"' + "co2(ppm)" + '"' + ":" + '"' + str(round(m[0],3)) + '"'
                mylist = [tim,temp,hum,co2]
                mystr = '{' + ','.join(map(str,mylist))+'}'
                if str(round(m[1],3))!="nan":
                    print(mystr)
                    time.sleep(1)
                else:
                    print("value is nan")
                    time.sleep(1)
        else:
            time.sleep(0.2)
    except OSError as e:
        print(e)
        pass
```


```
FROM debian:buster

ENV DEBCONF_NOWARNINGS=yes

RUN apt update && apt upgrade -y \
    curl cron python3 python3-pip python3-dev python -V git wget vim python3-numpy i2c-tools

RUN pip3 install scd30-i2c pytz

RUN git clone --depth 1 https://github.com/akihiro0000/raspi-k8s.git

WORKDIR /raspi-k8s/src

CMD ["python3","sdc30.py"]
```




以下でi2c通信を許可した状態でコンテナをrun

```
docker run  --device /dev/gpiomem  --device /dev/i2c-1 --device /dev/ttyACM0 --privileged -e TZ=Asia/Tokyo -it akihirodive/raspi-k8s:v1.0
```

出力

```
{"timestamp":"2021-07-03 22:57:30.753356","temp(degree)":"21.623","humid(%)":"59.055","co2(ppm)":"583.996"}
{"timestamp":"2021-07-03 22:57:40.654920","temp(degree)":"21.609","humid(%)":"58.885","co2(ppm)":"583.399"}
{"timestamp":"2021-07-03 22:57:51.792402","temp(degree)":"21.609","humid(%)":"58.971","co2(ppm)":"583.271"}
・・・
```

###参考資料
- apt-utils isn't installed 警告をなくす
https://mokuzine.net/ubuntu-apt-util-warn/

#scd30センサーデータ取得yamlファイル作成

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: scd30
spec:
  selector:
    matchLabels:
       role: app
  replicas: 2
  template:
    metadata:
      labels:
         role: app
    spec:
      containers:
      - name: scd30-container
        volumeMounts:
        - mountPath: /dev/i2c-1
          name: i2c
        securityContext:
          privileged: true
        image: akihirodive/raspi-k8s:v1.0 
        imagePullPolicy: Always
        ports:
        - containerPort: 1000
          hostPort: 1000
      volumes:
      - name: i2c
        hostPath:
          path: /dev/i2c-1
```

以下コードでpodをworker nodeに立てる

```
sudo kubectl apply -f <yaml名>.yaml
```

出力

```
>>sudo kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
scd30-59dfdcfb46-rgk2d   0/1     Pending   0          6m11s
scd30-59dfdcfb46-vpzxl   1/1     Running   0          6m11s
```

###参考
- ① "/dev/i2c-1"にsudoなしでも入り込めるようにする  
https://remoteroom.jp/diary/2020-11-19/
- ② "/dev/i2c-1"にsudoなしでも入り込めるようにする。以下を入れる

```
securityContext:
          privileged: true
```