構築環境
$ cat /etc/redhat-release
CentOS Linux release 8.1.1911 (Core)
概要
Kibanaを構築したが、Kibanaへアクセスする際のURLが http://[ホスト名]:[ポート番号]/ を指定する必要があり、ポート番号を指定することが扱いづらいと感じた。そこで、Nginxによって5601ポートへのアクセスを、リバースプロキシすることにした。また、合わせて Let's Encrypt によるSSL対応を実施した。
以下は、今回のやりたいことのイメージ図である。
なお、今回はDockerを使用してNginxのコンテナサービスを起動する。
実施内容
Certbotの設定
インストール
以下のコマンドを実行して、インストールする。公式インストール手順
$ wget https://dl.eff.org/certbot-auto
$ sudo mv certbot-auto /usr/local/bin/certbot-auto
$ sudo chown root /usr/local/bin/certbot-auto
$ sudo chmod 0755 /usr/local/bin/certbot-auto
Certbotとは?
Certbotは無料のOSSで、手動で管理するウェブサイト上で、Let's Encrypt証明書を自動で利用し、HTTPSを有効化するものです。
Let's Encrypt証明書の取得
以下のコマンドを実行して、Let's Encrypt証明書を取得する。
$ /usr/local/bin/certbot-auto certonly --standalone
緊急の更新およびセキュリティ通知に使用されるE-Mailアドレスの入力を求められるので、入力する。
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel):mail@exmaple.com
規約の同意を求められるので、同意する。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel:A
ニュースやキャンペーン等のメールを送ってよいか?と聞かれる。申し訳ないが、今回はNoを選択する。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
ドメイン名の入力を求められるので、入力する。なお、『,』またな『スペース』で複数のドメインを入力することが可能である。
Please enter in your domain name(s) (comma and/or space separated) (Enter 'c'
to cancel):www.example.com
Congratulations!と表示され、Let's Encrypt証明書の取得に成功した。
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/www.example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/www.example.com/privkey.pem
Your cert will expire on 2020-08-17. To obtain a new or tweaked
version of this certificate in the future, simply run certbot-auto
again. To non-interactively renew *all* of your certificates, run
"certbot-auto renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
上記の重要な注意事項に記載されている要点をまとめてみた。
用語 | 説明(配置パスや有効期限) |
---|---|
証明書と中間証明書を連結したファイル | /etc/letsencrypt/live/www.example.com/fullchain.pem |
秘密鍵 | /etc/letsencrypt/live/www.example.com/privkey.pem |
有効期限 | 2020-08-17 |
Certbot設定ファイル保存場所 | /etc/letsencrypt |
すべての証明書の更新コマンド | certbot-auto renew |
「有効期限が決まっているから、定期的に更新してね!」って説明されている。
Let's Encrypt証明書の自動更新設定
Certbot公式が、cronによる定期的な自動更新を推奨しているため、以下のコマンドを実行してcrontabに自動更新ジョブを追加する。
$ echo "0 0,12 * * * root python3 -c 'import random; import time; time.sleep(random.random() * 3600)' && /usr/local/bin/certbot-auto renew -q" | sudo tee -a /etc/crontab > /dev/null
自動更新ジョブがデフォルトのcrontabに追加されていることを確認する。
$ cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
0 0,12 * * * root python3 -c 'import random; import time; time.sleep(random.random() * 3600)' && /usr/local/bin/certbot-auto renew -q
ファイアーウォールの設定
http,httpsの通信を許可するために、ポートを解放する。
$ firewall-cmd --add-service=http --permanent
$ firewall-cmd --add-service=https --permanent
$ firewall-cmd --reload
http,httpsのポートが解放されたことを確認する。
$ firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client http https
ports: 10215/tcp
protocols:
masquerade: yes
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Dockerの設定
今回はDockerコンテナ上でNginxを動作させるため、予めDockerをインストールする
インストール
インストール手順に関しては、別記事で紹介しているのでこちらを参照ください。
Nginxの設定
DockerComposeにて、Nginxのコンテナサービスを起動する手順を説明する。
なお、Dockerを使用せずにNginxをインストールする場合は、以下の手順を参照ください。
ディレクトリ構成
以下のディレクトリ構成で、構築する。
$ tree --charset=C
.
|-- conf
| |-- default.conf
| `-- nginx.conf
|-- docker-compose.yml
`-- html
|-- 50x.html
`-- index.html
各種設定ファイルの作成
DockerComposeよってコンテナサービスを起動するため、docker-compose.ymlを作成する。
version: '3'
services:
nginx:
container_name: nginx
image: nginx
volumes:
- /etc/letsencrypt/live/example.com/fullchain.pem:/etc/nginx/letsencrypt/fullchain.pem
- /etc/letsencrypt/live/example.com/privkey.pem:/etc/nginx/letsencrypt/privkey.pem
- /opt/docker/nginx/conf/default.conf:/etc/nginx/conf.d/default.conf
- /opt/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
- /opt/docker/nginx/html:/usr/share/nginx/html
- /var/log/nginx:/var/log/nginx
ports:
- 80:80
- 443:443
networks:
- elasticstack
restart: always
networks:
elasticstack:
external: true
Nginxの設定ファイルを作成する。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 512;
multi_accept off;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
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;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name _;
ssl_certificate /etc/nginx/letsencrypt/fullchain.pem;
ssl_certificate_key /etc/nginx/letsencrypt/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
keepalive_timeout 75;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /kibana/ {
rewrite /kibana/(.*)$ /$1 break;
proxy_pass http://example.com:5601;
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-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>An error occurred.</h1>
<p>Sorry, the page you are looking for is currently unavailable.<br/>
Please try again later.</p>
<p>If you are the system administrator of this resource then you should check
the error log for details.</p>
<p><em>Faithfully yours, nginx.</em></p>
</body>
</html>
Nginxの設定に関する補足
worker_processes
ワーカープロセスの数を定義する。最適な値はCPUコア数、ハードディスクドライブ数、負荷パターンを含む多くの要因によって異なるが、基本的には使用可能なCPUコア数に設定することが推奨されている(autoを設定することで、CPUコア数を自動で検出し設定してくれる)。なお、デフォルトは「1」が設定されている。
今回は汎用的に利用可能な設定ファイルを作成したかったため、autoを設定した。
worker_connections
ワーカープロセスで開くことのできる同時接続の最大数を定義する。この数には、クライアントの接続だけでなく、すべての接続(プロキシサーバとの接続など)が含まれる点は注意が必要である。また、同時接続の実際の数は、worker_rlimit_nofileで設定可能なオープンファイルの最大数の制限を超えることができない点にも注意が必要である。なお、デフォルトは「512」が設定されている。
今回はアクセス増加等を考える状況になかったため、一旦デフォルトの512を設定した。
multi_accept
multi_acceptが無効である場合は、ワーカープロセスは一度に一つの新しい接続しか受け入れることができない。一方で有効である場合は、ワーカープロセスは一度にすべての新しい接続を受け入れることができる。なお、デフォルトは「off」が設定されている。
今回は低スペックのサーバを使用しようしているため、offで設定した。
server_tokens
エラーページ及び「サーバ」応答ヘッダーフィールドにおけるnginxバージョン情報表示の有無を設定する。なお、デフォルトは「on」が設定されている。
今回は、バージョン情報が表示されてしまうとセキュリティ上良くないため、無効に設定した。
sendfile
コンテンツのファイルの読み込みとクライアントへのレスポンスの送信にsendfileを使用するかを設定する。sendfileを使用するとカーネル空間内でファイルの読み込みと送信が完了するため、効率良くファイルの内容をclientに送信することができる。なお、デフォルトは「off」が設定されている。
今回は、あまり深く考えずにonを設定した。
HTTPSサーバの最適化
SSL操作は追加のCPUリソースを消費する。最もCPUを集中的に使用する操作はSSLハンドシェイクです。このCPUリソースを消費を最小限に留めるには2つの方法があります。1つ目はキープアライブ接続で1つの接続を介して複数のリクエストを送信できるようにすることです。2つ目は、SSLセッションパラメータを再利用して、並列接続と後続接続のSSLハンドシェイクを回避する方法です。詳しくは公式HTTPSサーバの最適化を参照。
keepalive_timeout
キープアライブ接続のタイムアウトを設定する。デフォルトは「60s」が設定されている。
今回は特別こだわる要件はなかったが、75sで設定した。
ssl_session_cache
セッションパラメータを格納するキャッシュのタイプとサイズを設定する。なお、デフォルトは「none」であり、セッションパラメータをキャッシュに保存しない設定となっている。
今回はすべてのワーカープロセス間で共有されるキャッシュのタイプであるsharedを設定した。また、サイズは10mで設定した。
ssl_session_timeout
クライアントがセッションパラメータを際利用できる時間を指定する。なお、デフォルトは「5m」が設定されている。
今回は、10mで設定した。
Nginxコンテナサービスの起動
DockerComposeによって、Nginxコンテナサービスを起動する
$ docker-compose up -d
動作確認
下記動作確認が期待通りであれば、完成!
- http://example.com/ にブラウザからアクセスする 期待する動作:ブラウザ上に表示されたURLが https://example.com/ であり、「Welcome to nginx!」ページが表示されること
- https://example.com/ にブラウザからアクセスする 期待する動作:ブラウザ上に表示されたURLが https://example.com/ であり、「Welcome to nginx!」ページが表示されること
- http://example.com/kibana/ にブラウザからアクセスする 期待する動作:ブラウザ上に表示されたURLが https://example.com/kibana/ であり、Kibanaトップページが表示されること
- https://example.com/kibana/ にブラウザからアクセスする 期待する動作:ブラウザ上に表示されたURLが https://example.com/kibana/ であり、Kibanaトップページが表示されること
実装でハマったこと
Dockerコンテナ内のローカルホストに関して
Nginxの設定で、プロキシパスをローカルホストに設定したが、ホストOS上のlocalhostへ通信することができなかった。これは、コンテナ内で要求したlocalhostが、コンテナ内を示してしまうためである。
proxy_pass http://localhost:5601;
そこで今回は、プロキシパスをホストOSのIPアドレス(正確にはドメイン)を指定することで通信を実現した。
proxy_pass http://example.com:5601;
なお、Docker Desktop for Macに限った話ではあるが、Docker18.03移行では、host.docker.internalと呼ばれるホストOSのlocalhostへ通信可能な特別なDNS名が提供されている。
KibanaのベースURLを変更する必要があった件
Kibanaのアクセスをリバースプロキシしたところ、上手く動作しなくなりました。原因はKibanaのhtmlが存在しないjavascriptファイルの呼び出しを行ってしまっていたためでした。
Kibanaのルートパスは、リバースプロキシでサブフォルダを指定したため http://example.com:5601/kibana/ がルートパスとなります。一方で、Kibanaのhtmlに書かれたjavascriptの呼び出しパスは、下記のようにルートパスとは異なっていたため、存在しないファイルとして扱われてしまい、上手く動作していませんでした。
</script><script src="/kibana/bundles/app/kibana/bootstrap.js"></script>
この解決策として、Kibanaの公式ドキュメントに以下の記載があります。
Enables you to specify a path to mount Kibana at if you are running behind a proxy. Use the server.rewriteBasePath setting to tell Kibana if it should remove the basePath from requests it receives, and to prevent a deprecation warning at startup. This setting cannot end in a slash (/).
つまり、server.basePathによってKibanaのベースURLを変更する必要があります。実際にkibana.ymlに対して以下の行を加えたところ、問題を解決することができました。
server.basePath: "/kibana"
なお、Kibanaの構築手順に関しては以下の記事で紹介しています。