AIをデーモン化しようとした戦いの記録

### はじめに
どうも。昨年の4月にプログラミング完全素人の身空で入社し、現在はAIを使った製品を開発しています。							
その中で、直近ぶつかった壁とその解決方法を書きました。
「そんなん当たり前やろ！」みたいなこともちょいちょい出てきます。

### やりたかったこと
詳しい内容は省きますが、このプロジェクトにおけるAIのタスクは物体検出です。		
送られてきた画像に対して処理を行うのですが、画像がいつやってくるかは分かりません。

そこで推論プログラムには、常に画像の有無をチェックし続けて、画像を見つけ次第推論をしてもらうのが適切だろうという結論に至りました。
「常に監視し続ける」ためには、プロセスをデーモン化する必要があったわけです。

### 環境
- EC2(Ubuntu 18.04.5 LTS)
- anaconda(コイツさえいなければもっと楽にできた気がしている)
- detectron2(正直今回は関係ないです)

デーモン化に使ったのはsystemdです。(いつかpython-daemonも使ってみたいですね。with句だけでデーモンプロセス指定できるとか素敵すぎる)


### 手順
まず、pythonコードにshebangを記述します。
最近のPythonはshebang無しでも実行できますが、デーモン化する場合はこれがないとsystemdがインタプリタを見失ってしまいます。かわいいね
shebangに書くべきインタプリタは、該当の環境をactivateした状態で```which python3```を叩けば分かるはずです。

なお、ここで改行コードをLFにしておかないと後々エラーが出ます。気をつけよう！(1敗)					


次に、Pythonコードに権限を与えてあげましょう。
個人的に権限を数字で示すこの記法は苦手です(へなちょこ)。

```sh
$ sudo chmod 0755 /home/ubuntu/hoge/hoge_detection.py
```



それから、/etc/systemd/system/hoge.service を作成します。ここが一番大事！
下記の設定だと色々と問題が出ます。ソレについては後述

```
[Unit]
Description = hoge daemonize test

[Service]
WorkingDirectory = /home/ubuntu/hoge
ExecStart = "/home/ubuntu/anaconda3/envs/hoge_env/bin/python3 /home/ubuntu/hoge/hoge_detection.py"  #問題2
Restart = always  #問題1
Type = simple

[Install]
WantedBy = multi-user.target
```

そして、あとは以下のコマンドで有効化しておしまいのはずでした。

```sh
$ sudo systemctl enable hoge
$ sudo systemctl start hoge
```



### 問題と解決法

#### 問題1：systemdが落ちる

> service starting too early

こんなメッセージが出て、プロセスを再起動できないということがありました。		
まぁ 'too early' っていうならちょっと待ってあげようかな‥‥ということで、```RestartSec = 10s```の記述を足します。
今回のプログラムでは10秒で落ちることなく再起動できましたが、各種条件によってはもっと待ってあげないと動かないかもしれません。

#### 問題2：推論プログラム内でimportError

前述のとおり、該当プログラムはanaconda環境で組んでます。
そのままだとsystemdにはcondaの仮想環境が見えません。だから、インタプリタを教えてあげる必要があったんですね。
一応前述のserviceファイル内では

```
ExecStart = "/home/ubuntu/anaconda3/envs/hoge_env/bin/python3 /home/ubuntu/hoge/hoge_detection.py"
```

こう書いてましたが、コレだと仮想環境をactivateできていないので意味がありません。まぁちょっと考えたら当たり前ですね。
[こちらの記事](https://qiita.com/tontonyoga/items/50536ddf22d740d8ecaa)を参考にして同じような書き方も試しましたが、何故か上手くいきませんでした。
仕方がないので、conda環境のパスを通すためのシェルを書きます。
といってもただ```export```書いてもエラー吐いたので、[こちら](https://qiita.com/tori_taro/items/ad3a4f488a49400fd457)を参考にさせていただきました。

```sh:hoge_daemon_test.sh
#!/bin/bash

__conda_setup="$('/home/ubuntu/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/home/ubuntu/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/home/ubuntu/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/home/ubuntu/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup

conda activate hoge_env

/home/ubuntu/anaconda3/envs/hoge_env/bin/python3 /home/ubuntu/hoge/hoge_detection.py										
```

推論プログラムを実行するところまでまとめてしまって、このシェルを```ExecStart```で指定します。

「それは挙動として正しくないんじゃないか。シェルには```conda activate```までをまとめて```ExecStartPre```で走らせて、推論部分は```ExecStart```で動かすべきでは？」

とお思いの方もいらっしゃいますよね。僕もそう思います。
ただStartPreとStartで分けると、折角通したパスや環境が推論プログラムから見えなくなってしまうみたいで‥‥。					

#### 問題3：AWSの認証エラー

此度の推論プログラムではAWSのサービスもいくつか利用していまして、認証を通らないと処理が進みません。													
手動で実行していた時は問題なく動作していたのですが、デーモン化した途端credentialsが無いよと言われました。													
もしやと思い調べてみると、案の定systemdが実行する場合はユーザが変わるようです。					
デフォルトだとsystemdはrootとして各プログラムを実行しようとするので、そこも変更してあげなければならなかったようです。

というわけで、idコマンドでユーザとグループ名を確認して追記しました。

### 最終的に落ち着いた形

```
[Unit]
Description = hoge daemonize test

[Service]
WorkingDirectory = /home/ubuntu/hoge
ExecStart = "/hoge_daemon_test.sh"
RestartSec = 10s  #追記
Restart = always
Type = simple
User = {ユーザ名}  #追記
Group = {グループ名}  #追記

[Install]
WantedBy = multi-user.target
```

### 残っている問題

これで推論プログラムはデーモン化できたのですが、実は推論部分の処理を```while True```でループさせています。(デーモン化とは)
モデルの読み込みに時間がかかるので、読み込みは実行開始時の一度だけにしたかったのです。
しかし前述の```ExecStartPre```の問題もあり、別々のプログラムにして実行タイミングを切り分けるのは少し難しいかなと。
ということで基本的にはwhileループで処理を続けるのですが、エラーなどで終了した際に自動で再起動するため~~悪魔~~守護神の力を借りています。

正直自分でも忍者のように汚いやり方だと思っているので、上手く分離できるようにもうちょっと捏ねくり回したいところです。

ここまで読んでいただき有難うございます。
以上、AIによる推論をデーモン化する方法でした。


### 参考リンク

貴重な情報を有難うございました。
[jupyterをsystemdで自動起動する](https://qiita.com/tontonyoga/items/50536ddf22d740d8ecaa)
[systemd のユニットファイルの作り方](https://tex2e.github.io/blog/linux/create-my-systemd-service)
[CommandNotFoundError: Your shell has not been properly configured to use 'conda activate'. への対処法](https://qiita.com/tori_taro/items/ad3a4f488a49400fd457)