Apache HTTP Server(以降Apache)をコンテナで稼働させたい場合の、コンテナ作成のコツを紹介します。
最近リバースプロキシコンテナをApacheで作成する機会があり、その時の経験から記事にしてみました。
なお、ApacheはDockerhubにて公式コンテナイメージが提供されており、こちらを使用すれば、以下のコツのうちの
1.ログを標準(エラー)出力に出す
2.Apache起動方法:daemon化しない
については対応済みで特段の検討は不要です(考え方は知っておいて損はありません)
1.ログを標準(エラー)出力に出す
Apacheは何も考えなければローカルディスクにアクセスログとエラーログを出すようになっています。このままコンテナ化すると、コンテナ内に入らないとログを見れないし、誰もログローテーションもしないままコンテナ内でログが肥大化し続ける事になってしまいます。
コンテナのログといえば、標準(エラー)出力に出して、コンテナ実行環境(ホスト)側に任せるというのが第一の選択肢です。
Apacheの公式コンテナも以下の通り設定ファイルで、エラーログは標準エラー出力に、アクセスログは標準出力に設定されています。
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 common
2.Apache起動方法:daemon化しない
Apacheの起動方法といえば多くの人が思い浮かべるのが
# apachectl start
が有名です。このコマンドはApacheを、daemon(デーモン)として起動します。何しろApacheの実行バイナリ名はhttpd
と"d"が付くぐらいですので、ある意味当たり前の話です。
しかしコンテナで動かしたいプログラムがdaemon化しては困るので、daemon化しないでApacheを動かす必要があります。(コンテナの起動コマンドをsleep infinity
にした上で裏でdaemon起動するようなことは止めましょう)
ではApacheを非daemonで起動する方法は?というと、以下のコマンドを実行しましょう。
# apachectl -DFOREGROUND
この-D
は様々なパラメータをApacheに与えるオプションです。FOREGROUND
は、daemon化しないで起動するためのパラメータです。1
試してみたい場合は、Apacheがインストールされた環境で、上記コマンドを実行してみて下さい。基本的にはあまり何も出ず、Apacheが実行状態になると思います。終了したい場合はCtrl+Cキーで終了できます。
apachectl start
でdaemon化起動した場合は以下の動画の通り、起動コマンドは即座に終了し、バックグラウンドでApacheが動作しています。
apachectl -DFOREGROUND
で非daemon起動した場合は以下の動画の通り、起動コマンドは終了せずそのままApacheそのものになります。終了するにはCtrl+Cを押します。
よって、Apacheを動かすコンテナでは、Dockerfileに以下のように書くのが良いでしょう。
ENTRYPOINT ["/usr/local/bin/apachectl","-DFOREGROUND"]
または起動用シェルスクリプトを用意し、最後に
exec /usr/local/bin/apachectl -DFOREGROUND "$@"
のように書きます。
なお、
# httpd -DFOREGROUND
を紹介する情報も多いかと思います。httpd
コマンドでも概ね結果は同じで大抵問題ありませんが、apachectl
の場合はhttpd
やapachectl
が置かれているディレクトリのenvvars
ファイルを読み込む処理などをしてからhttpd
コマンドを実行するという違いがあります。
Apache公式コンテナイメージでは当然、非daemonで起動されますが、apachectl
ではなくhttpd
を実行しています。
https://github.com/docker-library/httpd/blob/cd9f3170df90ef341c9c27fb4d17ffccd60b4ac0/2.4/httpd-foreground
3.環境変数やコマンドライン引数を活用する
頻繁に変わる値は環境変数やコマンドライン引数で外部から注入することで、汎用的にコンテナイメージを作成することが出来ます。
Apacheの場合は特に、以下のような方法で活用する事が出来ます。
3.1.環境変数値を設定ファイルで使用する
Apacheは環境変数値を設定ファイルで取り込むことが出来るので、設定ファイルを文字列置換して生成するなどの処理は不要です。
具体的には、${環境変数名}
の形式で設定ファイルに書くことで、起動時の環境変数値を使用することが出来ます。2
例えばポート番号を環境変数CONTAINER_PORT
とした場合は、以下のように書きます。
Port ${CONTAINER_PORT}
<VirtualHost *:${CONTAINER_PORT}>
# いろいろな設定
</VirtualHost>
なおこのように環境変数を参照する記述をした時にその環境変数が存在しない場合、${環境変数名}
がそのまま文字列として設定値になり、エラーログにWARNレベルでログが出力されます。環境変数が無かったときのデフォルト値を設定する機能はApacheにはありませんので、Dockerfileか起動シェルスクリプトでデフォルト値を設定しましょう。
3.2. コマンドライン引数でフラグを渡し、設定ファイルで処理を分岐する
Apacheの起動引数に、-DVAR
の形式でフラグを渡すことが出来ます。先ほど解説した-DFOREGROUND
もまさにこのフラグそのものです。
このフラグは、<IfDefine VAR>
と</IfDefine>
で囲うことで、VAR
が定義されている時のみ有効な設定を記述することが出来ます。
<IfDefine BASIC_AUTH_ENABLED>
AuthType basic
AuthName "private pages"
AuthBasicProvider file
AuthUserFile "/etc/htpasswd"
Require valid-user
</IfDefine>
3.3. 環境変数によって処理を分岐したい場合は?
3.2.をわざわざ「コマンドライン引数でフラグを渡し」として、環境変数としなかったのは、実は環境変数によって処理を分岐することは綺麗には行かない事情があるためです。
例えば、MAINTENANCE_HOST
環境変数があったら、その値をホスト名としてリダイレクトをかける設定は以下のよう<If>
3を使って書けば、動作します。
<Location />
<If "-n osenv('MAINTENANCE_HOST')">
Redirect / https://${MAINTENANCE_HOST}/
</If>
</Location>
しかしこの<If>
を使う方法には欠点があります。リクエストの度にこの<If>
の条件のチェックが行われてしまいますので、微々たるものではありますがCPUを使います。また、リクエストの度に評価されることが原因で、<If></If>
内には「ディレクトリコンテキスト」つまり<Directory>
や<Location>
内に書けるものしか書けません。
${環境変数}
の記述は起動時にのみ置き換え処理が走るのとは対照的です。
<If "-n osenv('ADDITIONAL_PORT')">
Listen ${ADDITIONAL_PORT}
</If>
root@3ba5dffc5a20:/usr/local/apache2/conf# export ADDITIONAL_PORT=81
root@3ba5dffc5a20:/usr/local/apache2/conf# apachectl -DFOREGROUND
AH00526: Syntax error on line 554 of /usr/local/apache2/conf/httpd.conf:
Listen not allowed in <If> context
仕方ないので、起動シェルスクリプトで環境変数をもとにフラグを設定することで、「2. コマンドライン引数でフラグを渡し、設定ファイルで処理を分岐する」のパターンに持ち込むしかありません。Apacheに<IfOSEnv>
のようなセクションがあればそれで済むのですが…。
4.起動スクリプトを用意することを検討する
先述の通り、Apacheの設定ファイルは環境変数値を取り込むことが出来るものの、環境変数による条件分岐はあまり得意では無いため、Apache起動前に前処理としてシェルスクリプトを挟むことで解決する方法があります。
また例えばApacheで提供するコンテンツをコンテナ内に仕込むのでは無く、Amazon S3に置いておき、起動時にダウンロードして利用するとか、設定ファイルを動的に生成するとか、様々な前処理を行うことが出来ます。
前処理完了後、最後にApacheを起動しましょう。
## 様々な前処理を実施
## 最後にApacheを起動する
exec /usr/local/apache2/bin/apachectl -DFOREGROUND "$@"
5.コンテナを非rootで動作させる
Apacheはデフォルトではrootで起動した上で、子プロセスを起動した後にユーザとグループを変更するようになっています。
# If you wish httpd to run as a different user or group, you must run
# httpd as root initially and it will switch.
#
# User/Group: The name (or #number) of the user/group to run httpd as.
# It is usually good practice to create a dedicated user and group for
# running httpd, as with most system services.
#
User www-data
Group www-data
# ps u -C httpd
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 456 0.0 0.0 5916 3848 ? Ss 15:10 0:00 /usr/local/apache2/bin/httpd -k start
www-data 457 0.0 0.0 1997016 9568 ? Sl 15:10 0:00 /usr/local/apache2/bin/httpd -k start
www-data 458 0.0 0.0 1997016 9568 ? Sl 15:10 0:00 /usr/local/apache2/bin/httpd -k start
www-data 460 0.0 0.0 1997016 9568 ? Sl 15:10 0:00 /usr/local/apache2/bin/httpd -k start
最初にrootで起動する理由は主に2つあり、
- 特権ポート(1024未満)でListenするため
- アクセスログ・エラーログをrootで追記モードでオープンするため(子プロセスがクラックされても、ログを消されないため)
ですが、前者はコンテナ内で8080番などでListenして、コンテナ実行環境の機能で80番などから転送することが一般的ですし、後者はログを標準(エラー)出力に出すなら関係ありません。よって、コンテナ上でApacheを動かす時は、rootである必要はありません。
コンテナ内はコンテナ外とはLinuxカーネルの機能(名前空間)で分離されており、root権限であってもコンテナ外に影響を及ぼすことは出来ませんが、脆弱性によってこの分離が破られた時に備えて、root権限を落としておくほうがより良いです。4
やり方は、Dockerfileの適切な場所でUSER
命令でユーザを指定し、Apacheの設定ファイルからUser
とGroup
の指定を消すだけです。
# コンテナをroot以外で起動する
USER nobody
このようにすることで、万一コンテナ内からコンテナ外にアクセス出来る脆弱性があったときも、被害がかなり抑えられます。
なお、Apache公式コンテナはこのようにはなっておらず、rootで起動するようになっています。
6.コンテナ終了時の挙動を検討する
Apacheのマニュアル「停止と再起動」によると、Apacheの停止には2種類、「急な停止」と「緩やかな停止」があります。
コンテナ停止時の挙動をどちらにするか、検討する必要があります。
急な停止(stop) | 緩やかな停止(graceful-stop) | |
---|---|---|
シグナル | SIGTERM | SIGWINCH |
挙動 | 処理中のリクエストを中止してApacheを終了する | 処理中のリクエストが全て完了するのを、GracefulShutdownTimeout だけ待ってからApacheを終了する |
緩やかな停止では、例えばクライアントが低速回線で大きなファイルをダウンロードしている場合にApacheの停止にとても時間がかかってしまうため、待つ最大時間を設定する事が出来ます(デフォルトでは無期限)。
緩やかな停止を採用する場合は、DockerfileにてSTOPSIGNAL
を指定し、コンテナ停止時にSIGWINCH
を送ってもらうようにします。
STOPSIGNAL SIGWINCH
Apacheの公式コンテナイメージも、この設定がいれてあり、「緩やかな停止」を採用しています。GracefulShutdownTimeout
は設定されておらず無期限のようです。
また、コンテナ実行環境側にもコンテナ終了までのタイムアウトが設定されていて、これを超えても終了しないコンテナはSIGKILL
で強制終了されることが一般的ですので、こちらのタイムアウトも意識する必要があります。Kubernatesの場合、Pod定義でterminationGracePeriodSecondsで設定することが出来るようです。
最後に
以上、Apacheをコンテナ化するコツでしたが、いくつかの内容はApacheに限らずコンテナ一般に応用できる考え方を紹介できたと思っておりますので、是非参考にして下さい。
-
実はこの
FOREGROUND
はあまりマニュアルにきちんと書かれていません。マニュアルはこちらです。-D
オプションのところに-DNO_DETACH (prevent the parent from forking) and -DFOREGROUND (prevent the parent from calling setsid() et al).
と書かれていますが、実はFOREGROUND
のsetsid() et al
のet al
にはfork(2)
も含まれるので、FOREGROUND
はNO_DETACH
を包含しています。こんな書き方をしないで、一言「daemon化しない」と書けば分かりやすいのに…と思います。 ↩ -
http://httpd.apache.org/docs/2.4/en/configuring.html#syntax
shell environment variables can be used in configuration file lines using the syntax ${VAR}
↩ -
Docker, Podman, Kubernatesなどのコンテナ実行環境では最近、ユーザ名前空間を使った"Rootlessコンテナ"機能が提供されています。この機能を使うと、コンテナ内のrootはホストのrootではなくなるので、コンテナを無理に非rootで動かす必要はないかもしれません。しかしRootlessコンテナ機能はまだ一般的とは言えませんので、それを前提とするのではなく、コンテナ側でroot権限を落としておくほうが良いです。 ↩