その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

proxy-protocol-jsリリースした & Kyoto.js #16で喋ってきました

表題の通りproxy-protocol-jsをリリースしました.

github.com

www.npmjs.com

Kyoto.js #16の発表資料は以下です.

スライドでも簡単に説明しましたが,PROXY protocolというのはHAProxyが提唱しているTransport層 (L4) においてoriginalの送信元を維持したままproxyするためのprotocolになります.HTTPにおけるX-Forwarded-ForのL4版という感じですね.詳しくはこちら: http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt


で,このPROXY protocolをnode.jsで触りたくなったのでこの度ライブラリをこさえた次第です.主なフィーチャーとしては

という感じになっています.nodeでPROXY protocolを取り扱う人はそう多くはない気はしているのですが,もし来るべき時が来た際にはどうぞご利用ください.

「テクノロジーのイノベーションと民主化、そのはじめの一歩」というタイトルで首都大学東京で話してきました

首都大学東京は私の母校でして,当時は本当に色々なことがあったのですが,今回たまたまお話しする機会を頂いたので表題のような話をしてきました.

www.slideshare.net

このタイトルを思いついた時は酔っ払っていたのでしょうね.かなり大上段に構えたタイトルになってしまい恐縮しているのですが,

・情報通信コース・学域を卒業して,会社に入ったけど,会社ってこんなところですよ,
・会社でこんなことやってますよ.
・うちの会社にはこんな特徴がありますよ.
・会社に入るとこんなことしますよ.
・入社して,困ったことはこんなことです.
・入社して良かったことはこんなことです.
・今思うと,学生時代にこんなことやっていたらよかったかな,って思います.
・会社に入ってからの私の年表
・会社はICTイノベーションとしてこんな取り組みをしていますよ,してきましたよ.私はそれにこんな形で携わりましたよ.

というような内容をオーダーされたので,ここは一丁エモい内容でいこうと思い,打ったのがこの発表となります.趣旨としては,テクノロジーイノベーションを起こしたい,それを起こすためにはーーというのを自分なりにテクニカルな観点から発表したものになります.
大学に所属する学部生の方々のために作ったスライドなので,もしかしたらそれ以外の人にはもしかしたら刺さらないかもわかりません *1.なお中盤のスライドにある英語に対する思いは心から思っていることです.本当に苦戦を強いられている……
普段はエモい発表あんましないので (というか苦手),発表のフィードバックがどうだったのかは気になるところですが…… (結構心配)


このスライドに書かれている内容については,LINEで働いていたときに僕に本当によくしてくれた@yappoさんと@tokuhirom さんのお陰で学ばせてもらったことが本当に,本当に多くあります.なかなかこれを改まって言う機会はないのですが,平成も終わることですし,この場を借りて感謝の意を表したいと思います.ありがとうございます!!


僕は「とにかくコードを書き続けることは良いことだと思っていて,コードを書かなければ終わる,どんどん書く,書いた量が質に転換するタイミングは必ずあるのだ」という思いを持っているのでこのような内容となっています.
オリジナルのスライドの最後には "Shut the fxxk out and write some code" という一言が書かれたスライドがあり,つまりこれは「グダグタ言ってねーでコードを書けよハゲ」という意味合いでして,僕を動かす原動力の一つであった言葉なのであります.これはものすごく好きな言葉なのですが,しかし自らの良識が働いた結果,公開版のスライドからは削除しました.ということを記します.


結びとして,人の感想を記したいと思います.

f:id:moznion:20190426235321p:plain

ハハハ,ありがとうございました.

*1:学生の人に必ずしも刺さると言っているわけではない

恐怖! パスワード無しでログインを受け付けるsshd Dockerコンテナを作る

表題の通りです.
ほら,ローカルでのテスト用途でそういうコンテナが欲しくなることが月2くらいであるじゃないですか……あるんですよ……本チャンの環境で使うともちろん即死なので使ってはいけません.

gist.github.com

このようにすると test ユーザーでパスワード無しでsshログインできるコンテナを作ることができます.便利ですね.
sedで気合を入れまくっている部分を,あらかじめ書き換えておいた sshd_config との COPY に置き替えても良いでしょう.

現場からは以上です.

outage reportを書くときに気をつけていること

そうは言っても障害は起きるものです.で,障害が起きて,終息したあとの振り返りとして社内向けにoutage report (障害報告書的な?) のようなものを書くと思うのですが,本記事ではそのときに気をつけていることについて書きたいと思います。

outage reportの目的

