サーバの監視
最近自宅サーバを作ったのですが、自宅サーバの状況を監視したいので監視してみます(手段が目的になっている)。
最終的にはこんな感じのダッシュボードを安全で非破壊的な環境で作っていきます。
タイトルを見るとやることがてんこ盛りなのですが、意外とこういう形でまとまっている記事が見当たらず、どれも
- DockerとGrafana
- オンプレでGrafanaとPrometheus
- GrafanaのSSL対応
- PrometheusとNode exporter使ってみた
などのように断片的な知見ばかりでした。
そこで今回はこうした流れに逆張りして、一通り構築ができるようなるチュートリアル的&作業記録を目指して書きました。
だいぶ冗長な大作になってしまいましたが、初心者でも一通りの流れがつかめる構成になっています。
しっかしインフラって色々なツールの知見が求められるなあ(白い目)
つくりたいものと要望
外部ネットワークからexample.comにアクセスしたらGrafanaのダッシュボードにアクセスできてサーバの状態がわかる感じにしたい。
ホストOSはUbuntu。
Grafanaはlets encryptでSSL対応したい。
Grafana, Prometheus, Nginxはすべてdocker上で動くものとする。
example.comは各自の環境で読み替えられたし。
目次
- dockerとdocker-composeをインストール
- 外部から自宅サーバにアクセスできるようにする
- ネームサーバの設定
- Aレコードの設定
- 自宅のサーバをDMZに設定する
- ファイアウォールを有効化して通信を許可する
- Nginxを経由してGrafanaにアクセスできるようにする
- Let’s EncryptでSSL対応
- Prometheusの設定
やってみる
1. dockerとdocker-composeをインストール
つよつよエンジニアの皆さんならdockerとdocker-composeくらい入れてますよね?(前提)
そのため、もしインストールしていない人がいても、ここは他の記事に任せることにしたいと思います。
ではなぜこのパートを残したのか?
それは「Ubuntuでdocker, docker-composeを入れるならsnapではなくaptを使え」と言いたかったからです。
最初はsnapで入れたのですが、permissionまわりでエラーが多発したんですよね。
で、snap版を削除してaptで公式リポジトリを使ってインストールしたら、今までのpermisson系のエラーが嘘みたいに消えたんですよ。
ということでUbuntuでDockerを使う場合は公式リポジトリを追加してaptでインストールしましょう。
2. 外部から自宅サーバにアクセスできるようにする
自分は独自ドメインをムームードメインを使っています(お名前.comのメールボムがうざかったので)。
そのためムームードメインの設定画面を使って説明しています。
とはいえやることはサービスにそこまで依存しないと思うので、適宜自分の環境に読み替えてください。
ネームサーバの設定
自分はムームードメインのネームサーバを使っています。
サブドメインの追加・反映がカンタンだからです。
ドメインを買った後はこんな感じになっていますね。
後述するAレコードの変更操作がきちんと反映・配信される状態ならなんでもOK。
Aレコードの設定
サブドメインのAレコードを追加します。
自分の家のIPアドレスは確認くんで確認しておいたので、とりたいサブドメインと自分の家のIPアドレスをここに設定します。
自分のサーバをDMZに設定する
続いて自分の家にあるルータの設定を変更します。
家のルータは192.168.1.1でアクセスできるのでログインして、DMZに自宅サーバのIPアドレスをセットします。
これで外部からの自宅のグローバルIPアドレスに向けた通信はすべてこのIPアドレスが振られたサーバに送り付けられます。
DMZを使うのが嫌な人はちまちまポートマッピング設定をしてください。
自宅サーバのIPが192.168.1.29だったので、こいつをDMZに指定してやります。
ファイアウォールを有効化して通信を許可する
$ sudo ufw enable
でファイアウォールを有効化します。
$ sudo ufw status
で有効にできたのか確認できます。
$ sudo ufw allow 80
$ sudo ufw allow 443
など、解放したポートは上記のように設定しましょう。
3. Nginxを経由してGrafanaにアクセスできるようにする
grafanaとnginxのディレクトリをホームディレクトリに作成
ホームディレクトリにgrafanaとnginx専用のディレクトリを作成します。
$ cd ~ $ mkdir grafana
$ mkdir nginx
grafanaとnginxが通信できるようにするためのネットワークを作成
$ docker network create nginx-proxy
nginx-proxyネットワークでgrafanaとnginxでネットワークを共有し、コンテナ同士が通信ができるようにします。
外部に公開するのでここでは–internalオプションはつけません。
grafanaのdocker-compose.ymlを作成して起動
$ cd ~/docker ~/docker
$vim docker-compose.yml
でdocker-compose.ymlを新規作成します。
docker-compose.ymlには
version: "3"
services:
grafana:
image: grafana/grafana:latest
container_name: grafana
volumes:
- ./data/grafana/grafana.ini:/etc/grafana/grafana.ini
- ./data/grafana:/var/lib/grafana
networks:
nginx-proxy:
networks:
nginx-proxy:
external: true
と記載します。
ポイントとしては
- nginxはgrafanaというサービス名を使ってgrafanaのdockerコンテナにアクセスできます。
- volumesの1つ目ではdata内に保存されたgrafana.iniを参照するように指定しています。
元となるファイルは適当にコンテナを立ち上げてdocker cpなどでコンテナの中にあるdocker.iniを引っ張り出してきましょう。 - volumesの2つ目ではdata/grafanaに/var/lib/grafanaのデータを保存するようにしています。
これによって、grafanaのダッシュボードやログイン情報などを永続化することができます。 - networksに先ほど作ったnginx-proxyを指定してやり、external: trueにすることでnginx-proxyネットワークに参加させます。
nginxのdocker-compose.ymlを作成して起動
$ cd ~/nginx ~/nginx
$ vim docker-compose.yml
でdocker-compose.ymlを新規作成します。
docker-compose.ymlには
version: "3"
services:
nginx:
image: nginx
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
ports:
- 80:80
- 443:443
networks:
nginx-proxy:
networks:
nginx-proxy:
external: true
と記載します。
ポイントとしては
- ここではdefault.confをvolumeで指定してあげることで、ホストの./default.confを読み取ってくれるようになります。
基となるファイルは先ほどと同様に、適当にdockerコンテナを立ててdocker cpで引っ張り出せばよいです。 - portについてはhostの80, 443番ポートをnginxのコンテナの80, 443番ポートに紐づけます。
- networkでは、grafanaの場合と同様に事前に作ったnginx-proxyにつなぐように設定します。
続いてnginxの設定を行います。
default.confはコメント部分を省略して
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://grafana:3000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
と記載しています。
ロードバランシングとかは特にやりませんが、やりたい人はupstreamブロックを使ってグループを定義しましょう。
ポイントとしては
- server_nameには先ほどAレコードで設定したものを記載します。
- proxy_passでリバースプロキシを設定します。
先ほどgrafanaのdocker-compose.ymlで設定したサービス名のdockerでgrafanaコンテナにアクセスします。
ポートは3000を使用します。 - エラーページについてはデフォルトのものを設定していますが、セキュリティ的にはここも独自ページに差し替えると良いです。
4. Let’s EncryptでSSL対応
ステップは大まかに分けて3つです。
- docker版のcertbotを使って証明書を取得し、certbot用のボリュームに放り込むこと。
- 取得した証明書をNginxにセットすること。
- 証明書の更新を自動化すること。
3つと言っていますが、作業量が多いのは1つ目だけです。
それでは1つずつ進めていきましょう。
証明書関連のデータを保管するボリュームを作成する
当初はディレクトリで管理しようと思っていたのですが、書き込み権限周りで試行錯誤するのが嫌だったのでボリュームを使用する方針に転換しました。
その代償としてボリュームを3つ使うことになりました。もっとうまい方法があれば教えてください。
ACME用と/etc/letsencrypt/用と/var/用でボリュームを分けています。
$ docker volume create --name=nginx-certs-etc
$ docker volume create --name=nginx-certs-var
$ docker volume create --name=nginx-certs-acme
で証明書関連のボリュームを作成します。
1つ目のnginx-certs-etcは/etc/letsencryptに生成されるデータに対応します。
/etc/letsencryptのディレクトリ構成が気になる方はこちらの記事を見て下さい。
2つ目のnginx-certs-varは/var/lib/letsencryptに生成されるデータに対応します。
詳細は調べていないのでよくわかっていませんが、certbotが使うようなので作りました。
3つ目のnginx-certs-acmeはAutomatic Certificate Management Environment(自動証明書管理環境)のために作っています。
http://<ドメイン(ここではexample.com)>/.well-known/acme-challenge/にファイルを生成して、それをLet’s Encryptが読み取ることでドメインの認証を行っています。
詳細が気になる方はこちらへ。
これらのボリュームを先ほどのネットワーク同様に、複数のコンテナで使いまわしていきます。
Nginxのdocker-compose.ymlがボリュームを使えるようにする
まずはnginxの方のdocker-compose.ymlを下記のように書き換えます。
version: "3"
services:
nginx:
image: nginx
ports:
- 80:80
- 443:443
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
- nginx-certs-etc:/etc/letsencrypt/
- nginx-certs-acme:/usr/share/nginx/html/.well-known/acme-challenge/
networks:
nginx-proxy:
networks:
nginx-proxy:
external: true
volumes:
nginx-certs-etc:
external: true
nginx-certs-acme:
external: true
volumesに1つ前のステップで作成したnginx-certs-etcとnginx-certs-acmeを設定しています。
これ以外は変えていません。
default.confでACME用のディレクトリが参照できるように変更
外部からhttp://example.com/.well-known/acme-challenge/にさえアクセスできれば問題なく認証できます。
default.confの設定を下記のように書き換えます。
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://grafana:3000;
}
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html/.well-known/acme-challenge/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
証明書取得スクリプトを作成
スクリプトを作成といってもワンライナーです。
ファイル名をget_cert.shとして、中には
#!/bin/sh
docker run -it --rm --name certbot \
-v "nginx-certs-etc:/etc/letsencrypt/" \
-v "nginx-certs-var:/var/lib/letsencrypt/" \
-v "nginx-certs-acme:/var/www/html" \
certbot/certbot certonly --non-interactive --webroot /var/www/html -d example.com --agree-tos -m example@example.com --dry-run
と記述します。
ここでもちゃんとdockerを使っていきます。
ポイントとしては
- volumeで/etc/letsencrypt、/var/lib/letsencrypt、/var/www/htmlは先ほど作成したボリュームを利用するように設定しています。
- 証明書の本体は/etc/letsencryptに割り当てられたボリュームであるnginx-certs-etcに入ります。
- certonlyなので、証明書を取得するところまでやってくれます。
取得した証明書をNginxにセットするのは手動でやります。 - example.comは各自の環境に置き換えてください。
- –non-interactiveモードでは–agree-tosで利用規約に同意し、-mで証明書の期限切れなどを通知するメールアドレスを事前に入力しておきます。
- –webrootプラグインと-wでドキュメントルートを指定することで、NginxなどのWebサーバを使ってACME認証を行うことができます。
もし–standaloneプラグインを使う場合は、standaloneで認証できる=certbot自身が80番ポートをバインドしてサーバを立てて認証するため、Nginxを終了して80番ポートを解放する必要があります。 - –dry-runで、このスクリプトを本番実行する前に適切に動作するか検証することができます。
本番実行は1時間に5回までという回数制限(rate limit)がありますが、–dry-runをつけることで回数制限の緩い仮実行を行うことができます。
–dry-runを外すことで本番実行ができます。
~/nginx$ chmod +x get_cert.sh
~/nginx$ ./get_cert.sh
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Simulating a certificate request for example.com
The dry run was successful.
と出力されればOKです!
–dry-runを外して実行
~/nginx$ ./get_cert.sh
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2022-08-30.
These files will be updated when the certificate renews.
NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
このように出力されれば取得は完了です!
Nginxに証明書をセット
Nginxのdefault.confにSSL対応の設定を書き加えます。
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_redirect off;
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;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_pass http://grafana:3000;
}
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html/.well-known/acme-challenge/;
}
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
ポイントとしては
- proxy_set_headerを記載しないとGrafanaで’Origin not allowed’とエラーが出てきます。
- httpアクセスはhttpsの方へリダイレクトするようにしています。
ここまで設定できたらNginxのコンテナを再起動します。
$ docker-compose restart
再起動して$ docker psなどでプロセスが死んでないかを確認したら、https://example.comにアクセスしてみましょう。
https通信の場合に出てくる錠前マークがでてきました!
Let’s Encryptの証明書更新スクリプトを作成
certbot renewを実行するスクリプトを作ります。
作るといってもさっきのワンライナーを微修正するだけです。
$ cp get_cert.sh renew.sh
でコピーし、renew.shの中身を
#!/bin/sh
docker run --rm --name certbot \
-v "nginx-certs-etc:/etc/letsencrypt/" \
-v "nginx-certs-var:/var/lib/letsencrypt/" \
-v "nginx-certs-acme:/var/www/html" \
certbot/certbot renew --dry-run
に書き換えます。書き換えたのは–itオプションを削除したのと、最後の行です。
–itオプションを削除したのは後述するcrontabでthe input device is not a TTYと怒られてしまうからです。
–dry-runで実行してみます。
~/nginx$ ./renew.sh
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account registered.
Simulating renewal of an existing certificate for example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
更新はうまくいったようです!
Cronの設定ファイルを作成
この更新をいちいち手動でやるのは面倒なので、定期実行させましょう。
Cronに食わせるファイルを作成します。
$ vim renew_cron
でファイルを新規作成&オープンして
* * * * * /home/example/nginx/renew.sh >> /home/example/nginx/cert_renew_result.log 2>&1
と記述します。
ポイントとしては
- 1分ごとに定期実行するという意味です。
動作確認のためにこの設定にしていますが、あとで適切なスケジューリングに変更します。 - Cronの文法が気になる人にはこちらのサイトがわかりやすくておすすめです。
- ファイルなどの指定は絶対パスで行いましょう。
- 出力先を指定する>>は、cert_renew_result.logに追記することを意味しています。> にすると上書きされてしまいます。
- 2>&1は、標準出力と標準エラー出力を一つのファイルにまとめることを意味しています。
Cronに食わせる
~/nginx$ crontab renew_cron
~/nginx$ crontab -l
00 3 * * 1 /home/example/nginx/renew.sh >> /home/example/nginx/cert_renew_result.log 2>&1
crontab -lで表示されていれば、適切に読み込まれています。
1分後にcert_renew_result.logを見てみましょう。
~/nginx$ cat cert_renew_result.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Saving debug log to /var/log/letsencrypt/letsencrypt.log
本番設定に書き換え
crontab -eを使って現在の1分ごとに実行される設定を削除します。
crontab -rは使ってはダメです。他に設定していたものも全部消えます。
現在の設定を削除した後、renew_cronをこのように書き換えます
00 03 * * 1 /home/example/nginx/renew.sh >> /home/example/nginx/cert_renew_result.log 2>&1
変えたのは先頭の部分。
毎週月曜日の午前3時に定期実行するようにしました。
これを先ほどと同様に
$ crontab renew_cron
で読み込ませます。
そしてrenew.shを編集し、–dry-runオプションを外します。
これでLet’s Encryptの自動更新設定も完了です!
5. PrometheusとNode Exporterの設定
ここではPrometheusを立ち上げ、Node Exporterからメトリクスを取得するように設定していきます。
まずprometheus向けのディレクトリを作成しましょう。
~$ mkdir prometheus
~$ cd prometheus
Prometheusのdocker-compose.yml
まずdocker-compose.ymlからです。
version: "3"
services:
prometheus:
image: prom/prometheus
hostname: prometheus
ports:
- 9090:9090
#volumes:
# - ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: always
networks:
prometheus-network:
networks:
prometheus-network:
ここでのポイントは
- volumeの部分をいったんコメントアウトしてdocker-compose up -dし、docker cpコマンドで/etc/prometheus/prometheus.ymlのひな型をコンテナ内からコピーしてきます。
コピーした後はdocker-compose.ymlのvolumeのコメントアウトを外してdocker-compose restartし、ホストPCにあるprometheus.ymlを使うようにします。 - networksには共通のネットワークを割り振って、コンテナ同士が通信できるようにしています。
prometheus.ymlは、コメントをすべて削除したら、デフォルトでこのような設定になっていると思います。
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
そして再度docker-compose up -dをして、localhost:9090に接続し、Prometheusの画面が出ればOKです!
Node Exporterを追加
続いてホストの監視のためにNode Exporterを追加していきます。
docker-compose.ymlの設定を下記のようにします。
version: "3"
services:
prometheus:
image: prom/prometheus
hostname: prometheus
ports:
- 9090:9090
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: always
networks:
prometheus-network:
node-exporter:
image: prom/node-exporter
container_name: node-exporter
ports:
- 9100:9100
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
networks:
prometheus-network:
networks:
prometheus-network:
ポイントとしては
- node-exporterを追加しています。
- node-exporterのデフォルトポートは9100です。
エクスポーターごとにデフォルトポートが違うので、もし新しくエクスポーターを追加する場合はドキュメントを読んで設定しましょう。 - volumeでホストOSの/proc, /sys, /をマウントして、commandでパスを渡してあげることでホストOSのメトリクスを取得することができます。
:roを指定し、読み込み専用で安全に利用することができます。 - prometheusと通信するため、prometheus-networkにつなぐようにしています。
続いてprometheus.ymlを下記のように設定します。
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: 'node_exporter'
static_configs:
- targets: ["node-exporter:9100"]
ポイントとしては
- 追加したのはjob_name: ‘node_exporter’以下になります。
- targetsには、docker-compose.ymlで設定したホスト名のnode-exporterの9100番ポートにアクセスするように指定します。
設定の変更を反映するために、docker-compose restartしてコンテナを再起動します。
Grafanaと連携
prometheusのdocker-compose.ymlを下記のように設定します。
version: "3"
services:
prometheus:
image: prom/prometheus
hostname: prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: always
networks:
nginx-proxy:
node-exporter:
image: prom/node-exporter
container_name: node-exporter
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
networks:
nginx-proxy:
networks:
nginx-proxy:
external: true
ポイントとしては
- ポートの指定を外して外部からアクセス不能にすること
- nginx-proxyを指定してgrafanaと通信可能にすること
です。
docker-compose downとdocker-compose up -dでprometheus関連コンテナを再起動し、https://localhostにアクセスします。
証明書エラーがでてアクセスできないようであれば、hostsファイルにexample.com(最初に設定したドメイン名)とIPアドレスを紐づけます。
開けたら左のタブからconfigを選びます。
そしてAdd data sourceをクリックします。
Prometheusをクリックします。
URLにhttp://prometheus:9090を指定します。
先ほどdocker-compose.ymlでnginx-proxyにprometheus関連のコンテナを追加したので、ホスト名prometheusでアクセスできます。
一番下にスクロールしてSave & Testを押してこのような表示が出たらOKです。
外部のダッシュボードを活用する
この章は「Prometheus実践ガイド」を参考にしました。2022年6月1日初版第1刷という最新の本です(これやり始めたのが5月末だったのでめっちゃタイムリーだった)。
こちらのサイトでは、Grafanaのダッシュボードのテンプレートファイルが公開されています。
今回は書籍で紹介されていたNode Expoter Fullというダッシュボードを使ってみます。
まずhttps://grafana.com/grafana/dashboardsにアクセスします。
Node Expoter Fullは一番ダウンロードされているダッシュボードみたいですね。
右のCopy ID to Clipboardと書かれた青いボタンをクリックして、IDをコピーします。
そしてGrafanaのダッシュボードに戻り、左側メニューの+のimportを選択して、Import via grafana.comにペーストします。
Loadボタンをクリックするとこの画面に遷移するので、Prometheusのところから先ほど追加したデータソースを選択して、Importをクリックします。
うまく表示されました!
かっけー!やっぱGrafanaのUIイケてて好きですねえ。
このUIにワクワクしない男なんているんでしょうか?
ということで今回の記事はここまでです!
おつかれさまでした!
参考