Translate

2023年2月21日火曜日

Kubernetes上にCNIを実現するためのCNIプラグイン選定に悩む



初心者がKubernetes環境を構築するとき、最初にとまどうのがCNI。

複数ノード間にまたがるPod間の通信を直接行えるようにするコンセプトらしいのだけど、その実装が公式ドキュメントにもCalicoがオススメと書かれて入るものの、いろんな人の記事ではflannelもしくは導入さえしていない..

導入さえしていない記事書いてる人たちは、インストールするだけで満足してしまったのかもしれないが、Kubernetesを「使う」場合はどうしてもいれておきたい。

で、どの実装を選べばいいのか迷っていたところ、以下の記事が引っかかった。


なにやらCNIの比較を(定性評価だけど)している感じ..

なので、この記事の CNI Comparisonセクションだけ勝手に翻訳してみました。
以下参照する場合は at your own risk でお願いします。

----

CNI 比較

Flannel

CoreOSが開発したプロジェクトであるFlannelは、おそらく最もわかりやすく、人気のあるCNIプラグインです。コンテナオーケストレーションシステムのためのネットワーキングファブリックの最も成熟した例の1つで、コンテナ間およびホスト間のより良いネットワーキングを可能にすることを意図しています。CNI コンセプトが提唱され始めた初期に Flannel 用の CNI プラグインが登場しました。


Flannel は他の選択肢と比べると、インストールや設定が比較的容易です。flanneldという単一のバイナリとしてパッケージ化されており、多くの一般的なKubernetesクラスタデプロイツールや多くのKubernetesディストリビューションでデフォルトでインストールすることができます。Flannelは、Kubernetesクラスタの既存のetcdクラスタを使用して、APIを使用して状態情報を格納することで、専用のデータストアをプロビジョニングする必要を回避することができます。

Flannelはレイヤ3のIPv4オーバーレイネットワークを構成します。クラスタ内のすべてのノードにまたがる大規模な内部ネットワークが作成されます。このオーバーレイネットワーク内で、各ノードには内部でIPアドレスを割り当てるためのサブネットが与えられます。Podがプロビジョニングされると、各ノード上のDockerブリッジインターフェイスは、各新規コンテナに対してアドレスを割り当てます。同じホスト内のPodはDockerブリッジを使用して通信できますが、異なるホスト上のPodは、適切な宛先にルーティングするために、flanneldによってUDPパケットにカプセル化されたトラフィックを持つことになります。

Flannelには、カプセル化とルーティングに利用できるいくつかの異なるタイプのバックエンドがあります。デフォルトかつ推奨されるアプローチは、VXLAN を使用することです。これは、パフォーマンスが良く、他のオプションよりも手動での介入が少ないからです。

全体として、Flannel はほとんどのユーザにとって良い選択です。管理面では、シンプルなネットワーキング・モデルを提供し、基本的なことだけが必要な場合に適した環境を構築することができます。一般的には、Flannel で提供できないものが必要になるまで、Flannel を使い始めるのが無難でしょう。

Calico

Project Calico、または単にCalicoは、Kubernetesエコシステムで人気のあるもう1つのネットワークオプションです。Flannelはシンプルな選択肢として位置づけられていますが、Calicoはそのパフォーマンス、柔軟性、そしてパワーでよく知られています。Calicoはネットワークをより包括的に捉え、ホストとポッド間のネットワーク接続を提供するだけでなく、ネットワークセキュリティや管理にも関与します。Calico CNIプラグインは、Calicoの機能をCNIフレームワーク内にラップします。

システム要件を満たすプロビジョニングされたばかりのKubernetesクラスタ上で、1つのマニフェストファイルを適用することにより、Calicoを迅速にデプロイすることができるのです。Calico のオプションのネットワーク ポリシー機能に興味がある場合は、追加のマニフェストをクラスターに適用することで有効にすることができます。

Calico をデプロイするために必要なアクションはかなり単純に見えますが、Calico が作成するネットワーク環境には単純なものと複雑なものの両方の属性があります。Flannel とは異なり、Calico はオーバーレイ・ネットワークを使用しません。その代わり、Calico は BGP ルーティングプロトコルを使ってホスト間のパケットをルーティングするレイヤ3ネットワークを構成します。つまり、ホスト間を移動する際にパケットをカプセル化の余分なレイヤで包む必要がないのです。BGPルーティングメカニズムは、トラフィックを追加のレイヤで包むという余分なステップを踏むことなく、パケットをネイティブに指示することができます。

このことによるパフォーマンスに加えて、副次的な効果として、ネットワークの問題が発生したときに、より従来通りのトラブルシューティングが可能になることが挙げられます。VXLANなどの技術を使ったカプセル化されたソリューションはうまく機能しますが、このプロセスではパケットを操作するため、トレースが困難になることがあります。Calicoでは、標準的なデバッグツールが単純な環境と同じ情報にアクセスできるため、より幅広い開発者や管理者が挙動を把握しやすくなっています。

ネットワーク接続性だけでなく、Calicoは高度なネットワーク機能でもよく知られています。ネットワークポリシーは、その中でも最も求められている機能の一つです。さらに、CalicoはサービスメッシュであるIstioと統合し、サービスメッシュ層とネットワークインフラ層の両方でクラスタ内のワークロードに対するポリシーを解釈し、実施することもできます。つまり、ポッドがどのようにトラフィックを送受信できるようにすべきかを記述した強力なルールを設定し、ネットワーク環境のセキュリティと制御を向上させることができるのです。

Project Calicoは、その要件をサポートし、パフォーマンスとネットワークおよびセキュリティ・ポリシーなどの機能が重要な場合に適した環境です。さらに、Calicoは、サポート契約を求めている場合や、将来的にその選択肢を残しておきたい場合、Calico EnterpriseやCalico Cloudとして商用サポートを提供しています。一般的には、一度設定したら忘れるのではなく、ネットワークをコントロールできるようにしたい場合に適した選択肢です。Kubernetesのネットワーキングとセキュリティの詳細については、電子ブックをダウンロードしてください。

Canal

Canal は、たくさんの理由からも興味深い選択肢といえます。

まず第一に、Canal は flannel が提供するネットワーキング層と Calico のネットワーキング・ポリシー機能を統合しようとするプロジェクトの名前なのです。しかし、貢献者たちが詳細を詰めていくうちに、標準化と柔軟性を確保するために両方のプロジェクトで作業を行えば、完全な統合は必ずしも必要ではないことが明らかになりました。その結果、公式プロジェクトはやや消滅しましたが、2つの技術を一緒に展開する意図した機能は達成されました。このため、たとえプロジェクトが存在しなくなったとしても、今でもこの組み合わせを「Canal」と呼ぶのが最も簡単な場合があります。

CanalはFlannelとCalicoの組み合わせであるため、その利点もこれら2つの技術の交差点になっています。ネットワーク層はFlannelが提供するシンプルなオーバーレイで、あまり追加設定をしなくても多くの異なるデプロイメント環境で機能します。その上に重ねられたネットワークポリシー機能は、Calicoの強力なネットワークルール評価によってベースネットワークを補完し、さらなるセキュリティと制御を提供します。

クラスタが必要なシステム要件を満たしていることを確認した後、Canal は 2 つのマニフェストを適用してデプロイすることができ、どちらかのプロジェクト単体よりも構成が難しくなることはありません。Canalは、チームが実際のネットワーキングを変更する実験をする前に、ネットワークポリシーの実験を開始し、経験を積むのに良い方法です。

一般に、Canal は Flannel が提供するネットワーキング・モデルが好きで、Calico の機能のいくつかに魅力を感じている場合には良い選択となります。ネットワークポリシールールを定義できることはセキュリティの観点から非常に大きな利点であり、多くの意味で Calico のキラー機能です。この技術を使い慣れたネットワーキング・レイヤに適用できるということは、より高性能な環境を移行することなく手に入れられるということです。

Weave Net

WeaveworksのWeave Netは、Kubernetes向けのCNI対応ネットワーキング・オプションで、これまで説明してきた他の製品とは異なるパラダイムを提供します。Weave はクラスタ内の各ノード間にメッシュのオーバーレイネットワークを作成し、参加者間の柔軟なルーティングを可能にします。これは、他のいくつかのユニークな機能と相まって、Weaveは他の方法では問題が発生する可能性がある状況でもインテリジェントにルーティングすることができます。

ネットワークを構築するために、Weaveはネットワーク内の各ホストにインストールされたルーティング・コンポーネントに依存しています。これらのルータはトポロジ情報を交換し、利用可能なネットワーク環境の最新情報を維持します。別のノードにあるポッドにトラフィックを送ろうとするとき、Weave ルータは「高速データパス」で送るか「スリーブ」パケット転送方式でフォールバックするかを自動的に決定します。

高速データパスは、カーネルのネイティブなOpen vSwitchデータパスモジュールに依存し、ユーザ空間に何度も出入りすることなく、パケットを適切なポッドに転送する手法です。WeaveルータはOpen vSwitchの設定を更新し、受信パケットのルーティング方法についてカーネル層が正確な情報を持っていることを確認します。一方、スリーブモードは、ネットワークのトポロジーが高速データパス・ルーティングに適していない場合のバックアップとして利用できます。これは、高速データパスが必要なルーティング情報や接続性を持っていない場合にパケットをルーティングできる、より低速なカプセル化モードです。トラフィックがルータを流れるにつれて、ルータはどのピアがどのMACアドレスと関連しているかを学習し、後続のトラフィックに対してより少ないホップでよりインテリジェントにルーティングできるようになります。これと同じ仕組みで、ネットワークの変更によって利用可能な経路が変更された場合、各ノードが自己修正することができます。

Calicoと同様に、Weaveもクラスタにネットワークポリシー機能を提供します。これはWeaveのセットアップ時に自動的にインストールされ、設定されるので、ネットワークルールを追加する以上の追加設定は必要ありません。Weaveが提供するもののうち、他のオプションにないものの1つは、ネットワーク全体の簡単な暗号化です。これはかなりのネットワーク・オーバーヘッドを追加しますが、Weave は、スリーブ・トラフィックに NaCl 暗号を使用し、カーネルで VXLAN トラフィックを暗号化する必要があるので、高速データパス・トラフィックに IPsec ESP を使用して、すべてのルート・トラフィックを自動的に暗号化するように設定することができます。

