ちゃなべの備忘録

ほぼ備忘録です。

サーバーが起動した時に特定のバイナリファイルを実行したい【備忘録】

はじめに

先日簡単なマイクロサービスをdeployしたのですが、サーバーを起動した時に自動でアプリケーションが動くように設定していなかった。だからやる。

qiita.com

このタスクの学び

  • root環境とユーザー環境で環境変数が変わる
  • godotenvのLoadはカレントディレクトリ内しか読み込まない

参考

基本的にはこれを参考にします。 やってみる前にざっと読んでみました。 とても丁寧でいい記事。

qiita.com

やってみよ

ファイルの用意

2つのファイルを準備する必要があります。

  • ユニットファイル(起動時に実行される設定ファイル)
  • シェルスクリプト(ユニットファイルを通じて実行させるファイル)

しかし今回はバイナリファイルの実行のみなので、シェルスクリプトは作らない方針で行きます。

ユニットファイル

とりあえずこんな感じのユニットファイルを作った。

[Unit]
Description=Start nabelog API server

[Service]
Type=simple
ExecStart=/usr/local/src/main &
Restart=no

[Install]
WantedBy=multi-user.target

Type=simple -> プロセスの起動方法を指定している。simpleはデフォルトであり、プロセスが起動した時点で起動完了のステータスとする。 ExecStart=/usr/local/src/main & -> 起動時に実行していているコマンド。バイナリファイルをバックグラウンド実行している。 WantedBy=multi-user.target -> 起動の依存を一応表している。(一応というのは、Unitに記載するBefore・Afterで十分だからだ。)multi-user.targetは通常のサービスの実行グループだと思ってもらえれば良い。

このファイルを移動させて、権限設定を行う。 なお、フォルダ階層は/etc/systemd/system/におく。 (ユーザー自ら追加したものはここに入るらしい。)

# mv start-apiserver.service /etc/systemd/system/
# cd /etc/systemd/system/
# sudo chown root:root start-apiserver.service
# sudo chmod 644 start-apiserver.service

自動起動設定

ユニットファイルを自動起動設定にする

ユニットファイルを追加,削除,修正したら必ず以下のコマンドを実行します。 これをすることで、システムがユニットファイルを認知します。

# sudo systemctl daemon-reload
# sudo systemctl enable start-apiserver.service
Created symlink /etc/systemd/system/multi-user.target.wants/start-apiserver.service → /etc/systemd/system/start-apiserver.service.
# sudo systemctl status start-apiserver.service
○ start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: inactive (dead)

enabledとなっているので、起動待機状態になっております。

起動してみる
# sudo systemctl start start-apiserver.service
# sudo systemctl status start-apiserver.service
× start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Sat 2023-01-14 19:21:19 JST; 1min 43s ago
    Process: 580417 ExecStart=/usr/local/src/main & (code=exited, status=1/FAILURE)
   Main PID: 580417 (code=exited, status=1/FAILURE)
        CPU: 10ms

Jan 14 19:21:19 ik1-127-70159.vs.sakura.ne.jp systemd[1]: Started Start nabelog API server.
Jan 14 19:21:19 ik1-127-70159.vs.sakura.ne.jp main[580417]: 2023/01/14 19:21:19 Error loading .env file
Jan 14 19:21:19 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Main process exited, code=exited, stat>
Jan 14 19:21:19 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Failed with result 'exit-code'.

あんれ、起動してない。というか.env fileが読み込めないって言ってんな。 普通に実行してみる。

# cd /usr/local/src/
# ./main
:@tcp(:3306)/~~~~~~~
...........................

envファイルは読み込めてそうだな。だけど、dbと接続できてない。 rootじゃなくてrockyユーザーでやってみるか。

# ./main
DB接続成功
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] Loaded HTML Templates (2):
    -
    - index.html

