Nginx入門
やりたいこと:
・nginxのセットアップ(nginx.conf利用)
・リバースプロキシの設定と動作確認
とりあえずConfをみてみたいので、ローカル端末に落とす
$ docker run --name tmp-nginx-container -d nginx
$ docker cp tmp-nginx-container:/etc/nginx/nginx.conf /host/path/nginx.conf
$ docker rm -f tmp-nginx-container
取得したnginx.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;
}
これの意味を調べていく
-
user nginx;
- http://nginx.org/en/docs/ngx_core_module.html#user
- 実行ユーザー名
- あんまり気にすることなさそう
- Groupを指定することもできるみたいだ
-
worker_processes auto;
- http://nginx.org/en/docs/ngx_core_module.html#worker_processes
- ワーカープロセス数
- 最適な値は諸々の条件により変わるため何とも言えないが、autoにしとくのが無難みたいだ。
- auto 設定の場合、CPUコア数が設定されるとのこと
-
error_log /var/log/nginx/error.log notice;
- http://nginx.org/en/docs/ngx_core_module.html#error_log
-
Syntax: error_log file [level];
となっている - パス指定しているのがエラーログの配置場所
-
notice
の部分がログレベルに当たる。これはオプションパラメータ- デフォルトはerrorとなっている
- debug, info, notice, warn, error, crit, alert, or emerg が指定可能
- “syslog:” prefixで syslog吐き出しもできるみたいだ?
-
pid /var/run/nginx.pid;
- http://nginx.org/en/docs/ngx_core_module.html#pid
- あんまり気にすることなさそう
- 使いたいPidをファイルに書いといたら、それを使ってくれるのかな?
events {
worker_connections 1024;
}
- http://nginx.org/en/docs/ngx_core_module.html#worker_connections
- ワーカープロセスにより開かれる接続の最大数を定義する
- クライアントだけではなく、プロキシサーバーとの接続数などすべてがここに含まれる
- 値は好きに設定できるが、OSで開けるファイル数の最大値を超えることはできないので注意
- ファイル数の最大値~の部分は
worker_rlimit_nofile
にて設定できるとのこと - 接続数を増やしたいとき実際どうすればいいのか?
- ここが分かりやすい
- OSで開けるファイル数の最大値をX、ワーカープロセス数をYとしたとき
-
worker_rlimit_nofile
< 0.95 * X/Y - ここでOSで開けるファイル数の最大値の95%をNginxに割り当てるよう係数をかけている
-
- 算出した
worker_rlimit_nofile
から適切なworker_connections
を計算-
worker_connections
* 4 <worker_rlimit_nofile
- ファイル接続時の諸々で~4倍くらいの接続数が必要になるらしい
-
http
ブロックの中身をみていく
-
include /etc/nginx/mime.types;
-
include /etc/nginx/conf.d/*.conf;
- 文字通りinclude
-
default_type application/octet-stream;
- http://nginx.org/en/docs/http/ngx_http_core_module.html#default_type
- レスポンスのデフォルトMIMETYPE
-
log format
-
logformat
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';
- http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
- ログフォーマットの設定
Syntax: log_format name [escape=default|json|none] string ...;
- 上の例ではmain という名前が定義されている
- 必要に応じてログにescapeを設定できるらしい
- Nginxのドキュメントに各項目の意味するところも記載されていた
- 時間関係はきちんと定義確認しておきたい
- `$time_local‘:ローカル日時
- 公式ドキュメントをサラッと見た感じでは、これがどのタイミングなのか記載なし
- ここ でレスポンスを返した日時と確認されている
-
$msec
:ミリ秒解像度でのログ書き込み時刻 -
$request_time
:リクエストの処理時間- ミリ秒解像度
- リクエストの最初の1byteを読み込んだ時点から、レスポンスの最後の1byteを送信しログ書き込みをするまでの時間
- その他、upstreamとの通信に関する詳細ログも出せるらしい
-
-
access_log /var/log/nginx/access.log main;
- http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
-
Syntax: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
- 無効化:
access_log off;
- 無効化:
- format にはログフォーマット名を指定する
- buffer か gzip が指定されているとログは(メモリ上に?)バッファとして保持される
- あまり具体的な記載がない。想像。
- flushはバッファに保持されるログデータの生存時間を設定するみたいだ
- バッファに持ったままでflush指定の時間が経過したデータはファイルに書き込まれる
- gzipは1~9が指定可能。1は圧縮甘いが早く、9はしっかり圧縮するが遅い
- gzip指定時はバッファからログファイル書き込み時に圧縮適用される
- ログ書き込みするかどうかの条件を
if
で定義可能らしい。すごい- status 2xx, 3xxは書き込まないようにする設定のサンプル
map $status $loggable {
~^[23] 0;
default 1;
}
access_log /path/to/access.log combined if=$loggable;
-
sendfile on;
- http://nginx.org/en/docs/http/ngx_http_core_module.html#sendfile
- むつかしい
- ディスクIOをブロックせず非同期でファイル送信するための仕組みらしい
-
#tcp_nopush on;
- http://nginx.org/en/docs/http/ngx_http_core_module.html#tcp_nopush
-
sendfile
有効時のみ有効 - レスポンスヘッダーと送信ファイルの最初部分を1パケットにまとめて送ってくれる設定?
- ファイル送信を効率化するためのオプションということで理解した
-
keepalive_timeout 65;
- http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
Syntax: keepalive_timeout timeout [header_timeout];
- クライアントとの間でアクセスがないまま開きっぱなしとなっているコネクションを閉じるまでの時間
-
header_timeout
を指定するとレスポンスヘッダーにKeep-Alive: timeout=time
が付与される
keepalive系は気になるので他のも確認しておく
-
keepalive_requests
- http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests
- 1つのkeep-aliveコネクションで捌けるリクエスト数の最大値
- ⇔指定されたリクエストを超える場合はコネクションを繋ぎ直す必要あり
- version 1.19.10以前はデフォルト100、以後は1000
- 100でよさそうだが
-
keepalive_time
- http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_time
- 1つのkeep-aliveコネクションでリクエストを処理できる最大時間
- デフォルト1時間。もっと短くて良さそう
↓が非常に分かりやすかった
-
#gzip on;
- http://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip
- レスポンスのGzip圧縮を有効化するかどうか
timeout周りの確認
-
client_header_timeout
- http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_timeout
- デフォルト60秒
- クライアントリクエストのヘッダーを読み込むのにサーバー側で指定時間以上かかるとタイムアウト
- タイムアウトで408 (Request Time-out) error
-
client_body_timeout
- http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout
- ↑のリクエストボディ版
- デフォルト60秒
-
resolver_timeout
- http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver_timeout
- デフォルト30秒
- 名前解決のタイムアウト設定
-
send_timeout
- http://nginx.org/en/docs/http/ngx_http_core_module.html#send_timeout
- デフォルト60秒
- クライアントにレスポンスを送る際のタイムアウト
全体のタイムアウト設定は見つからず、、
これ全部デフォルトの場合 リクエストheader受信59秒, body受信59秒, サーバー内での処理X秒, クライアントへのレスポンスsend59秒 ... というケースでタイムアウト発生しないということなのか?
プロキシ設定する場合は proxy*timeout を設定するらしい
-
proxy_connect_timeout
- http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_connect_timeout
- プロキシ先サーバーとの接続タイムアウト設定
- デフォルト60秒
-
proxy_read_timeout
- http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout
- プロキシ先サーバーからのレスポンスを読み取る時間のタイムアウト設定
- デフォルト60秒
-
proxy_send_timeout
- http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_send_timeout
- プロキシ先サーバーへリクエストを送る時間のタイムアウト設定
- デフォルト60秒
きりがないので一旦Confはここまで。あとは必要に応じて読もう
https://zenn.dev/teasy/articles/nginx-reverse-proxy を読む
- リバースプロキシを構成するには設定ファイルを読み込ませる必要がある
- 設定ファイルは
/etc/nginx/conf.d/*.conf
として配置すればOK(nginx.confでincludeしているため) - 設定ファイルでは
server
ブロックを使う -
location
としてパスを指定し、そのエンドポイントで有効にしたい設定を書いていくのが基本なようだ -
proxy_pass
でプロキシ先を指定するらしい
プロキシ用の設定ファイルを準備
server {
location /home {
proxy_pass http://localhost:3000/internal/home;
}
}
Docker準備
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
COPY proxy.conf /etc/nginx/conf.d/proxy.conf
実行
cd {作業ディレクトリ}
docker build -t custom-nginx .
docker run --name my-custom-nginx-container -d -p 8080:80 custom-nginx
とりあえず、これで起動はOK。 http://localhost:8080/
にアクセスできる
サーバー側をExpressで準備
var express = require("express");
var app = express();
var server = app.listen(3000, function(){
console.log('express start. port:' + server.address().port);
});
app.get("/internal/home", function(req, res){
res.send('get /internal/home success!');
});
npm init
{適当な設定}
npm install -s express
node main.js
http://localhost:3000/internal/home
にアクセスして文字列表示を確認。OK
これでhttp://localhost:8080/home
にアクセスすればhttp://localhost:3000/internal/home
にプロキシされるはず... →404。なぜ
docker やり直しコマンドメモ
docker rm -f my-custom-nginx-container
docker image rm custom-nginx
docker build -t custom-nginx .
docker run --name my-custom-nginx-container -d -p 8080:80 custom-nginx
- 確認1:location, proxy_pass末尾を/にする
Docker内外の通信ができずにコケている予感。後で調べる
I want to connect from a container to a service on the hostの通りhost.docker.internal
, gateway.docker.internal
を試してみるも改善せず。Docker for Windowsでは使えないのかな、、
検証環境の情報書いてなかった
- OS: Windows 11 Home 22H2
- Docker Desktop v20.10.7
docker-composeで nginxも serverもまとめて管理する方針に切り替えることにする。
nodejsの公式ページを参考にセットアップ。
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
COPY main.js ./
RUN npm install
CMD [ "node", "main.js" ]
docker build . -t node-server
docker run -p 3000:3000 -d node-server
localhost:3000/internal/home
にアクセスできることを確認。
一旦Dockerの準備はOKそうなので、docker-composeの準備に移ることにする
version: '3'
services:
server:
build: ./server/
nginx:
build: ./nginx/
ports:
- "80:80"
docker compose up
nginx port を80に変更。
解決せず
var express = require('express');
var morgan = require('morgan');
var app = express();
app.use(morgan('combined'));
var server = app.listen(3000, function(){
console.log('express start. port:' + server.address().port);
});
app.get('/internal/home', function(req, res){
res.send('get /internal/home success!');
});
server側にログ出力を追加。
/internal/home
以外へのアクセスでもログが出るようになったが、Nginxからプロキシアクセスされた形跡はなし。何か設定が間違っているのか足りないのか
server_name 定義して再トライしたら違うログ出てきた。これ必須なのか
プロキシが動くところまではOK, あとはexpressに接続できてないところをどうにかすればよい
nginx_1 | 2023/06/20 01:04:19 [error] 33#33: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 172.18.0.1, server: localhost.example.com, request: "GET /home/ HTTP/1.1", upstream: "http://127.0.0.1:3000/internal/home/", host: "localhost.example.com"
nginx_1 | 172.18.0.1 - - [20/Jun/2023:01:04:19 +0000] "GET /home/ HTTP/1.1" 502 559 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"
-
resolver
- http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver
- 名前解決先を指定する
- ドメイン名、IPどちらでも可。ポートしていない場合は53ポートになる
- デフォルトでは IPv4, v6どちらも使われる。必要ない場合は
ipv6=off
などと無効化する - キャッシュの仕組みがある。基本はTTL5分。
valid
パラメータで上書き可能
-
127.0.0.11
の正体- ここに書いてあった
- DockerデーモンにDNSサーバが内蔵されているらしい
その他、ざっと抑えた方がよさそうな設定を確認
-
server_tokens off;
- http://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens
- Syntax: server_tokens on | off | build | string;
- バージョンなどサーバー情報の公開設定。非公開が吉
client_header_buffer_size 1k;
-
large_client_header_buffers 4 8k;
その他、セキュリティ対策系は確認しておきたい。
このあたり後で読む
各種モジュールも見ておきたい
認証モジュール(ngx_http_auth_request_module)
-
サブリクエストを飛ばし、その結果でアクセスを許可するか決定する
- 2xxなら許可
- 401 or 403なら拒否、そのエラーを返却(?)
- その他はエラー
-
Syntax: auth_request uri | off;
- サブリクエストの送信先を指定
-
Syntax: auth_request_set $variable value;
- 認証サブリクエストの完了後、リクエストに変数を設定する
- 認証リクエストの値も設定可能
auth_request
確認用のサーバーを準備する。
/internal/home
に加え、認証エンドポイントとして/internal/auth
を追加する(例なので認証はヘッダーの確認のみとする)
var express = require('express');
var morgan = require('morgan');
var app = express();
app.use(morgan('combined'));
var server = app.listen(3000, function(){
console.log('express start. port:' + server.address().port);
});
app.post('/internal/home', function(req, res){
const authHeader = req.headers.authorization;
if (authHeader === "accessToken_home") {
res.send('get /internal/home success!');
} else {
res.status(401).send('Unauthorized /internal/home')
}
});
app.get('/internal/auth', function(req, res){
const authHeader = req.headers.authorization;
if (authHeader === "accessToken_auth") {
res.setHeader('Authorization', 'accessToken_home')
res.send('get /internal/auth success!');
} else {
res.status(401).send('Unauthorized /internal/auth')
}
});
動作確認。
指定のAuthorizationヘッダーが付与されている場合のみ200を返す
C:\work\nginx-test>curl -X POST http://localhost:3000/internal/home
Unauthorized /internal/home
C:\work\nginx-test>curl -X POST http://localhost:3000/internal/home -H "Authorization: accessToken_home"
get /internal/home success!
C:\work\nginx-test>curl http://localhost:3000/internal/auth
Unauthorized /internal/auth
C:\work\nginx-test>curl http://localhost:3000/internal/auth -H "Authorization: accessToken_auth"
get /internal/auth success!
これで動いた。
server {
listen 80;
server_name localhost.example.com;
location /home {
resolver 127.0.0.11 ipv6=off;
auth_request /auth;
auth_request_set $authorization $upstream_http_authorization;
proxy_set_header Authorization $authorization;
set $url http://node-server:3000/internal/home;
proxy_pass $url;
}
location /auth {
resolver 127.0.0.11 ipv6=off;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_pass http://node-server:3000/internal/auth;
}
}
動作確認
C:\work\nginx-test>curl -X POST http://localhost.example.com/home -H "Authorization: accessToken_auth"
get /internal/home success!
node-server_1 | ::ffff:172.19.0.3 - - [25/Jun/2023:00:35:15 +0000] "GET /internal/auth HTTP/1.0" 200 27 "-" "curl/8.0.1"
nginx_1 | 172.19.0.1 - - [25/Jun/2023:00:35:15 +0000] "POST /home HTTP/1.1" 200 27 "-" "curl/8.0.1" "-"
node-server_1 | ::ffff:172.19.0.3 - - [25/Jun/2023:00:35:15 +0000] "POST /internal/home HTTP/1.0" 200 27 "-" "curl/8.0.1"
ちょっとハマったポイント
-
auth_request
でURL指定したら 404。/auth
ではうまくいった -
location /auth
の中でもset $url xxx
からproxy_pass
としていたが、そうすると/internal/auth
にGETとPOSTが1度ずつ飛んでしまった。どうも/auth
での書き換えが/home
に影響してしまっているようにみえる -
auth_request_set
は認証リクエストの値を変数にセットするもの。それを本リクエストに利用したい場合は変数をproxy_set_headerなどでセットする必要あり