Translate

2024年10月31日木曜日

bolt.new をローカルPC(Windows10)で動かす




生成AIによるコード生成ツールはだいぶ進化しており、
ユーザ要件を入力するとJavaScript/TypeScriptコードにしてくれる
サービスが登場し始めた。

特に、bolt.newはOSS(MITライセンス)があり
ローカルPCで動作するとのこと。

ただ生成AIモデルは外部のサービスAnthropic社のclaude-sonetを使うので
あらかじめ10ドルほど入れAPIキーを取得しておく必要がある。


Windowsでのインストール手順としては、以下の通り。

1 nvm-windowsのインストール


https://github.com/coreybutler/nvm-windows/releases からダウンロードして
nvm-setup.exeを実行する。

2 node 20.15.1 環境にする


nvm install 20.15.1
nvm use 20.15.1


node -v を実行してバージョンを確認


3 pnpm のインストール


npm install pnpm


pnpm -v を実行してバージョンを確認(9.4.0以降)

4 リポジトリのclone


適当なディレクトリで以下のコマンドを実行

git clone https://github.com/stackblitz/bolt.new.git

cd bolt.new
pnpm install


.env.local ファイルを新規作成して以下の行を記述

ANTHROPIC_API_KEY=xxxxxxxxx
VITE_LOG_LEVEL=debug


5 開発サーバ起動


pnpm run dev --host



ブラウザからhttp://localhost:5173/ を開く。

----

 

 

 


画面は英語だが、Claudeなので日本語でも動作する。

別のPCからだと以下のメッセージが出て、server設定を修正しないといけなさそうだったので、ローカルPC上にnodeをインストールして動かすのがてっとりばやい。

Failed to execute 'postMessage' on 'Worker': SharedArrayBuffer transfer requires self.crossOriginIsolated.


ただ..なかなかいいプロンプトが与えられない..

議事録アプリをつくってもらったら、画面は出たものの、OpenAIのキーがないと動かないコードをだしてきた。Claudeなのに..

 1行くらいの短いプロンプトで

「Claudeで動作するようにして」 

とか

「XXというエラーが出ました。修正して。」

を何度も繰り返していく。

まだうまく動作していないが、多分こんな感じでペアプログラミング..というより一方的にああでもないこおでもない..しながらつくっていくのが正解なんだろう。

がっつりユースケース記述みたいなものを入れるとピクリとも動かないものができたりする。

生成されるコードがWebAssemblyベース、つまりブラウザ上で動くコンテナで動作するので、TypeScript/JavaScriptコードしか対応してない。そういった制約をふまえて指示していかないとだめだ。

 

今後はGitHub Sparkもでてきそうだし、プログラマはデバッガ&テスターになっていく運命なのだろうか..


「チーズはどこへ行った」じゃないけど、あたらしいチーズを探しに行く時期かなあ..

 

2024年9月18日水曜日

既存の複数のEC2インスタンスを毎週月~金の8:58から18:02まで起動させておくCloud Formation定義ファイル

トライアンドエラーを繰り返し表題の定義ファイルをつくった。

ようやく動作したので、グログにのこしておく。

 

 忘備録として:

 

  • AWS EventBridgeを使っている(Lambdaでやる方法もある)
  • イベントバスはdefaultでないとスケジュール化できなかった
  • パラメータでInstanceIdを複数選択させるためにList<AWS::EC2::Instance::Id>を使うとStringに直接できなかったので、(負けた気がするが)Stringで書くことにした
  • 対象のEC2インスタンスはプライベートでもいいが、System Managerを使っているのでエンドポイントかNATでIGWに出ていける設定は必要(このテンプレート対象外)
  • 複数リージョンにまたがるEC2インスタンスは指定できない
  • AWS::Events::RuleにはなぜかTimeZone指定でAsia/Tokyoできなかった

 

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  CloudFormation template for EC2 start/stop scheduling using EventBridge and Systems Manager.

Parameters:
  Project:
    Type: String
    Default: "ai-agent"
    Description: "The project name."
  Environment:
    Type: String
    Default: "demo"
    AllowedValues:
      - "demo"
      - "ita"
      - "itb"
      - "st"
      - "prod"
      - "dpsc"
    Description: "The environment name."
  Version:
    Type: String
    Default: "0.0.12"
    Description: "The version of the stack."
  Instances:
    Type: String
    Description: 'The list of EC2 Instance IDs to be managed. ex) ["i-xxxxxxx", "i-yyyyyyy"]'
  StartSchedule:
    Type: String
    Default: "cron(58 23 ? * Sun-Thu *)"
    Description: "Cron expression for the EC2 start schedule (UTC)."
  StopSchedule:
    Type: String
    Default: "cron(32 8 ? * Mon-Fri *)"
    Description: "Cron expression for the EC2 stop schedule (UTC)."

