proxy-protocol-jsリリースした & Kyoto.js #16で喋ってきました
表題の通りproxy-protocol-jsをリリースしました.
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で触りたくなったのでこの度ライブラリをこさえた次第です.主なフィーチャーとしては
- V1プロトコル (テキストプロトコル) と V2プロトコル (バイナリプロトコル) の両対応
- プロトコルの解釈 (parse) と構築 (build),入力に基づいたプロトコルバージョンの識別 (identify) をサポート
- TypeScriptサポート (TypeScriptで書いたので……)
- 既存実装よりもパフォーマンス (と取り回し) が良い
- 外部ライブラリへの依存無し
という感じになっています.nodeでPROXY protocolを取り扱う人はそう多くはない気はしているのですが,もし来るべき時が来た際にはどうぞご利用ください.
「テクノロジーのイノベーションと民主化、そのはじめの一歩」というタイトルで首都大学東京で話してきました
首都大学東京は私の母校でして,当時は本当に色々なことがあったのですが,今回たまたまお話しする機会を頂いたので表題のような話をしてきました.
このタイトルを思いついた時は酔っ払っていたのでしょうね.かなり大上段に構えたタイトルになってしまい恐縮しているのですが,
・情報通信コース・学域を卒業して,会社に入ったけど,会社ってこんなところですよ,
・会社でこんなことやってますよ.
・うちの会社にはこんな特徴がありますよ.
・会社に入るとこんなことしますよ.
・入社して,困ったことはこんなことです.
・入社して良かったことはこんなことです.
・今思うと,学生時代にこんなことやっていたらよかったかな,って思います.
・会社に入ってからの私の年表
・会社はICTイノベーションとしてこんな取り組みをしていますよ,してきましたよ.私はそれにこんな形で携わりましたよ.
というような内容をオーダーされたので,ここは一丁エモい内容でいこうと思い,打ったのがこの発表となります.趣旨としては,テクノロジーイノベーションを起こしたい,それを起こすためにはーーというのを自分なりにテクニカルな観点から発表したものになります.
大学に所属する学部生の方々のために作ったスライドなので,もしかしたらそれ以外の人にはもしかしたら刺さらないかもわかりません *1.なお中盤のスライドにある英語に対する思いは心から思っていることです.本当に苦戦を強いられている……
普段はエモい発表あんましないので (というか苦手),発表のフィードバックがどうだったのかは気になるところですが…… (結構心配)
このスライドに書かれている内容については,LINEで働いていたときに僕に本当によくしてくれた@yappoさんと@tokuhirom さんのお陰で学ばせてもらったことが本当に,本当に多くあります.なかなかこれを改まって言う機会はないのですが,平成も終わることですし,この場を借りて感謝の意を表したいと思います.ありがとうございます!!
僕は「とにかくコードを書き続けることは良いことだと思っていて,コードを書かなければ終わる,どんどん書く,書いた量が質に転換するタイミングは必ずあるのだ」という思いを持っているのでこのような内容となっています.
オリジナルのスライドの最後には "Shut the fxxk out and write some code" という一言が書かれたスライドがあり,つまりこれは「グダグタ言ってねーでコードを書けよハゲ」という意味合いでして,僕を動かす原動力の一つであった言葉なのであります.これはものすごく好きな言葉なのですが,しかし自らの良識が働いた結果,公開版のスライドからは削除しました.ということを記します.
結びとして,人の感想を記したいと思います.
ハハハ,ありがとうございました.
*1:学生の人に必ずしも刺さると言っているわけではない
恐怖! パスワード無しでログインを受け付けるsshd Dockerコンテナを作る
outage reportを書くときに気をつけていること
そうは言っても障害は起きるものです.で,障害が起きて,終息したあとの振り返りとして社内向けにoutage report (障害報告書的な?) のようなものを書くと思うのですが,本記事ではそのときに気をつけていることについて書きたいと思います。
outage reportの目的
そもそもですが,outage reportを書く目的としては以下のような物があるのかなと思っています。
- A: 障害が起きたという事実に関する周知
- 障害についてお客様からお問い合わせが来たりした時に正しい情報を届けられるようにするため
- B: 根本原因の洗い出し
- 再発防止のため
- C: 障害検知フローの確認
- 障害に対する初動までにかかる時間を短くするため
- D: トポロジの形成
- 知見の醸成
- 似たような問題が起きたときに,outage reportに書いてある対処法を逆引き的に利用できるようにするため
outage reportに書いている内容
以下は個人的に (というか所属している組織的に) 書いている内容なので千差万別だとは思いますが,このような感じのことを書いていますというご紹介です.カッコ内のアルファベットは目的の部分に書いたアルファベットと対応しています (Dについては横断的に有効だと思うので省略しています).
障害のサマリ (A)
- なるべく簡潔に書くように心がけている.多くても3行以内で済むように.
- 外部向けに簡潔に障害内容を伝えられるような内容を心がける
- このサマリだけを読む非技術系のスタッフもいる
障害で発生した症状・お客様から見た問題の詳細 (A)
- 外から見た時の症状について書くようにしている
- 内部のシステム構成などは関係なく,お客さんが実際に対面した (であろう) 問題について書く
- 詳細に書くようにする
機能単位の影響範囲 (A)
- 実際にどの機能が利用不可能になったか,について列挙するようにしている
コンポーネント単位の影響範囲 (B)
障害期間 (A, C)
- 以下の4つについて明記する
- 発生日時
- 問題自体が発生した日時
- 検知日時
- 問題を検知した (初動可能になった) 日時
- お客様影響があった期間
- お客様が当該機能を利用できなかった期間 (from/to)
- 終息日時
- 問題の終息が正しく確認された日時
- 発生日時
- 文字だけだとわかりにくいので,「時系列で追いやすい図」や「グラフ」があると良いと思っている (余裕があれば)
検知フロー (C)
- 以下の3つについて書いている
- いつ検知したのか
- 何によって検知したのか
- 誰が (人・物にかかわらず) 検知したのか
- これによって,検知して初動対応可能になるまでにかかった時間がわかる
- 検知までの時間が長くかかってしまっている場合は,アラートが見落とされているとか,あるいはアラートの仕組みが不足しているとか,なんらか非効率な方法になっているとか,そういった問題を浮き彫りにすることができる
対応タイムライン (B)
- 対応した内容を時系列順に記す
- 「いつ・誰が・何を」という情報が重要
- 「効いた・効かなかった」という情報についても記すべき
原因と解決 (B)
その他反省
- 気づいたことをつらつらと書いてサービス改善につなげる
- 例えば「初動までの時間が長く,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.js
がpuppeteer.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以降のバージョンをお使いであれば *2,docker 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-initやkrallin/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の偉大さを再認識させられますね!!
参考資料
追記
背景としてはこれがgrafanaのスクリーンショットを取るためpuppeteerを使っててchromeを立ち上げちゃうのよね〜,コンテナ環境で素直に使うとゾンビが増える https://t.co/FvqizKQh3T
— moznion (@moznion) 2019年3月2日
本来は1コンテナ1プロセスみたいな感じで扱うべきだと思っているし,コンテナ内でプロセスをガンガンspawnするのは良くないと思っている,少なくとも自分が設計するアプリケーションでは1コンテナ1プロセスという感じ (あるいはそれに近しい感じ) にすると思うのですが,生きていると色々ありますね.
更に追記
puppeteerの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プラットフォーム」が出ます
- 作者: ソラコム
- 出版社/メーカー: 日経BP社
- 発売日: 2019/03/21
- メディア: 単行本
- この商品を含むブログを見る
出ます,出るのです.
ソラコムプラットフォームの概観やその設計思想,実際のユースケースに応じた参考アーキテクチャなどが満載されている楽しい本です!!
ソラコムをすでにご利用の方にもそうでない方にもお役立ちな情報が記されていると確信しております.
いやー久々に商業誌に文章を書きました (WEB+DB PRESS Vol.81以来なので5年ぶりくらいでしょうか?).とは言っても本当にほんの一部なのですが……
手に取ってもらえると嬉しいです,よろしくお願いします!!
YAPC::Tokyo 2019に参加してきた & LTしてきた #yapcjapan
遅くなりましたが,表題のとおりです.YAPCが東京に凱旋してきたので参加してきました.
じつはこのような形で事前に関わっていたりもします:
んで,LTをやってきました.資料は以下です.
AWS LambdaでPerlを動かすというテーマのLTで,これはかつてブログにも書いた内容だったのですが,ここらで一丁LTにでもしてみるか〜となり,なったという感じです.
ベストLT賞は、 @moznion さんの「Perl meets AWS Lambda」です。おめでとうございます!ベストLTスポンサーのコルシス様より賞品の贈呈です!#yapcjapan pic.twitter.com/ndjhT7kZga
— yapcjapan (@yapcjapan) January 26, 2019
ありがたいことにベストLTをいただきました.大感謝です.
さて今回のYAPCはなんやかやPerlの話題が多く,まだまだホットではあるな〜とは思うものの,charsbarさんの発表にあったように「年々CPAN Authorの数が減少している」など少々寂しい話題もあり,いろいろ考えさせられるものがある回でした.とはいえtokuhiromさんのキーノートにもあったように,なんやかやperlは便利なので今後も使っていく気はしており,まあここらへんはやっていくことになるのであろう……
次回の開催地・会場はまだ未定とのことでしたが,楽しみに待ちたいと思います.今回も運営の皆さんお疲れ様でした,ありがとうございました.