これだけ覚えればOK、rsyncのディレクトリ、ファイルパスの指定方法
ディレクトリどうしをまるごとコピーしたい場合
コピー元・先両方に末尾に / をつけて、コピー先のディレクトリまで指定する。
rsync -av /path/to/dir/ remote:/path/to/dir/
rsync -av /path/to/dir remote:/path/to
とか別の書き方もあるけどパット見わかりづらいので ダメ 。
ディレクトリの末尾に /
をつけるのが肝要。
ファイルをコピーしたい場合
コピー元・先両方をファイルのパスで指定する、 もしくは、 コピー元をファイルのパスで、コピー先を末尾に / をつけたディレクトリのパスで指定する。
rsync -av /path/to/file remote:/path/to/file もしくは rsync -av /path/to/file remote:/path/to/
おまけ
コピー元・先でパスが同じ場合
コピー元・先でパスが同じなら -R
を使うのもよい。コピー先のパス指定ミスが防げるので。
rsync -avR /path/to/dir/ remote:/ rsync -avR /path/to/file remote:/
コピー元がリモートで、複数指定する場合
rsync -av remote:/path/to/file1 remote:/path/to/file2 /path/to/dir/
と書いてもよいが、リモートホスト名は省略できる。
rsync -av remote:/path/to/file1 :/path/to/file2 /path/to/dir/
Ubuntu 18.04 で Postfix の multi instances の service unit を有効にする方法
Managing multiple Postfix instances on a single host に従い、multi instances の設定をした後、その service unit を用意する方法のメモ。
/lib/systemd/system-generators/*
などにあるファイルはOS起動時や systemd のリロード (systemctl daemon-reload
) 時に実行される- postfix パッケージに
/lib/systemd/system-generators/postfix-instance-generator
が含まれている postfix-instance-generator
は、postconf -h multi_instance_directories
の結果に応じて/run/systemd/generator/postfix.service.wants/postfix@postfix-XXX.service
な symlink を作るpostmulti -I postfix-XXX -G mta -e create
すると、/etc/postfix/main.cf
のmulti_instance_directories
に/etc/postfix-XXX
が追加される
/run/systemd/generator/postfix.service.wants/*
は、systemctl start postfix
したときに一緒に start される- なので、
systemctl start postfix
で子インスタンスも起動される。stopとかrestartも同様
結論
子インスタンスの設定したあとに systemctl daemon-reload
すればおk
Apache が AH00144 で落ちる件
事象
- Ubuntu 18.04
- apache2 (2.4.29-1ubuntu4.12)
で、apache2 プロセスが次のエラーメッセージを吐いて落ちるという連絡を受けて調べました。これはその原因と対処法のメモです。
[mpm_prefork:emerg] [pid 18633] (43)Identifier removed: AH00144: couldn't grab the accept mutex [mpm_prefork:emerg] [pid 18632] (43)Identifier removed: AH00144: couldn't grab the accept mutex [core:alert] [pid 18624] AH00050: Child 18632 returned a Fatal error... Apache is exiting! [:emerg] [pid 18624] AH02818: MPM run failed, exiting
原因
簡単にいうと、Apache が Mutex に使っているセマフォを systemd-logind が消してしまうのが原因です。
Apache の Mutex
Mutex ディレクティブ のデフォルトは Mutex default
で、どの機構が採用されるかは APR に委ねられています。
どの機構が採用されるかはこのようにして確認できます。
$ cat mutex-default.c #include <apr_portable.h> int main(int argc, char** argv) { printf("%s\n", apr_proc_mutex_defname()); return 0; } $ gcc $(apr-config --includes) mutex-default.c $(apr-config --link-ld) $ ./a.out sysvsem
存在する IPC セマフォなどの確認は ipcs
コマンドでできます。
Apache が起動していれば、セマフォがいくつか表示されるはずです。
$ ipcs -a ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems 0x00000000 5111808 gyoza 600 1 0x00000000 5144577 gyoza 600 1 0x00000000 5046274 gyoza 600 1 0x00000000 5177347 gyoza 600 1 0x00000000 5210116 gyoza 600 1
systemd-logind の RemoveIPC
systemd-logind はデフォルトで RemoveIPC=yes
になっています。
RemoveIPC
とは logind.conf(5) によれば次のように説明されています。
RemoveIPC= Controls whether System V and POSIX IPC objects belonging to the user shall be removed when the user fully logs out. Takes a boolean argument. If enabled, the user may not consume IPC resources after the last of the user's sessions terminated. This covers System V semaphores, shared memory and message queues, as well as POSIX shared memory and message queues. Note that IPC objects of the root user and other system users are excluded from the effect of this setting. Defaults to "yes".
つまり、
- logind 管理下のセッションが全てなくなるときに、そのユーザーの IPC セマフォなどを全て削除する
- ただし、root とシステムユーザーは除外される
ということです。
システムユーザーとは、 uid が 999 以下のユーザーのことです。
再現方法
- Apache を非システムユーザー (uid が 1000 以上) で起動する → セマフォが作られる)
- その非システムユーザーで SSH ログインする
- ログアウトする → セマフォが消される
- Apache にリクエストが来るとプロセスが死ぬ
これで再現します。
通常は Apache はシステムユーザである www-data の権限で起動されるのでこの問題は発生しませんが、この環境では深淵な理由で非システムユーザーで起動していました。
対処法
内製のデーモンで IPC を使っていると同じようにハマりそうなので、後者の対処法を採用しました。
Apache でセマフォではない Mutex を使う
Mutex file:/var/lock/apache2 default
systemd-logind で IPC を消さないようにする
# install -d -o root -g root -m 755 /etc/systemd/logind.conf.d/ # cat << EOF > /etc/systemd/logind.conf.d/ipc.conf [Login] RemoveIPC=no EOF # systemctl restart systemd-logind.service
kernel panic 時の oops メッセージを netconsole でインターネット越しに送信する
自宅サーバー (!) がちょいちょい落ちるんです。たぶんコンソールには kernal panic 時の oops メッセージが表示されてると思うんですが、モニタは繋いでないし宅内には他にサーバーもいないのでシリアル経由で送信することもできないので確認する術がなく。
で、そういえば netconsole ってのがあったなーと思い出したんで、設定してみた次第です。
ちなみに送信先は GCE の Always Free のインスタンス (ちゃんと中国とオーストラリアからのアクセスはフィルタしています) です。
疎通の確認
netconsole は UDP を使います。受信側のポート番号を決めて (6666 とします) 、まずはそれが通るようにフィルタリングの設定をします。
疎通確認はこんな感じで。
# 受信側 sudo tcpdump -i any -nlxX port 6666
# 送信側 echo konyanya-chiwa > /dev/udp/DEST_IP_ADDRESS/6666
送信側の設定
echo 'options netconsole netconsole=6665@SRC_IP_ADDRESS/eth0,6666@DEST_IP_ADDRESS/GW_MAC_ADDRESS' | sudo tee /etc/modprobe.d/netconsole.conf echo netconsole | sudo tee -a /etc/modules sudo modprobe netconsole
SRC_IP_ADDRESS
- 送信元の eth0 についてるプライベート IP アドレス
DEST_IP_ADDRESS
- 送信先のグローバル IP アドレス
GW_MAC_ADDRESS
- 送信元のデフォルトゲートウェイの MAC アドレス
- 送信先が同じサブネット内の場合は送信先の MAC アドレスを指定しますが、サブネット外の場合はデフォルトゲートウェイの MAC アドレスを指定します
00:00:5e:00:53:31
な感じで指定
試行錯誤するときはこんな感じでおk
sudo modprobe netconsole 'netconsole=6665@SRC_IP_ADDRESS/eth0,6666@DEST_IP_ADDRESS/GW_MAC_ADDRESS' sudo rmmod netconsole
受信側の設定
syslog は使わずに udplogger を使いました。
curl -L -o udplogger.c 'https://osdn.net/projects/akari/scm/svn/blobs/head/branches/udplogger/udplogger.c?export=raw' gcc -Wall -o udplogger udplogger.c mkdir /var/log/netconsole udplogger dir=/var/log/netconsole port=6666
動作確認
送信側で
echo s | sudo tee /proc/sysrq-trigger
すると、受信側で
# /var/log/netconsole/YYYY-MM-DD.log 2020-02-21 13:25:36 192.0.2.100:6665 [271267.178610] sysrq: Emergency Sync
と出力されるはずです。
参考
Ubuntu 18.04 で OS 起動時の apt update と unattended-upgrade を抑制する方法
時間のない人向けのまとめ
sudo systemctl edit apt-daily.timer sudo systemctl edit apt-daily-upgrade.timer
どちらも次の内容で保存します。
[Timer] Persistent=false
もしくは、直接ファイルを編集して反映してもよいです。
sudo install -d -o root -g root -m 755 /etc/systemd/system/apt-daily.timer.d cat <<EOF | sudo tee /etc/systemd/system/apt-daily.timer.d/override.conf [Timer] Persistent=false EOF cp -pR /etc/systemd/system/apt-daily.timer.d/ /etc/systemd/system/apt-daily-upgrade.timer.d/ sudo systemctl daemon-reload
何が問題なのか?
Ubuntu 18.04、少なくとも EC2 用の Ubuntu cloud image の 20200131 版 (18.04.4) では、毎日 6:00 頃に apt update
と unattended-upgrade
(セキュリティ関連の更新パッケージのインストール) が行われる (詳しくは後述) のですが、その時間帯に電源がオフで稼働していなかった場合は、次の OS 起動時に実行される設定になっています。
「その時間帯に電源がオフで稼働していなかった」は、AMI からインスタンスを起動したときも同じ状況になるので、AMI が古ければ古いほど更新パッケージが多くなり、起動直後に負荷が高まったり、しばらく apt install
できない (unattended-upgrade
がロックを獲得しているので) 時間が続いたりします。
その結果、次のような問題が発生します。
- インスタンス起動後に自動的にプロビジョニングを行うようにしている運用の場合、プロビジョニング (の過程のパッケージのインストール) が完遂するまでの時間が安定しなかったり、場合によってはタイムアウトしてエラー終了してしまう
特にオートスケーリングの場合は、一刻も早くインスタンスを投入したいので深刻な問題になります。
またもし、AMI 採取用のインスタンスから日次で AMI を作る運用の場合は、パッケージはほぼ最新であることが期待できます。
というわけで、 OS 起動時の更新処理を実行されないようにした、というお話でした。
時間のある人向けの詳細
まず定期的に更新処理が実施される仕組みの説明から。
次の 2 つの systemd の timer ユニットがトリガーとなります。
apt-daily.timer
apt-daily-upgrade.timer
内容を確認すると、
$ systemctl cat apt-daily.timer # /lib/systemd/system/apt-daily.timer [Unit] Description=Daily apt download activities [Timer] OnCalendar=*-*-* 6,18:00 RandomizedDelaySec=12h Persistent=true [Install] WantedBy=timers.target $ systemctl cat apt-daily-upgrade.timer # /lib/systemd/system/apt-daily-upgrade.timer [Unit] Description=Daily apt upgrade and clean activities After=apt-daily.timer [Timer] OnCalendar=*-*-* 6:00 RandomizedDelaySec=60m Persistent=true [Install] WantedBy=timers.target
となっており、 apt-daily.timer
は毎日 6:00 と 18:00 頃、 apt-daily-upgrade.timer
は毎日 6:00 頃に発火するのがわかります。
発火すると、対応する systemd の service ユニットが実行されます。
systemctl cat
で確認すると、実行されるコマンドライン (ExecStart
) が確認できます。
apt-daily.service
ExecStart=/usr/lib/apt/apt.systemd.daily update
apt-daily-upgrade.service
ExecStart=/usr/lib/apt/apt.systemd.daily install
ざっくり言うと、
/usr/lib/apt/apt.systemd.daily update
は
- apt-get update
- apt-get --download-only dist-upgrade
- unattended-upgrade --download-only
/usr/lib/apt/apt.systemd.daily install
は
- unattended-upgrade
を行うためのもので、APT の設定で個別に実施する/しないを制御することができます。
デフォルトでの次のような設定になっていて、
$ apt-config dump | grep Periodic APT::Periodic ""; APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "0"; APT::Periodic::AutocleanInterval "0"; APT::Periodic::Unattended-Upgrade "1";
apt-get update
実施する--download-only
系は実施しないunattended-upgrade
実施する
という挙動になります。
ここまでが定期的に実行される仕組みの説明で、次からは主題の OS 起動時の更新処理の仕組みについてです。
timer ユニットの定義をみると Persistent=true
というのがあり、これがキモです。
systemd.timer(5) から引用すると
Persistent=
Takes a boolean argument. If true, the time when the service unit was last triggered is stored on disk. When the timer is activated, the service unit is triggered immediately if it would have been triggered at least once during the time when the timer was inactive. This is useful to catch up on missed runs of the service when the system was powered down. Note that this setting only has an effect on timers configured with OnCalendar=. Defaults to false.
とのことなので、冒頭の方法で Persistent=false
と上書き設定したわけです。
ちなみに
定期的なのも含め一切の自動的な更新処理をオフにしたい場合は、 /usr/lib/apt/apt.systemd.daily
に
# check if the user really wants to do something AutoAptEnable=1 # default is yes eval $(apt-config shell AutoAptEnable APT::Periodic::Enable) if [ $AutoAptEnable -eq 0 ]; then exit 0 fi
という処理があるので
echo 'APT::Periodic::Enable "0";' | sudo tee /etc/apt/apt.conf.d/99disable-periodic
とかするといいんじゃないかと思います。
あと /etc/cron.daily/apt-compat
という crontab があるんですが、
if [ -d /run/systemd/system ]; then exit 0 fi
となってるので systemd な環境では無いのと同じです。
MySQL 5.7でクライアントプログラムがCPUを食いつぶす件
同根のバグレポートが散見されますが、ここ数ヶ月、状況をみるに修正される見込みがなさそうなので記録しておきます。
事象
MySQL 5.7 の libmysqlclient (libmysqlclient.so.20) を使用しているプロセスが、無限ループに突入して CPU (user%) を食いつぶし続ける。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 28150 hirose31 20 0 32488 6080 5492 R 93.8 0.3 0:16.70 mysql
発現条件
MySQLが提供しているパッケージのMySQL 5.7.25, 5.7.26, 5.7.27 で確認。
- Ubuntu 18.10, mysql-community-client 5.7.25-1ubuntu18.10
- Ubuntu 18.04, mysql-community-client 5.7.25-1ubuntu18.04
- Ubuntu 18.04, mysql-community-client 5.7.26-1ubuntu18.04
- Ubuntu 18.04, mysql-community-client 5.7.27-1ubuntu18.04
- Ubuntu 16.04, mysql-community-client 5.7.25-1ubuntu16.04
- CentOS 7.6, mysql-community-client-5.7.25-1.el7.x86_64
- Fedora 29, mysql-community-client-5.7.25-1.fc29.x86_64
- Oracle Linux 7.6, mysql-community-client-5.7.25-1.el7.x86_64
再現手順
- mysql_real_connect() でサーバーに接続する
- なんらかのシグナルを受信する
- mysqld サーバー側からコネクションを切断する
- 例えば以下のようにして切断する
- wait-timeout を超過する
- mysqld を停止する
- mysql_real_query() もしくは mysql_ping() が呼ばれる
- 無限ループに突入して CPU (user%) を食いつぶす
なお、MySQL 5.6 (libmysqlclient 18) や MySQL 8.0 (libmysqlclient 21) では発生しない。原因は yaSSL にあるのだけど、MySQL 8.0 は yaSSL ではなく OpenSSL を使っているので。
再現コード
回避方法
MySQL サーバーの my.cnf の [mysqld]
グループに skip-ssl
と書く。
# /etc/my.cnf [mysqld] ... skip-ssl ...
追記 2019-11-22
SSL の実装はソースツリー内にある yaSSL と OpenSSL とで選択できたのですが、MySQL 5.6.46, 5.7.28 から yaSSL が削除され OpenSSL のみとなりました。
当然、バイナリで配布されているパッケージも OpenSSL を使うようにビルドされています。そこで 5.7.28 で試したところ、問題が再現しないことが確認できました。
gunicornをsupervisorで制御しつつhot deployできるようにする
タイトルの通り、gunicornなWebアプリをsupervisorで制御しつつhot deployできるようにしたメモです。
登場人物
- supervisor v3.3.1
- gunicorn v19.9.0
- start_server v0.34
- unicornherder v0.1.0
- envdir v0.7 の standalone版
- ちなみに checkinstall で雑にdeb化
問題
- gunicornのhot deployを利用する場合、直接はsupervisorの制御下に置けない
- gunicornはhot deployの仕組みを持っている
- 流れ
- gunicornのmasterプロセスにUSR2シグナルを送ると、新しいmasterプロセスを産む
- つまり新masterの親プロセスは旧master
- 旧masterと旧workerは生き続けているので、ほどよい頃合いで旧masterにQUITシグナルなどを送って死んでもらう
- 新masterの親が死んだので、init (PID 1) が新masterの親となる
- gunicornのmasterプロセスにUSR2シグナルを送ると、新しいmasterプロセスを産む
- というわけで、supervisor視点だとgunicorn(旧master)が死んだのでまた立ち上げようとするが、新masterはinitを親として起動しているので競合しちゃう次第
結論
- supervisor + unicornherder + gunicorn
案1: start_server配下でgunicornを立ちあげ、hot deployはstart_serverの機能を使う
## /etc/supervisor/conf.d/oreno.conf [program:oreno] command=/home/hirose31/oreno/script/start directory=/home/hirose31/oreno ...
## /home/hirose31/oreno/script/start #!/bin/bash export PYENV_ROOT="/home/hirose31/oreno/pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" exec start_server \ --port 127.0.0.1:1919 \ --signal-on-term=TERM \ --signal-on-hup=QUIT \ --kill-old-delay=10 \ -- \ /home/hirose31/oreno/script/start-gunicorn
## /home/hirose31/oreno/script/start-gunicorn #!/bin/bash if [[ -n "$SERVER_STARTER_PORT" ]]; then GUNICORN_FD= for l in $(echo $SERVER_STARTER_PORT | tr ';' ' '); do GUNICORN_FD="$GUNICORN_FD $(echo $l | cut -d= -f2)" done export GUNICORN_FD=$(echo $GUNICORN_FD | tr ' ' ',') fi exec /usr/local/bin/envdir /home/hirose31/oreno/env \ gunicorn -w 3 --reuse-port --capture-output oreno:application
これで supervisorctl signal HUP oreno
で start_server に HUP を送ると、start_serverが新たにgunicornを起動して、10秒後に古いgunicornにQUITを送って殺してくれる。はず。
つまり、(gunicornの機能ではなく)start_serverの機能でhot deployが実現できる。はず。
start_server に対応するにはいくつかの要件が必要ですが、
- Server::Starterに対応するとはどういうことか - limitusus’s diary
- 「Server::Starterに対応するとはどういうことか」の補足 - sonots:blog
- Server::Starter で Unicorn を起動する場合の Slow Restart - sonots:blog
Unicornの UNICORN_FD
と同様のものが gunicorn には GUNICORN_FD
という環境変数名で存在するので、gunicornもstart_serverに対応できる。はず。
そう。はず、なのであった!
gunicornの このpull request で、GUNICORN_FD
を評価するところのがこのような条件付きになり、
### gunicorn/arbiter.py: start elif self.master_pid: fds = [] for fd in os.environ.pop('GUNICORN_FD').split(','): fds.append(int(fd))
初っ端の起動時は master_pid
は 0 なので GUNICORN_FD
は評価されないのであった!!
条件を elif os.environ.get('GUNICORN_FD', ''):
にすれば期待通り動くことは確認できたんだけど、できればgunicornに手を入れないで使いたいのでこの案はとりあえずボツに…
将来、外部由来の GUNICORN_FD
を評価するようになったら、gunicornは--config
オプションでpythonの文法で設定が書かれたファイルを指定することができるので、そこで SERVER_STARTER_PORT
を参照して GUNICORN_FD
をセットする処理を書いたり、pre_fork, post_fork hookを使って、Server::Starter で Unicorn を起動する場合の Slow Restart - sonots:blog と同じことも実現できるんじゃないかと思います。
余談ですが、start_server
の server-prog
の指定は、ラップしたスクリプトのみ、オプション指定無しにするのをお勧めします。
例えば start_server -- /my/app -p 8
で起動していて -p 16
に変えたいときは、start_server
自体の再起動が必要(つまりhot deployできない)になってしまいます。これがラップしていて start_server -- /my/app_wrapper
で起動している場合は、app_wrapper
の中を書き換えて start_server
に HUP を送ればOKになります。
start_server -- envdir ./env sh -c 'exec /my/app -p ${MAX_PROCESS:-8}'
と書けばHUPで引数の値を(envdir経由で)変えることは可能ですが、やや技巧的だしオプションの種類の増減には対応できないので、素直にラッパースクリプトを用意した方がよいでしょう。
案2: unicornherder配下でgunicornを立ちあげ、hot deployはgunicornの機能を使う
「gunicorn supervisor」でググるとみなさんお困りのようで、対応として rainbow-saddle と unicornherder がよく挙げられています。
rainbow-saddleは「cannot actively maintain it」だそうなので、unicornherderを試してみます。
## /etc/supervisor/conf.d/oreno.conf [program:oreno] command=/home/hirose31/oreno/script/start directory=/home/hirose31/oreno ...
## /home/hirose31/oreno/script/start #!/bin/bash export PYENV_ROOT="/home/hirose31/oreno/pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" exec unicornherder -o 10 -p /var/run/oreno.pid -- \ -w 3 --reuse-port --capture-output oreno:application
unicornherderの流れはこんな感じです。
- gunicornを
--pid FILE --daemon
つきで起動する- masterのPIDは所定のファイルに書き出される
- unicornherderの
--pidfile PATH
でPIDファイルを指定しないと、cwdにPIDファイルが作られるので注意
- unicornherderの
- gunicornはunicornherderの元を離れ、initの子プロセスとなる
- masterのPIDは所定のファイルに書き出される
- unicornherderはHUPを受けると、gunicornのmasterにUSR2を送る
- gunicornのhot deployが開始され、新masterが誕生する
- gunicornによってPIDファイルが更新される
- unicornherderはPIDファイルを2秒間隔でポーリングして、masterの変化を検知する
- masterの変化を検知すると、
- overlap (デフォルト120秒) の時間だけsleepする
- この間は新旧workerが混在する
- 後、旧masterを殺す
- masterにWINCH, QUITシグナルを送る
- QUITを受けたworkerは処理中であっても即、死ぬ
- overlap (デフォルト120秒) の時間だけsleepする
unicornherderはgunicornを子プロセスとしておかず、gunicornはinitの子プロセスとなる点に最初「オッ!」っと思いましたが、これは古き良きdaemonしぐさですからまぁ違和感はないです。
これで一応、問題は解決できたんですが、unicornherderはgunicornコマンドでのgunicornの起動しかサポートしていません。
gunicornの起動方法、Pythonの各種WAFとの連携方法は gunicornコマンド以外にもあります。例えば、Bottle だと bottle.run()
に server='gunicorn'
を与えると、gunicornが起動します。
その場合は、unicornherderは--gunicorn-bin
, -g
オプションでgunicornのフルパスを指定できるのでこれを使います。
## /home/hirose31/oreno/script/start #!/bin/bash export PYENV_ROOT="/home/hirose31/oreno/pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" exec unicornherder -o 10 -p /var/run/oreno.pid \ -g /home/hirose31/oreno/script/oreno-gunicorn \ --
oreno-gunicorn
はお好みの方法でgunicornなプロセスを立ち上げる処理を書いたスクリプトです。
## /home/hirose31/oreno/script/oreno-gunicorn #!/bin/bash exec /home/hirose31/oreno/bin/oreno-python /home/hirose31/oreno/omaeno.py
注意点は以下の通りです。
- unicornherderの
-p
オプションで指定したPIDファイルと、gunicornが書き出すPIDファイルを一致させる - gunicornはdaemonモードで起動するようにする
おわりに
unicornherderを使えば目的が達成できることがわかりましたが、個人的に安心と信頼と実績のある start_server が使いたい今日この頃です。