Resources:

  # IAM Role for EventBridge to interact with Systems Manager
  EC2StartStopRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${Project}-${Environment}-${Version}-ec2-start-stop-role"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
      Tags:
        - Key: Project
          Value: !Ref Project
        - Key: Environment
          Value: !Ref Environment
        - Key: Version
          Value: !Ref Version

  # Start Rule for EC2
  StartRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub "${Project}-${Environment}-${Version}-ec2-start-rule"
      ScheduleExpression: !Ref StartSchedule
      EventBusName: default
      Targets:
        - Arn: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/AWS-StartEC2Instance"
          RoleArn: !GetAtt EC2StartStopRole.Arn
          Id: StartTarget
          Input: !Sub '{"InstanceIds": ${Instances}}'


  # Stop Rule for EC2
  StopRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub "${Project}-${Environment}-${Version}-ec2-stop-rule"
      ScheduleExpression: !Ref StopSchedule
      EventBusName: default
      Targets:
        - Arn: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/AWS-StopEC2Instance"
          RoleArn: !GetAtt EC2StartStopRole.Arn
          Id: StopTarget
          Input: !Sub '{"InstanceIds": ${Instances}}'


Outputs:
  StartRuleName:
    Description: "The name of the EventBridge rule for EC2 start."
    Value: !Ref StartRule
  StopRuleName:
    Description: "The name of the EventBridge rule for EC2 stop."
    Value: !Ref StopRule
  IAMRoleName:
    Description: "The IAM role used by EventBridge for EC2 start/stop automation."
    Value: !Ref EC2StartStopRole

 

ちなみにこのCloudFormation定義をつくるのにLLM Chatモデル yi-coder でやり取りをひたすらしていたが、結構粘ってもLLMは完璧な正解を返してはくれなかった(InstanceIdの配列のところは、人力で完成させざるを得なかった)。


コード専門LLMでもかけないCloudFormation..難易度高いなあ..

 

以上

2024年7月16日火曜日

AWS Glueジョブ(Python/Spark)にPythonパッケージを追加する方法

 AWS Glueジョブには2つの種類が存在します。

  • ETLジョブ(Spark)

PySparkコードが実行できるジョブ。
最低でも10並列で実行するので、DynamicFrame/DataFrame操作以外の処理はPython Shellジョブを使ったほうがよい。


  • Python Shellジョブ

通常のPythonコードを実行することのできるジョブ。



上記のジョブはともにAWSが提供する固定のコンテナイメージを使ってFaaSとして実行されます。なので、あらかじめ用意されたPythonパッケージがプリロードされた状態でコードを実行します。
コード内にプリロードされていないPythonパッケージを使う場合は、Pythonパッケージの導入を設定する必要があります。
 

AWS Glueのドキュメントにかかれている方法は以下のとおり

  • library-setを使う(Python Shell Python3.9ジョブのみ)

library-setというパラメータに"analytics"を指定すると、データ分析によく使われるパッケージがプリインストールされます(導入されるパッケージはドキュメントを参照してください)。このプリインストールパッケージで済む場合は、この方法を使ってください。

ただし、これはPython ShellジョブだけなのでETLジョブの場合は使用できません。



  • S3上にライブラリを配置してジョブから参照させる


ExtraPythonLibsS3Path/--extra-py-filesというパラメータを使うと、ジョブスクリプトファイル以外のPythonコードファイルを追加できます。このパラメータにファイルもしくはファイル群(Zip)をS3に置いて実行時参照させることができます。Zipではなくwhlファイルでも有効らしいのですが私はためしていません。そして重要なことは、純粋なPythonコードのみが対象になります。libxxxx.soなどのシェアードオブジェクトを使うPythonライブラリの場合Zipに含めても動作しませんでした(個人試行の結果です)。

ジョブ実行ロールにS3へのアクセス許可設定は必要です。


  • --additional-python-modulesを使ってPythonパッケージをpip installさせる


このオプションを使うとスクリプト実行前にpip installしてくれます。先程シェアードオブジェクトを含むzipがうまくいかないという場合は、こちらを試してみてください。ただし、AWS GlueジョブがIGW経由でPyPIにつながるVPC環境で実行される必要があります。PROXY指定しないとインターネットへ接続できない環境下のVPCの場合は失敗します。

AWS Glueジョブは公式ドキュメントどおりのセキュリティグループ設定をすると、接続するDBがある場合はそのDBがあるVPC内にコンテナが作られているかのような動作を行います。コンテナ自体のIPアドレスはAutoNumberになっていて、正直どのVPC傘下なのかわかっていませんが、おそらくAWS Glue用のプライベートVPCが暗黙にあって、そこでインスタンス化され、指定のセキュリティグループの許可する通信のみを実行するようです。そこで何も接続しない場合はよいのですがDB接続などを指定したジョブはそのDBにしかつながらなくなるようなのです。これも私個人の経験則ですけど..


  • Pythonジョブスクリプト内でpip installを実行する(Python Shellジョブのみ)


PROXY管理下のVPC環境などでpip installしたい場合は、スクリプトの中でpip install --proxy http://proxy.sample.com:8080 hogehogeするしかありません。

Python Shellジョブをlibrary-set:analyticsにして動かすとプリインストールパッケージにpipがあるので、このライブラリを使ってinstallします。


import pip

