あるファイルがn日以上更新されてないか調べる方法

find -mtime を使う

# 最終更新から 24 時間以上経過しているか
if [[ "$(find /path/to/file -mtime +0)" != "" ]]; then
  echo 'OLD!'
else
  echo 'NEW!'
fi

-mtime +0 がなぜ「24時間以上前」になるのか?について:

-mtime n
  ファイルの最終内容更新日時が、基点となる時刻から計算して n 日前に当たれば、真を返す (訳注: 基点となる時刻は、デフォルトでは find を実行している今現在である)。
  (snip)
  なお、デフォルトの動作のように、現在時刻から数えて 24 時間前から 48 時間前までを 1 日前とする

つまり、

  • 1日前 = 24時間前から48時間前

なので、

  • n日前 = n*24時間前から(n+1)*24時間前

つまり、

-mtime 0 = 最終更新が0日前 =  0時間前から24時間前
-mtime 1 = 最終更新が1日前 = 24時間前から48時間前
-mtime 2 = 最終更新が2日前 = 48時間前から72時間前

次に n の記法について。

数値の引き数は、以下の形で指定することができる。

+n
  n より大きい。
-n
  n より小さい。
n
  ちょうど n。

なので、

  • -mtime +0
  • = -mtime 1 または -mtime 2 または ...
  • = 最終更新が0日前より過去
  • = 24時間前より過去

となる。

ほかにイケてる方法があったら教えてください!!><

これだけ覚えれば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.cfmulti_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

試行錯誤するときはこんな感じでお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 updateunattended-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 で確認。

再現手順

  1. mysql_real_connect() でサーバーに接続する
    • その際に以下の条件を満たすこと
    • reconnect option を有効にしない
    • TCP で接続する(unix domain socket では再現しない)
    • 同じホストの mysqld に接続する
  2. なんらかのシグナルを受信する
  3. mysqld サーバー側からコネクションを切断する
    • 例えば以下のようにして切断する
    • wait-timeout を超過する
    • mysqld を停止する
  4. mysql_real_query() もしくは mysql_ping() が呼ばれる
  5. 無限ループに突入して 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 で試したところ、問題が再現しないことが確認できました。