Table of Contents
モチベーション
- まっさらなUbuntuにNginxコンテナを立てて,それをWEBサーバーとして運用したい.
- Let’s encryptでHTTPSに対応したい.
前準備
- UbuntuがインストールされたAmazon EC2やAmazon Lightsail,GCP Compute Engine, VPSや物理サーバなどを用意し,グローバルなIPアドレスを設定しておく.
- ドメインを取得し,UbuntuサーバーのIPアドレスと関連付けておく.
パッケージをアップデートする
apt update
でパッケージ一覧を更新apt upgrade
でパッケージを更新apt autoremove
で必要なくなったパッケージを削除apt autoclean
で.debファイルを削除/var/log/apt/history.log
にaptコマンドの履歴がある/var/log/apt/term.log
にaptコマンドのログがある
パッケージの自動アップデートを設定する
セキュリティ対策のため,パッケージは常に最新になるようにする.
20-auto-upgradesを編集する
vi /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::AutocleanInterval "7"; APT::Periodic::Unattended-Upgrade "1";
- Update-Package-Lists : アップデートの確認頻度[Day]
- Download-Upgradeable-Packages : アップデートをダウンロードしておくか
- AutocleanInterval : クリーンアップ頻度[Day]
- Unattended-Upgrade : アップデートをインストールするか
50unattended-upgradesを編集する
vi /etc/apt/apt.conf.d/50unattended-upgrades
"${distro_id}:${distro_codename}-updates"; をコメントアウト
20-auto-upgradesを適用する
dpkg-reconfigure -plow unattended-upgrades
自動アップデートのサービスがactiveかを確認
systemctl status unattended-upgrades.service
テストする
unattended-upgrade --dry-run --debug
一般ユーザを新規追加してsudoが使えるようにしておく
adduser <user>
gpasswd -a <user> sudo
一般ユーザでサーバーにSSHできるようにする
ローカルで公開鍵を生成する
以下はmacbookをローカルとした場合の手順
1. cd ~/.ssh
2. ssh-keygen -t rsa -b 4096
3. chmod 600 <秘密鍵>
4. vi config
```
Host <nickname>
HostName <ip address>
Port <port for ssh>
User <username>
IdentityFile ~/.ssh/<秘密鍵>
```
cat < ~/.ssh/<公開鍵> | pbcopy
サーバーに公開鍵を登録する
su <user>
mkdir ~/.ssh
chmod 700 .ssh
~/.ssh/authorized_keys
に公開鍵をコピペchmod 600 authorized_keys
cd /etc/ssh
sudo cp sshd_config sshd_config.bk
sshd_config
を編集するPort <port for ssh> <== 22番以外にしたほうが良い PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes UsePAM no
sudo systemctl restart ssh
ローカルからサーバにsshできれば優勝
rm <公開鍵>
Firewallを設定する
sudo ufw reset
sudo ufw default DENY
で全パケットを拒否する設定にするsudo ufw allow <port for ssh>
でSSHのポートは許可しておくsudo ufw enable
でファイアウォールを有効化sudo ufw status verbose
で状態を確認
- ufwのログは
/var/log/ufw.log
に出力される - 設定したルールを削除したいなら
sudo ufw status numbered
sudo ufw delete <消したい番号>
Docker(rootless)をインストールする
cat /etc/lsb-release
でUbuntuのバージョンを確認する.ここではUbuntu 20.04を対象とした.uidmap
が必要なので,sudo apt install uidmap
を実行する.- Install Docker Engine
- Run the Docker daemon as a non-root user
- systemctl周辺でエラーが生じる場合は,
export XDG_RUNTIME_DIR=/run/user/$UID
して実行してみる.
- systemctl周辺でエラーが生じる場合は,
- rootlessだと1024番未満のportをbindすることができない.
/etc/sysctl.conf
にnet.ipv4.ip_unprivileged_port_start=0
を追加してsudo sysctl --system
を実行することで,1024番未満のportをbindできるようになる. sudo ufw allow 80
とsudo ufw allow 443
を実行して,80番portと443番portへの外部からのアクセスを許可する.- Rootlessはデフォルトで,client ipをコンテナに伝搬しないので,(コンテナ内のnginxでclient ipを取得しようとしても,コンテナの接続しているdocker bridgeのgatewayになってしまう)以下を実行してclient ipをコンテナ内で受け取れるようにする.ただし,デフォルトの方がパフォーマンスが良いので,コンテナでclient ipを受け取らなくても良いのなら設定する必要は無い.
~/.config/systemd/user/docker.service.d/override.conf
に以下を追加する[Service] Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns"
sudo systemctl daemon-reload
sudo systemctl restart docker
reboot
Nginxコンテナを立ち上げてHTTPでアクセスできることを確認する
今回は以下のようなフォルダ構成を仮定して説明する.
/web/
├ nginx/
| ├ letsencrypt/ <=== let's encrypt関連
| ├ logs/
| | ├ access.log <=== アクセスログ
| | ├ error.log <=== エラーログ
| | └ certbot.log <=== cronによる証明書の自動更新の際のログ
| ├ default.conf <=== nginx.confからincludeされる設定
| ├ nginx.conf <=== Nginxの設定
| └ Dockerfile
└ docker-compose.yml
まずは.Nginxコンテナを起動するためのDockerfileを書く.詳しくはDockerコンテナのライフサイクルを参照のこと.
FROM nginx:latest
RUN apt-get update && \
apt-get install -y certbot python-certbot-nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
ADD nginx.conf /etc/nginx/
ADD default.conf /etc/nginx/conf.d/
- Nginxコンテナのベースイメージはnginx at docker hubを用いることにする.
FROM nginx:latest
で取得できるNginxイメージはDebian10.4ベース. - 今回はHTTPS対応のためにLet’s encryptを用いることにする.Let’s encryptを使いやすくするためのパッケージであるcertbotをコンテナにインストールするため,DockerfileのRUNにて
apt-get install -y certbot python-certbot-nginx
を指定している.- インストール後,コンテナ内に
/etc/letsencrypt
というディレクトリが作られるので,後のdocker-compose.ymlにて,ホストの/web/nginx/letsencrypt
とリンクさせている.
- インストール後,コンテナ内に
- ホスト側の
nginx.conf
とdefault.conf
をコンテナ内にコピーするようにADDを指定する.
次に,以下のようなNginxの設定ファイルを作成&編集する.パラメータの説明は今回は省く.
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
http {
server_tokens off;
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;
tcp_nodelay on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf; <=== 別の.confファイルをincludeする
}
また,以下のようなnginx.confからincludeされるdefault.confを作成&編集する.パラメータの説明は今回は省く.
server {
server_name <hostname>;
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
そして,以下のようなdocker-compose.ymlを書く.詳しくはDockerコンテナのライフサイクルを参照のこと.
version: '3'
services:
proxy:
build: ./nginx <=== /web/nginx/Dockerfileへの相対パス
tty: true
container_name: proxy
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
ports:
- "80:80" <=== HTTPで外部からアクセスできるように
- "443:443" <=== HTTPSで外部からアクセスできるように
volumes:
- '/web/nginx/logs:/var/log/nginx' <=== nginxのログはホスト側と共有する
- '/web/nginx/letsencrypt:/etc/letsencrypt' <=== letsencryptのディレクトリもホストと共有する
最後に,ホストの/web/
ディレクトリ内でdocker-compose up -d
を実行すると,docker-compose.ymlから新たなDockerイメージがビルドされてNginxコンテナが立ち上がる.ブラウザからhttp://<hostname>
でアクセスできれば,HTTP部分は開通.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5320cf3e8eff web_proxy "/docker-entrypoint.…" 21 seconds ago Up 20 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp proxy
HTTPSに対応する
ホストでdocker exec -it proxy bash
を実行することでNginxコンテナに入り,cerbot
を実行することでLet’s encrypt証明書を発行する.発行された証明書はコンテナ内の/etc/letsencrypt/live/<hostname>/
に保存される.
次に,default.confを以下のように編集する.80番ポートへのアクセスは443番ポート(https://)へリダイレクトするように設定し,443番ポートにはsslの設定を行う.
server {
server_name <hostname>;
listen 80;
return 301 https://$host$request_uri;
}
server {
server_name <hostname>;
listen 443;
ssl on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/letsencrypt/live/<hostname>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<hostname>/privkey.pem;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
最後に,ホストの/web/
ディレクトリ内でdocker-compose restart
を実行して,Nginxコンテナを再起動することで,default.confを再読み込みさせる.ブラウザからhttps://<hostname>
でアクセスできれば,HTTPSの対応が完了する.
Let’s encryptの証明書は90日で期限切れになるので,自動で更新するようにする
Let’s encryptの証明書は90日で期限切れになるので,定期的に更新する必要がある.証明書の更新を自動で行うため,cronにjobを登録する.
cp /etc/crontab /etc/cron.d/certbot_docker
にて,crontabフォーマットファイルをコピーするcertbot_docker
に以下のような設定を行う.以下は,毎日午前3時にdate; docker exec proxy certbot renew
を実行し,結果を/web/nginx/log/certbot.log
に出力するというジョブである.# m h dom mon dow user command 0 3 * * * <username> { date; docker exec proxy certbot renew; } >> /web/nginx/log/certbot.log 2>&1
sudo service cron restart
により,certbot_docker
をcronに反映する.毎日午前3時に以下のようなログが
/web/nginx/log/certbot.log
に出力される.証明書の期限に余裕がある場合は,証明書の更新はスキップされ,証明書の期限が近づくと更新される.Thu Jul 23 03:00:01 JST 2020 Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/<hostname>.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cert not yet due for renewal - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The following certs are not due for renewal yet: /etc/letsencrypt/live/<hostname>/fullchain.pem expires on 20XX-XX-XX (skipped) No renewals were attempted. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -