はじめに
DockerCompose は、関連するDockerイメージを複数実行できる便利なものですが、システム上の一部のサービスをdockerで運用するような場合、他のサービスと同様にOS標準のサービス管理機構で管理したくなります。
今回、RedmineのDockerイメージ(sameersbn/docker-redmine)をsystemd経由で実行できるようにしてみました。
単にsystemdで動かすだけなら難しくありませんが、具体的な運用シーンでの要求(後述)を実現するときに苦労したポイントを中心に紹介します。
何を実現したいのか
Redmineには、メールによる通知機能があります。筆者の所属する会社では Gmail を使用していますが、送信するだけでもアカウントが必要になります。送信専用の共用アカウントがありますが、そのアカウント情報はあまりばらまきたくないため、社内ネットワークにGmailにリレーできる送信専用のメールサーバをPostfixで構築し、そのメールサーバ経由でメール送信できるようにしました。
Redmineのコンテナがそのリレーメールサーバと同じホストで動いている時には、ホストの物理IPアドレス(下の図では 192.168.1.2)ではなく、DockerコンテナからみたホストのIPアドレス(172.17.0.1)を指定しなければ、メールサーバにアクセスできません。
また、このDockerホストのIPアドレス(正確にはDockerの仮想ブリッジに接続されたホスト側のIPアドレス)はDockerの設定によって変化する可能性があるため、ハードコードにしたくありません。このため、Redmineからみたホストは、IPアドレスではなくホスト名で参照できるようにすることが望ましいです。
また、使用するのは一般公開されているDockerイメージなので、極力ここに手をつけたくありません。
要件をまとめると以下のようになります。特にホストのIPアドレスを動的に取得してコンテナに渡す方法で困ったのですが、考える過程や情報原も含めて紹介したいと思います。
- 一般公開されているDockerイメージを使用する。このイメージには手をつけない
- DockerコンテナはSystemdでサービスとして実行したい
- Dockerコンテナ上から、ホストをホスト名で参照できるようにしたい
なお、Docker 本来の思想からいえば、メールサーバも Docker 上で動かしてリンクするのが筋かもしれません。
しかし、今回のように「基本はDockerは使わないが、部分的にDockerで動かしたいサービスがある」といったニーズもあるように思います。
実行環境
今回は、以下のような環境で試しました。
# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
# uname -r
3.10.0-693.2.2.el7.x86_64
# docker -v
Docker version 1.12.6, build 85d7426/1.12.6
# docker-compose -v
docker-compose version 1.14.0, build c7bdf9e
導入編
systemd について
systemd について、あまりなじみがない方もいるかもしれませんので、簡単に紹介しておきます。
systemd とは
systemd は、最近のほとんどのLinuxディストリビューションで採用されているサービス管理機構です。サーバ向けディストリビューションとしてシェアの高いRHEL(Red Hat Enterprize Linux)やCentOSでは、2014年に公開された Version7 から systemd が採用されています。
従来は、「SysV init」と呼ばれるUNIXの伝統的なサービス管理機構が多く利用されており、サービスの制御には service
コマンドが使われていました。RHEL/CentOS7 でも service
コマンドは健在ですが、内部の動きは systemd で置き換えられています。systemd では、service
コマンドに替わり、systemctl
コマンドでサービスを制御します。
Linux の古い解説書ではサービス制御に service
コマンドが使用されているので、このあたりの変化を知らないと混乱するかもしれません。
systemd の特長
systemd の大きな特長は、各サービスの依存関係(たとえば、データベースが起動していなければ、それを使用するアプリケーションは起動できない)を考慮した上で、できるかぎり並列に起動処理を行うようにしていることです。このため、RHEL/CentOS 7 以降ではOSの起動/終了がそれ以前と比較して圧倒的に速くなりました。
また、今回のようにサービスを作る側にとって大きな変更点は、サービス制御用のシェルスクリプトをいちいち作成する必要がなくなったことです。「ユニットファイル」とよばれる、iniファイル形式の定義ファイルを作成して登録するだけで、プロセスをサービスとして管理させることができるようになります。
【参考】なぜsystemdなのか? | インフラ・ミドルウェア | POSTD
systemd のデメリット
一方でデメリットもあります。サービス制御用のシェルスクリプトが排除されたことで、自由度が失われたことです。従来であればシェルスクリプトを駆使して実現できていたことが、systemdの制約で簡単に実現できないこともあります。その場合は、systemd の機能を深く調べていかねばなりません。今回詰まったのはまさにこの部分でした。
幸い、systemd は man ページのドキュメントが非常に充実しています。systemd(3) を開くと、一番最後の SEE ALSO にこれだけの man ページが紹介されています。すべて英語ですが丹念に読めば必要な情報は得られます。(私もまだすべて読んでいません)
systemd 関連の man ページ |
---|
systemd(3) |
systemd-system.conf(5) |
locale.conf(5) |
systemctl(1) |
journalctl(1) |
systemd-notify(1) |
daemon(7) |
sd-daemon(3) |
systemd.special(5) |
pkg-config(1) |
kernel-command-line(7) |
bootup(7) |
systemd.directives(7) |
Docker Compose について
Docker Composeは、Dockerのサブプロダクトの1つで、複数の関連するDockerコンテナを束ねて制御できるようにするものです。
Redmineの場合、PostgreSQLやMySQLなどのデータベースが必須です。Dockerの運用思想としては1つのコンテナで1つのアプリケーションを動かすことが基本なので、この場合、2つのコンテナを動かすことになります1。
この場合、RedmineのコンテナはPostgreSQLのコンテナのIPアドレスを知らなければアクセスすることはできません。しかし、DockerコンテナのIPアドレスは通常DHCPによって割り振られるため不定です。DockerComposeを使用すると、このように連携する複数のコンテナの起動/終了を同時に制御しつつ、「リンク機能」によって、互いのコンテナのIPアドレスを直接意識することなく互いにアクセスできるようになります。
DockerComposeが提供する機能はこれだけではありませんが、このような機能が提供されているため、複数コンテナから構成されるコンテナを実行する場合には DockerComposeを使用することが増えています。
Redmine を Docker で動かしたい理由
Redmineを自分で構築した経験があるかたはご存じかもしれませんが、RedmineはRuby、データベース、運用方法によっては Web サーバ等、依存するミドルウェアが多く、インストールが比較的大変です。特に Ruby 関連では各種ライブラリパッケージの依存もあるため、同一ホストで他にRubyを使用するアプリケーションを同居させようとすると、要求されるライブラリのバージョンが異なるなど、トラブルを多く生みます2。従来であれば、rbenvのようにRuby環境を分離するツールを併用することで乗り切っていました。
Dockerを使用すると、このような依存関係を含めてコンテナの中に閉じ込めてくれるため、ホストの環境を汚すことがありません。
今回使用する Redmine のDockerイメージは、公式のイメージではありませんが、Redmineに加えPostgreSQL(MySQLに切り替えも可能)もセットで提供されており、docker-composeの定義ファイルも提供されているため、とても手軽にRedmineを導入できます。
DockerCompose の難点
一方で、DockerComposeは systemd のようなOSのサービス管理機能とは無縁です。このため、DockerCompose で管理されるコンテナを起動するには、DockerComposeの定義ファイル(docker-compose.yml)があるディレクトリに移動するか、起動引数に docker-compose.yml のパスを指定して docker-compose
コマンドを実行するといった、独自の起動手段を取らなければなりません。
Dockerコンテナだけで構成されるシステムであればこれでも構いませんが、今回のように一部サービスだけDockerで実現するような場合、サービス管理は systemd で統一しておきたいのがシステム管理者の心境です。
RedmineをDockerComposeで起動する
前置きが長くなりましたが、具体例を説明します。ここからは、DockerとDockerComposeは既にインストールされているものとして話しを進めます。
まず、公式サイトのQuickStartを参考に docker-compose で Redmine を起動します。
# mkdir /opt/redmine
# cd /opt/redmine
# wget https://raw.githubusercontent.com/sameersbn/docker-redmine/master/docker-compose.yml
# docker-compose up -d
※ docker-composeの -d
はバックグラウンド起動するためのオプションです
このイメージでは、10083ポートでRedmineが待ち受けるようになっています。あとはファイアウォールを解放するだけで、Redmineが使えるようになります。
# firewall-cmd --add-port 10083/tcp --zone public
systemd で起動できるようにする
さて、ここからが本題。これを systemd 配下でサービスとして動くようにします。たとえば、redmine というサービス名で登録すれば、他のサービスと同様、以下のようなコマンドで制御できます。
起動
# systemctl start redmine
終了
# systemctl stop redmine
自動起動設定
# systemctl enable redmine
ログ表示
# journalctl -u redmine
ユニットファイルの作成
systemd で動かすには、ユニットファイルという定義ファイルを所定の場所に登録するだけです。
以下のように、/etc/systemd/system/サービス名.service
という名前のファイルを作成します。ユニットファイルは ini ファイル形式で、ExecStart
にサービス起動時に実行するコマンド、ExecStop
にサービス終了時に実行するコマンドを指定するだけで良いです。冒頭でも説明したとおり、これが systemd の大きなメリットです。
docker-compose
コマンドでは、 up
や down
といったサブコマンドでコンテナの制御ができますから、これらを ExecStart
や ExecStop
に指定するだけです。
[Unit]
Description=Redmine
[Service]
ExecStart=/usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml down
ExecReload=/usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml restart
Restart=always
Type=simple
[Install]
WantedBy=multi-user.target
docker-compose の -p
オプションで指定しているのは DockerComposeのプロジェクト名で、DockerComposeが起動したコンテナ名の一部として使用されます。省略時は docker-compose.yml がおかれているディレクトリの名前になりますので、ここでは変化しないように固定しておきました。(変化してもあまり困るものではありませんが。。)
-【参考】
ユニットファイルを追加/修正したときは、以下のコマンドを実行しないと systemd に認識されないので注意してください。
# systemctl daemon-reload
以上で、systemctl
コマンドでRedmineの制御ができるようになりました。実際に試してみましょう。
# systemctl start redmine
# systemctl status redmine
● redmine.service - Redmine
Loaded: loaded (/etc/systemd/system/redmine.service; enabled; vendor preset: disabled)
Active: active (running) since 水 2017-12-20 20:35:52 JST; 4s ago
Process: 21874 ExecStop=/usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml down (code=exited, status=0/SUCCESS)
Process: 22368 ExecStartPre=/bin/sh -c /bin/systemctl set-environment DOCKER_HOST_IP=`ip route | grep ' docker0 ' | awk '{ print $9 }'` (code=exited, status=0/SUCCESS)
Main PID: 22374 (docker-compose)
Memory: 37.6M
CGroup: /system.slice/redmine.service
├─22374 /usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml up
└─22376 /usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml up
ログの確認
systemdではプロセスの標準出力をそのままログとして journald で管理してくれます。今回使用しているRedmineのDockerイメージもログを標準出力に出しているので、特に何もせずに journalctl
コマンドでログを見られるようになります。
従来であればログの確認といえば、以下のように tail コマンドを使用するのが定番でした。この方法では、ログファイルの場所を知らなければ、確認することができません。
# tail -f ログファイル
journald では、以下のように journalctl
コマンドでサービス名を指定するだけで、サービスのログを見ることができます。
# journalctl -f -u サービス名
-u
オプションの引数で指定しているのがサービス名で 3 、-f
オプションはtailの同名オプションと同じ動きをします。-f
をつけなければ、デフォルトで less 等のページャ経由で表示されます。単に出力させたいだけならば、--no-pager
オプションを指定します。
私が利用したことがないのですが、Dockerでは logging driversという機能で標準出力を journald や各種ログ管理ツールに転送する機能もあるので、住み分けは考え無ければならないと思います。
systemd配下のdockerコンテナにパラメータを渡す
ここからが応用編です。
今回は、systemd でサービスとして動かしているDockerコンテナから、ホストを名前で参照させたいのでした。
たとえば、コンテナから見たホストのIPアドレスが 172.17.0.1
ならば、コンテナ側の/etc/hosts
に以下のように記述できれば、docker-host
という名前でホストにアクセスできます。
172.17.0.1 docker-host
extra_hosts で hosts ファイルを動的に設定する
DockerComposeでは、docker-compose.yml
に extra_hosts を記述すると、コンテナ起動時に/etc/hostsにホスト名とIPアドレスの対応を挿入してくれます。
・・・
extra_hosts:
- docker-host:172.17.0.1
・・・
ただし、このままでは固定値しか書き込めません。IPアドレスが変化しても大丈夫なように、環境変数から参照できるようにします。
以下のようにして、redmineサービスの定義にextra_hosts
を追加しておきます。
version: '2'
services:
postgresql:
・・・(省略)・・・
redmine:
image: sameersbn/redmine:3.4.2
depends_on:
- postgresql
extra_hosts: # ← 追加箇所
- docker-host:${DOCKER_HOST_IP} # ← 追加箇所
environment:
- TZ=Asia/Kolkata
・・・(省略)・・・
これで、docker-compose でコンテナを起動する前に、前述のワンライナーでホストのIPアドレスを調べておき、環境変数DOCKER_HOST_IPに設定するだけでOKです。いったん、systemd を挟まずに確認してみましょう。
# DOCKER_HOST_IP=172.17.0.1 docker-compose up -d
Creating network "redmine_default" with the default driver
Creating redmine_postgresql_1 ...
Creating redmine_postgresql_1 ... done
Creating redmine_redmine_1 ...
Creating redmine_redmine_1 ... done
ここでは、docker-compose コマンド実行前に DOCKER_HOST_IP=172.17.0.1
と記述していますが、コマンド実行時に一時的に環境変数を変更することができます4。
# docker exec -it redmine_redmine_1 cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.1 docker-host # ← 追加されている
172.18.0.3 989962a01424
たしかに設定されていることが確認できました。
ホストのIPアドレスを調べる
さらに、ホストのIPアドレスを動的に取得できるようにします。この方法はさまざまなところで紹介されていますが、コンテナからみたホストのIPアドレスは、ホスト側で docker0
インターフェース5のIPアドレスを抽出することで得られます。
たとえば、以下のようなワンライナーで取得できます。
ip route | grep ' docker0 ' | awk '{ print $9 }'
これをコマンド置換6で実行すれば、環境変数に代入できます。
DOCKER_HOST_IP=`ip route | grep ' docker0 ' | awk '{ print $9 }'`
(おまけ) ワンライナーの解説
たいしたワンライナーではありませんが、自分が Linux を勉強しはじめたとき、このようにいきなり出てくるワンライナーの意味がわからずに困ったことが多かったため、解説してみます。
まず、ip
コマンドでインタフェースとIPアドレスの一覧を列挙させます。
# ip route
default via 10.7.255.254 dev eno1 proto static metric 100
10.7.0.0/16 dev eno1 proto kernel scope link src 10.7.30.74 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.0.0/16 dev br-fbe3e623b303 proto kernel scope link src 172.18.0.1
172.19.0.0/16 dev br-3aae67cf78ba proto kernel scope link src 172.19.0.1
172.20.0.0/16 dev br-4e0536b6e7e4 proto kernel scope link src 172.20.0.1
192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1
欲しいのは3行目なので、これを grep で絞ります。このとき、docker01
のような文字列があったときに引っかからないように、あえてdocker0
のように前後に空白を入れています。
ip route | grep ' docker0 '
より厳密に docker0
が docker1
だったりしても引っかけられるようにするなら、正規表現にした方が良いかもしれません。
ip route | grep -E ' docker[0-9]+ ' | head -n 1
これで、求める行が得られます。
# ip route | grep -E ' docker[0-9]+ ' | head -n 1
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
この中から最後に 172.17.0.1
を awk
コマンドで抽出します。'{ print $9 }'
の部分がawkのプログラムです。入力をスペースで区切られたカラムと見なして、9番目のカラムを抽出するという命令です。
# ip route | grep -E ' docker[0-9]+ ' | head -n 1 | awk '{ print $9 }'
172.17.0.1
Systemd に対応させる
あとは systemd で起動する前に上記のワンライナーを実行すればよいように思えます。
ちょっと長いですがこんな感じです。
ExecStart=DOCKER_HOST_IP=`ip route | grep ' docker0 ' | awk '{ print $9 }'` /usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml up
しかし、これだとうまくいきません。コマンド実行前に一時的に環境変数を変更する機能は、bash などのシェルが提供している機能です。systemd の ExecStart はシェルを介さずに直接実行しているので、この機能が使えないのです。
環境変数を一時設定するには、以下のようにbashの-c
オプションでコマンド文字列を渡すことで、bash経由で実行させる必要があります。
ExecStart=/bin/bash -c "DOCKER_HOST_IP=`~` docker-compose~"
少し長いですが正確に書くと以下のようになります。
ExecStart=/bin/bash -c "DOCKER_HOST_IP=`ip route | grep ' docker0 ' | awk '{ print $9 }'` /usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml up"
さて、systemd を使用するときのハマリポイントですが、以下のように systemctl の外側で環境変数を設定しても、systemd で起動されるプロセスには引き継がれません。ユニットファイル内の環境変数は独立した空間になっている7ためです。
# DOCKER_HOST_IP=172.17.0.1 systemctl start redmine
サービスにパラメータを渡す方法を極める
この問題を解決するには、どうすればよいでしょうか。
systemd で起動されるサービスに環境変数を渡すには、以下のいずれかの手段を取ります。
- ユニットファイル内で
Environment
やEnvironmentFile
ディレクティブで文字列やファイルから渡す。(systemd.exec
のmanページを参照) - プロセスを bash 経由で起動して環境変数を設定する。(今回の方法)
-
ExecStartPre
ディレクティブでExecStart
前にコマンドを実行、systemctl
のset-environment
サブコマンドで設定する。
1. Environment
, EnvironmentFile
を使用する方法
1 番目の方法は、固定値を渡したい場合に一番簡単な方法です。たとえば、Environment
ディレクティブでは以下のように記述できます。
Environment="DOCKER_HOST_IP=172.17.0.1"
また、EnvironmentFile
ディレクティブでは、以下のようにして外部ファイルから読み込ませることもできます。(多くの標準サービスでは、この方式で利用者がパラメータを変更できるようにしています)
EnvironmentFile=/etc/sysconfig/redmine
DOCKER_HOST_IP=172.17.0.1
しかし、いずれの場合も変数展開などで値を動的に変更することはできません。man systemd.exec
で man ページを参照すると、Environment
ディレクティブの解説には以下のような記述があり、変数展開が行われないことがわかります。
Variable expansion is not performed inside the strings, however, specifier expansion is possible. The $ character has no special meaning.
(特別な展開を除き、文字列の中で変数展開は行われません。$ 文字(シェルで $FOO のように変数を表すのに使用する文字) は特別な意味を持ちません)
なお、「特別な展開」というのは、たとえば %H
でホスト名が得られるなど、ユニットファイルの中だけで利用できる識別子のことを言っています8。
2. bash を使って環境変数を設定する方法
さきほど説明した、ExecStart
ディレクティブで bash 経由で環境変数を設定しつつ、プロセスを起動する方法です。
ExecStart=/bin/bash -c "DOCKER_HOST_IP=`~` docker-compose~"
この方法はうまく行きますが、複数の環境変数を渡したいような場合、コマンドラインが長くなって可読性が下がるのが欠点です。
3. ExecStartPre
ディレクティブで set-environment
サブコマンドを使用する方法
最後に、もう少しスマートな3番目の方法を紹介します。
ユニットファイルには ExecStartPre
というディレクティブがあります。その名の通り、ExecStart
の前に実行するコマンドを記述するものです。
さきほども書きましたが、systemd にはシェルが管理するものとは別の環境変数の空間を持っています。systemd が起動するプロセスが参照できるのは、こちらの環境変数の空間のようです9 。先の Environment
や EnvironmentFile
で環境変数が設定される先もこの空間です。適切な呼び方かわかりませんが、本稿ではこれを仮に「systemd環境変数空間」と呼ぶことにします。
systemctl
の set-environment
サブコマンドを使用すると、systemd環境変数空間に値を設定することができます。たとえば、以下のような使い方です。
systemctl set-environment DOCKER_HOST_IP=172.17.0.1
これを ExecStartPre
ディレクティブでシェル経由で実行すれば、動的に取得したIPアドレスを systemd 環境変数空間に設定し、サービスに渡すことができます。なお、シェル経由で起動しているのはコマンド置換を使用するためです。
ExecStartPre=/bin/sh -c "/bin/systemctl set-environment DOCKER_HOST_IP=`ip route | grep ' docker0 ' | awk '{ print $9 }'`"
また、ExecStartPre
は ExecStart
とは違って複数記述できるため、複数の環境変数を渡したい場合はその数だけ ExecStartPre
を並べておけば良いので、ユニットファイルの見通しが良くなります。
systemctl
コマンドには、他にも show-environment
、unset-environment
、import-environment
といった systemd 環境変数空間を操作するためのサブコマンドが用意されています。詳しくは、man systemctl
で調べてみてください。
最終版のユニットファイル
以上を踏まえた、最終版のユニットファイルです。
[Unit]
Description=Redmine
[Service]
ExecStartPre=/bin/sh -c "/bin/systemctl set-environment DOCKER_HOST_IP=`ip route | grep ' docker0 ' | awk '{ print $9 }'`"
ExecStart=/usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml down
ExecReload=/usr/local/bin/docker-compose -p redmine -f /opt/redmine/docker-compose.yml restart
Restart=always
Type=simple
[Install]
WantedBy=multi-user.target
これでサービスを起動し、ホストのIPアドレスがコンテナに渡されているか確認します。
# systemctl redmine start
# docker exec -it redmine_redmine_1 cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.1 docker-host # ← 追加されている
172.18.0.3 989962a01424
これで、無事コンテナから docker-host
というホスト名で、ホスト上のメールサーバを指定できるようになりました。
まとめ
本稿では以下のようなことを紹介しました。
- systemd と DockerCompose の概要
- RedmineのようなアプリケーションをDockerで動かすと、なぜ嬉しいのか
- DockerCompose で管理されるコンテナを systemd で制御する方法
- systemd と連携するときの、Docker コンテナへのパラメータの渡し方
- systemd には独自の環境変数の空間があることに注意!
特に、Systemd から Docker コンテナにパラメータを渡す方法についてはいろいろな方法があるので、今回取り組んでみてとても勉強することができました。皆さんもぜひ試してみてください。
-
もちろんRedmineとPostgreSQLをひとまとめにしたコンテナを作ることも可能です。 ↩
-
私の偏見かもしれませんが。 ↩
-
正確にはユニット名で、
サービス名.service
としてしますが、.service
を省略しても大丈夫です。 ↩ -
よく出てくるのは
LANG=C man XXX
とかで一時的に英語版の man を参照するとかですね。 ↩ -
Dockerのネットワークを知るにあたっては、こちらのブログエントリの図が参考になりました。
docker0
はホストとコンテナを接続する仮想ブリッジで、今得ようとしているのはこの仮想ブリッジにつながったホストのIPアドレスです。Dockerのネットワークを理解するために覚えたことまとめ ↩ -
コマンド置換には、
`コマンド`
のようにコマンドをバッククォートで括る方法と、$(コマンド)
のように記述する方法の2種類があります。後者の方がより新しい方法ですが、使い分けは好みのように思えます。私はバッククォートが好みです。 ↩ -
原文が見つかりませんでしたが、systemdの仕様のようです。 ↩
-
詳細は
man systemd.unit
のTable 2. Specifiers available in unit files
に記載されています。 ↩ -
おそらく、悪意あるユーザが環境変数を勝手に変更してサービスの挙動に影響を与えないようにするためだと思います。 ↩