[GIN-debug] GET    /                         --> main.main.func1 (4 handlers)
[GIN-debug] GET    /hello                    --> main.main.func2 (4 handlers)
[GIN-debug] GET    /shops                    --> main.main.func3 (4 handlers)
[GIN-debug] POST   /shops                    --> main.main.func4 (4 handlers)
[GIN-debug] GET    /shops/:id                --> main.main.func5 (4 handlers)
[GIN-debug] PATCH  /shops/:id                --> main.main.func6 (4 handlers)
[GIN-debug] DELETE /shops/:id                --> main.main.func7 (4 handlers)
[GIN-debug] GET    /influencers              --> main.main.func8 (4 handlers)
[GIN-debug] POST   /influencers              --> main.main.func9 (4 handlers)
[GIN-debug] GET    /influencers/:id          --> main.main.func10 (4 handlers)
[GIN-debug] PATCH  /influencers/:id          --> main.main.func11 (4 handlers)
[GIN-debug] DELETE /influencers/:id          --> main.main.func12 (4 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

あれ、繋がった...? じゃあ、rockyアカウントでユニットファイルを実行してみる。

# systemctl start start-apiserver.service
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ====
Authentication is required to start 'start-apiserver.service'.
Authenticating as: rocky
Password:
==== AUTHENTICATION COMPLETE ====
# systemctl status start-apiserver.service
× start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Sat 2023-01-14 19:34:36 JST; 7s ago
    Process: 580585 ExecStart=/usr/local/src/main & (code=exited, status=1/FAILURE)
   Main PID: 580585 (code=exited, status=1/FAILURE)
        CPU: 8ms

Jan 14 19:34:36 ik1-127-70159.vs.sakura.ne.jp systemd[1]: Started Start nabelog API server.
Jan 14 19:34:36 ik1-127-70159.vs.sakura.ne.jp main[580585]: 2023/01/14 19:34:36 Error loading .env file
Jan 14 19:34:36 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Main process exited, code=exited, stat>
Jan 14 19:34:36 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Failed with result 'exit-code'.

変わんね〜〜。

シェルスクリプトを通じて実行できるか確認。

作成

./main

これを実行。

# sh start-main.sh
DB接続成功
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] Loaded HTML Templates (2): - - index.html

