プログラミング初心者がアーキテクトっぽく語る

見苦しい記事も多数あるとは思いますが訂正しつつブログと共に成長していければと思います

dockerでnginxを試してみる

nginxをよく見かけます。

1度くらい触ってみようと思って数年。ようやく重い腰を上げたいと思います。

とりあえず試して削除したいときに便利なのがdockerです。

dockerを利用してローカルPCで基本機能を試してみました。


コンテナ実行

docker run -it --name nginx -p 8080:80 nginx /bin/bash

ローカルにnginxのイメージがなければDockerhubから自動で取得して実行してくれます。

本来のnginxコンテナの使い方としては末尾の「/bin/bash」は不要ですが、今回は中に入って色々いじってみたいのでこのようにしました。

実行するとコンテナの中に自動で入ります。

タグを指定していないのでnginx:latestが使われます。nginx:latestのベースはdebian:buster-slimですのでコンテナの中ではdebian系のコマンドを使う必要があります。


nginx実行

docker runでコマンドを指定(/bin/bash)したので、デフォルトの「CMD ["nginx", "-g", "daemon off;"]」が上書きされしまい、nginxが起動していません。

手動で起動します。

/etc/init.d/nginx start
/etc/init.d/nginx status

確認

ブラウザでhttp://localhost:8080にアクセスしてWelcomeページが表示されることを確認します。

f:id:hogehoge666:20210115152621p:plain


設定確認

基本的な設定を確認します。

more /etc/nginx/nginx.conf

nginx.confのhttpディレクティブに以下の行があります。具体的なWebサイト関連の設定については/etc/nginx/conf.d/配下のconfファイルを参照する設定です。