そもそもですが,outage reportを書く目的としては以下のような物があるのかなと思っています。

  • A: 障害が起きたという事実に関する周知
    • 障害についてお客様からお問い合わせが来たりした時に正しい情報を届けられるようにするため
  • B: 根本原因の洗い出し
    • 再発防止のため
  • C: 障害検知フローの確認
    • 障害に対する初動までにかかる時間を短くするため
  • D: トポロジの形成
    • 知見の醸成
    • 似たような問題が起きたときに,outage reportに書いてある対処法を逆引き的に利用できるようにするため

outage reportに書いている内容

以下は個人的に (というか所属している組織的に) 書いている内容なので千差万別だとは思いますが,このような感じのことを書いていますというご紹介です.カッコ内のアルファベットは目的の部分に書いたアルファベットと対応しています (Dについては横断的に有効だと思うので省略しています).

障害のサマリ (A)
  • なるべく簡潔に書くように心がけている.多くても3行以内で済むように.
  • 外部向けに簡潔に障害内容を伝えられるような内容を心がける
  • このサマリだけを読む非技術系のスタッフもいる
障害で発生した症状・お客様から見た問題の詳細 (A)
  • 外から見た時の症状について書くようにしている
    • 内部のシステム構成などは関係なく,お客さんが実際に対面した (であろう) 問題について書く
  • 詳細に書くようにする
機能単位の影響範囲 (A)
  • 実際にどの機能が利用不可能になったか,について列挙するようにしている
コンポーネント単位の影響範囲 (B)
  • これは内部のシステム構成について書く
  • 実際にどのコンポーネントが利用不能状態になったか,について列挙するようにしている
    • microservicesのようになっているとここらへんは割と書きやすい気がする
障害期間 (A, C)
  • 以下の4つについて明記する
    • 発生日時
      • 問題自体が発生した日時
    • 検知日時
      • 問題を検知した (初動可能になった) 日時
    • お客様影響があった期間
      • お客様が当該機能を利用できなかった期間 (from/to)
    • 終息日時
      • 問題の終息が正しく確認された日時
  • 文字だけだとわかりにくいので,「時系列で追いやすい図」や「グラフ」があると良いと思っている (余裕があれば)
検知フロー (C)
  • 以下の3つについて書いている
    • いつ検知したのか
    • 何によって検知したのか
    • 誰が (人・物にかかわらず) 検知したのか
  • これによって,検知して初動対応可能になるまでにかかった時間がわかる
  • 検知までの時間が長くかかってしまっている場合は,アラートが見落とされているとか,あるいはアラートの仕組みが不足しているとか,なんらか非効率な方法になっているとか,そういった問題を浮き彫りにすることができる
対応タイムライン (B)
  • 対応した内容を時系列順に記す
  • 「いつ・誰が・何を」という情報が重要
  • 「効いた・効かなかった」という情報についても記すべき
原因と解決 (B)
  • 以下の3つについて書いている
    • 根本的な原因
      • 例えば「特定のコンポーネントhogeがxxxという状況下でyyyするとエラーを返してしまうため」とか
      • ここがoutage reportのコアだと思うのでガチっと書くように心がけている
    • 有効だった対応・復旧作業
    • 再発防止策
      • 根本的な原因と有効だった作業を振り返って,再発させないための今後のアクションについて書く
      • あるいはもう適用済みのアクションについて書く
その他反省
  • 気づいたことをつらつらと書いてサービス改善につなげる
  • 例えば「初動までの時間が長く,alertを上手く検知できていないので改善すべき」みたいな (これは例なのでざっくりしてますが……)

こんなところでしょうか.気づいたら追記するかもわかりません.

outage reportを書くという気持ちを高める

outage reportを書く時ってあまり気持ちがアガるものではないし,障害明けで疲れてたりピリピリしていたり,えてして「なんで俺が書かなきゃならんのだ……」みたいな罰当番的な感じにもなりがちだと思うんですが,実際はそうじゃないですよという雰囲気と仕組みを作ることが重要な気がしています.

例えばoutage reportは関連する人が複数人で書くようにするだとか,あるいはoutage reportを書いた人が評価されるというようにしていくのが良いのではないかと思っている次第です.非常に重要なドキュメントだと思うので.
所属している組織ではscrapboxを使っているので自然と複数人でoutage reportを書く感じになっており,ここらへんは上手くワークしているという感じがしているところです.

今後の課題

