自分で作ったサーバーや、パッケージマネージャでインストールしたサービスをsystemdで自動起動したとき、どうやってもネットワークのコンフィグレーション前にアプリが起動してしまうみなさん、こんにちは。
いや、わかってはるんですよ。この問題で困るということは、だいたい特定のインタフェースとかアドレスとかに依存してなにかしようみたいな筋の悪いことをやっているはずなんです。いまどき繋がったり切れたり、刺されたり引っこ抜かれたりすることを前提にしないとうまく行かないんですね。でも、現実問題としてそういうことをしたいこともあるわけですよ。それが大人ってもんです。以上、ツッコミへの予防線でした。
理想論の続きですけど、依存関係なんてものは少ないに越したことはないわけです。だから、本来はsystemdに頼って解決しないほうがいいんですよ、変な話。だからrtnetlinkを監視して、コンフィグレーションが完了していることを確認するのが安心確実です、変な話。もちろん僕はいつもそうしていますよ、えぇ、当然ですよね。でもこの手法は少々手間がかかります。もし一撃で片付けたいならIP_FREEBINDソケットオプションを使えば存在しないアドレスにバインドできます。自発的に通信するのではなく、接続を待つだけなら取れる手段かもしれません。
aptとかyumとかでパッケージを入れて設定ファイルを編集したら自動起動に失敗する、けど手動起動はうまくいく、みたいなパターンで、[YOUR_SERVICE].serviceファイルを見るとAfter=network.targetなんて書いてあったりなんかしますね。信じていたnetwork.targetに裏切られた気分を引きずってこの記事にたどり着いた方も多いことでしょう。残念ながら、network.targetは通信できるIPアドレスが存在するかどうかというようなことはなにも保証してくれません。なんのためにあるかというと、シャットダウン時にネットワークが使えなくなる前に終了されることで適切にファイナライズできるという点で有用なターゲットです。
なので、おそらく今我々が求めているのはこれです。network-online.targetです。これはネットワーク管理サービスによって、ネットワークがコンフィグレーション済みであることを保証します。ただし、たとえばDHCPでアドレスを振る設定をしているポートにアドレスが降ってこないとき、タイムアウトするまでサービスの起動が遅延します。
まず、ネットワークのコンフィグレーションを待つサービスを有効にしておきます。
sudo systemctl enable systemd-networkd-wait-online.service
もし、NetworkManagerを使っているならこれも必要かもしれません。
sudo systemctl enable NetworkManager-wait-online.service
サービスファイルを編集してAfterとWantsを追記しましょう。
$ sudo systemctl edit --full [YOUR_SERVICE].service [Unit] After=network-online.target Wants=network-online.target
network-online.targetでは救えない問題または簡単に問題を切り分けるために試したいときの手段としては、起動前に準備万端整うまでブロックするスクリプトを書くことです。そして、ExecStartの前に実行させるべくExecStartPreに書いておきましょう。ここでは、例示としてサービスの起動前にsleepを挟んでみます。
$ sudo systemctl edit --full [YOUR_SERVICE].service ~~ [Service] ExecStartPre=/bin/sleep 15
参考: https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/