include /etc/nginx/conf.d/*.conf;

/etc/nginx/conf.d/を見に行くとdefault.confがあります。

default.confを確認すると以下の行があります。/usr/share/nginx/htmlにあるファイルをクライアントに返す設定です。

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

Welcomeページの変更

/usr/share/nginx/html/index.htmlを編集してWelcomeページを変更しましょう。

buster-slimは軽量なのでエディタがありません。viをインストールします。

apt-get update
apt-get install vim

index.htmlを適当に編集します。

vi /usr/share/nginx/html/index.html

ブラウザを再読み込みしてWelcomeページが変更されたことを確認します。


リバースプロキシ

nginxといえばリバースプロキシとして利用されることが多いです。リバースプロキシを試します。

default.confを変更します。localhost:8080/へのアクセスをGoogleに転送する設定です。非現実的な構成ですが手っ取り早いです。

    location / {
        proxy_pass   http://www.google.com;
    }

設定ファイルのValidationをしてからnginxを再起動します。

/etc/init.d/nginx configtest
/etc/init.d/nginx restart

ブラウザを再読み込みしてGoogleが表示されることを確認します。


静的ファイル配信

APIコールはWebアプリケーションに転送して、静的ファイルはnginxが配信するというのもよく見る構成です。

TCP 5000番で待っている適当なWebアプリケーションをローカルPCで立ち上げます。

default.confを以下のように変更してnginxを再起動します。

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    location /api {
        proxy_pass   http://host.docker.internal:5000;
    }

localhost:8080にアクセスするとnginxが静的ファイルを返します。html、js、cssファイルやfavconなどを返す想定です。

localhost:8080/api配下へのAPIコールはTCP 5000番で待っているアプリケーションに転送されます。

「host.docker.internal」はdockerを実行しているローカルPCを表しています。Docker for Mac固有のようです。これでローカルPCの5000番で待機しているアプリケーションに転送されます。誤って「localhost」と記載するとコンテナ自身を表すのでnginxコンテナの5000番に転送してエラーになります。

「proxy_pass http://host.docker.internal:5000」の後ろに/をつけると厄介なことになります。このリンクに説明があります。

https://www.xmisao.com/2014/05/09/nginx-proxy-pass.html

アプリケーションもコンテナの場合

本番環境ではアプリケーションもコンテナ化してDockerで起動することでしょう。その場合はコンテナ同士はコンテナ名やnetwork-aliasで通信できます。

よって本番ではproxy_passディレクティブは以下のようにアプリケーションコンテナの名前(myapp等)にします。アプリケーションは5000番を外部にEXPOSEする必要はありません。

    location /api {
        proxy_pass   http://myapp:5000;
    }

コンテナの起動順序

nginxは起動時にupstream(上の例のmyapp)の名前解決を行っているようです。名前解決できないと「host not found in upstream 'myapp'」のようなエラーを出して起動に失敗します。

これはdocker-composeを利用するときに注意が必要です。myappコンテナの起動がmydbコンテナの起動待ちで遅れるとnginxが起動できなくなります。depends_onなどで起動順序を制御しましょう。

try_filesディレクティブ

try_filesディレクティブを使う場合はこうなります。

    location / {
        root   /usr/share/nginx/html;
        try_files  $uri $uri/ @webapp;
    }

    location @webapp {
        proxy_pass   http://host.docker.internal:5000;
    }
  • まず指定されたURIと同名のファイルを探します。
  • なければ指定されたURIと同じディレクトリを探します。
  • なければ@webappを使ってプロキシされます。

私が用意したアプリケーションは静的ファイルを持っていません。try_filesだとアプリケーションに不要な静的ファイル要求が多く飛ぶ気がするのでやめました。


プロキシヘッダの付与

アプリケーションのアクセスログを見ると要求を送信したクライアントではなくnginxのIPアドレスからアクセスがあったように見えます。nginxがパケットの送信元アドレスを書き換えてしまうからです。

本当に見たいのは要求を送信したクライアントのアドレスです。default.confを以下のように変更してnginxを再起動します。

    location /api {
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass   http://host.docker.internal:5000;
    }

nginxはパケットを書き換えるときにX-Forwarded-ForヘッダにクライアントPCのアドレスを追記します。多くのアプリケーションはX-Forwarded-Forヘッダがある場合はアクセスログにX-Forwarded-Forヘッダの値を出力します。これでアクセスログにクライアントのアドレスが出力されるようになります。

Macではまった

上記の設定でDocker for Mac(Docker version 20.10.0, build 7287ab3)ではうまく動いてくれずdocker bridgeのアドレスが出力されました。悩んだ末、Centosで試したら期待通りに動きました。バージョン20.10.0の不具合かDocker for Mac固有の問題かはわかりません。

bridgeがだめなら、と試したhost networkが動かなかったり、Mac上でのDockerは落とし穴が多いです。

その他のヘッダ情報の転送

アドレスだけでなくホスト名やスキームなど諸々の情報もアプリケーションへ転送したい場合は下記のようにします。

    location /api {
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-Host $host;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_pass   http://host.docker.internal:5000;
    }

Basic認証

認証をかけてみましょう。

さくっと試せるBasic認証を使います。

htpasswdコマンドを利用するためにパッケージを追加します。

apt-get install apache2-utils

ログインIDとパスワードを登録します。

htpasswd -c /etc/nginx/.htpasswd hoge
  New password: hoge
  Re-type new password:hoge 
  Adding password for user hoge

default.confのlocation /とlocation /apiに下記の2行を追加してngnxを再起動します。

auth_basic   "Login Required.";
auth_basic_user_file   /etc/nginx/.htpasswd;

ブラウザでlocalhost:8080/にアクセスするとログイン画面が表示されるようになります。ログインするとコンテンツが表示されます。

Postmanを使って認証なしでlocalhost:8080/apiをGETすると「401 Authorization Required」が返ってきます。Basic認証付きでGETすると値を取得できました。


HTTPS

長くなるので別の記事として投稿します。


ログ

default.conf内の下記行のコメントアウトを削除してnginxを再起動します。

    access_log  /var/log/nginx/host.access.log  main;

/var/log/nginx/host.access.logにアクセス情報が記録されます。


終了

exitするとnginxコンテナも終了します。

exit

掃除

終了したnginxコンテナを削除します。

docker rm nginx

nginxのDockerイメージを削除します。後でまた試したいなら残しておいてもいいです。

docker rmi nginx:latest