これは気をつけている事というよりも抱えている課題なんですが,

  • 対応タイムラインを自動的に収集して,あとはコピペするだけにしたい
  • outage reportを効果的に社内に周知したい

というのがあって,前者はchat opsに全振りしているような組織だと簡単にできそうで良いな〜と思っているところです (ただそれだけの理由でchat opsに全振りする理由にはならないな……という感じでもある).なんらかのソリューションが欲しい……
またoutage reportを効果的に社内に通知する方法についてもいい方法を探していて,今の所朝会的なところでoutage reportへのポインタを示すくらいしかできていない状況で,もうちょっとなんかいい方法はないかと探っているという段階です.

なんらか良い方法をご存知の方がいたら教えてもらえると嬉しいです.あと他のみなさんがどういう感じでoutage reportを書いているのかも気になる.

Dockerコンテナ内でpuppeteerを使うとChromeゾンビプロセスがたまる問題

表題のような問題があり,その調査したという記録です.なお,結論を一言で言うと--initを使え,ということになります.


そもそもDockerコンテナを起動すると,CMDあるいはENTRYPOINTに指定されたコマンドがコンテナ内でPID 1として起動します.これが何を意味するかと言うと,「CMDあるいはENTRYPOINTに指定されたコマンド」はそのコマンド自体の責務をまっとうするのと同時に,initプロセスとしての振る舞いも行わなければならないということになります (id:hayajo_77さんにこの辺を詳しく教えてもらいました,ありがとうございます).
つまりPID 1で動いているプロセスは「SIGCHLDをトラップすることで孤児プロセスを適切に回収し,waitpidをかける」という処理も適切に行う必要があります.


さて,puppeteerを使ってChromeブラウザを起動するとどうなるでしょうか *1.以下に検証で利用したコード片を記します.

なお検証環境は

$ uname -a
Linux ip-198-18-0-91.ap-northeast-1.compute.internal 4.14.88-88.76.amzn2.x86_64 #1 SMP Mon Jan 7 18:43:26 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/system-release
Amazon Linux release 2 (Karoo)
$ docker --version
Docker version 18.06.1-ce, build e68fc7a215d7133c34aa18e3b72b4a21fd0c6136

となります.

index.js:

const puppeteer = require('puppeteer');

new Promise((resolve) => {
  resolve(puppeteer.launch({
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox'
    ]
  }));
}).then((browser) => {
  console.log('launched');
  setTimeout(() => {
    browser.close();
    console.log('closed');
    setTimeout(() => {
      console.log('finished');
    }, 10000);
  }, 10000);
});

Dockerfile:

FROM node:10-jessie

WORKDIR /app
ADD . /app/

RUN apt-get update && apt-get install -y libx11-dev libx11-xcb-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxi-dev libxtst-dev libnss3-dev libcups2-dev libxss-dev libxrandr-dev libasound2-dev libatk1.0-dev libatk-bridge2.0-dev libgtk-3-dev
RUN npm install

ENTRYPOINT ["node", "index.js"]

そしてこのDockerコンテナを起動すると以下のようなプロセスツリーとなります (一部の長大なコマンドについては省略しています).

root     12029  0.1  7.1 792184 72480 ?        Ssl  Feb28   3:50 /usr/bin/dockerd --default-ulimit nofile=1024:4096
root     12040  0.1  1.8 644872 18304 ?        Ssl  Feb28   3:54  \_ docker-containerd --config /var/run/docker/containerd/containerd.toml
root     29692  0.0  0.3   8792  3888 ?        Sl   06:15   0:00      \_ docker-containerd-shim -namespace moby 
root     29728  4.0  3.7 595548 37676 pts/0    Ssl+ 06:15   0:00          \_ node index.js
root     29789  1.2  6.4 535180 65444 ?        Ssl  06:15   0:00              \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome 
root     29791  0.4  4.2 374876 42716 ?        S    06:15   0:00                  \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=zygote --no-sandbox --headless --headless
root     29813  0.4  6.2 607496 63388 ?        Sl   06:15   0:00                  |   \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=renderer --no-sandbox 
root     29810  0.6  5.4 432496 55320 ?        Sl   06:15   0:00                  \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=gpu-process 

index.jspuppeteer.launch()を実行することによってChromeのプロセスがspawnされ,さらにそのChromeプロセスがChromeのプロセスをspawnしていることが分かります.つまり「子プロセスであるChrome」と「孫プロセスであるChrome」が存在しているという状況になります.

そしてその10秒後にbrowser.close()によってブラウザをクローズすると……