Weave は、大量の複雑な機能や管理を追加することなく、豊富な機能を持つネットワーキングを求める人々にとって素晴らしい選択肢となります。セットアップが比較的簡単で、多くの組み込み機能や自動設定機能を備えており、他のソリューションでは失敗する可能性のあるシナリオでもルーティングを提供することができます。メッシュ構造のため、合理的に収容できるネットワークのサイズに制限がありますが、ほとんどのユーザにとって、これは問題にはならないでしょう。さらに、Weaveは、ヘルプやトラブルシューティングのための連絡先を確保したい組織のために、有償サポートを提供しています。

まとめ

KubernetesがCNI標準を採用したことで、同じエコシステムの中に多くの異なるネットワークソリューションが存在するようになりました。利用可能なオプションの多様性は、ほとんどのユーザが現在のニーズとデプロイ環境に適したCNIプラグインを見つけることができ、同時に状況が変化したときの解決策も提供できることを意味します。運用要件は組織によって大きく異なるため、複雑さと機能の豊富さのレベルが異なる成熟したソリューションを多数用意することで、Kubernetesが独自の要件を満たしながら、かなり一貫したユーザエクスペリエンスを提供できるようになります。
---

勝手な個人の感想ですが、

  • セットアップしたいだけの人は、CNIを入れない
  • ちょっと使い・学習環境程度の人は、flannel
  • 小規模ながらも実行環境で..の人でサポート無用なら Calico、BGPよりもVXLANのトラブルになれていれば Canal(かflannel)
  • 有償でもサポートは必須の人は Weave Net


といったところだろうか..


Kubernetes 完全ガイドのセットアップ章でもflannelを使っているので、とりあえず最新のKubernetesでもflannelをまず入れてみればいいか。
どうせkubectl apply でのインストールなので引数同じで kubectl deleteすればもとにもどるし..

CNIという概念を適用したところで、Kubernetesの設計を基盤屋が担わないと難しくなったような気がする。CloudStack3.xの時代だったらまだVLANくらいの知識まででなんとかなっていたけど、VXLANやBGPなんか出てきたら..もはやネットワーク屋でないとCNIプラグインの選定なんて無理じゃないだろうか..

それに..ネットワークコンテナを隠しコンテナ(?)でつくるDockerと管理用namespaceで動かすCNIプラグインをわざわざいれさせるKubernetes..DockerとKubernetesの距離が空いたのはこのあたりなのかもしれないなあ..と。本当の事情は知らないけど..

2023年2月14日火曜日

Raspberry Pi OS bullseye/64bit を使って Kubernetes クラスタを構築

 Kubernetesを勉強するためにPlay with Kubernetesを使っていたのだけど..そもそもどんな構成になっているのかが気になり、余ったRaspberry Pi4B(2GB)とRaspberry Pi3B+(1GB)×3で作ってみた。


 

先に書いておくとコントロールプレーン/マスターノードにするRaspberry Piはメモリが2GB以上ないと動作しない。実は全部3B+(1GB)で色々こねくり回してみたのだけど、kubelet を動かすところで失敗してしまった。

ただ..後述の追記を読んでもらえるとわかると思いますが、運用するとワーカーノードにした3B+はバンバン落ちる..正直flannelのノードを維持することすら..4B 4GB秋月で14,800円(2023/2/15時点)..もっと待てば下がると思うんだよなあ..まだ上がるかもしれないしなあ..試しに動かすだけなのに購入するのは..ということで、まだ購入してません

自宅でやったので、いわゆる家庭用WiFiルータを使っている。デフォルトで192.168.11.0/24ネットワークをDHCPで割り当てる設定だが、DHCP 割当対象外の IP アドレスを固定 IP として割り当て直している。

有線LANの方は使用しなかった。CloudStackの経験から2つのNICをつかえたらいいんじゃとwlan0とeth0両方有効にしてはじめたが、とたんにセットアップの難易度が上がってしまい、結局WiFiのみにしてしまった。

以下、セットアップの手順である。

動作確認は2023/2/13時点なので、最新バージョンをいれているところで、動作が変わっている場合がある。参考にする場合は注意のこと。

※上図の構成は予め組んでいる状態とします。

Raspberry Pi OS のセットアップ

  • WiFi接続したPCからpingを打ち、レスポンスのないIPアドレスをメモ
  • OSイメージを書き込んだSDカードを4枚作成
    https://www.raspberrypi.com/software/ からRaspberry Pi Imagerをダウンロード
  • SDカードを刺す(フォーマット)
  • Raspberry Pi OS Imagerを起動
  • OSイメージはRaspberry Pi OS Lite 64bitを選択

試行したバージョンは、bullseye 2022/9/22版 64bit。

  • ホスト名(node01~04)、SSH、WiFi、ロケール・キーボードを設定し書き込みを押下
  • 1台づつ書き込んだSDカードをRaspberry Pi に刺し起動、WiFi接続したPCからpingを再度打ちDHCP割り当てされたIPアドレスをメモ

Kubernetesのセットアップ

TerminalソフトウェアからメモしたIPアドレスへSSH接続し、以下の作業を実行

cgroup 有効化

sudo sed -i 's/$/ cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory/g' /boot/cmdline.txt

NTP 設定

  • sudo vi /etc/systemd/timesyncd.conf を実行し、以下の行を `[Time]` 内に記述、保存
NTP=ntp.jst.mfeed.ad.jp ntp.nict.jp
FallbackNTP=time.google.com

  • 以下のコマンドを実行

sudo timedatectl set-ntp true
sudo systemctl daemon-reload

ファイルシステム領域の拡張

sudo raspi-config  --expand-rootfs

固定IPアドレス指定

  • WiFiルータ(DHCP)から割り当てられたIPアドレスが XXX.YYY.ZZZ.AAA である場合、node01~node04までの固定IPアドレス割り当てされない範囲でアドレスを決める
  • ここでは node01 ~ node04 に XXX.YYY.ZZZ.201 ~ XXX.YYY.ZZZ.204 を割り当てることとする
  • WiFiルータのIPアドレスは XXX.YYY.ZZZ.1/24 とする
  • sudo vi /etc/dhcpcd.conf を実行し、以下のように `wlan0` 設定を変更、保存する (以下の記述は node01 の場合)

interface wlan0
static ip_address=XXX.YYY.ZZZ.201/24
static routers=XXX.YYY.ZZZ.1
static domain_name_servers=XXX.YYY.ZZZ.1

hosts 設定

  • sudo vi /etc/hosts を実行し、固定IPアドレスを記述、保存

XXX.YYY.ZZZ.201    node01
XXX.YYY.ZZZ.202    node02
XXX.YYY.ZZZ.203    node03
XXX.YYY.ZZZ.204    node04

固定IPアドレスでSSH接続

  • sudo reboot を実行し、再起動
  • Terminal ソフトウェアで、割り当てた固定IPアドレスでSSH接続

OS/パッケージ の最新化

sudo apt update && sudo apt dist-upgrade -y
sudo apt update && sudo apt upgrade -y

スワップの無効化

sudo swapoff --all
sudo systemctl stop dphys-swapfile
sudo systemctl disable dphys-swapfile

Kubernetes 事前設定

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sudo apt install -y iptables arptables ebtables
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy

sudo sysctl --system

cat <<EOF | sudo tee /etc/modules-load.d/crio.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system

OS 再起動

  • sudo rebootTerminal ソフトウェアで、割り当てた固定IPアドレスでSSH接続

CRIO のインストール

OS=Raspbian_10
VERSION=1.24
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /
EOF
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -
OS=Raspbian_11
VERSION=1.24
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list
deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /
EOF
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -

sudo apt update
sudo apt upgrade -y
sudo apt install -y cri-o cri-o-runc
sudo systemctl daemon-reload
sudo systemctl enable crio
sudo systemctl start crio

執筆時点(2023/2/13)で、Raspbean_11のcontainerdがなかった。もうできているかもしれない..

Kubernetes のインストール

sudo apt update && sudo apt install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt update
sudo apt upgrade -y
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
cat <<EOF | sudo tee /etc/default/kubelet
KUBELET_EXTRA_ARGS=--container-runtime-endpoint='unix:///var/run/crio/crio.sock'
EOF

sudo systemctl daemon-reload
sudo systemctl restart kubelet

マスターノードの設定

  • node01 にて以下のコマンドを実行

sudo kubeadm init --apiserver-advertise-address=192.168.11.201 --pod-network-cidr=10.244.0.0/16

  • 表示されている以下の箇所をメモ(以下は、実行時のコンソール表示例)

pi@node01:~ $ sudo kubeadm init --apiserver-advertise-address=192.168.11.201 --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.26.1
[preflight] Running pre-flight checks
        [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local node01] and IPs [10.96.0.1 XXX.YYY.ZZZ.201]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [localhost node01] and IPs [192.168.11.201 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [localhost node01] and IPs [192.168.11.201 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 34.004780 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node node01 as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node node01 as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: xua9wk.gwe59et1dix4dw1q
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:


  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config


Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:


kubeadm join 192.168.11.201:6443 --token xua9wk.gwe59et1dix4dw1q \
        --discovery-token-ca-cert-hash sha256:4775e46c92965e5fc383731aa58429bb1d1246af22e85768bf8474f5869a4666

config 設定

  • node01 にて以下のコマンドを実行

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

以降、node01では sudo なして kubectl が実行可能になる

ワーカーノードの設定

ワーカーノードのjoin

  • 全ワーカーノードに対して、先の kubeadm init コマンド実行結果最後に表示されたコマンドを sudo をつけて実行

sudo kubeadm join 192.168.11.201:6443 --token xua9wk.gwe59et1dix4dw1q \
        --discovery-token-ca-cert-hash sha256:4775e46c92965e5fc383731aa58429bb1d1246af22e85768bf8474f5869a4666

  • マスターノードにて kubectl get nodes を実行し、全ワーカーノードがReadyであることを確認(以下、実行例)

pi@node01:~ $ kubectl get nodes
NAME     STATUS   ROLES           AGE    VERSION
node01   Ready    control-plane   3m7s   v1.26.1
node02   Ready    <none>          111s   v1.26.1
node03   Ready    <none>          74s    v1.26.1
node04   Ready    <none>          72s    v1.26.1
pi@node01:~ $
kubectl get pod -A
NAMESPACE     NAME                             READY   STATUS    RESTARTS   AGE
kube-system   coredns-787d4945fb-6brdq         1/1     Running   0          3m38s
kube-system   coredns-787d4945fb-fm7bs         1/1     Running   0          3m38s
kube-system   etcd-node01                      1/1     Running   0          3m49s
kube-system   kube-apiserver-node01            1/1     Running   0          3m49s
kube-system   kube-controller-manager-node01   1/1     Running   0          3m53s
kube-system   kube-proxy-254ht                 1/1     Running   0          3m38s
kube-system   kube-proxy-9dtjv                 1/1     Running   0          2m38s
kube-system   kube-proxy-p9qmf                 1/1     Running   0          2m1s
kube-system   kube-proxy-q24qn                 1/1     Running   0          119s
kube-system   kube-scheduler-node01            1/1     Running   0          3m49s
pi@node01:~ $
kubectl get ns -A
NAME              STATUS   AGE
default           Active   4m12s
kube-node-lease   Active   4m14s
kube-public       Active   4m14s
kube-system       Active   4m14s
pi@node01:~ $

Flannelのインストール

  • コントロールプレーンノード(node01)上にて、以下のコマンドを実行し最新版の Flannel をデプロイする。
pi@node01:~ $ kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
namespace/kube-flannel created
serviceaccount/flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
pi@node01:~ $


PodのCIDR指定を10.244.0.0/16以外にした場合、直接kubectl applyしないで、node01上にyamlをcurl保管してネットワーク指定を変更する必要がある、とflannelドキュメントにかかれている。

 

2023/2/15 注記:

ワーカノード(Raspberry Pi 3B+)を参加(kubeadm join)させた状態で、flannelを以下の手順でkubectl applyすると、 OSが突然落ちます。kubectl deleteして、ワーカノードを再起動すると動作します。

flannelは全ワーカノードにコンテナをあげるので、おそらくメモリ1GBだとそれらを上げきることはできないのだとおもいます。

 

 

Swapの再有効化

Swapを無効化するのは、Kubernetesをインストールする際の前提となっている。しかし今回のワーカノードはRaspberry Pi 3B+なので、メモリが1GBしかない。

なのでここではワーカノードだけSwapを再有効化しておく。

  • sudo vi /var/lib/kubelet/config.yaml を実行し、最終行に以下の行を追加、保存
failSwapOn: false
  • 以下のコマンドを実行する
sudo systemctl daemon-reload
sudo systemctl enable dphys-swapfile
sudo systemctl start dphys-swapfile
sudo systemctl restart kubelet

 

2023/2/15 追記:

ワーカノードのSwapを再度有効化した状態でkubeadm joinをかけると以下のようなエラーがでて動作しませんでした。

pi@node02:~ $ sudo kubeadm join 192.168.11.201:6443 --token 0hfvvz.63g939bbbv4iuoqz \
        --discovery-token-ca-cert-hash sha256:e4fad29f4cadb1fc68464bbae4767e18766365d1fa8c46678301c26a1f0911c7
[preflight] Running pre-flight checks
        [WARNING Swap]: swap is enabled; production deployments should disable swap unless testing the NodeSwap feature gate of the kubelet
        [WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
[kubelet-check] Initial timeout of 40s passed.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.
[kubelet-check] It seems like the kubelet isn't running or healthy.
[kubelet-check] The HTTP call equal to 'curl -sSL http://localhost:10248/healthz' failed with error: Get "http://localhost:10248/healthz": dial tcp [::1]:10248: connect: connection refused.

Unfortunately, an error has occurred:
        timed out waiting for the condition

This error is likely caused by:
        - The kubelet is not running
        - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)

If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
        - 'systemctl status kubelet'
        - 'journalctl -xeu kubelet'
error execution phase kubelet-start: timed out waiting for the condition
To see the stack trace of this error execute with --v=5 or higher

このあとSwap無効化してkubeadm resetしたあと、再度kubeadm joinしたらkubectl get nodeにReadyで登場するようになりました。


以上

Dockerの場合、ネットワークを管理するコンテナを隠しでいれてくれているので、意識しなくても良かったが、Kubernetesの場合はインストールする側がきちんと選んでインストールしないといけない。ドキュメントによると、ネットワークポリシーを適用するCalicoなどがあるが、このあたりはサードパーティ製になるので、Kubernetesの責務の外というかんじのようだ。

CloudStackやOpenStack、Docker (Swarm)と学習していると、オーケストレーションを実現する思想的なものの違いがなんとなく見えてくる。こういったソフトウェアの責任範囲の切り分けがどこにあるかを見極められると、理解が少し早くなるのだと思う。

 再度書きますが、Raspberry Pi 3B+ をkubernetesクラスタに加えることは、ほぼできないと考えておいたほうが良いです。インストールはできますが、それまでです。書籍の学習でちょっとうごかしてみよう的な環境でも落ちるかもしれません。そうしたらsyslogやjouralctlとにらめっこが始まってしまい、学習は中断..という風になるでしょう。そういう場合はDocker Desktop(いつまでKubernetesが動くか..)やplay with kubernetes 環境(いつまで..再び)を使いましょう。

 

 

2023年1月26日木曜日

Play with Kubernetes 上でPodをcreateしてもPendingのままになる

 Kubernetes を勉強するとき、Docker DesktopをローカルPCにいれたり、Minikubeをインストールしたりするが、会社のルールでローカルPCにDockerや仮想マシンをインストール出来ない場合がある。

そういったときにPlay with Kubernetes https://labs.play-with-k8s.com/ を使う(
ログインするにはGitHubもしくはDockerHubのアカウントを作成しておく必要がある)。

Loginした状態になると、記事執筆時点(2023/1/26)では4時間だけ使用可能な環境が与えられる。

ADD NEW INSTANCE を押すと以下のメッセージが表示されたコンソール画面が表示される。


                          WARNING!!!!

 This is a sandbox environment. Using personal credentials
 is HIGHLY! discouraged. Any consequences of doing so, are
 completely the user's responsibilites.

 You can bootstrap a cluster as follows:

 1. Initializes cluster master node:

 kubeadm init --apiserver-advertise-address $(hostname -i) --pod-network-cidr 10.5.0.0/16
    

 2. Initialize cluster networking:

kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml


 3. (Optional) Create an nginx deployment:

 kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/application/nginx-app.yaml


                          The PWK team.


(日本語訳)
                         警告!!!!

 これはサンドボックス環境です。個人の認証情報を使用することは非常にお勧めできません。そうすることで生じるいかなる結果も、完全にユーザの責任となります。

 以下の手順でクラスタをブートストラップできます。

 1. クラスタマスタノードを初期化します。

 kubeadm init --apiserver-advertise-address $(hostname -i) --pod-network-cidr 10.5.0.0/16
    

 2. クラスタネットワーキングを初期化します。

kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml


 3.オプション)nginxのDeploymentを作成します。

 kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/application/nginx-app.yaml


                          PWKチーム

 

 

3はオプションになっているけど、nginxが動作した状態になるので、きちんとKubernetes環境がPlay with Kubernetes上に作れているかを確認する場合は3も実行する。


だがノードを1つ作成し、そのまま手順に従って1から3の順にコマンドをコピペ実行して kubectl get pod してみるが、3で作成されたはずのnginxがPendingのままになる..


これはmasterノードだけしか作成されていないためで、別途workerノードを1つ以上つくっておかないといけない。

なのでログインしたら、

  • ADD NEW INSTANCE を押し、node1を作成
  • ADD NEW INSTANCE を押し、node2を作成
  • node1のコンソール画面で1を実行(ex. kubeadm init --apiserver-advertise-address $(hostname -i) --pod-network-cidr 10.5.0.0/16)

 

:
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.0.X:6443 --token XXXX...XXX \
    --discovery-token-ca-cert-hash sha256:XXXX...XXX
Waiting for api server to startup
Warning: resource daemonsets/kube-proxy is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
daemonset.apps/kube-proxy configured
No resources found
[node1 ~]$
 

(日本語訳)

:

Kubernetesのコントロールプレーンが正常に初期化されました!

クラスタの利用を開始するには、一般ユーザで以下を実行する必要があります。

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

また、rootユーザの場合は、以下のように実行します。

  export KUBECONFIG=/etc/kubernetes/admin.conf を実行します。

これで、クラスタにポッドネットワークがデプロイされました。
kubectl apply -f [podnetwork].yaml "を、以下に記載されているいずれかのオプションで実行します。
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

次に、ワーカーノード上でrootとして以下を実行することで、任意の数のワーカーノードに参加することができます。

kubeadm join 192.168.0.X:6443 --token XXXX....XXXX \
    --discovery-token-ca-cert-hash sha256:XXXX...XXX


apiサーバーの起動を待っています
警告: リソース daemonsets/kube-proxy には、kubectl apply で必要とされる kubectl.kubernetes.io/last-applied-configuration アノテーションがありません。kubectl apply は kubectl create --save-config または kubectl apply によって declaratively に作成したリソースに対してのみ使用されるべきものです。不足するアノテーションは自動的にパッチが適用されます。
daemonset.apps/kube-proxyが設定されています。
リソースが見つかりません


 

  • 実行メッセージ内の以下の箇所をコピー


kubeadm join 192.168.0.X:6443 --token XXXXXXX...XXX \
    --discovery-token-ca-cert-hash sha256:XXXXXX....XXXX


  • node2 のコンソール画面に移動し、コピーしたkubeadmコマンドを実行

 [node2 ~]$ kubeadm join 192.168.0.X:6443 --token XXX...XXX  --discovery-token-ca-cert-hash sha256:XXXXXX....XXXXX
