環境

・Raspberry Pi 3 Model B+ x3

・Raspbian GNU/Linux 9.8 (stretch)

手順

Kubernetesクラスタの構築

今回は、3台のRaspberry Pi 3 Model B+を使ってKubernetesクラスタを構築しています。

下記の画像のように、

1台をmasterノード(緑のraspberrypi)、2台をworkerノード(青、赤のraspberrypi)として、

クラスタ構築にはkubeadmを使っています。

Kubernetesクラスタの構築方法は、色々なサイトで紹介されているので省略します。

 

配線

イメージの作成の前に配線を行います。

KubernetesのRollingUpdateなどの動きを確認できるように、

2つのRaspberry Pi(workerノードとなっている2つ)に、LEDを3つずつ接続して、下記の図のようにします。

どちらのノードで起動しているかわかりやすいように、

赤いRaspberry Piに赤い色LED、青いRaspberry PIに青いLEDを接続しています。

(図に使えるパーツの都合で、ブレッドボードに刺している位置など、後ででてくる実物と少し違ってます。)

 

Lチカのイメージ作成

次に上記のクラスターと配線で動作するイメージを作成します。

今回は、バージョンアップ時の動作などを確認したいため、

見た目で異なるバージョンだとわかるように、

LED点滅イメージとLED点灯イメージの2つのイメージを作成します。

作成する上で悩んだ点

イメージを作成する上で、少し悩んだ点を2点記載します。

1点目は、複数Podで動かすための空いているPINの判定です。

RollingUpdateなどの動きを確認するために、

1workerノードで複数のPod(LED)が動かせる状態にし、かつPodが立ち上がる時には、

自動的に空いているLEDを選択できるようにする必要がありました。

それを行うのに、当初はRPI.GPIOライブラリを使用しようとしていたのですが、

よいメソッドを見つけることができず、最終的にはwiringpiライブラリのgetAlt()メソッドを使用しました。

ドキュメントはこちら

https://metacpan.org/pod/WiringPi::API#get_alt($pin)

 

2点目は、Podが停止する際の後処理です。

Podが停止した際に、GPIOのPINを解放する必要があるのですが、

停止するタイミングによるのか、プログラムだけだと、それがうまくいかないことがありました。

そこで、使用するPIN番号を記載したテキストファイル(target_pin.txt)を作成し、

Kubernetesのlifecycle処理で、Pod停止時にそのファイルからPIN番号を読み込み、PIN番号を解放する処理を行いました。

 

どちらももっと良い方法があるかもしれないですが、

一旦、上記の方法で今回は解決しています。

 

LED点滅イメージの作成

プログラムの作成

まずは、LED点滅イメージについて、説明していきます。

実際に、PIN番号を利用し、Lチカさせるプログラムは下記になります。

直書きですが、、上記の図のようにPIN番号は4, 18, 26を利用しています。

wiringpiライブラリのgetAlt()を利用し、4番PINから順番に評価していき、

空いているPIN番号を見つけたら割り当てます。

また、その際に後処理で使うために、割り当てたPIN番号をテキストファイルに出力しておきます。

Lチカは、0.3秒の間隔で点滅するようにしています。

led_blink.py

import wiringpi
import time, sys
import os

LED_PORT = 4
PIN1 = 4
PIN2 = 18
PIN3 = 26

wiringpi.wiringPiSetupGpio()

if (wiringpi.getAlt(PIN1) == 0):
    LED_PORT = PIN1
elif (wiringpi.getAlt(PIN2) == 0):
    LED_PORT = PIN2
else:
    LED_PORT = PIN3

path = 'target_pin.txt'
with open(path, mode='w') as f:
    f.write(str(LED_PORT))

print("use " + str(LED_PORT) + "PIN")

wiringpi.pinMode(LED_PORT, wiringpi.OUTPUT)
try:
    while True:
        wiringpi.digitalWrite(LED_PORT, wiringpi.HIGH)
        time.sleep(0.3)

        wiringpi.digitalWrite(LED_PORT, wiringpi.LOW)
        time.sleep(0.3)

except KeyboardInterrupt:
    wiringpi.pinMode(LED_PORT, wiringpi.INPUT)

次に、Kubernetesのlifecycle機能を使って実行する後処理用のファイルの作成を行います。

起動時に割り当てを行ったPIN番号を記載しているファイルから、

番号を読み取り、割り当ての解放を行います。

cleanup.py

import wiringpi
import time, sys

path = 'target_pin.txt'
with open(path) as f:
    s = f.read()

wiringpi.wiringPiSetupGpio()
wiringpi.pinMode(int(s), wiringpi.INPUT)

Dockerイメージの作成

3-1で作成したled_blink.pyとcleanup.pyがあるディレクトリに、下記のDockerfileを作成します。

 内容はシンプルで、2つのファイルを取り込み、

必要なライブラリをインストールした後に、led_bling.pyを実行しています。

Dockerfile

FROM python:slim-buster

WORKDIR /app

COPY led_blink.py /app/
COPY cleanup.py /app/

#RUN pip3 install --upgrade pip
RUN apt-get update && apt-get install -y gcc \
    iputils-ping
RUN cd /tmp \
    wget https://project-downloads.drogon.net/wiringpi-latest.deb \
    dpkg --add-architecture armhf \
    dpkg -i wiringpi-latest.deb
RUN pip3 install --no-cache wiringpi

CMD ["python3", "led_blink.py"]

Dockerイメージの作成は、下記のコマンドで行います。

"名前:タグ“は、自分の環境にあわせてください。

(sudoは、設定によってはいらないかもしれないです。)

$ sudo docker build -t "名前:タグ" .

Dockerイメージの作成

Dockerイメージの作成に成功したら、pushします。

"名前:タグ“は、先ほどの設定にあわせてください。

$ sudo docker push "名前:タグ"

ここでDockerイメージについて、動作確認をする場合は、

下記の記事を参考に試してみてください。

LED点灯イメージの作成

プログラムの作成

基本的には点滅時と同じです。

Lチカの処理の部分だけ異なるため、下記のようなプログラムになります。

間違えたりしないようにファイル名をlight.pyとしています。

light.py

import wiringpi
import time, sys
import os

LED_PORT = 4
PIN1 = 4
PIN2 = 18
PIN3 = 26

wiringpi.wiringPiSetupGpio()

if (wiringpi.getAlt(PIN1) == 0):
    LED_PORT = PIN1
elif (wiringpi.getAlt(PIN2) == 0):
    LED_PORT = PIN2
elif (wiringpi.getAlt(PIN3) == 0):
    LED_PORT = PIN3
else:
    sys.exit()

path = 'target_pin.txt'
with open(path, mode='w') as f:
    f.write(str(LED_PORT))

print("use " + str(LED_PORT) + "PIN")

wiringpi.pinMode(LED_PORT, wiringpi.OUTPUT)
try:
    while True:
        wiringpi.digitalWrite(LED_PORT, wiringpi.HIGH)

except KeyboardInterrupt:
    wiringpi.pinMode(LED_PORT, wiringpi.INPUT)

cleanup.pyは、点滅時と同じものを使用します。

 

Dockerイメージの作成

Dockerfileもほぼ同じです。使用するファイル名のみlight.pyに変更してください。

イメージを作成する際に、先ほどと違う"名前:タグ“をつけてください。

Dockerfile

FROM python:slim-buster

WORKDIR /app

COPY light.py /app/
COPY cleanup.py /app/

#RUN pip3 install --upgrade pip
RUN apt-get update && apt-get install -y gcc \
    iputils-ping
RUN cd /tmp \
    wget https://project-downloads.drogon.net/wiringpi-latest.deb \
    dpkg --add-architecture armhf \
    dpkg -i wiringpi-latest.deb
RUN pip3 install --no-cache wiringpi

CMD ["python3", "light.py"]

Dockerイメージの作成

こちらもDockerイメージの作成に成功したら、pushします。

KubernetesのManifestファイルを作成

ここまでで、使用するイメージが2つ作成できました。

次に、Manifestファイルを作成します。色々編集しますが、基本のManifestファイルは下記になります。

3点ほど、設定について触れると、

1点目は、securityContext.privilegedについて、trueとなっていますが、これを設定しないと権限がないため、

GPIOを操作できません。

2点目は、上記の手順で作成したcleanup.pyについて、lifecycleの部分でpreStop処理として実行しています。

3点目は、livenessProbeについて、後の検証のため外(GoogleのDNS)と疎通できていることを確認しています。

また、イメージは点滅用のイメージを指定しています。

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: blinkapp
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 3
  replicas: 3
  selector:
    matchLabels:
      app: blinkapp
  template:
    metadata:
      labels:
        app: blinkapp
    spec:
      containers:
        - name: blinkapp
          image: "名前:タグ"
          securityContext:
              privileged: true
          lifecycle:
            preStop:
              exec:
                command: ["python3", "cleanup.py"]
          livenessProbe:
            exec:
              command: ["/bin/sh", "-c", "ping -c 1 8.8.8.8"]
            timeoutSeconds: 3
            successThreshold: 1
            failureThreshold: 3
            initialDelaySeconds: 8
            periodSeconds: 3

動作を確認

ここから実際に、Kubernetesクラスタにデプロイして動作を確認していきます。

上記の手順で紹介したManifestファイルを基本に、一部編集しながら動きを確認します。

デプロイは、applyコマンドで実行しています。

$ kubectl apply -f deployment.yaml

RollingUpdateの動きを確認(maxSurge=1の場合)

点滅イメージから点灯イメージへのRollingUpdate

・変更点

maxSurge=1に変更

・結果

 点灯イメージ(update後のイメージ)のPodが1つ起動できたのを確認してから、

 点滅イメージ(update前のイメージ)のPodが1つ停止するというのを繰り返して、

 updateが行われているのが確認できました。

 

RollingUpdateの動きを確認(maxSurge=100%の場合)

点灯イメージから点滅イメージへのRollingUpdateをmaxSurge=100%設定で行った場合の動きを確認します。

・変更点

maxSurge=100%に変更

・結果

 1つ前のmaxSurge=1の設定のときと異なり、同時に2つのPodのupdateが行われているのを確認できました。

 

動作するノードを指定してデプロイしたときの動きを確認

Podが稼働するNodeを青のラズパイ指定でデプロイした後、

赤のラズパイ指定に変更してデプロイしたときの動きを確認します。

・変更点

 nodeName: “青のラズパイのノード名”
 ↓
 nodeName: “赤のラズパイのノード名”

・結果

 指定したノードでPodが起動されるようになるのを確認できました。

LivenessProbeが失敗するときの動作を確認

LivenessProbeで、外との疎通を確認しているので、それが失敗するようになったときの動きを確認します。

動画を撮るにあたって、デフォルト設定だと待つ時間がながすぎたので、

Node Monitor Grace Period の値を変えるために、

master node (greenpi)の/etc/kubernetes/manifests/kube-controller-manager.yamlを修正したり、

LチカのManifestで、tolerationsの値を少し修正したりしましたが、待ち時間を減らすためなので省略します。

 

・変更点

 特になし

・結果

Livenessで設定している疎通ができなくなった後、

自動的に、正常なノードの方でPodが起動するようになったのを確認できました。

 

今回の動作確認は、ここまでで終了です。

普段からログなどでどういう動きをするのかわかっていたつもりですが、

Podの動きなどをLEDで可視化することで、よりわかりやすくすることができました。