root     12029  0.1  7.1 792184 72480 ?        Ssl  Feb28   3:50 /usr/bin/dockerd --default-ulimit nofile=1024:4096
root     12040  0.1  1.8 644872 18304 ?        Ssl  Feb28   3:54  \_ docker-containerd --config /var/run/docker/containerd/containerd.toml
root     29692  0.0  0.3   8792  3888 ?        Sl   06:15   0:00      \_ docker-containerd-shim -namespace moby
root     29728  4.0  3.7 595548 37676 pts/0    Ssl+ 06:15   0:00          \_ node index.js
root     29791  0.0  0.0      0     0 ?        Z    06:15   0:00              \_ [chrome] <defunct>
root     29813  0.2  0.0      0     0 ?        Z    06:15   0:00              \_ [chrome] <defunct>
root     29810  0.1  0.0      0     0 ?        Z    06:15   0:00              \_ [chrome] <defunct>

孫プロセスであるChromeがゾンビプロセスになってしまっていますね.
これはbrowser.close()でkillしたプロセスは「子プロセスであるChrome」のプロセスで,その配下にいた「孫のChromeプロセス群」はPID 1のプロセス (つまりこの場合のnode index.js) に回収されてしまうものの,node index.js はその孫プロセスたちについてwaitpid(2)を発行して看取るということをしないため,このようにゾンビプロセスとしてコンテナ内に溜まっていくということになります.
puppeteerとしては,孫プロセスはinitに回収されることを期待しているという感じですね.非コンテナ環境であれば期待通りに動く仕組みと言えます.


で,どうすべきかと言うと向かうべき道は2つくらいあると思っていまして,

  • PID 1のプロセス上で適切にSIGCHLDをトラップしてwaitpid(2)を発行して子孫プロセスを看取る
  • コンテナ内にinitを導入する

というものが考えられると思います.

前者はまさに文面通りPID 1となるプロセスの中にSIGCHLDをトラップしてwaitpid(2)を発行するようなロジックを入れるというものです.良識的なプログラミング言語をご利用であればわりかしシンプルに実現可能でしょう.これによってChromeのゾンビプロセスが爆発するというのを避けられると思います.

後者はコンテナ内にinitを導入する,つまりPID 1のプロセスをinitにして,その配下に任意のコマンドをぶら下げるという方法です.
Docker 1.13以降のバージョンをお使いであれば *2docker runに対して--initオプションを付与することでPID 1にinitを指定することができるので,これによりinitのメカニズムをコンテナ内に持ち込むことが可能となります: https://docs.docker.com/engine/reference/run/#specify-an-init-process

root     12029  0.1  5.0 792184 51268 ?        Ssl  Feb28   4:03 /usr/bin/dockerd --default-ulimit nofile=1024:4096
root     12040  0.1  1.7 644872 17516 ?        Ssl  Feb28   3:58  \_ docker-containerd --config /var/run/docker/containerd/containerd.toml
root     27900  0.0  0.3   8792  3888 ?        Sl   07:08   0:00      \_ docker-containerd-shim -namespace moby
root     27945  0.5  0.0    956     4 pts/0    Ss   07:08   0:00          \_ /dev/init -- node index.js
root     27982  8.5  3.7 595548 37708 pts/0    Sl+  07:08   0:00              \_ node index.js
root     27999  3.0  6.5 535212 65616 ?        Ssl  07:08   0:00                  \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome
root     28001  0.5  4.1 374876 42320 ?        S    07:08   0:00                      \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=zygote --no-sandbox --headless --headless
root     28022  1.5  6.1 607496 62492 ?        Sl   07:08   0:00                      |   \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=renderer --no-sandbox
root     28020  1.5  5.3 432496 54448 ?        Sl   07:08   0:00                      \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=gpu-process

このようにinitが差し込まれるので,孫プロセスが孤児になった際はこのinitが回収して看取ってくれることになります.

あるいは様々な理由により *3 --initオプションを使えない場合はYelp/dumb-initkrallin/tiniを手で差し込むという方法も可能です.例えば今回の例のDockerfileが以下のようになります:

FROM node:10-jessie

WORKDIR /app
ADD . /app/

RUN apt-get update && apt-get install -y libx11-dev libx11-xcb-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxi-dev libxtst-dev libnss3-dev libcups2-dev libxss-dev libxrandr-dev libasound2-dev libatk1.0-dev libatk-bridge2.0-dev libgtk-3-dev
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64.deb
RUN dpkg -i dumb-init_*.deb
RUN npm install

ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["node", "index.js"]