Initializing machine ID from random generator.
[preflight] Running pre-flight checks
        [WARNING Service-Docker]: docker service is not active, please run 'systemctl start docker.service'
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
        [WARNING FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables does not exist
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 4.4.0-210-generic
DOCKER_VERSION: 20.10.1
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUACCT: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: enabled
CGROUPS_PIDS: enabled
CGROUPS_HUGETLB: enabled
        [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 20.10.1. Latest validated version: 19.03
        [WARNING SystemVerification]: failed to parse kernel config: unable to load kernel module: "configs", output: "", err: exit status 1
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

(日本語訳)
このノードはクラスタに参加しました。
* 証明書署名要求が apiserver に送信され、応答が受信されました。
* Kubeletに新しいセキュアな接続の詳細が通知されました。

control-plane の 'kubectl get nodes' を実行して、このノードがクラスタに参加したことを確認します。

  • node1 にもどり kubectl get nodes でmaster以外のノードnode2が追加されていることを確認

このあとnode1で2と3を実行し kubectl get pod すれば Pending ではなく Ready になる。

[node1 ~]$ kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml
configmap/kube-router-cfg created
daemonset.apps/kube-router created
serviceaccount/kube-router created
clusterrole.rbac.authorization.k8s.io/kube-router created
clusterrolebinding.rbac.authorization.k8s.io/kube-router created
[node1 ~]$ kubectl get nodes
NAME    STATUS     ROLES                  AGE    VERSION
node1   NotReady   control-plane,master   12m    v1.20.1
node2   NotReady   <none>                 5m1s   v1.20.1
[node1 ~]$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/application/nginx-app.yaml
service/my-nginx-svc created
deployment.apps/my-nginx created
[node1 ~]$ kubectl get nodes
NAME    STATUS   ROLES                  AGE     VERSION
node1   Ready    control-plane,master   12m     v1.20.1
node2   Ready    <none>                 5m28s   v1.20.1
[node1 ~]$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
my-nginx-66b6c48dd5-kwx58   1/1     Running   0          34m
my-nginx-66b6c48dd5-qw9zf   1/1     Running   0          34m
my-nginx-66b6c48dd5-r7n5p   1/1     Running   0          34m
Play with Kubernetes環境は、使用中もとても遅くなることがあり、Pendingになっている時間が結構かかる場合もある。なのでゆっくりめのトイレタイムをあけて再度kubectl get podすると動いている場合がある。自分はだめな場合と判断しているのは、2トイレタイムくらい間を空けてだめなときだ。
 
Play with Kubernetes 環境はおそすぎてADD NEW INSTANCEしても無視することがある。そういった場合はセッションをCLOSEしてしばらく待ってから再度Loginしてみる。
30分後くらいには普通の動作に戻ることもあるし、1日だめなばあいもある。

 

いらいらいすることもあるけど、無料で使わせていただいている身なので、ありがたく使わせていただいている。 


ps

Speaker Deckに「Play with Kubernetesはじめにやること」というスライドを投稿しました。初心者でもできるように一挙手一投足をスライドにしています。ご笑覧ください。

 

ps2

..にしてもよく落ちる..

Killacodaのk8s playgroundにしようかな.. 

でも、こっちはexposeしたサービスを外部からみれないし、60分だし..

2022年12月29日木曜日

 国土地理院 基盤地図情報数値標高モデルをGeoJSON化する

国土地理院は、日本のすべての測量の基礎となる基本測量を行う国交省の特別機関です。
国土地理院ではいろんなデータを公開しているのですが、そのなかでも地図作成の基本となるデータ「基盤地図情報」がダウンロード可能になっています。




Google MapやYahoo地図だけでなくOpenStreetMapなどXYZタイルをWeb APIで取得できるようになっているので、正直地図をイチから作成することはあまりなかったことですが、近年の機械学習のヒットで地図情報も入力データとして扱うようになってきました。

ただ基盤地図情報からダウンロードできるデータは、GML、XML、Shape、ASCIIといった形式で、TensorFlow/PyTorchの入力データ化するには、事前の処理としてのパースがどうしても必要になります。

基盤地図情報は以下の3つの種類にわかれています。

  • 基本項目
  • 数値標高モデル
  • ジオイド・モデル


このうち、一番利用されるのが基本項目。道路や鉄道、海取り口の境界線、建物などがGML形式になっています。ダウンロードできるファイルの種類が多く、それぞれの種類で比空間データの属性がことなるため厄介です。



 

ただ、Python geopandasにあるread_file()を使えば簡単にGeoDataFrame化できるので、パースは簡単です。


 


ジオイド・モデルはXMLかASCIIかのどちらかで、どちらも独自のパースが必要ですが、ASCII形式のデータが固定長なので、比較的実装はしやすいので、こちらも問題ないとおもいます。



 

ただ、ジオイド・モデルの特性上、GPSの生データの標高補正か球体にテクスチャを貼り付けて地球の球面を表示する場合くらいにしか使い道がないので、必要になればその時実装すればいいだけだとおもいます。


 


数値標高モデルは、いわゆるDEM: Digital Elevation Model というやつで、日本の国土の地面の標高(m)を5m/10mメッシュ単位にまとめたもので、XML形式でダウンロードできます。
この数値標高モデルのXMLはGMLベースですが、標高が格納されている要素内に直列でならんでいたり、ほかの要素や属性値を使わないときちんとデータとして使用できないので、しっかり仕様を読み込んでパース処理を実装しなくてはなりません。


 


そこで、DEM(XMLファイル)を読み込み、GeoJSON化して出力する変換プログラムを作成しました。

以下のGitHubリポジトリにおいてあるコードをおきました。



まず実行環境を作成します。

conda create -n geo python numpy geopandas
conda activate geo


リポジトリのクローンします。

cd hogehoge
git clone https://github.com/coolerking/basemap
cd basemap/dem



download ファイルを作成し、基盤地図情報ダウンロードサイトから取ってきた数値標高モデルを取得、展開して対象のXMLをここに格納します。

mkdir download
# XMLファイルをdownloadディレクトリに格納する


models.py を実行します。omit_no_dataオプションをつけると、-9999.0のデータをすべて削除します。

python models.py --omit_no_data


作成されたGeoJSONはoutputディレクトリに格納されます。

GeoJSONを確認したい場合は下図のように QGIS などの統合GISソフトウェアで読み込ませるか、GeoPandasのGeoDataFrame化してから、plot()すれば表示できます。


 

 

p.s.

国土地理院基盤地図情報については国土地理院サイトもしくは、以下のスライドを参考にしてみてください。

 

複数の入手元から複数GISデータを統合してデータ分析する場合、測地座標系をあわせるという作業がとても厄介です。基盤地図情報はJGD2011v2.1(本記事執筆時点)ですが、GPSの測地系であるWGS84とは完全に一致しません。地図の世界では表示する縮尺によりこの精度を無視することがありますが、データ分析でどうするかはどのくらい誤差があるか、そもそもWGS84とはなにでJGD2011とはどうちがうのか、がわからないと正確に扱うことはできません。

まあ30年くらい前の車載GPSのように海岸線を走っているのに画面では海の上にいることになっていたりするかもですが、そのあたりどう"目をつぶるか"がGISデータの取り回しする人間の裁量になってくるのでしょう。 

2022年12月2日金曜日

Raspberry PiにUSBマイクをつけて音声異常検知をためす



 数年前からやっている Donkeycarを使った自律走行カーですが、もうすこし機能を拡張しようかなと、音声で相手の接近を検知できないかを試してみました。

音声を考えたのは現在使っている Donkeycar のカメラが前にしか向いていないので、後方からの追い抜きをブロックしたらウケるおもしろいのではないか..というのが最初の動機です。

とりあえずはいきなりDonkeycarに実装しないで、単体のRaspberry Piにマイクをつけて試してみました。

なお、今回紹介する内容の Python コードは以下のGitHubリポジトリにおいてあります。
 


環境構築

Raspberry Pi は4B/8GB を使用してます。
 

近年すっかり手に入りにくくなったRaspberry Pi、一瞬去年作った スパコンもどきで使用していたRaspberry Piを使おいかと思ったのですが、たまたまスイッチサイエンスで1箱買うことができたので、壊さずにすみました..


Raspberry Pi OSは当時最新(今でもかな) Bullseye 64bitを使っています。

今回使用したのは Amazonでみつけてきたこの安いUSBマイク。選択の理由は、自分の車体はちょうど後方にRaspberry PiのUSBコネクタが向いているので、この小ささならアクセサリのじゃまにならず欲しい方向に設置できるからです。



 

必要なライブラリのインストール


DonkeycarはそもそもPythonベースで、Donkeycarのフレームワーク上に組み込むにはPythonでPartクラスを作成することになります。

sudo apt-get install -y build-essential python3 python3-dev python3-pip python3-virtualenv python3-numpy python3-pandas python3-pillow


Donkeycar アプリケーションはvirtualenv環境下で作成するので、実際には環境を作ってその中でライブラリをセットアップしました。

また音声異常検知にはRaspberry Pi用のTensorFlow 2.8.0を使っています。whlファイルは こちら からダウンロードしました。


:
sudo apt-get install -y libhdf5-dev libc-ares-dev libeigen3-dev gcc gfortran libgfortran5 libatlas3-base libatlas-base-dev libopenblas-dev libopenblas-base libblas-dev liblapack-dev git cython3 openmpi-bin libopenmpi-dev
:
pip install keras_applications==1.0.8 --no-deps
pip install keras_preprocessing==1.1.0 --no-deps
pip install numpy==1.22.1 -U
pip install h5py==3.6.0
pip install pybind11
pip install python_speech_features
pip install six wheel mock -U
pip install sklearn
:
wget "https://raw.githubusercontent.com/PINTO0309/Tensorflow-bin/main/previous_versions/download_tensorflow-2.8.0-cp39-none-linux_aarch64_numpy1221.sh"
chmod +x ./download_tensorflow-2.8.0-cp39-none-linux_aarch64_numpy1221.sh
./download_tensorflow-2.8.0-cp39-none-linux_aarch64_numpy1221.sh
pip install tensorflow-2.8.0-cp39-none-linux_aarch64.whl
:


Pythonからこのマイクを操作できないと意味がありません。マイクから音声データを取得するPythonライブラリは PyAudio を使いました。

PyAudioは内部でPortAudioを使用しているので、こちらもインストールします。

sudo apt-get install -y libportaudio2 libportaudiocpp0 portaudio19-dev

USBマイクの検出


PyAudioからUSBマイクを操作するには、まず搭載したUSBマウスのインデックス番号を知らないといけません。

USBマウスを挿した状態で、以下のプログラムを実行して表示内容からUSBマイクの番号をメモします。

import pyaudio
audio = pyaudio.PyAudio()
for i in range(audio.get_device_count()):
    print(audio.get_device_info_by_index(i))



先のUSBマイクではありませんが、Sound Blasterを指した状態だと次のような表示がでてきます。

aultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
{'index': 1, 'structVersion': 2, 'name': 'Sound Blaster Play! 3: USB Audio (hw:1,0)', 'hostApi': 0, 'maxInputChannels': 2, 'maxOutputChannels': 2, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
:

上記の表示ならインデックス番号は `1` であることがわかります。

## 録音

まず、対象音源を録音する必要がありますが、今回はwav形式で音声データを取得しました。

定期的にwavファイルを所定のディレクトリに保管するプログラムを別プロセスで実行しておき、音声異常検知は別のプロセスが保管先の最新音声ファイルを使って判断するしくみでつくりました。


import wave
import pyaudio
:
# 配列frames データをwavファイルにして保存
wavefile = wave.open('test_10.wav','wb')
wavefile.setnchannels(1) # チャネル:1
wavefile.setsampwidth(audio.get_sample_size(pyaudio.paInt16)) # ビットレート:int16ビット
wavefile.setframerate(44100) # サンプリングレート:44100kHz



チャネルの`1`はモノラルです。ステレオにするには`2`とします。
サンプリングレートはCDと同等の音質とものの本にはかかれている 44100 kHz にしています。
ビットレートは、wavファイルに格納される要素1つのサイズですが今回はint16ビットを使用しています(参考にしたコードもこの値だったので..)。


# PyAudio インスタンス化
audio = pyaudio.PyAudio()

# 引数情報に従って、PyAudio ストリーム生成
stream = audio.open(
    format=pyaudio.paInt16, # ビットレート:int16ビット,
    rate = 44100,           # サンプリングレート:44100kHz
    channels = 1,           # チャネル:1
    input_device_index = 1, # デバイスインデックス番号:1 ← 検出した数字
    input = True,
    frames_per_buffer=4096) # チャンク:4096

# 録音秒数
record_secs = 10

# 指定秒数の音声をchunkサイズごとに取得し、配列framesへ追加
max_count = int((44100 / 4096) * record_secs)
for i in range(0, max_count):
    frames = []
    # IOError対策 exception_on_overflow=False
    frames.append(stream.read(chunk, exception_on_overflow=False))
    wavefile.writeframes(b''.join(frames))
    if i != 0 and i % 100 == 0 and debug:
        print(f'wrote {i}/{max_count} frame(s).')

# ストリームの停止およびクロース
stream.stop_stream()
stream.close()
# PyAudioインスタンスの停止
audio.terminate()


上記コードでは10秒の音声を録音しています。

長時間録音するとIOErrorがたまに出てとまるので無視して続けるように`exception_on_overflow=False`を指定しています。
 

音声異常検知


音声異常検知は、古くから研究されている領域です。なので方法もある程度先人の方が考えてくれています。

特徴抽出


まず、音声データを音響特徴量というデータに加工します。
これにより巨大な音声データを検出したい特徴をなるべくそこなわないようにかつある程度操作しやすいサイズにしています。

以下の表は日本音響学会の学会誌より一部引用したものです。




検知したい対象に合わせて音響特徴量のアルゴリズムを変えています。今回はログフィルタバンクを使用しました。


フィルタバンクというのは音響学ではよく使われる用語で、元の音声にフィルタバンク行列のドット積をとり特徴を損なわないように次元を下げる方法です。
 

上表のようにさまざまな特徴量があるります。人の感性に近い音響特徴量を得たいという方はメルフィルタバンクをよくつかうそうですが、今回は`python_speech_features`の`logfbank`を使用しました。

from python_speech_features import logfbank
:
(rate,sig) = wav.read(eval_path)
input_data = logfbank(sig,44100,winlen=0.01,nfilt=20)


分類器


音響特徴量をもとに正常か異常かを判定する機能です。
むかしはOCSVNなどの数理最適化で使用されたアルゴリズムを使用していましたが、最近は機械学習モデルを使うようになってきました。

なので、音響特徴量でごりごりにサイズを落とすことはしなくて良くなっているそうです。

今回は全結合4層で真ん中で半分の次元に落としている簡単なエンコーダデコーダ型のモデルを使いました。



このモデルは入力データと出力データが同じ形式で、入力データをそのまま復元するように学習させます。


なのでモデルの真ん中で絞らないとすぐに収束してしまいます。
ですが個のモデルでは真ん中で半分のサイズになるので、情報損失が必ず発生して完全にもとには戻せません。
なので他のモデル同様トレーニング時間は長くはないですが短くないです(私のPCだと10秒データで5~10分くらい)。

このモデルのメリットは、トレーニング用の学習データとして異常音声データをわざわざ収集しなくても良いところです。


工場とかの実際にシビアに使用される現場だと異常音を録音する機会はなかなかめぐってこないとおもいます。
先人の方々はよく考えておられます。
 

ちなみに 評価、テストのためには異常音声データは必要です。 むしろ正常動作ではないことを教えていない以上、すべての異常ケースの音声が必要になってきます。

IT企業のソリューションとして音声異常検知を掲げているところで、正常音声だけでできますとうたっている会社も一部ありますが、個人的にはそういう会社は信用しないほうがいいと思います。

モデルの実装はTensorFlow.. というかほぼ keras ですね。

from keras.layers import Dense, BatchNormalization, Activation
from keras.models import Sequential, load_model
:
input_size = 20
:
model = Sequential()
model.add(Dense(input_size,input_shape=(input_size,)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dense(int(input_size/2)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dense(input_size))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dense(input_size))
model.compile(optimizer='adam', loss='mean_squared_error')
model.summary()

損失関数

損失関数はどうするかというと、入力データとモデル出力データの距離(平均二乗誤差)をとります。
距離が0に近い、つまり入力データとほぼ同じ音響特徴量である、ということです。

算出された距離にしきい値を設けて、その値より小さい場合は「正常」、そうでない場合は「異常」と判断させます。

今回は`sklearn.metrics`の`mean_squared_error`を使用しています。

from sklearn.metrics import mean_squared_error
:
# 異常判定スコア計算
score = mean_squared_error(input_data, output_data)

 

学習データ


今回学習データとして10秒間の音声データ(wav形式)を使用しています。

wavファイルならどこで取ったものでもトレーニング用のデータとして使えますが、本番と同じ環境で収集したものが望ましいのはいうまでもありません。
 

評価データ

学習データは10秒だったのですが、評価は2秒データにしています。

Donkeycarはデフォルトで1秒間に20回ループが回るようになっています。なので音声データを2秒間取り続けるわけには行きません。なので別プロセスで音声を収集していて、Donkeycarアプリ側は最新の2秒データを評価しています。

なのでループの40回くらいは同じ音声データの結果で判断してしまいます。このあたりはしきい値を緩めて早めに検知するようにして対応することになりますね。
 

テスト


Donkeycarに実装せずにテストしたかったので、タミヤの高速・低速ギア変更可能な2輪駆動バギーをつかってテストしました。

定期的に録音した音声の異常検知スコアをFlaskでグラフ化するWebアプリを別途動かしておき、バギーを近づけたり遠ざけたりしてみました。
 

単独バギーの接近検知




バギーのギア変速検知(高速→低速)




チューニングの違うバギーを聞き分け




二輪駆動四輪駆動の聞き分け


 

上記いずれの動画では、どれも判別できている様子がわかるとおもいます。

..が、実は上記のモデルのうち「二輪駆動四輪駆動の聞き分け」で使用した分類器以外の3つのユースケースの学習データは、何も動いていない状態で録音した無音声データを使っています。

だからわざわざ機械学習モデルなんぞつかわなくてもwavファイルをそのまま評価するだけで、無音→音ありはすぐに判別できるのです。

音声異常検知モデルは、デモ検証ではいい成績を残すことが多いですが、実際の現場のデータの場合はそうはいきません。
Donkeycarレースであっても、参加者が徐々に増えるなど現場の環境音が常時変化する場合はそのたび学習し直すことになります。

現場に合わせて音響特徴量を磨く作業が必要になります。
結論を言うと、音声異常検知はDonkeycarレースのような常に環境音に変化のある場合は難しいのです。



 

超音波の送受信で距離をはかるセンサなども安く手に入るので、後方に向けて設置して於けばいいだけの話なのです。
(きちんと計測してませんが)そのほうがRaspberry Piのコンピュータリソースを食わないとおもいます。
 

【スイッチサイエンス】ベーシックモジュール用距離センサー







2022年11月28日月曜日

Tello EDU をPythonから動かそうとしたがtakeoff/landコマンドを受け付けない

DJI社というサイトから教育用ドローン Tello EDU を購入した。




TELLO EDUダウンロードサイトのSDK2.0 User Guideを読むと

  1. TELLO EDUの電源を入れる
  2. PCのWiFi設定をTELLO EDU (パスワード不要)にする(Tello EDUは192.168.10.1として接続可能に)


上記手順を行うとPCからIPアドレス192.168.10.1のポート番号8889に対してコマンドを入力できるようになるとあった。

そこで

  • command SDKモードに入る
  • streamon ビデオストリームをONにする
  • takeoff 離陸
  • land 着陸

の4つのコマンドを順番に入力したが、command と streamonはOKというレスポンスがあったが、takeoffとlandはerrorが帰ってきた。



 

Pythonプログラムで書いていたが、JavaやTelnetなどためしてもても同じ状態に。

どうも本体がtakeoff/landというコマンドが存在しないかのよう..

最新のPDFにはちゃんと書いてあるのに..


アプリからならうごくかな..と、「Tello EDU」 というiOSアプリを起動して接続しようとしたが、つながりはするしステータスも取れるが離陸・着陸はできなかった..

..いろいろ調べた挙げ句、iOSアプリ「Tello」をインストールし、携帯のWiFiをドローンにして接続し、このアプリを起動すると、アクティベーションするかというダイアログがでた..

ようは最初にiOS/Androidアプリ「Tello」でアクティベーション作業を行わないとダメなことがわかった。


commandって送れば使えるようになるんだと、ずっと思いこんでいた..

User Guide p2の章「Download the Tello App」に以下の文が書かれてました(原文英語)。


App StoreまたはGoogle Playで「Tello」を検索するか、
右のQRコードを読み込んでください。
最新版のアプリをダウンロードしてアプリをタップしてください。
TelloアプリAndroidバージョンは Android v4.4以降と互換性があります。
iOS バージョンの Tello アプリは、iOS v9.0 以降と互換性があります。



User Manual 先に読みなさいよ、というだけでした..なさけない..


2022年4月26日火曜日

Raspberry Pi4B上でPyAudioを使っていたら、途中 OSError [Errorno -9981] で落ちる

 Raspberry Pi4B にUSBマイクを刺し、

マイク入力をPython で扱いたくて PyAudio で以下のようなコードを記述した。

 

import pyaudio

# PyAudio インスタンス化
audio = pyaudio.PyAudio()

# 引数情報に従って、PyAudio ストリーム生成・開始
stream = audio.open(
format=pyaudio.paInt16, # 振幅:ビット解像度
rate = 44100, # サンプリング周波数
channels = 1, # モノラル
input_device_index = 1, #USBインデックス1にUSBマイク
input = True, # マイク入力
frames_per_buffer=4096) # チャンクサイズ:1フレーム4096バイト
stream.start_stream()

# 指定秒数の音声をchunkサイズごとに取得し、配列framesへ追加
for _ in range(0, 3600): # 6分間読み込み
# チャンクサイズ分読み込み
buf = stream.read(4096)
:

:

# クローズ
stream.stop_stream()
stream.close()
audio.terminate()  


Windows10 上で実行すると問題なく動作するのだけど、

Raspberry Pi4B(8GBmem/bullseye64bit)で動かすと、

buf = stream.read(4096) の行で

OSError: [Errorno -9981] Input overflowed

というエラーで止まってしまった。

発生する箇所は、毎回違うが4分以降5分半くらいでだいたい止まってしまう..

 

エラーの内容が Input overflowed となっており、例外が出ているので

overflow時に例外を発生させないように

buf = stream.read(4096, exception_on_overflow=False)

として実行したら発生しなくなった。


p.s.

あとで調べたら、StackOverflowにも同じ内容が出てました。

2022年4月14日木曜日

2022/4/4 版 Raspberry Pi OSからイメージコピーから直接SSH接続できなくなった

 2022/4/4 にRaspberry Pi OS 64bit版が更新された。

この更新でデフォルトで用意されていたpiアカウントがなくなった。

で、この更新で困るのがセットアップ。

 これまで

  1. SDカードをフォーマット
  2.  Image Writer でRaspberry Pi OS イメージをコピー
  3. SDカードにwpa_supplicant.confを作成し、WiFi設定を記述
  4. SDカードに空のsshファイルを作成
  5. SDカードをRaspberry Pi本体に刺してACアダプタへ接続
  6. 同じWiFiにつながっている新規デバイスをpingで総当りしてIPアドレスを特定
  7. PuttyなどからpiユーザでSSH接続

という手順でセットアップしていたが、できなくなった。


この方法を使っていたのはUSBキーボードやモニタをつながなくていいこと。

とくにRaspberry Pi 4になってからはHDMIコネクタがmicroなので、変換コネクタが必要になってめんどくさい。

なので、この方法でやっていたのだけど..

 

それにしても..みんなびっくりしてないなあ..

コロナ→ロシア情勢などで品薄が続いていて新規のRaspberry Piセットアップが途絶えているのだろうか..

 

この対処法のひとつとして、Raspberry Pi Imagerを使うこと。

 

このImagerをPCにインストールして、このアプリケーションを使ってSDカードを作成する。

 

ただし、注意が1点。

SDカードに書き込む前に歯車ボタンを押して、SSIDやデフォルトユーザ情報を設定しておくことだけ。

これでとりあえず対処した。

 

あと..これは自分だけかもしれないけど、プロクシ設定をのこしたまま、プロクシなし環境でImagerを使うとプロクシにつながらないというエラーが出る。

最近ではリモート勤務もおおくなりProxyを設定したりはずしたりして使用しているPCでは注意が必要だ。

Windows10の場合、環境変数HTTP_PROXY/HTTPS_PROXY、OS側のインターネット接続設定変更だけではだめだった。

Proxyを使ったりやめたりしている人はProxy環境でImagerを使わないといけないらしい(自分は別のPCで実行している)。

 


2022年4月6日水曜日

Raspberry Pi に接続したUSBマイクからの音声入力をpyaudioで操作する

Raspberry Piで音声を入力としたシステムを組もうとしたが、 音声出力用ジャックコネクタはあるけどマイクがないことにきづいた。

なのでUSBコネクタに「Sound Blaster Play! 3」をさしてコネクタにピンマイクをつけてみた。

このピンマイクから音声入力するとき、どうやってデバイスを認識するのか、ちょっとしらべてみた。

ここでの前提は Python プログラムで、とする。

まずPythonからこのマイクの入力を操作するには pyaudio パッケージがあるのでこれをインストールする。

ただし pyaudio は portaudio というライブラリを使っているらしいので先にapt-getしておく必要がある。

sudo apt install libportaudio0 libportaudio2 libportaudiocpp0 portaudio19-dev
pip install pyaudio


pyaudio でマイクデバイスを指定するには input_device_index が必要になる。

なので Raspberry Pi にSound Blaster Play! 3を刺して以下のコードを実行する。

import pyaudio
audio = pyaudio.PyAudio()
for i in range(audio.get_device_count()):
    print(audio.get_device_info_by_index(i))

するとずらずらっと、USB接続されたデバイスが表示される。以下実行例:

{'index': 0, 'structVersion': 2, 'name': 'bcm2835 Headphones: - (hw:0,0)', 'hostApi': 0, 'maxInputChannels': 0, 'maxOutputChannels': 8, 'defaultLowInputLatency': -1.0, 'defaultLowOutputLatency': 0.0016099773242630386, 'defaultHighInputLatency': -1.0, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
{'index': 1, 'structVersion': 2, 'name': 'Sound Blaster Play! 3: USB Audio (hw:1,0)', 'hostApi': 0, 'maxInputChannels': 2, 'maxOutputChannels': 2, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
:


上記の例だと、indexが1であるデバイスに「Sound Blaster Play! 3: USB Audio (hw:1,0)」とあるので、input_device_indexに1を指定すれば良い。

あとはいろんなブログ記事に乗っかっているサンプルを少し加工して実行してやれば良い。

import pyaudio
import wave
import sys
import time
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
CHUNK = 2**11
MIC_INDEX = 1 # ここに指定
RECORD_SECONDS = 5 # 録音秒数

try:
    WAVE_FILE = sys.argv[1]
except:
    print('no output file name')
    sys.exit(1)

audio = pyaudio.PyAudio()

frames = []

def callback(in_data, frame_count, time_info, status):
    frames.append(in_data)
    return(None, pyaudio.paContinue)
stream = audio.open(
    format=FORMAT,
    channels=CHANNELS,
    rate=RATE,
    input=True,
    input_device_index=MIC_INDEX,
    frames_per_buffer=CHUNK,
    start=False,
    stream_callback=callback
)

if __name__ == '__main__':
    stream.start_stream()
    time.sleep(RECORD_SECONDS)
    stream.stop_stream()
    stream.close()
    audio.terminate()
    wf = wave.open(WAVE_FILE, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(audio.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    wf.close()



マイクに「あー」といいながら以下のコマンドが終了するまでいいつづける。

python sample.py ah.wav


できあがったah.wavを再生し録音できていればOK。

 

#一応、Pi4/Bullseye 64bit で動作確認しました

2022年3月16日水曜日

DonkeycarにIntel(R) Realsense T265 を使った経路追跡機能がついたらしい

Donkeycarのバージョンが4.3.6になったので、すごく久しぶりに公式ドキュメントを読んでみた。

 すると

 

というページができていなので、とりあえず勝手に翻訳してみた。

 以下、翻訳した内容である。

参照される方は at your own risk でお願いします。

--------

Intel (R) Realsense T265センサによる経路追跡


Donkeycarは、標準的なカメラを使ってネットワークを学習させながら運転するのではなく、Intel(R) Realsense T265「トラッキングカメラ」を使って経路を追跡することをサポートしています。このアプリケーションでは、手動で一度だけ道を走れば、Donkeycarはその道を「記憶」し、自律的にそれを繰り返すことができます。

Intel T265は、ステレオカメラと内蔵の慣性計測ユニット(IMU)、そして独自のMyriad Xプロセッサを組み合わせて、視覚的慣性オドメトリを行います。これは、移動しながら周囲の光景を見て自分の位置を把握し、それをIMUのセンシングと関連付けることで自分の位置を特定し、GPSセンサと同様にX、Y、Z位置をドンキーに出力するという凝った方法です(ただし理想的にはもっと正確に、センチメートルの精度で出力されることが必要です)。
 

注意

Realsense T265は Nvidia Jetson Nanoでも使用できますが、Raspberry Pi(メモリ4GB以上のRPi 4を推奨)を使用すると、もう少し簡単にセットアップが可能です。

また、Intel Realsense D4XXシリーズは、通常のカメラとしてDonkeycarでも使用できますので(深度センシングデータの利用は近日中)、準備ができ次第その手順も追加します。

ステップ1:Ubuntuマシン上にLibrealsenseをセットアップ


RPi上で最新版のRaspberry OS (Busterでテスト済) を使う場合、こちら の手順でIntelのRealsenseライブラリ(Librealsense)と依存関係を設定します。これらの手順は、別のRealsenseセンサについて述べていますが、T265でも同様に動作します。また、ビデオによる説明 もあります。

ステップ2:Donkeycarのセットアップ

こちらの標準的な手順 に従ってください。経路追跡では、Tensorflowをインストールする必要はありませんが、numpy/upgradeをインストールしてから `pip install -e .[pi]` を実行します。

ステップ3:Donkeycarの経路追跡アプリを作成


donkey createcar --path ~/follow --template path_follow

ステップ4:config 設定の変更/確認


cd ~/follow
sudo nano myconfig.py


デフォルト値のまま同意するか、好みに合わせて調整してください(例:"throttle"や"steering"、PIDなど)。
変更した行をアンコメント(#を削除)します。
nano で cntrl-o を押してファイルを保存し、cntrl-x を押して終了します。

ステップ 5:Donkeycar 経路追跡アプリの実行


以下のコマンドを実行します。

ssh pi@<your pi’s IP address or "raspberrypi.local">
cd ~/follow
python3 manage.py drive


アプリの実行中に表示される出力を見るために、ターミナルを開いておいてください。

T265が見つからないというエラーが表示された場合は、センサを一旦抜き、挿し直して再試行してください。
ゲームパッドがオンになっており接続されていることも確認してください(青いランプがコントローラについています)。

起動したら、ノートパソコンでブラウザーを開き、URLバーに次のように入力します: `http://:8890`

運転すると、Webインターフェースに経路が赤い線で、ロボットの位置が緑の円で描かれます。もし、緑の点は表示されるが、赤い線が表示されない場合は、パスファイルがすでに書き込まれていることを意味します。
`donkey_path.pkl` を削除( `rm donkey_path.pkl` )、再起動すると、赤い線が表示されるはずです。

PS4 ゲームパッドの場合、次のような操作を行います:
 
control action
share toggle auto/manual mode
○ save_path
△ erase_path
× emergency_stop
L1 increase_max_throttle
R1 decrease_max_throttle
options toggle_constant_throttle
□ reset_origin
L2 dec_pid_d
left_stick_horz set_steering
right_stick_vert set_throttle

ステップ6:運転上の注意事項

  • ロボットのスタート地点に印をつけてください。スタートするたびに、必ずそこに戻してください。
  • 車をループ状に走らせます。赤い線が道を示しています。
  • PS3/4コントローラの○を押して、経路を保存します。
  • ロボットをスタート地点に戻します。
  • 次に、PS3コントローラの "select "ボタン、またはPS4コントローラの "share "ボタンを2回押し、パイロットモードにします。これで道なりに走り出します。速くしたり遅くしたりしたい場合は、`myconfig.py` ファイルの次の行を変更します:


THROTTLE_FORWARD_PWM = 400

`myconfig.py` の下の方に、微調整するための設定項目があるので確認してみてください。
PID値、マップオフセット、スケールなどです。
私のリポジトリにある `myconfig.py` をダウンロードして使うことから始めるといいかもしれません。

ヒント:

  • 起動すると、緑の点がボックスの左上隅に表示されます。中央の方がいいかもしれません。その場合は、`myconfig.py` ファイルの `PATH_OFFSET = (0, 0)` を `PATH_OFFSET = (250, 250)` に変更します。
  • 小さなコースの場合、パスが小さすぎてよく見えないということがあるかもしれません。その場合は、`PATH_SCALE = 5.0` を `PATH_SCALE = 10.0` に変更します(必要であれば、それ以上の値でもかまいません)。
  • 自動モードで実行しているときは、緑のドットが青に変わります。
  • デフォルトでは0.3mごとにパスポイントを記録するようになっています。より滑らかにしたい場合は、`myconfig.py` のこの行でより小さな数値に変更することができます: `PATH_MIN_DIST = 0.3`

-------

経路追跡なので、自動運転だけど機械学習ではなくあくまで手動運転したときの座標移動になるようにPID制御するようだ。

機械学習、AIの勉強を目的とした人は、使うことはないかもしれない。

それになによりIntel(R) Realsense T265 の販売価格が全然安くないこと。初売り価格でたしか5万円弱で、執筆時点のネット通販価格が8万円台が一般的のようだ。

Donkeycar本体が3万円くらいでつくれるので、さらに同額以上をカメラデバイスにかけるのか..と考えると、あまりやろうとする人は少ないかもなあ..

 

2022年2月10日木曜日

手っ取り早く異常検知をAIでためす方法としてPyCaretのチュートリアルAO101を翻訳してみた

 異常検知を行うAIを、誰でもかんたんに手っ取り早く実装したい。しかもOSS(ただ)で。

 という、わがままボディな要求があり、そんなものあるわけねえだろ..とおもって調べてみると..見つかった。

PyCaret というPythonパッケージだ。

PyCaret Home: https://pycaret.org/ 


どうもKaggle界では有名なAutoMLパッケージの一つで、極小の手数で複数のモデルを一気に並行評価でき、しかも(AutoMLなので)ハイパーパラメータまで勝手にチューニングしてくれるという..

 kaggleでPyCaretを使っても成績上位にはならないのだけど、どうも就職活動生は人事がわからないだろうことを見越して、「Kaggleに参加している」「成績は上位ではないが、(PyCaretを使って)12のモデルを評価してスコアを出した」とかいってごまかす定番のツールで、これを知ってる知ってないでIT企業人事部の民度が学生にバレるのだという、恐ろしや..

 

とはいえ、機械学習の勉強をあまりしなくても使えるほど、実装のための手数が少ないので、このPyCaretをためすことにした。

 

おあつらえ向きにPyCaretチュートリアルに異常検知に関するチュートリアルがあったので翻訳してみた。

以下、翻訳文であるが、参照の際は At your own riskでお願いします。

-----

異常検知チュートリアル AN0101(Anomaly Detection Tutorial ANO101) - レベル初級

  • PyCaret 2.2を使用
  • 更新日 2020年11月25日

1.0 チュートリアルの目的


異常検知チュートリアル(ANO101) へようこそ。
このチュートリアルは、あなたがPyCaretの初心者で、`pycaret.anomaly` モジュールを使って異常検知を始めようとしていることを想定しています。


このチュートリアルでは、以下のことを学びます:

  • データの取得 :PyCaret リポジトリからデータをインポートするには?
  • 環境のセットアップ :PyCaret で実験環境をセットアップして異常検知モデルの構築を始めるには?
  • モデルの作成 :モデルを作成し、分析に必要なアノマリーラベルを元のデータセットに割り当てるには?
  • モデルのプロット :様々なプロットを使ってモデルの性能を分析する方法
  • モデルによる予測 :学習されたモデルに基づいて、新しいデータセットや未知のデータセットに異常ラベルを割り当てる方法。
  • モデルの保存/読込 :モデルを保存する。将来使用するためにモデルを保存/ロードする方法


読了までの時間:約25分

1.1 PyCaret のインストール


PyCaret を使い始めるための最初のステップは、PyCaret をインストールすることです。PyCaret のインストールは簡単で、数分しかかかりません。以下の手順に従ってください。

ローカルの Jupyter Notebook に PyCaret をインストールする。

pip install pycaret

Google ColabまたはAzureノートブックへのPyCaretのインストール


!pip install pycaret

1.2 前提条件


  • Python 3.6 以上
  • PyCaret 2.0 またはそれ以上
  • PyCaret のリポジトリからデータを読み込むためのインターネット接続環境
  • 異常検知の基礎知識

1.3 Google Colaboratory 使用者への追記


Google Colabで実行している場合、インタラクティブなビジュアルを表示するために、以下のコードをノートブックの先頭で実行してください。

from pycaret.utils import enable_colab
enable_colab()

1.4 参考

Anomaly Detectiom Tutorial (ANO102) - Level Intermediate

https://github.com/pycaret/pycaret/blob/master/tutorials/Anomaly%20Detection%20Tutorial%20Level%20Intermediate%20-%20ANO102.ipynb

Anomaly Detection Tutorial (ANO103) - Level Expert

https://github.com/pycaret/pycaret/blob/master/tutorials/Anomaly%20Detection%20Tutorial%20Level%20Expert%20-%20ANO103.ipynb

訳者注:2022年2月10日時点での上記リンク先はともに未作成。

2.0 異常検知とは?


異常検知とは、データの大部分と大きく異なることによって疑いを抱かせるような、稀な項目、イベント、または観察を特定するタスクをさします。
通常、異常な項目は、銀行詐欺、構造的欠陥、医療問題、テキストの誤りなど、何らかの問題に変換されます。
異常検知技術には大きく分けて3つのカテゴリが存在します。

  • 教師なし異常検知 :ラベル付けされていないテストデータセットから、データセット内の大部分のインスタンスが正常であるという仮定のもと、残りのデータセットに最も適合しないと思われるインスタンスを探し、異常を検出する技術。
  • 教師あり異常検出 :この手法では、「正常」と「異常」のラベルが付けられたデータセットが必要で、分類器を学習させる。
  • 半教師付き異常検知:与えられた正常な学習データセットから正常な振る舞いを表すモデルを構築し、学習したモデルによって生成されるテストインスタンスの尤度をテストする手法です。


`pycaret.anomaly` モジュールは教師なしおよび教師ありの異常検知技術をサポートします。このチュートリアルでは、教師なし異常検知技術のみを取り上げます。

3.0 PyCaret 異常検知モジュール概要


PyCaret 異常検知モジュール `pycaret.anomaly` は、教師なし機械学習モジュールで、データの大部分と大きく異なることによって疑いを持たれるような、稀な項目、イベント、観測を識別するタスクを実行します。

PyCaret 異常検知モジュールは、`setup()` 関数でセットアップを初期化する際に設定できるいくつかの前処理機能を提供します。
12以上のアルゴリズムと、異常検知の結果を分析するためのいくつかのプロットを備えています。
また、PyCaret の異常検知モジュールには `tune_model()` というユニークな関数が実装されており、分類のための `AUC` や回帰のための `R2` など、教師あり学習の目的を最適化するために異常検知モデルのハイパーパラメータを調整することが可能です。

4.0 チュートリアル用データセット


このチュートリアルでは、UCI の Mice Protein Expression というデータセットを使用します。
このデータセットは、大脳皮質の核画分に検出可能なシグナルを生成した77個のタンパク質/タンパク質修飾の発現レベルから構成されています。
このデータセットには、1つのタンパク質につき合計1080の測定値が含まれています。
各測定値は、独立したサンプル/マウスとみなすことができます。
データセットの詳細については、こちらをご覧ください。

データセットへの謝辞

  • Clara Higuera インフォマティクス学部ソフトウェア工学・人工知能学科、コンプルテンセ大学化学部生化学・分子生物学科、スペイン、マドリッド( mailto:clarahiguera@ucm.es )
  • Katheleen J. Gardiner、タンパク質発現データの作成者および所有者は、現在Linda Crnic Institute for Down Syndrome, Department of Pediatrics, Department of Biochemistry and Molecular Genetics, Human Medical Genetics and Genomics, and Neuroscience Programs, University of Colorado, School of Medicine, Aurora, Colorado, USAに在籍( mailto:katheleen.gardiner@ucdenver.edu)
  • Krzysztof J. Ciosは現在、米国バージニア州リッチモンドのバージニア・コモンウェルス大学コンピューターサイエンス学部、およびポーランドのIITiSポーランド科学アカデミーに在籍。( mailto:kcios@vcu.edu )


オリジナルのデータセットとデータディクショナリーは、ここで参照可能。

5.0 データの取得


データをダウンロードし、`pandas` を使って読み込むか、PyCaret のデータレスポジトリを使って `get_data()` 関数で読み込むことができます(インターネット接続が必要)。

from pycaret.datasets import get_data
dataset = get_data('mice')


未見データに対する `predict_model()` 関数のデモを行うため、実験終了時にオリジナルデータセットから5%のサンプル(54サンプル)を取り出し、予測に使用します。
これは、train/test の分割と混同しないように注意する必要があります。
この特別な分割は、実生活のシナリオをシミュレートするために行われます。
別の見方をすれば、この 54 個のサンプルは、この実験が行われた時点では利用できません。

data = dataset.sample(frac=0.95, random_state=786)
data_unseen = dataset.drop(data.index)

data.reset_index(drop=True, inplace=True)
data_unseen.reset_index(drop=True, inplace=True)

print('Data for Modeling: ' + str(data.shape))
print('Unseen Data For Predictions: ' + str(data_unseen.shape))

6.0 PyCaretの環境設定


`setup()` 関数は、PyCaret の環境を初期化し、モデリングとデプロイのためのデータを準備する変換パイプラインを作成します。
`setup()` は PyCaret の他の関数を実行する前に呼び出す必要があります。
この関数は 1 つの必須パラメータである pandas dataframe を受け取ります。
他のパラメータはオプションで、前処理パイプラインをカスタマイズするために使用されます(後のチュートリアルで紹介します)。

`setup()` が実行されると、PyCaret の推論アルゴリズムが、特定のプロパティに基づいて全ての特徴のデータ型を自動的に推論します。
しかし、ほとんどの場合、データ型は正しく推論されますが、常にそうとは限りません。
そのため、`setup()`が実行された後、PyCaret は素性とその推定されたデータ型を含むテーブルを表示します。
この段階で、すべてのデータ型が正しく推論されているかどうかを確認し、Enter キーを押して実験を続行するか、 quit キーを押して実験を終了することができます。
PyCaret は、機械学習実験を行うために必要ないくつかの前処理を自動的に行うため、データ型 を正しく特定することは基本的に重要です。
これらの前処理タスクは、データ型ごとに異なる方法で実行されます。
そのため、データ型が正しく設定されていることが非常に重要です。

後のチュートリアルでは、`setup()` の `numeric_features` と `categorical_features` パラメータを使って PyCaret が推論したデータ型を上書きする方法を学びます。

from pycaret.anomaly import *

exp_ano101 = setup(data, normalize = True,
                   ignore_features = ['MouseID'],
                   session_id = 123)


セットアップが正常に実行されると、いくつかの重要な情報を含むグリッドが表示されます。
情報の多くは `setup()` 実行時に構築される事前処理パイプラインに関連するものです。
これらの機能の多くは、このチュートリアルの目的からは外れています。
しかし、この段階で注意すべきいくつかの重要なことがあります。

  • セッションID session_id :後々の再現性のために、すべての関数でシードとして配布される擬似乱数。`session_id` が渡されない場合、乱数は自動的に生成され、すべての関数に配布されます。本実験では、後日の再現性を考慮して、`session_id` を `123` としています。 
  • 欠測値:元データに欠損値がある場合、 `True` と表示されます。上の情報グリッドで欠損値が `True` になっているのは、データに欠損値が含まれているためで、数値特徴の場合は平均値、カテゴリー特徴の場合は定数を用いて自動的にインプットされます。代入(インピュテーション)方法は `setup()` の `numeric_imputation` と `categorical_imputation` パラメータで変更することができます。
  • オリジナルデータ:データセットの元の形状を表示します。本実験では、`(1026, 82)` で、 1026 個のサンプルと 82 個の特徴を意味しています。
  • 変換後データ :変換後のデータセットが表示されます。元のデータセット `(1026, 82)` の形状が `(1026, 91)` に変換されていることに注意してください。データセットにカテゴリ特徴(後述)が含まれているため、特徴の数が増えています。
  • 数値特徴量 :数値として推定された特徴量の数。本データセットでは、82 特徴中77特徴が数値として推定されました。
  • カテゴリ特徴量 :カテゴリ(分類)として推定された特徴の数。本データセットでは、82特徴中5特徴がカテゴリとして推論されました。また、`ignore_feature` パラメータを用いて、1つのカテゴリ特徴である `MouseID` を無視していることに注目してください。


このように、欠損値の代入(インピュテーション)やカテゴリカルエンコーディングなど、モデリングを行う上で必須となるいくつかのタスクが自動的に処理されることに注目してください。
`setup()` の他のパラメータのほとんどはオプションであり、前処理パイプラインをカスタマイズするために使用されます。
これらのパラメータはこのチュートリアルの対象外ですが、中級者、上級者になれば、もっと詳しく説明する予定です。

7.0 モデルの作成


PyCaret で異常検知モデルを作成するのは簡単で、PyCaret の supervised モジュールでモデルを作成する方法と似ています。
異常検知モデルは `create_model()` 関数を用いて作成します。
この関数では、モデルの名前という必須パラメータを文字列で受け取ります。
この関数は学習済みのモデルオブジェクトを返します。
以下の例をご覧ください:

iforest = create_model('iforest')
print(iforest)


`create_model()` で Isolation Forest モデル を作成しました。
`contamination` (混成)パラメータは `0.05` に設定されていますが、これは `create_model()` で `fraction` (割合)パラメータを渡さない場合のデフォルト値です。
`fraction` パラメータはデータセット中の外れ値の割合を決定します。
以下の例では、`0.025` の割合で1クラスサポートベクターマシンモデルを作成します。

svm = create_model('svm', fraction = 0.025)
print(svm)


`create_model()` の引数を `iforest` から `svm` に置き換えるだけで、1クラスサポートベクターマシンモデル(OCSVM)の異常検知モデルを作成することができました。
`pycaret.anomaly`モジュールでは12のモデルが用意されています。
完全なリストを見るには、`docstring` を見るか、`models` 関数を使用してください。

models()

8.0 モデルの割り当て


モデルを作成したので、データセット(1080サンプル)に異常ラベルを付与し、結果を分析したいと思います。
これを実現するには、`assign_model()` 関数を使用します。

以下の例をご覧ください:

iforest_results = assign_model(iforest)
iforest_results.head()


`Anomary` と `Anomary_Score` の2列が最後に追加されていることに注目してください。
`Anomary` 値 `0` は範囲内の値、`1` は孤立値/Anomalyを表します。
`Anomary_Score` はアルゴリズムによって計算された値で、外れ値にはより大きな異常値スコアが割り当てられます。

`iforest_results` には、`setup()` で削除した `MouseID` 特徴も含まれていることに注意してください。
これはモデルには使用されず、`assign_model()` を使用したときのみデータセットに追加されます。
次のセクションでは、`plot_model()` を使って異常検知の結果を分析する方法を見ていきます。

9.0 モデルのプロット


`plot_model()` 関数は、異常検知モデルを様々な角度から分析するために使用することができます。
この関数は,学習済みモデルオブジェクトを受け取り、プロットを返します。
以下の例を参照してください:

9.1 T分散確率的近傍埋込み法(t-SNE)

plot_model(iforest)



9.2 一様多様体近似と射影

plot_model(iforest, plot = 'umap')

METADATAファイルがないというエラーが出たが..パッケージ導入ミス?!

10.0 未知データに対する予測


`predict_model()` 関数は、新しい未閲覧のデータセットに異常ラベルを割り当てるために使用されます。
ここでは、`data_unseen` に格納されたデータを予測するために `iforest` モデルを使用します。
これは実験の最初に作成されたもので、これまでPyCaretに公開されていなかった54個の新しいサンプルが含まれています。

unseen_predictions = predict_model(iforest, data=data_unseen)
unseen_predictions.head()


`Anomary` 欄は外れ値(1=外れ値、0=非外れ値)を示しています。
`Anomary_Score` 欄はアルゴリズムによって計算された値で、外れ値にはより大きな異常値スコアが割り当てられます。
`predict_model()` 関数を使用して、学習データにラベルを付けることもできます。
以下の例を参照してください:

data_predictions = predict_model(iforest, data = data)
data_predictions.head()

11.0 モデルの保存


ここまでのチュートリアルでは、`iforest` モデルを使って未見データの外れ値ラベルを予測する実験が終了しました。
これで実験は終わりですが、まだ一つの疑問が残っています。
予測すべき新しいデータが増えたらどうなるのだろうか?
もう一度、この実験をやり直さなければならないのだろうか?
答えは「いいえ」です。

実験全体を再実行し、新しいデータに対して予測を生成するためにパイプラインを再構築する必要はありません。PyCaret の組み込み関数 `save_model()` は、変換パイプライン全体と共にモデルを保存し、再利用することができます。

save_model(iforest,'Final IForest Model 25Nov2020')

12.0 保存されたモデルのロード


保存したモデルを将来同じ環境または異なる環境でロードするには、PyCaret の `load_model()` 関数を使用し、保存したモデルを新しい未知のデータに簡単に適用して予測します。

saved_iforest = load_model('Final IForest Model 25Nov2020')

一旦モデルが環境にロードされると、同じ `predict_model()` 関数を使用して新しいデータに対して予測するためにそれを単純に使用することができます。
以下では、上記のセクション10で使用したのと同じ `data_unseen` を予測するために、ロードされたモデルを適用しています:

new_prediction = predict_model(saved_iforest, data=data_unseen)
new_prediction.head()

`unseen_prediction` と `new_prediction` の結果は同じであることに注意してください。

13.0 まとめ/次のステップへ?


このチュートリアルでは、`pycaret.anomaly` の基本的な部分のみを取り上げました。
次のチュートリアルでは、データサイエンティストなら必ず知っておくべき、機械学習パイプラインを完全にカスタマイズできる高度な前処理技術に深入りしていきます。

それでは、次回のチュートリアルでお会いしましょう。Anomaly Detection Tutorial (ANO102) - Level Intermediate へのリンクをたどってください。

------

AO102以降のリンクは、本記事執筆時点では未作成だった。

上記チュートリアルには教師なし学習でのクラスタリングしか書かれていないが、分類についても別のチュートリアル(異常検知データセットではないが)があるので、すぐに理解できると思う。

AutoMLか..むかし昭和一桁の親父が安物カメラをくれたとき、「バカ○○○だからだれでも使えるぞ」といっていたが、AutoMLがまさにそうだ。

情報という科目はAIやデータ分析のちからを養うために入試化されるそうだけど、使うほうがどんどん簡単になるので、なくてもいいかもなあ...



Google Colab相当の環境をローカルPC上のDocker Composeで実現する

 Docker Composeの有料版に、お金を払いたくない ^H^Hが使えない ため古いゲーミングPC(GeForce3080)にDocker Composeを入れてその上のコンテナでGoogle Colab相当のノート操作ができないかためしてみた: コンテナイメージとして a...