一連の処理をするシステムを作る際に次のようなアプローチがある。
- 一連のサービスの集まりとして全体システムを作る。
- 個々のサービスは、systemctlで制御される。
systemctl status ${Unit}
systemctl start ${Unit}
systemctl stop ${Unit}
などだ。
それらのサービスのプログラムはsudo apt install パッケージ.deb
でインストールされるように書く。
想定読者
- Linux ユーザー
- Pythonプログラマ
- 画像認識・機械学習は熟知しているが、システム全体の構築経験は少ないプログラマ
特に推奨する想定読者
- 作成しようとするシステムが未確定の要素が多い場合
- 利用しているアルゴリズムが今後差し替えになることか確実の場合
- システムをどのようにモジュールに分割しようか検討している場合
システム全体の入出力を明らかにする
- システム全体にも入出力はある。
- システム全体での要件を明確にする。
- 応答性の許容時間がある。
組み込みの分野でも処理のスループット、遅延時間の許容の範囲が存在する。
MVP(Minimum Viable Product)を考えよう
開発したものが価値をもってユーザーに届くためには、MVPを明確にすることだ。
MVPが明確にできない場合は、開発に失敗する。
必須のものと、「あったらなおいい」とを区別できなくなり
開発のリソースを無駄遣いしてしまって、開発に失敗する。
TBDを含んだ要件定義を開始しよう
- システム全体で何を実現するのかをmarkdownファイルでgitで管理しよう。
- 最新を関係者が誰でも見れるようにしよう。
- TBD(To be determined、未確定) を含む段階で書き出さないと、共有されずに、誰かの頭の中だけになってしまう。
- TBDの影響の受けない範囲:
開発に着手できる。 - TBDの影響を受ける範囲:
- TBDの欄の値を変更した場合にどのような違いを生じるのか評価に着手できる。
- その評価結果を元に関係者で、その項目への決断をできる。
システムをサービス、モジュールへと分割すること
アプリケーションを作る際に考えることは
テストされたモジュールの集まりとしてアプリケーションを実装すること
(テスト駆動開発だ。)
システムをどのようにサービスやモジュールに分割するか
- いい事例を見てまねてください。
- 巨大する単一のシステムとして設計してしまうと、作業の切り分けができません。
- 仕様に矛盾をはらんだテスト不能なシステムにはしないこと。
処理を分解することは、複雑性を減らすとともに、処理を別のプロセスとして動作させることでマルチコアの利点を活かしやすくする。
例:画像分野で使われるGtreamerは処理をパイプラインに分割することで、それぞれの処理を単純化している。
物理デバイスがある場合
- 一つの物理デバイスを専有するには、一つのサービスプログラムであって、その中の一つのモジュールの中の1つのインスタンスであるべきだ。
その物理デバイスが標準的なものではない場合
場合によっては、デバイスドライバを新規に作成する必要があるかもしれません。
しかし、それは並のpythonプログラマの範囲外です。
専門的な能力を持っている方に委ねるのがいいでしょう。
システム全体からサービスへ
システム全体:
実現したい内容全体を示す。
提供する価値は、そのシステム全体で達成されている。
-> 個々のサービスの集まりに分解
サービス:
systemctl でサービス単位による制御に分解される。
サービス間での通信(=ROSでは、ノード間の通信)
所定の手続きをすると自動で起動される対象になる。
要は、プログラムのうち自動で起動対象になっているプログラムだ。
サービスという単位に一連のシステムが分割されたことで、
巨大すぎて誰にも理解できないシステムという状況を回避できる。
別々のプログラムとなったことで、一方の改変によって、それ以外の部分に悪影響が出るリスクを減らせる。
システム全体の中での許容できる遅延時間は、システムをサービスに分解する部分の設計に関わってくる。
通信についての異なる立場
- 最新が重要
- 順序どおりが重要
作ろうとするシステムによっては、異なる立場が存在します。
最新さえあればいい立場と、順序通りに全てを処理する必要がある立場とが存在します。
あなたが組み上げようとするシステムがどちらのタイプなのかに応じて、利用する立場を選んでください。
それによって必要な通信手段が変わってきます。
データ量が多い時のデータの受け渡しの課題
- バイナリデータをテキスト化して受け渡すプロトコールだと、データの受け渡しの帯域を使いすぎる。
- 対策例:
- バイナリデータのまま受け渡す
- 帯域の削減にはYUV422などの形式もある。
サービス間でデータの共有手法の1つ 共有メモリ
Linuxのサービスとして実行しているプログラム間で、データを共有する方式の1つに共有メモリがある。
私は詳しくないので、詳細は別途調べてほしい。
サービスの起動
Ubuntuでの起動手順の中で、自動で起動されるサービスは/etc/systemd/system/
の下に書かれている。
どのようなのが登録されているのかは、次のように見ることができる。
ls /etc/systemd/system/*.service
だから、
ssh コマンドでログインする先のhostには
/etc/systemd/system/sshd.service
あって、/usr/sbin/sshd が所定のoptionでバックグラウンドで動作している。
このようにサービスは、所定の入力を受け付け、定められた何かをして結果を返している。
複雑なシステム全体は、このようなサービスへの分解されて実現されている。
システム全体をサービスに分解するとき
・どのサービスが何に対して責任を負っているのかを明確にすること。
・その分割に重なりがあったり、欠損領域を生じないように分割すること。
・接続先のデバイスを専有するようなものであるときに、つじつまがあうようにシステムを構築すること。
・別々のプログラムが、同時に同一のカメラにアクセスすることは許されない。
debパッケージを作る。
debian パッケージを作って、それをdpkg コマンドでインストールできるようにする手順が
以下の記事で書かれています。
debianパッケージを作ろう(go-bin-debのススメ)
sudo dpkg --install example-package_0.0.1_all.deb
systemd のサービスにする場合は systemd-file や preinst,postinst,prerm,postrm など活用します
そのような設定を記入したうえで、debパッケージを作ると、インストール時に
自動起動されるようにすることができます。
異常発生時の対処方法
- 異常が発生したときに、巨大なシステムの全体を停止させなくて済む(ことがある)。
- 問題を生じたサービス(=プロセス)だけを停止して、対処して再起動させることができる。
- エラーを吐いたサービスのプログラムごとにlogファイルを別にしておけば、どのサービスでエラーや警告を生じたのかが記録できる。
(標準出力や標準エラー出力は、コンソールの彼方に消え去ってしまう。)
ここでも、システムをサービスに分解した利点がでてくる。
プロセスの状況をモニタする
Grafanaやprometheusなどの枠組みがネットワークに接続にした状況で利用できる。
そうすることで、対象の機器が正常に動作しているのかを知ることができる。
log出力を用意しよう
プログラムは様々な要因で動作不良になります。
コンソールが受け付けずに強制resetをするはめになることもあります。
print文によるデバッグ目的の出力は、
役に立たないまま既に、表示画面から消えています。
対処する方法は、それまでの経過をloggerで書き出すことです。
そうすれば、再起動後にそのloggerの出力を見ることで、どこまでは動作していたのか、どういうERROR やWARNING がでていたのかを知ることができます。
サービスをどの言語で書こうが自由だ
画像認識を得意とするPython使いでも、プログラムには適材適所があることを知っている。
C++言語、Go言語、Rust言語など、そのサービスプログラムを実装するのに適した言語を使おう。