はじめに
本記事では、nginx(openresty)のログファイルを別のlogrotate用コンテナ(以下logrotateコンテナ)を用いてログローテーションを実装する方法について記述します。
※この記事はpart2です。part1から読んでいただけるとわかりやすいかと思います。
part1: https://qiita.com/toyo_mura/items/4e8b0849df2cb8208f8c
part1の復習
- logrotateコンテナはできた
- ローテーション後のファイルにログを出力してくれない問題が発生
- コンテナ間でのローテーションを完成させるには、logrotateを実行したあと、nginxにログファイルを開きなおさせる(以下: reopen)かnginxの再起動をする必要がある。
ゴール
複数のコンテナで吐き出されているnginxのログをlogrotateコンテナで一括管理・ローテーションできるようになることをゴールとします。
検討したログファイルの開きなおし(reopen)&nginxの再起動
前提
・コンテナ間でのローテーションを完成させるには、logrotateを実行したあと、nginxにログファイルをreopenさせるかnginxの再起動をする必要がある
・具体的にはnginxコンテナでopenresty -s reopen
を実行またはnginxコンテナの再起動をする必要がある
これを実現させるために検討したのが以下です。
-
docker.sockをlogrotateコンテナにマウントしてdockerコマンドを使う
セキュリティ的に問題あり
参考:https://qiita.com/YuasaJunki/items/8da1ef708a6f826f5915 -
dockerをリモートから制御できるようにして、logrotateコンテナからdockerコマンドを使う
セキュリティ的に問題あり
参考:リモートサーバーの中のDockerにローカルから接続する
https://qiita.com/YuasaJunki/items/8da1ef708a6f826f5915 -
nginxコンテナにsshdを起動してlogrotateコンテナからsshで実施
若干セキュリティ的に問題あり
参考:https://postd.cc/docker-ssh-considered-evil/
これだけのためにsshdを起動するのは無駄かも -
LUAを使ってAPI(CGI)を実装(nginxにファイルをreopenさせるAPI)
作るのが少しめんどくさい
既存コンテナの変更が必要 -
pythonを使ってAPI(CGI)を実装(nginxにファイルをreopenさせるAPI)
作るのが少しめんどくさい
既存コンテナの変更が必要
LUAよりも汎用性がありそう
上記のように数個検討しました。
docker.sockをマウントする方法だったり、sshdを起動したりする方法が実装するのは簡単そうでしたが、セキュリティ的に問題があるため、運用していく上でそれではいけないということになり、選択肢から外れました。
結果LUAかpythonかの2択になったときに、「luaはopenrestyには入っているがnginxには入っていない・他の環境でもpythonならだいたい入っている・pythonならアプリに関係なくcgiサーバを起動するだけでいい」などの理由からpythonのほうが汎用性がありそうだったのでpythonでAPIが良さそうという結論に至りました。
想定動作としては以下です。
nginxコンテナ上でreopenのAPI(CGI)を常時動かしておきます。
logrotateコンテナがローテーションを完了したタイミングでlogrotateの機能を使ってnginxコンテナにreopen用のシグナルを乗せたhttpリクエストを送信します。
するとnginxコンテナで動いているCGIサーバ*がCGIを動かします。
CGIがhttpリクエストから取得したシグナルをnginxに送信し、ファイルがreopenされるという仕組みです。
※CGIサーバはpythonのビルトインサーバです。
実装の流れ
- ローテーションしたいファイルのあるコンテナ(以下nginxコンテナ)にディレクトリをマウント(コンテナ起動オプション変更)
- logrotateコンテナ作成
- 動作確認(途中経過)
- logrotateコンテナとnginxコンテナ通信用docker network作成 ←この記事ではここから
- nginxがファイルをreopenするためのAPIを作成
- nginxコンテナの設定変更
- ホストの設定変更
- logrotateコンテナの設定変更
- 動作確認
環境
- ホスト
- OS: CentOS 7.9.2009
- Docker: 20.10.9
- HOSTNAME: development
- nginxコンテナ
- OS: CentOS 7.6.1810
- nginx: openresty/1.19.3.1
- HOSTNAME: development_c
- logrotateコンテナ
- OS: ubuntu 20.04
- HOSTNAME: 5d76504e84c1
logrotateコンテナとnginxコンテナ通信用docker-network作成
とりあえずlogrotateコンテナとnginxコンテナが通信できるようにするためにdocker network create
コマンドでdocker-networkを作成します。
この作業をしないとlogrotateコンテナからnginxコンテナにコンテナ名でcurlコマンドを送信できないので大切な作業です。
このコマンドはコンテナ内ではなく、ホストで行います。
[root@development ~]# docker network create openresty-network
作成できたか確認します。
[root@development ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
2a90d0faac26 bridge bridge local
e091384cb914 host host local
9151cbd0142e none null local
0a1ad870469e openresty-network bridge local
問題なく作成できているようです。
nginxがファイルをreopenするためのAPIを作成
次にCGIプログラムを作成します。今回はPython2(nginxコンテナにデフォルトで入ってたやつ)で実装します。
ファイル名はなんでもいいですが、今回はわかりやすいようにreopen.py
としています。
CGIファイルの中身は以下です。
(汎用性をもたせるために、今回のreopenの機能には必要のない部分もあります。)
#!/usr/bin/env python
import os
import signal
import cgi
p_pid = None
p_sig = None
s_sig = None
form = cgi.FieldStorage()
if form.has_key("pid"):
p_pid = form["pid"].value
if form.has_key("sig"):
p_sig = form["sig"].value
sigs = {
"USR1": signal.SIGUSR1,
"HUP" : signal.SIGHUP
}
if sigs.has_key(p_sig):
s_sig = sigs[p_sig]
if p_pid != None and int(p_pid) > 0 and s_sig != None:
print("Content-Type: text/plain\n")
print("pid: " + p_pid)
print("sig: " + p_sig)
print("signal: " + str(s_sig))
try:
os.kill(int(p_pid), s_sig)
print("res: OK")
except OSError as e:
print("res: " + str(e))
else:
print("Status: 400 Bad Request\n")
print("param error")
参考:
cgi.FieldStorage
has_key
CGI
ビルトインサーバーだと200固定
ビルトインサーバー
全体を通しての動きを説明します。
- logrotateコンテナからcurlで
pid=1
とsig=USR1
を乗せたhttpリクエストを送信
(USR1はnginxにログファイルをreopenさせるシグナル) - nginxコンテナで起動しているCGIサーバがhttpリクエストを受け付けて、
pid=1
とsig=USR1
をreopen.py
に渡す - 受け取ったプログラムは、pidが1のプロセス(ここではnginx)にUSR1のシグナルを送信
- これによってnginxのログがreopenされる
参考:nginx に USR1 シグナルを送ってログを開き直す
プログラムの動きを簡単に説明します。
-
pid=1
とsig=USR1
を受け取る(cgi.FieldStorage
のおかげでクライアントから送られるフォームの内容を取得できる) - 受け取った値が
p_pid
,s_sig
にセットされる -
os.kill(int(p_pid),s_sig)
の部分でnginxにUSR1シグナルを送信 - パーミッションエラーなどの場合はエラーを吐く
このような動作になっています。
また、sigs = {...}
の部分で受け付けるシグナルを限定しています。
参考:https://qiita.com/pythonista/items/ef1cbbf8991e3a5921ff
nginxコンテナの設定変更
次はnginxコンテナの設定を変更していきます。
CGIプログラムを作成したはいいものの、このままでは期待通りの動作をしません。
nginxコンテナの起動ユーザがrootのままCGIを動かすと、nginxにreopenのシグナルを送ってもパーミッションエラーとなります。
pythonをrootで動かしてもCGI実行のユーザがrootじゃないのが理由です。
nginxユーザで起動すれば問題なく動作します。
そのため、nginxコンテナの起動ユーザをrootからnginxに変更する必要があります。
というわけでnginxコンテナの方に変更を加えていきます。
作業内容は以下です。
- nginxコンテナのDockerfileを編集
- entrypoint.shを編集
- コンテナ起動時のオプション変更
nginxコンテナのDockerfileを編集
nginxコンテナをnginxユーザで起動するためにDockerfileを編集します。
nginxユーザで起動するにあたって、ディレクトリのパーミッション等も変更しないといけないのでその設定も記述します。
nginxコンテナのDockerfileは人によって様々だと思いますが、ここでは私が追加した内容を記述します。
下記の項目を追記します。
:
COPY entrypoint.sh /tmp/
COPY reopen.py /var/tmp/cgi-bin/
RUN chmod 707 /etc/nginx/conf
RUN chmod 707 /usr/local/openresty/nginx/logs
RUN chmod 707 /usr/local/openresty/nginx
RUN chmod 707 /usr/local/openresty/nginx/logs/access
RUN chmod 707 /usr/local/openresty/nginx/logs/error
RUN chmod 755 /var/tmp/cgi-bin/reopen.py
RUN useradd -m nginx
ENTRYPOINT ["sh","/tmp/entrypoint.sh"]
USER nginx
CMD ["/usr/bin/openresty", "-g", "daemon off;"]
差分
[root@development nginx]# diff Dockerfile_old Dockerfile
4a5,6
> COPY entrypoint.sh /tmp/
> COPY reopen.py /var/tmp/cgi-bin/
13a16,26
> RUN chmod 707 /etc/nginx/conf
> RUN chmod 707 /usr/local/openresty/nginx/logs
> RUN chmod 707 /usr/local/openresty/nginx
> RUN chmod 707 /usr/local/openresty/nginx/logs/access
> RUN chmod 707 /usr/local/openresty/nginx/logs/error
> RUN chmod 755 /var/tmp/cgi-bin/reopen.py
>
> RUN useradd -m nginx
>
> ENTRYPOINT ["sh","/tmp/entrypoint.sh"]
> USER nginx
entrypoint.shを編集
次にentrypoint.shを編集します。
entrypoint.shはDockerfileの中、ENTRYPOINT
のコマンドで使用してます。
記述内容は以下です。
#!/bin/sh
cd /var/tmp/
nohup python -m CGIHTTPServer &
exec "$@"
nohup python -m CGIHTTPServer &
でnginxコンテナ上でCGIサーバーを常時起動しています。
コンテナ起動時のオプション変更
nginxコンテナの起動ユーザがrootではなくなったので、1024番より小さい値のポートが使えません。
そのため、ポートを変更します。
また、先程作成したdocker-networkを利用するためのオプションも追記します。
以下のように記述します。
docker run -d \
-p 80:8080 \ #80:80だと使えない
:
-v /hogehoge/nginx/access/:/usr/local/openresty/nginx/logs/access/ \
-v /hogehoge/nginx/error/:/usr/local/openresty/nginx/logs/error/ \
:
--network openresty-network \
--name nginx1 toyo_mura/nginx:latest
[root@development nginx]# diff run.sh_old run.sh
17c17
< -p 80:80 \
---
> -p 80:8080 \ #80:80だと使えない
30a31
> --network openresty-network \
ホストの設定変更
ホストの設定を少し変更します。
変更点は以下です
- ログが出力されるディレクトリのパーミッション変更
ログが出力されるディレクトリのパーミッション変更
ログが出力されるホストのディレクトリのパーミッションを変更しないとログを書き出せないので変更します。
[root@development ~]# chmod 707 /hogehoge/nginx/access
[root@development ~]# chmod 707 /hogehoge/nginx/error
logrotateコンテナの設定変更
logrotateコンテナの設定がpart1で設定した内容のままだと動かないので追記を行います。
作業内容は以下です。
- logrotateコンテナのDockerfileにcurlコマンドインストール行を追記
- /etc/logrotate.d/に持っていっているnginxのログ用logrotate定義ファイルにcurlコマンドを追記
- コンテナ起動時のオプション変更
logrotateコンテナのDockerfileにcurlコマンドインストール行を追記
logrotateコンテナからnginxコンテナにcurlコマンドでhttpリクエストを送信したいのでlogrotateコンテナのDockerfileを下記のようにします。
FROM ubuntu:20.04
COPY entrypoint.sh /tmp/
##### rsyslog settings #####
RUN apt-get update
RUN apt-get -y install rsyslog
RUN sed -i -e "s/^#cron/cron/" /etc/rsyslog.d/50-default.conf
##### vim settings #####
RUN apt-get -y install vim
RUN echo 'set encoding=utf-8\n\
set fileencodings=iso-2022-jp,euc-jp,sjis,utf-8\n\
set fileformats=unix,dos,mac' >> ~/.vimrc
##### cron setting #####
RUN apt-get -y install cron
##### logrotate settings #####
RUN apt-get -y install logrotate
COPY ./nginx /etc/logrotate.d/
##### curl setting #####
RUN apt-get -y install curl
##### timezone settings #####
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y install tzdata
ENV TZ Asia/Tokyo
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENTRYPOINT ["sh","/tmp/entrypoint.sh"]
CMD ["cron", "-f"]
差分
[root@development logrotate]# diff Dockerfile_old Dockerfile
22a23,25
> ##### curl setting #####
> RUN apt-get -y install curl
>
/etc/logrotate.d/にCOPYしているnginxのログ用logrotate定義ファイルにcurlコマンドを追記
logrotate終了後にcurlコマンドを自動で叩いてほしいので、/etc/logrotate.d/に持っていっているnginxのログ用logrotate定義ファイルを下記のように編集します。
/var/log/nginx/access/*.log
/var/log/nginx/error/*.log {
ifempty
dateext
missingok
compress
delaycompress
daily
rotate 10
lastaction
curl -v "nginx1:8000/cgi-bin/reopen.py?pid=1&sig=USR1"
endscript
}
差分
[root@development logrotate]# diff nginx_old nginx
9a10,12
> lastaction
> curl -v "nginx1:8000/cgi-bin/reopen.py?pid=1&sig=USR1"
> endscript
lastaction/endscriptはすべてのログがローテーションされた後に、一度だけ実行されるスクリプトです。
参考:https://hackers-high.com/linux/man-jp-logrotate/#lastactionendscript
ここのcurlの部分でnginxコンテナにpid=1
とsig=USR1
シグナルを乗せたhttpリクエストを送信しています。
curlの行を増やすことで複数のコンテナにhttpリクエストを投げることも可能です。
また、送信ポートが8000なのはpythonのCGIHTTPServerの待受ポートが8000だからです。
(nginxコンテナとlogrotateコンテナはopenresty-network
で疎通できているので、特にポート開放等の作業はいらないです)
コンテナ起動時のオプション変更
先程作成したdocker-networkを利用するためのオプションを追記します。
以下のように記述します。
[root@development logrotate]# diff run.sh_old run.sh
6a7
> --network openresty-network \
動作確認
まず、nginxコンテナとlogrotateコンテナ両方docker build
、docker run
してコンテナを作り直しましょう。(編集した設定を反映させるため)
コンテナを作り直したらnginxコンテナでCGIサーバが動いているはずなので本当に動いているか確認します。
[root@development logrotate]# docker exec nginx1 ps -ef
UID PID PPID C STIME TTY TIME CMD
nginx 1 0 0 15:10 ? 00:00:01 nginx: master process /usr/bin/openresty -g daemon off;
nginx 9 1 0 15:10 ? 00:00:02 python -m CGIHTTPServer
nginx 37 1 0 15:11 ? 00:00:00 nginx: worker process
nginx 38 0 0 18:34 ? 00:00:00 ps -ef
PID9番で動いているようです。
ちなみにnginxコンテナでssコマンドを叩くと、CGIサーバが8000番ポートで待ち受けていることがわかります。
[root@development logrotate]# docker exec nginx1 ss -nltp
:
LISTEN 0 5 *:8000 *:* users:(("python",pid=9,fd=3))
:
次に、reopen.py
が正常に動作するか確認します。
logrotateコンテナからcurlでシグナルを乗せたhttpリクエストを送信してみます。
logrotateコンテナ
root@5d76504e84c1:/# curl -v "nginx1:8000/cgi-bin/reopen.py?pid=1&sig=USR1"
* Trying 172.21.0.3:8000...
* TCP_NODELAY set
* Connected to nginx1 (172.21.0.3) port 8000 (#0)
> GET /cgi-bin/reopen.py?pid=1&sig=USR1 HTTP/1.1
> Host: nginx1:8000
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 Script output follows
< Server: SimpleHTTP/0.6 Python/2.7.5
< Date: Fri, 18 Mar 2022 06:20:51 GMT
< Content-Type: text/plain
<
pid: 1
sig: USR1
signal: 10
res: OK
* Closing connection 0
root@5d76504e84c1:/#
問題なくreopen.py
は動作しているようです。
そしたら、実際にログローテーションができてかつ、ログファイルのreopenができているかの最終確認を行います。
nginxコンテナは再起動しておきましょう。
part1までの実装で、ログファイルをローテーションしてくれるようにはなったはずです。
ここでは、ローテーション後のファイルにしっかり書き込めるようになっているか確認します。
まずはpart1と同様、ローテーションの確認をします。
[root@development logrotate]# docker exec -it logrotate bash
root@5d76504e84c1:/# ls /var/log/nginx/access/
hogehoge.com.log fugafuga.com.log piyopiyo.com.log
hogehoge.com.log-20220226.gz fugafuga.com.log-20220226.gz piyopiyocom.log-20220226.gz
hogehoge.com.log-20220227.gz fugafuga.com.log-20220227.gz piyopiyo.com.log-20220227.gz
hogehoge.com.log-20220228.gz fugafuga.com.log-20220228.gz piyopiyo.com.log-20220228.gz
hogehoge.com.log-20220301.gz fugafuga.com.log-20220301.gz piyopiyo.com.log-20220301.gz
hogehoge.com.log-20220302.gz fugafuga.com.log-20220302.gz piyopiyo.com.log-20220302.gz
hogehoge.com.log-20220303.gz fugafuga.com.log-20220303.gz piyopiyo.com.log-20220303.gz
hogehoge.com.log-20220304.gz fugafuga.com.log-20220304.gz piyopiyo.com.log-20220304.gz
hogehoge.com.log-20220305.gz fugafuga.com.log-20220305.gz piyopiyo.com.log-20220305.gz
hogehoge.com.log-20220306.gz fugafuga.com.log-20220306.gz piyopiyo.com.log-20220306.gz
hogehoge.com.log-20220307 fugafuga.com.log-20220307 piyopiyo.com.log-20220307
問題なくローテーションできているようです。
次に、ローテーション後のファイルを掴んでいるか確認します。
(このチェックで、同時にlogrotateのlastaction/endscliptの動作チェックも行っています)
[root@development logrotate]# docker exec nginx1 ls -l /proc/1/fd
total 0
lrwx------ 1 nginx nginx 64 Jan 14 15:37 0 -> /dev/null
l-wx------ 1 nginx nginx 64 Jan 14 15:37 1 -> pipe:[25985041]
l-wx------ 1 nginx nginx 64 Mar 7 14:55 10 -> /usr/local/openresty/nginx/logs/access/hogehoge.com.log
l-wx------ 1 nginx nginx 64 Mar 7 14:55 11 -> /usr/local/openresty/nginx/logs/error/hogehoge.com.log
:
問題なくローテーション後のファイルを掴んでいるようです。
本当にログが出力されるか確認します。
[root@development logrotate]# curl -I 127.0.0.1
:
[root@development logrotate]# docker exec -it nginx1 cat /usr/local/openresty/nginx/logs/access/hogehoge.com.log
172.21.0.1 - - [10/Mar/2022:17:00:05 +0900] "HEAD / HTTP/1.1" 403 0 "-" "curl/7.29.0" "-"
ログが出力されていることが確認できました。
ついでにnginxコンテナ2を立ててみて、複数コンテナでも動作するか確認します。
/etc/logrotate.d/にCOPYしているnginxのログ用logrotate定義ファイルに下記を追加します。
[root@development logrotate]# diff nginx_old nginx
12a13
> curl -v "nginx2:8000/cgi-bin/reload.py?pid=1&sig=USR1"
ローテーションの確認は飛ばします。
ローテーション後のファイルを掴んでいるか確認します。
[root@development logrotate]# docker exec nginx2 ls -l /proc/1/fd
total 0
lrwx------ 1 nginx nginx 64 Mar 28 15:11 0 -> /dev/null
l-wx------ 1 nginx nginx 64 Mar 28 15:11 1 -> pipe:[50709858]
l-wx------ 1 nginx nginx 64 Mar 28 18:10 10 -> /usr/local/openresty/nginx/logs/access/fugafuga.com.log
l-wx------ 1 nginx nginx 64 Mar 28 18:10 11 -> /usr/local/openresty/nginx/logs/error/fugafuga.com.log
:
最後に本当にログが出力されているか確認します。
[root@development logrotate]# curl -I 127.0.0.1:8080
:
[root@development logrotate]# docker exec -it nginx2 cat /usr/local/openresty/nginx/logs/access/fugafuga.com.log
172.21.0.1 - - [28/Mar/2022:16:14:02 +0900] "HEAD / HTTP/1.1" 403 0 "-" "curl/7.29.0" "-"
複数コンテナでも問題なくログが出力されていることが確認できました。
まとめ
- logrotateコンテナとnginxコンテナ間でlogrotateを実装することができた
- logrotateを実装するためには既存のnginxコンテナに変更を加える必要あり
おわりに
part1,part2とここまで読んでいただきありがとうございます。
誰かの役に立ったなら幸いです。
またどこかでお会いしましょう。