@ucan-lab さんの docker-laravel の環境をちゃんと理解するための記事
今回は Dockerfile の Nginx 編です。
docker-compose 編
Dockerfile 編 - PHP
Dockerfile
# --- ① ビルドイメージの指定 ---
FROM node:16-alpine as node
FROM nginx:1.20-alpine
# ------
# ② メタデータを追加
LABEL maintainer="ucan-lab <yes@u-can.pro>"
# ③ デフォルトのシェルをオーバーライド
SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]
# ④ 環境変数を設定
ENV TZ=UTC
# ⑤ イメージ上に任意のコマンドを実行
RUN apk update && \
apk add --update --no-cache --virtual=.build-dependencies g++
# --- ⑥ COPY 命令 ---
# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt
# ------
# ⑦ Nginx の設定ファイルをコピー
# nginx config file
COPY ./infra/docker/nginx/*.conf /etc/nginx/conf.d/
# ⑧ 作業ディレクトリを指定
WORKDIR /work/backend
Dockerfile のコマンド については Dockerfile 編 - PHP を参照
① ビルドイメージの指定
- Dockerfile 内に複数の FROM 命令をかける
- 各 FROM 命令から新しいビルドステージが開始される
- イメージ内に生成された内容、一方から他方にコピーすることができる
- PHP の Dockerfile で Composer をインストールする時にもマルチステージビルドを使っている
- この時は直前のイメージじゃなくて、別のイメージから composer をコピーしてきている
- 外部イメージの「ステージ」としての利用
FROM node:16-alpine as node
- Docker Hub から node のイメージを持ってきている
- as 構文でビルドステージに名前をつけている
- 別のビルドステージで使用することができる
- 名前をつけただけでこのビルドステージは終了
- alpine は Linux ディストリビューション
nginx:1.20-alpine
- nginx のイメージを取得
② メタデータを追加
LABEL maintainer="ucan-lab <yes@u-can.pro>"
-
メタデータの設定のみ動作に影響はなし
③ デフォルトのシェルをオーバーライド
SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]
-
デフォルトのシェルを今回は bash じゃなくて、ash に変更している
- alpine のイメージは ash を使ってるらしい
④ 環境変数を設定
ENV TZ=UTC
- OS(Debian) のタイムゾーンを UTC に設定している
⑤ イメージ上に任意のコマンドを実行
RUN apk update && \ apk add --update --no-cache --virtual=.build-dependencies g++
-
\
ワンライナーで実行してイメージのレイアを軽くしている
apk update
-
apk は
Alpine Linux package management
の略 -
Alpine のパッケージマネージャー
-
パッケージの最新リストを取得
-
おそらく、 apt-get updateと似たような仕組みになってるんだろうな
apk add --update --no-cache --virtual=.build-dependencies g++
-
apk add g++
-
g++
をインストールしている - https://pkgs.alpinelinux.org/package/v3.4/main/x86/g++
- GNU C++ standard library and compiler
- ちょっと何のためにあるのかわかってない
- 試しに、丸っと削除削除して動かしてみたけど、
make test
までは通った
- 試しに、丸っと削除削除して動かしてみたけど、
-
-
--update
-
- 要するに
--update-cache の省略形
らしい
- 要するに
-
- 他の apk コマンドの前にまず apk update を実行するのと同じ効果が得られる
-
前の
apk update
コマンドはなくてもいいのかも?
-
-
--no-cache
- ダウンロードされたパッケージは
/var/cache/apk
にキャッシュされるらしい - 不要なキャッシュでイメージのサイズが大きくならないようにしている
- ダウンロードされたパッケージは
-
--virtual=.build-dependencies
- インストールした複数のパッケージと依存関係を一つの仮想パッケージとして名前をつけて保存
- 今回は、
.build-dependencies
って名前をつけている - パッケージを削除するときに名前単位で削除したりできるらしい
- 今回は
g++
だけだからなくてもいいのかも?
⑥ COPY 命令
# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt
node のベースイメージから node と npm と yarn をコピーしています。
もし npm, yarn どちらか使用したくない場合は削除して使用してokです。
- View や React で画面を作った際に、動作するのが Nginx 側だから node を Nginx にコピーしてきてると思われる
-
docker-compose 編
- PHP は unix ソケットを使って、 PHP コンテナで動く、静的コンテンツは Nginx コンテナで動く
-
docker-compose 編
⑦ Nginx の設定ファイルをコピー
COPY ./infra/docker/nginx/*.conf /etc/nginx/conf.d/
-
./infra/docker/nginx/default.conf
をコピーしている
-
nginxの設定は、
/etc/nginx
ディレクトリのnginx.conf
に記載される -
nginx.confに記述してある、
include /etc/nginx/default.d/*.conf;
という記述で、conf.d
ディレクトリ内の拡張子がconfであるファイルを読み込んでいる -
/etc/nginx/conf.d/default.conf
にサーバーの設定を記載するのが一般的 -
設定ファイルは別途確認
Nginx コンテナ内の /etc/nginx/nginx.conf
は下記のような記載になっている。
include /etc/nginx/conf.d/*.conf;
でコピーした設定ファイルが読み込まれるようになっている。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
⑧ 作業ディレクトリを指定
WORKDIR /work/backend
-
PHP 編と同じ
- ここではディレクトリが生成されるだけのはず
Nginx の設定ファイル
Laravel の公式ドキュメントを参考にしているらしい
access_log /dev/stdout main; # ① アクセスログの設定
error_log /dev/stderr warn; # ② エラーログの設定
server { # 以下、server コンテキスト
listen 80; # ③ リクエストを受け付けるポート番号を指定
root /work/backend/public; # ④ ドキュメントルートを設定
# ⑤ レスポンスヘッダに値を追加
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php; # ⑥ インデックスとして使用されるファイルを指定
charset utf-8; # ⑦ Content-Type を utf-8 に設定
location / { # ⑧ アクセス URI にファイルがなければ index.php へ遷移させる
try_files $uri $uri/ /index.php?$query_string;
}
# ⑨ アクセスログとエラーログが出ないように設定
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
# ⑩ 404 エラーの場合は index.php に遷移する
error_page 404 /index.php;
# ⑪ .php にアクセスした場合の設定
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
# ⑫ well-known 以外のドットファイルへのアクセスを全て拒否
location ~ /\.(?!well-known).* {
deny all;
}
}
nginx の設定ファイルについ概要だけ確認
-
http{ }
とかserver{ }
はディレクティブ(命令) - ブロック{ }内の記述は、そのブロック内でのみ有効。その範囲をコンテキストという
- ディレクティブによって、どのコンテキストで使えるかが決まっている
- serverディレクティブはhttpコンテキスト内でしか使えない
- どのブロックにも囲まれていないところはmainコンテキスト
ホストの./infra/docker/nginx/default.conf
が実行される場所
-
Nginx の Dockerfile でホストの
./infra/docker/nginx/default.conf
をコンテナ内の/etc/nginx/conf.d/
ディレクトリ内にコピー -
コピーしたファイルはコンテナ内の Nginx の設定ファイル
/etc/nginx/nginx.conf
の http コンテキスト内からinclude
されている -
つまり、
./infra/docker/nginx/default.conf
には http コンテキスト内で実行されるディレクティブが書かれている
① アクセスログの設定
access_log /dev/stdout main;
-
指定したログフォーマットでアクセスログを書き出してくれる
-
アクセスログのフォーマット
access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
-
path が
/dev/stdout
になっているので、ここに access_log が出力されているはず
~ # ls -la /dev/
drwxr-xr-x 5 root root 340 Dec 30 06:05 .
drwxr-xr-x 1 root root 4096 Dec 30 06:05 ..
lrwxrwxrwx 1 root root 11 Dec 30 06:05 core -> /proc/kcore
lrwxrwxrwx 1 root root 13 Dec 30 06:05 fd -> /proc/self/fd
crw-rw-rw- 1 root root 1, 7 Dec 30 06:05 full
drwxrwxrwt 2 root root 40 Dec 30 06:05 mqueue
crw-rw-rw- 1 root root 1, 3 Dec 30 06:05 null
lrwxrwxrwx 1 root root 8 Dec 30 06:05 ptmx -> pts/ptmx
drwxr-xr-x 2 root root 0 Dec 30 06:05 pts
crw-rw-rw- 1 root root 1, 8 Dec 30 06:05 random
drwxrwxrwt 2 root root 40 Dec 30 06:05 shm
lrwxrwxrwx 1 root root 15 Dec 30 06:05 stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Dec 30 06:05 stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Dec 30 06:05 stdout -> /proc/self/fd/1
crw-rw-rw- 1 root root 5, 0 Dec 30 06:05 tty
crw-rw-rw- 1 root root 1, 9 Dec 30 06:05 urandom
crw-rw-rw- 1 root root 1, 5 Dec 30 06:05 zero
-
/dev/stdout
はシンボリックリンクになっている
~ # ls -la /proc/self/fd
total 0
dr-x------ 2 root root 0 Dec 30 06:51 .
dr-xr-xr-x 9 root root 0 Dec 30 06:51 ..
lrwx------ 1 root root 64 Dec 30 06:51 0 -> /dev/pts/0
lrwx------ 1 root root 64 Dec 30 06:51 1 -> /dev/pts/0
lrwx------ 1 root root 64 Dec 30 06:51 2 -> /dev/pts/0
ls: /proc/self/fd/3: cannot read link: No such file or directory
lr-x------ 1 root root 64 Dec 30 06:51 3
~ # ls -la /dev/pts
total 0
drwxr-xr-x 2 root root 0 Dec 30 06:05 .
drwxr-xr-x 5 root root 340 Dec 30 06:05 ..
crw--w---- 1 root tty 136, 0 Dec 30 06:52 0
crw-rw-rw- 1 root root 5, 2 Dec 30 06:52 ptmx
-
シンボリックリンクを辿っていくと デバイスファイルになっていた
-
アクセスログはどうやって確認したらいいんだろ、わからん
-
format は main になってる
- おそらくコンテナ内の
nginx.conf
で設定されているmain
のフォーマットを指定している
- おそらくコンテナ内の
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
② エラーログの設定
error_log /dev/stderr warn;
error_log file [level];
2番目のパラメータはログのレベルを決定し、debug, info, notice, warn, error, crit, alert, emergのいずれかを指定します。上記のログレベルは、深刻度の高い順に記載されています。あるログレベルを設定すると、>指定された、より深刻なログレベルのすべてのメッセージがログに記録されるようになります。例えば、デフォルトレベルのerrorは、error、crit、alert、およびemergメッセージがログされるようにします。このパラメータ
が省略された場合、errorが使用されます。
-
/dev/stderr
にエラーログを出力 -
warn
レベル以上のログを出力 -
access_log と同様にシンボリックリンクになって、たどるとデバイスファイルになっていた。確認方法がわからん
③ リクエストを受け付けるポート番号を指定
listen 80;
- listen ディレクティブでリクエストを受け付けるポートや IP アドレスを指定できる
- 今回は 80 番ポートでリクエストを受け付けるように設定している
docker-compose.yml で nginx 側の 80 番ポートとホストの 80 番ポートをマッピングしていたのはこのため
④ ドキュメントルートを設定
root /work/backend/public;
-
rootディレクティブでドキュメントリュートを指定
-
/work/backend
は docker-compose.ymlで Laravel のソースコードをマウントするディレクトリ -
つまり、 Lravel の
public
ディレクトリをドキュメントルートに指定している
⑤ レスポンスヘッダに値を追加
- add_header ディレクティブ
レスポンスコードが 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), または 308 (1.13.0) であれば、指定したフィールドを応答ヘッダに追加します。パラメータ値には変数を含めることができる。
X-Frame-Options "SAMEORIGIN";
X-Frame-Options は HTTP のレスポンスヘッダーで、ブラウザーがページを 、
- SAMEORIGIN
- 同じオリジンのフレーム内でのみ表示可能 - nginx での設定
X-XSS-Protection "1; mode=block";
HTTP の X-XSS-Protection レスポンスヘッダーは Internet Explorer, Chrome, Safari の機能で、反射型クロスサイトスクリプティング (XSS) 攻撃を検出したときに、ページの読み込みを停止するためのものです。
1; mode=block
XSS フィルタリングを有効化します。攻撃を検知すると、ページをサニタイジングするよりも、ページのレンダリングを停止します。
X-Content-Type-Options "nosniff";
X-Content-Type-Options は HTTP のレスポンスヘッダーで、 Content-Type ヘッダーで示された MIME タイプを変更せずに従うべきであることを示すために、サーバーによって使用されるマーカーです。これにより、MIME タイプのスニッフィングを抑止するこ
とができます。言い替えれば、 MIME タイプを意図的に設定することができます。
nosniff
リクエスト先のタイプが style でありその MIME タイプが text/css ではない場合、または、タイプが script で MIME タイプが JavaScript の MIME タイプではない場合にリクエストをブロックします。
⑥ インデックスとして使用されるファイルを指定
index index.html index.htm index.php;
- incex ディレクティブ
インデックスとして使用されるファイルを定義します。ファイル名には変数を含めることができる。ファイルは指定された順序でチェックされる。
-
今回は index.html, index.htm, index.php の優先順位で index ファイルが決まる
-
index.html, index.htm ファイルがなければ、Laravel の index.php ファイルがインデックスファイルとして読み込まれる
-
つまり、
http://localhost
でアクセスした時にはindex.php
が呼ばれる
⑦ Content-Type を utf-8 に設定
charset utf-8;
レスポンスヘッダーフィールド "Content-Type" に、指定された文字セットを追加します。
⑧ アクセス URI にファイルがなければ index.php へ遷移させる
location
- リクエスト URI に応じた設定を行う
- 今回は、
/
にアクセスがきた時の設定を行っている
-
/
で始まる全てのリクエスト URI に対して適応される
try_files $uri $uri/ /index.php?$query_string;
指定された順番でファイルの存在を確認し、最初に見つかったファイルをリクエスト処理に使用する。処理は、現在のコンテキストで実行される。ファイルへのパスは、fileパラメータからrootとaliasディレクティブに従って構築される。また、"$uri/" のよう>に名前の最後にスラッシュを指定することで、ディレクトリの存在を確認することができる。いずれのファイルも見つからなかった場合、最後のパラメータで指定された uri に内部リダイレクトされます。
- アクセスしてきた URL にファイルがあるか
- アクセスしてきた URL にディレクトリがあるか
- いずれもなければ、
/index.php$query_string
へ遷移する
⑨ アクセスログとエラーログが出ないように設定
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
-
/favicon.ico
と/robots.txt
にアクセスされた場合の設定
ファイルが見つからない場合のエラーログをerror_logに記録するかどうかを設定します。
-
- アクセスログを off にしている
⑩ 404 エラーの場合は index.php に遷移する
指定されたエラーに対して表示されるURIを定義する。
⑪ .php にアクセスした場合の設定
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ \.php$
-
~
正規表現(大文字・小文字を区別する) -
~ \.php$
拡張子が .php のファイルに対して設定を行う
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
FastCGI サーバーのアドレスを設定します。
UNIX-domain socket path:
fastcgi_pass unix:/tmp/fastcgi.socket;
- unix ドメインソケットのパスを指定している
- docker-compose.yml で unix ドメインソケットをマウントしているので、そこへのパスを指定する
- おそらくこれで、.php ファイルにアクセスした時は php コンテナと通信をして、 PHP コンテナの FastCGI で PHP を実行することができる
fastcgi_index index.php;
スラッシュで終わる URI の後に付加されるファイル名を $fastcgi_script_name 変数の値に設定します。
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
FastCGI サーバーに渡すべきパラメータを設定します。値にはテキスト、変数、およびそれらの組み合わせを含めることができます。
PHPでは、SCRIPT_FILENAMEパラメータはスクリプト名を決定するために使用され、QUERY_STRINGパラメータはリクエストパラメータを渡すために使用されます。
-
FastCGI サーバー(PHP コンテナ)に SCRIPT_FILENAME を渡している
- SCRIPT_FILENAME パラメータは、 PHP FPM に渡す必要があるらしい
現在のリクエストに対する root または alias ディレクティブの値に対応する絶対パス名。 すべてのシンボリックリンクは実際のパスに解決されます。
- とりあえず、ベタがきでも index.php のパスを指定すればいいらしい
なんで必要かまではわかっていない。
include fastcgi_params;
-
fastcgi_params を include している
- PHP の
$_SERVER
内に値が入る
- PHP の
⑫ well-known 以外のドットファイルへのアクセスを全て拒否
location ~ /\.(?!well-known).* {
deny all;
}
-
.well-known
ディレクトリは Let's Encrypt で SSL の設定をする際に使ったりすることがあるらしい -
それ以外のドットディレクトリへのアクセスは全て拒否している
指定されたネットワークまたはアドレスのアクセスを拒否する