これを起動すると以下のようなプロセスツリーになります:

root     12029  0.1  7.1 792184 72232 ?        Ssl  Feb28   4:09 /usr/bin/dockerd --default-ulimit nofile=1024:4096
root     12040  0.1  1.9 644872 20024 ?        Ssl  Feb28   3:59  \_ docker-containerd --config /var/run/docker/containerd/containerd.toml
root     11466  0.0  0.3   7384  3892 ?        Sl   07:19   0:00      \_ docker-containerd-shim -namespace moby
root     11502  0.2  0.0    212     4 ?        Ss   07:19   0:00          \_ /usr/bin/dumb-init -- node index.js
root     11547  2.2  3.7 595548 37840 pts/0    Ssl+ 07:19   0:00              \_ node index.js
root     11564  1.0  6.4 535180 65576 ?        Ssl  07:19   0:00                  \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome
root     11566  0.1  4.2 374876 42672 ?        S    07:19   0:00                      \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=zygote --no-sandbox --headless --headless
root     11587  0.2  6.1 607496 62228 ?        Sl   07:19   0:00                      |   \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=renderer --no-sandbox
root     11585  0.3  5.3 432528 54468 ?        Sl   07:19   0:00                      \_ /app/node_modules/puppeteer/.local-chromium/linux-624492/chrome-linux/chrome --type=gpu-process

dumb-initがPID 1として差し込まれており,--initによってinitを差し込んだときと同じような効果が得られます.


というわけで色々と調査した結果でした.今回はたまたまPuppeteerで発生した事例でしたが,他のケースでも有効かと思います.initの偉大さを再認識させられますね!!

追記

本来は1コンテナ1プロセスみたいな感じで扱うべきだと思っているし,コンテナ内でプロセスをガンガンspawnするのは良くないと思っている,少なくとも自分が設計するアプリケーションでは1コンテナ1プロセスという感じ (あるいはそれに近しい感じ) にすると思うのですが,生きていると色々ありますね.

更に追記

puppeteerのtroubleshooting.mdに書いてたことに気づいた.

https://github.com/GoogleChrome/puppeteer/blob/82bef7021263c17716bfcda5ff02a3c2f097cac0/docs/troubleshooting.md

*1:ちなみにpuppeteerは内部でchild_process.spawnを利用してプロセスをspawnしている

*2:Add init process for zombie fighting and signal handling by crosbymichael · Pull Request #26061 · moby/moby · GitHub

*3:たとえばElastic BeanstalkのDocker Single Container runtime environmentを使っているとか

「公式ガイドブック SORACOMプラットフォーム」が出ます

公式ガイドブック SORACOMプラットフォーム

公式ガイドブック SORACOMプラットフォーム

出ます,出るのです.
ソラコムプラットフォームの概観やその設計思想,実際のユースケースに応じた参考アーキテクチャなどが満載されている楽しい本です!!
ソラコムをすでにご利用の方にもそうでない方にもお役立ちな情報が記されていると確信しております.

いやー久々に商業誌に文章を書きました (WEB+DB PRESS Vol.81以来なので5年ぶりくらいでしょうか?).とは言っても本当にほんの一部なのですが……

手に取ってもらえると嬉しいです,よろしくお願いします!!

YAPC::Tokyo 2019に参加してきた & LTしてきた #yapcjapan

遅くなりましたが,表題のとおりです.YAPCが東京に凱旋してきたので参加してきました.

yapcjapan.org

じつはこのような形で事前に関わっていたりもします:

blog.yapcjapan.org

んで,LTをやってきました.資料は以下です.

AWS LambdaでPerlを動かすというテーマのLTで,これはかつてブログにも書いた内容だったのですが,ここらで一丁LTにでもしてみるか〜となり,なったという感じです.

ありがたいことにベストLTをいただきました.大感謝です.


さて今回のYAPCはなんやかやPerlの話題が多く,まだまだホットではあるな〜とは思うものの,charsbarさんの発表にあったように「年々CPAN Authorの数が減少している」など少々寂しい話題もあり,いろいろ考えさせられるものがある回でした.とはいえtokuhiromさんのキーノートにもあったように,なんやかやperlは便利なので今後も使っていく気はしており,まあここらへんはやっていくことになるのであろう……

次回の開催地・会場はまだ未定とのことでしたが,楽しみに待ちたいと思います.今回も運営の皆さんお疲れ様でした,ありがとうございました.