[GIN-debug] GET / --> main.main.func1 (4 handlers) [GIN-debug] GET /hello --> main.main.func2 (4 handlers) [GIN-debug] GET /shops --> main.main.func3 (4 handlers) [GIN-debug] POST /shops --> main.main.func4 (4 handlers) [GIN-debug] GET /shops/:id --> main.main.func5 (4 handlers) [GIN-debug] PATCH /shops/:id --> main.main.func6 (4 handlers) [GIN-debug] DELETE /shops/:id --> main.main.func7 (4 handlers) [GIN-debug] GET /influencers --> main.main.func8 (4 handlers) [GIN-debug] POST /influencers --> main.main.func9 (4 handlers) [GIN-debug] GET /influencers/:id --> main.main.func10 (4 handlers) [GIN-debug] PATCH /influencers/:id --> main.main.func11 (4 handlers) [GIN-debug] DELETE /influencers/:id --> main.main.func12 (4 handlers) [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details. [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default [GIN-debug] Listening and serving HTTP on :8080

おお、起動した。 じゃあ、やっぱりshellスクリプトを実行する形にするか。

シェルスクリプトの準備

ファイルの作成

シェルスクリプト自体はさっきのファイル。 一応改めて書いておく。

#!/bin/sh
./main &
権限の付与

権限付与して、確認する。

# sudo chown root:root start-main.sh
# sudo chmod 755 start-main.sh
# ll
drwxr-xr-x 2 rocky rocky     4096 Dec 24 17:46 config
-rw-r--r-- 1 rocky rocky     1212 Jan  2 15:09 go.mod
-rw-r--r-- 1 rocky rocky    11547 Jan  2 15:09 go.sum
-rwxr-xr-x 1 rocky rocky 15576907 Jan  2 15:09 main
-rw-r--r-- 1 rocky rocky     6376 Jan  2 15:09 main.go
drwxr-xr-x 2 rocky rocky     4096 Dec 24 16:25 mock
drwxr-xr-x 2 rocky rocky     4096 Dec 24 16:25 model
-rwxr-xr-x 1 root  root        19 Jan 14 19:55 start-main.sh
drwxr-xr-x 2 rocky rocky     4096 Dec 24 16:25 templates

試しに実行
# sudo sh start-main.sh
.....................

多分なんだけど、root権限で実行しようとするとエラー起こるわ。 root以外の権限で実行してみると、envファイルを読み込めてないわ。

。。。。。あーーーーーーー! root環境に環境変数を置いてないからかも。 確認。

# echo $NABELOG_ENV
production
# su -
Password:
Last login: Sat Jan 14 19:02:09 JST 2023 on pts/2
Last failed login: Sat Jan 14 20:04:50 JST 2023 from ~~~~ on ssh:notty
There were 298 failed login attempts since the last successful login.
# echo $NABELOG_ENV

# 

その通りすぎた。ということで、root環境にも環境変数を導入してあげましょ。

++ export NABELOG_ENV="production"

反映と確認。

# source ~/.bashrc
# echo $NABELOG_ENV
production

いいね、そしたら実行してみよう。

# sudo sh start-main.sh
.....................

だめなんかい!!! ようわからんかったけど、まぁ実行ユーザー変えれば良いかな。

ユニットファイルの実行ユーザーを変更

systemdの.serviceファイルで、実行ユーザーを指定する - Qiita

これを参考にしてやってみる。 変更後のファイルがこちら。 UserとGroupを足しました。

[Unit]
Description=Start nabelog API server

[Service]
Type=simple
ExecStart=/usr/local/src/main &
Restart=no
User=rocky
Group=rocky

[Install]
WantedBy=multi-user.target

じゃあ、ユニットファイルを反映して実行。

# sudo systemctl reset-failed start-apiserver.service
# sudo systemctl start start-apiserver.service
# sudo systemctl status start-apiserver.service
× start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Sat 2023-01-14 20:27:18 JST; 10s ago
    Process: 581608 ExecStart=/usr/local/src/main & (code=exited, status=1/FAILURE)
   Main PID: 581608 (code=exited, status=1/FAILURE)
        CPU: 9ms

Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: Started Start nabelog API server.
Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp main[581608]: 2023/01/14 20:27:18 Error loading .env file
Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Main process exited, code=exited, stat>
Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Failed with result 'exit-code'.

なんやねん!!!!

ユニットファイルからシェルスクリプトを実行

以下のように変更

#!/bin/sh
cd /usr/local/src/
./main &
[Unit]
Description=Start nabelog API server
After=local-ts.target
ConditionPathExists=/opt/nabelog/

[Service]
Type=simple
Restart=no
User=rocky
Group=rocky
ExecStart=/opt/nabelog/start-main.sh
StandardOutput=journal+console

[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl reset-failed start-apiserver.service
$ sudo systemctl start start-apiserver
$ sudo systemctl status start-apiserver.service
× start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Sat 2023-01-14 20:27:18 JST; 10s ago
    Process: 581608 ExecStart=/usr/local/src/main & (code=exited, status=1/FAILURE)
   Main PID: 581608 (code=exited, status=1/FAILURE)
        CPU: 9ms

Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: Started Start nabelog API server.
Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp main[581608]: 2023/01/14 20:27:18 Error loading .env file
Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Main process exited, code=exited, stat>
Jan 14 20:27:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Failed with result 'exit-code'.

なんかエラーは出ないけど、Deactivatedになって、何もされない。 せめてログに出てほしいなぁ。 別のコマンドでログを出してみるも

$ journalctl -xeu start-apiserveer.service
~
~
-- No entries --

何もでねぇ〜〜。 これを参考にちょっと変えてみるか。

systemdので起動したアプリケーションのprintfを標準出力に表示したい | Armadillo

[Unit]
Description=Start nabelog API server
After=local-ts.target
ConditionPathExists=/opt/nabelog/

[Service]
Type=simple
Restart=no
User=rocky
Group=rocky
ExecStart=/opt/nabelog/start-main.sh
StandardOutput=tty

[Install]
WantedBy=multi-user.target

結論、でねぇ。 journalctlでログを探ってみてるが、なんとも手がかりがない。げせぬ。

ユニットファイルの実行対象をmainに戻してもう一度考察

そしたらちゃんと前のエラーログも出た。よかった。 そして、エラーログを見たら新たな事実がわかった。

  1. やっぱり環境変数を読み込めていない
  2. カレントディレクトリ以外のenvファイルをgodotenvで読み込めていない。

調べてみたらやっぱりそう。

systemd のユーザーインスタンスは .bashrc などに設定された環境変数を全く継承しません。
参考: systemd/ユーザー - ArchWiki

いやしろよww

そしたら、以下の方針。

  • systemdでも環境変数を読み込むようにする
  • ユニットファイルを変更

systemdでも環境変数を読み込むようにする

参考

systemdで環境変数を読み込む - Qiita

以下を作成

NABELOG_ENV="production"

そして、ユニットファイルを編集

++ EnvironmentFile=/etc/sysconfig/nabelog_env

リロードして、実行。

$ sudo systemctl status start-apiserver.service
× start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Sat 2023-01-21 18:31:18 JST; 7s ago
    Process: 710112 ExecStart=/usr/local/src/main & (code=exited, status=1/FAILURE)
   Main PID: 710112 (code=exited, status=1/FAILURE)
        CPU: 8ms

Jan 21 18:31:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: Started Start nabelog API server.
Jan 21 18:31:18 ik1-127-70159.vs.sakura.ne.jp main[710112]: 2023/01/21 18:31:18 Error loading .env.production file
Jan 21 18:31:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Main process exited, code=exited, status=1/FAILURE
Jan 21 18:31:18 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Failed with result 'exit-code'.

おおーーー。環境変数読み込めてる〜〜!

ユニットファイルを編集

じゃあ、ユニットファイルからcdファイルも実行するようにする。 けど、これみる限りシェル実行でいけるっぽいな、やってみよ。

[CentOS7] systemdにサービスを登録して、サーバ起動時に自動でサービスを立ち上げる | RE:ENGINES

うーん、ダメだな。できなかった。 そしたら、シェル実行はやめて、コマンド自体をユニットファイルに書こう。

 [Unit]
Description=Start nabelog API server

[Service]
Type=simple
ExecStart=cd /usr/local/src; /usr/local/src/main &
Restart=no
User=rocky
Group=rocky

[Install]
WantedBy=multi-user.target
$ sudo systemctl status start-apiserver.service
× start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Sat 2023-01-21 22:44:32 JST; 6s ago
    Process: 713420 ExecStart=cd /usr/local/src/ && /usr/local/src/main & (code=exited, status=1/FAILURE)
   Main PID: 713420 (code=exited, status=1/FAILURE)
        CPU: 2ms

Jan 21 22:44:32 ik1-127-70159.vs.sakura.ne.jp systemd[1]: Started Start nabelog API server.
Jan 21 22:44:32 ik1-127-70159.vs.sakura.ne.jp cd[713420]: /usr/bin/cd: line 2: cd: too many arguments
Jan 21 22:44:32 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Main process exited, code=exited, status=1/FAILURE
Jan 21 22:44:32 ik1-127-70159.vs.sakura.ne.jp systemd[1]: start-apiserver.service: Failed with result 'exit-code'.

cdがダメっぽいな。 なんか、WorkingDirectoryで設定できるっぽい。

[Unit]
Description=Start nabelog API server

[Service]
WorkingDirectory=/usr/local/src/
EnvironmentFile=/etc/sysconfig/nabelog_env
Type=simple
ExecStart=/usr/local/src/main &
Restart=no
User=rocky
Group=rocky
StandardOutput=journal+console

[Install]
WantedBy=multi-user.target

じゃあ、実行。

$ sudo systemctl status start-apiserver.service
● start-apiserver.service - Start nabelog API server
     Loaded: loaded (/etc/systemd/system/start-apiserver.service; enabled; vendor preset: disabled)
     Active: active (running) since Sat 2023-01-21 22:50:23 JST; 17min ago
   Main PID: 713575 (main)
      Tasks: 3 (limit: 2696)
     Memory: 8.7M
        CPU: 36ms
     CGroup: /system.slice/start-apiserver.service
             └─713575 /usr/local/src/main "&"

Jan 21 22:50:23 ik1-127-70159.vs.sakura.ne.jp main[713575]: [GIN-debug] PATCH /influencers/:id --> main.main.func11 (4 handlers) Jan 21 22:50:23 ik1-127-70159.vs.sakura.ne.jp main[713575]: [GIN-debug] DELETE /influencers/:id --> main.main.func12 (4 handlers) Jan 21 22:50:23 ik1-127-70159.vs.sakura.ne.jp main[713575]: [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. Jan 21 22:50:23 ik1-127-70159.vs.sakura.ne.jp main[713575]: Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details. Jan 21 22:50:23 ik1-127-70159.vs.sakura.ne.jp main[713575]: [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default Jan 21 22:50:23 ik1-127-70159.vs.sakura.ne.jp main[713575]: [GIN-debug] Listening and serving HTTP on :8080 Jan 21 22:50:43 ik1-127-70159.vs.sakura.ne.jp main[713575]: DB接続成功

へ!!!でけた!!!!wwwwww WorkingDirectoryを設定すればよかったのか。 enable設定もして、掃除して終わろう。

$ sudo systemctl enable start-apiserver.service
$ rm -rf /opt/nabelog/

確認作業

さくらインターネットでサーバーを再起動してみましょ。 サーバーをシャットダウンして、また起動してみる。 順番は、client → db → serverの順番にしましょう。

client起動

おおーー、おk

db起動

ちゃんと接続できるからok

server起動

データがちゃんと取得できる!!

よし、done。