Docker環境で便利なnginx設定メモ
公開日:
カテゴリ: 開発
nginxの設定のメモ書きです。Docker環境でサブドメインで運用している場合の設定方法が中心です。
DNSはここでは扱いません。すべてnginxサーバを立てているIPに向けられていることを前提としています。
基本的な設定類
言わずもがなですが、おさらいを兼ねて書いておきます。
httpサーバを立てる
単純にサーバを立てる場合の設定ファイルです。serverディレクティブを複数書くと、その分だけバーチャルホストとして立てられます。
http {
	server {
		listen 80;
		server_name foo.subdomain.example.com;
		location / {
			root /var/www;
		}
	}
}httpsサーバを立てる
httpsサーバは証明書が必要なので、別途証明書の作成が必要です。自己証明書も使用できますが、ルート証明書として追加したPCやスマホ以外ではブラウザから警告が出ます。
ssl_certificateに証明書、ssl_certificate_keyに秘密鍵のパスを指定します。http2 onを記述するとhttp2のサーバを立てられます。
http {
	server {
		listen 443 ssl;
		http2 on;
		server_name bar.subdomain.example.com;
		ssl_certificate /certs/subdomain.example.com.crt;
		ssl_certificate_key /certs/subdomain.example.com.key;
		location / {
			root /var/www;
		}
	}
}URIパス分岐
locationディレクティブにパスを指定することで、パスによって処理を振り分けることができます。
http {
	server {
		listen 80;
		server_name baz.subdomain.example.com;
		location / {
			root /home/nginx/doc;
		}
		location /img {
			root /home/nginx/img;
		}
	}
}httpからhttpsへのリダイレクト
httpからhttpsへリダイレクトするサーバを立てます。server_nameをピリオドで始めることで、すべてのサブドメインでリダイレクトが行われます。同時にTLSサーバ側ではHSTSヘッダーも追加しておきます。
http {
	server {
		listen 80;
		server_name .subdomain.example.com;
		return 301 https://$host$request_uri;
	}
	server {
		listen 443 ssl;
		add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
		# 他の設定項目は省略
	}
}リバースプロキシ
proxy_passを指定することで、別のサーバへリクエストを転送できます。
http {
	server {
		listen 80;
		server_name qux.subdomain.example.com;
		location / {
			proxy_pass http://another_container_name;
			proxy_set_header Host $host;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Host $host;
			proxy_set_header X-Forwarded-Server $host;
			proxy_set_header X-Real-IP $remote_addr;
		}
	}
}基本を思い出したところで、ここからが本題です。
普通にサーバを立てる
http {
	server {
		listen 80;
		server_name foo.subdomain.example.com;
		location / {
			proxy_pass http://foo_container_name;
		}
	}
	server {
		listen 80;
		server_name bar.subdomain.example.com;
		location / {
			proxy_pass http://bar_container_name;
		}
	}
}普通に設定するとサーバの数だけserverディレクティブが必要になります。またnginxの起動時にプロキシ先の名前解決が行われるため、プロキシに設定しているコンテナを1つでも落としている場合はサーバが起動できなくなります(Dockerの内部DNSは起動していないコンテナの名前解決はできません)。
パスに変数を設定する
nginxはパスが変数の場合は起動時に名前解決が行われません。そのため、コンテナを落とした状態でもnginxを起動できるようになります。だいぶ強引な解決方法に見えますが・・・。
DockerネットワークのDNSは127.0.0.11ですので、このIPを名前解決のリゾルバに設定しておきます。
http {
	server {
		listen 80;
		server_name foo.subdomain.example.com;
		set $server_host foo_container_name;
		location / {
			resolver 127.0.0.11;
			proxy_pass http://$server_host;
		}
	}
	server {
		listen 80;
		server_name bar.subdomain.example.com;
		set $server_host bar_container_name;
		location / {
			resolver 127.0.0.11;
			proxy_pass http://$server_host;
		}
	}
}サーバ名に正規表現を使う
サブドメイン名をコンテナ名またはネットワークエイリアス名として名前解決する方法です。正規表現を用いたパターンマッチを行います。
一応トラバーサル対策のため、リバースプロキシ用のDockerネットワークを作成しておきます。以下の例ではnginxという名前のネットワークをリバースプロキシ用のDockerネットワークとします。
http {
	server {
		listen 80;
		server_name ~^(?<req_subdomain>.+).subdomain.example.com;
		location / {
			resolver 127.0.0.11;
			proxy_pass http://$req_subdomain.nginx;
		}
	}
}なおnginxのserver_nameに正規表現を使用すると、他のserverディレクティブにもマッチする場合に正規表現を使ったディレクティブの優先順位が低くなります。
mapディレクティブを使う
ここまででおおよそ解決できますが、必ずしも設定したいサブドメインとコンテナ名が一致するとは限りません。また、コンテナによっては公開ポートを変更できない場合もあります。
そこでmapディレクティブを使います。デフォルトのままで運用できるものは設定不要、どうしても変更が必要な場合だけmapディレクティブに書き足すようにします。
http {
	map $req_subdomain $server_host {
		default $req_subdomain;
		foo bar;
	}
	map $req_subdomain $port {
		default 80;
		foo 3000;
	}
	server {
		listen 80;
		server_name ~^(?<req_subdomain>.+).subdomain.example.com;
		location / {
			resolver 127.0.0.11;
			proxy_pass http://$server_host.nginx:$port;
		}
	}
}foo.subdomain.example.comにアクセスした場合はbar.nginx:3000、それ以外にアクセスした場合は第4レベルの80番ポート(第4レベル.nginx:80)に転送します。
ひとまずの完成形
ここまでのディレクティブを組み合わせて、ひとまず以下の形式になりました。TLSの証明書はサブドメインのワイルドカード証明書であることを前提としています。ワイルドカードの証明書が使えない場合は$server_host変数を証明書の名前に使えばいけると思います(未検証)。
http {
	map $req_subdomain $server_host {
		default $req_subdomain;
		foo bar;
	}
	map $req_subdomain $port {
		default 80;
		foo 3000;
	}
	server {
		listen 80;
		server_name .subdomain.example.com;
		return 301 https://$host$request_uri;
	}
	server {
		listen 443 ssl;
		http2 on;
		server_name ~^(?<req_subdomain>.+).subdomain.example.com$;
		ssl_certificate /certs/subdomain.example.com.crt;
		ssl_certificate_key /certs/subdomain.example.com.key;
		location / {
			resolver 127.0.0.11;
			proxy_pass http://$server_host.nginx:$port;
			proxy_set_header Host $host;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Host $host;
			proxy_set_header X-Forwarded-Server $host;
			proxy_set_header X-Real-IP $remote_addr;
		}
		add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
	}
}カテゴリ: 開発