def pip_install(package:str, proxy_url=None) ->None:
    if hasattr(pip, 'main'):
        if proxy_url is None:
            pip.main(['install', package])
            #print('pip.main no_proxy')
        else:
            pip.main(['install', '--proxy', proxy_url, package])
            #print(f'pip.main proxy:{proxy_url}')
    else:
        if proxy_url is None:
            pip._internal.main(['install', package])
            #print('pip._internal.main no_proxy')
        else:
            pip._internal.main(['install', '--proxy', proxy_url, package])
            #print(f'pip._internal.main proxy:{proxy_url}')


pip_install('psycopg2-binary', 'http://proxy.sample.com:8080')



AWS Glueは提供側の想定がデータ分析が中心なので、ここまでして外部ライブラリをインストールしないといけない場合は、他の方法での実装が正しい解なのかもしれませんが..ご参考まで。

2023年3月6日月曜日

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。

 

1. The Twelve Factors

(日本語訳からの転記)
  • コードベース

  バージョン管理されている1つのコードベースと複数のデプロイ

  • 依存関係

  依存関係を明示的に宣言し分離する

  • 設定

  設定を環境変数に格納する

  • バックエンドサービス

  バックエンドサービスをアタッチされたリソースとして扱う

  • ビルド、リリース、実行

  ビルド、リリース、実行の3つのステージを厳密に分離する

  • プロセス

  アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する

  • ポートバインディング

  ポートバインディングを通してサービスを公開する 

  • 並行性

  プロセスモデルによってスケールアウトする

  • 廃棄容易性

  高速な起動とグレースフルシャットダウンで堅牢性を最大化する

  • 開発/本番一致

  開発、ステージング、本番環境をできるだけ一致させた状態を保つ

  • ログ

  ログをイベントストリームとして扱う

  • 管理プロセス

  管理タスクを1回限りのプロセスとして実行する

2. ソフトウェアデザイン2023年3月号p39-

(原則的な書きっぷりではなく、もうすこし具体性のある書き方をしている記事の要約)
 

  • アプリケーションをステートレスなプロセスとして実行する
    • セルフヒーリングなどのK8s機能が使えなくなる場合があるため
  • コンテナではないステートフルなバックエンドサービスに保存する
    • パブリッククラウドの場合、マネージドなデータストアで永続化する
    • 再起動時のコンテナでもファイル利用する場合は、永続化ボリュームをコンテナでマウントする
  • グレースフル(優雅)なシャットダウンできるようにする
    • シグナルSIGTERMの送信、HTTP GETリクエスト、任意コマンドをkubectl execなど
    • コンテナ内で独自プロセスをrunさせる場合はSIGTERMでもシャットダウンできるようにする
  • ログは標準出力・標準エラー出力に出力する
    • K8sでは永続的にログが保管できないため
    • マネージドなモニタリングサービスや、外部のGrafana Lokiなどを使ってログ集約システムを作って送信する
  • 環境変数や実行コマンド引数を用意する
    • AWS認証系ファイルの指定など環境変数は実行コマンド引数で渡せるようにしておく
    • docker run や crictl run の --env などで指定できるようにする

-----

まず市販のRDBMSとかはコンテナであまり挙げないほうがいいのだろう。うごかしてもMySQLなどで済むようなサービスレベルであればいいのだろうけど、そうでない場合はK8sサーバ外にしておくのが無難か。

あとファイルへ書き込む場合はボリュームをつかうこと、これはいわれなくてもわかる。

グレースフルなシャットダウン..Cなどで作ったプロセスだとsignalを実装しておかないと..ファビュラスでないのか..

ログはkubectl logs で見れるようにするには標準出力・エラー出力でないと見れないものなあ..ログ集約システムもコンテナ外で用意しておかないとまずそうだ..

引数やコマンドラインは..これもいわれなくてもやってそうな設計か..

なんにせよコンテナなので1コンテナ1プロセス(RUN)で基本設計して、通常のアプリはprevalegeモードを使わないで動くようにしておかないと..

昔ながらのJavaWebアプリなら、Tomcatをコンテナにして、バックエンドのDBはコンテナにしないで外だし、ラウンドロビンはK8sまかせ..かなにかコンテナをまえにかませる..とかかな.. 

がっつりDBを外だしにするなら、Kubernetesクラスタの各ノードもサービス系、運用系の2系統のLANをくんだほうがいい。Raspberry Piは幸いにしてWiFiとEthernet2つ同時に使えるので、これは簡単にできる。

ビジネスでKubernetesを本気に使う人たとはRedHatのOpenShiftでつかったりするのだろうけど、ネットワーク構成は結局シビアになるなあ..TakeOverできるように2口×2のNICを刺すんだろうか..


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をつかえたらいいんじゃとwlan0eth0両方有効にしてはじめたが、とたんにセットアップの難易度が上がってしまい、結局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にも同じ内容が出てました。

bolt.new をローカルPC(Windows10)で動かす

生成AIによるコード生成ツールはだいぶ進化しており、 ユーザ要件を入力するとJavaScript/TypeScriptコードにしてくれる サービスが登場し始めた。 特に、 bolt.new はOSS(MITライセンス)があり ローカルPCで動作するとのこと。 ただ生成AIモデルは...