先月末リリースし, 近日中に社内にお披露目予定のDoX正式版(DoXは, DockerでオレオレVPSを作った話で軽く触れている, Dockerを使った社内向けのステージング環境のことです)で使わざるを得なかった, Dockerのバッドノウハウについてメモしておきます.
恐らくあの辺りのコードについては, 数カ月後DoXにまた手を加えようとした時に「...なんでこんな事してるんだ...?」と頭を悩ませると思うので, 忘れないようにQiita:Teamに書いておいたのを, ブログに転載することにします.
Dockerの使い方としても, かなりバッドノウハウ感がある状況で起きる, ある特定事例を解決するためのバッドノウハウなので, 恐らくこんな所で詰まる人はいないと思うのですが... まあ, 悩みぬいた記念にこうやって晒しておくことにします.
問題
「システムコンテナ内でrebootすると, 再起動はするものの, runlevelがunknownになって, 各種プロセスが立ち上がらない」
前振り
DoXでは, Dockerのコンテナをまるで1つのサーバのように扱う(複数のプロセスが立ち上がる)「システムコンテナ」と, Dockerが想定している「1コンテナ = 1プロセス」のように動かす「アプリコンテナ」という, 2つのコンテナを扱うことができます.
今現在社内に提供しているDoXのベータ版では, システムコンテナしか対応していませんが, DoX正式版ではDockerHubなどから取得したイメージをアプリコンテナとして立ち上げることが出来ます.
これによって, 例えばDockerHubからElasticsearchのイメージを取得してから起動すれば, すぐさまElasticsearchを試す環境が出来上がる, なんてことが出来ます.
今回バッドノウハウを使わざるを得なかったのは, そのアプリコンテナではなく, システムコンテナで生じたある問題を解決する為でした.
症状
システムコンテナは, イメージを起動する際の起動コマンドを/sbin/init
にして, そこから各種プロセス(例えばsshd
とか, httpd
とか...)を立ち上げることで実現しています.
ここまではいいのですが, 問題はそのシステムコンテナの中でreboot
をしようとした場合です.
システムコンテナの中でreboot
を叩くと, 各種プロセスが正常に落ちていくのですが, /sbin/init
が再度立ち上がったタイミングで処理が止まってしまいます.
docker exec
コマンドでコンテナの中に侵入し, runlevel
コマンドでランレベルを確認すると, なんとunknown
という値が...
そこから手動で, /sbin/init 3
のように, ランレベルを指定(この場合は3)してあげると, 各種プロセスが立ち上がるのですが, これではうっかりコンテナ内でreboot
コマンドを実行してしまうと, ユーザがそのコンテナに接続する術がなくなってしまいます(SSHで接続出来ないので).
幸い, docker stop
してからdocker start
した場合や, docker restart
で再起動した場合は正常に立ち上がるので, これらのコマンドを内部的に利用してコンテナを操作しているDoXのWebアプリケーション経由で, 例えばコンテナを再起動するなどすれば, 再びコンテナ内に各種プロセスが立ち上がりコンテナを利用出来るようになるのですが, これは非常に面倒です.
解決策という名のバッドノウハウ
解決策という名のバッドノウハウは,
- コンテナ起動時に
--add-cap=SYS_BOOT
を与える - コンテナ起動時に
--restart=always
を与える
...でした. このオプションを与えると, Dockerのシステムコンテナがどのように動いていくか, 具体的に説明していきましょう.
まず, --add-cap
というオプションは, コンテナに対して特権を与えるオプションです(こちらの記事が詳しいです: (ヽ´ω`) < Dockerで--cap-addオプションを試す).
このオプションで, SYS_BOOT
という権限をコンテナに与えると, コンテナ内でreboot
を実行した際, 何故かコンテナそのものが停止します.
ちなみにその詳細な理由はまだ調べられていません...
これが「再起動」であれば問題は解決だったのですが, 残念ながら「コンテナの停止」であり, 各種プロセスが立ち上がらないのはこれまでと同じです.
ここで登場するのが, --restart
オプション.
これはDockerのコンテナが異常停止した際に再起動するかどうかを指定することが出来るオプションです.
今回のように--restart=always
にすると, コンテナが停止した場合, 即座に/何度もコンテナを起動してくれます(docker stop
など, 正しい手段でコンテナを停止した場合はその限りではありません).
...もうおわかりですね?
--cap-add=SYS_BOOT
でreboot
実行時にコンテナを停止させる- この停止は
docker stop
などの正しい手段ではないので, Dockerは即座にコンテナを起動(恐らく,docker start
)する - この際のコンテナは正常な起動なので, ランレベルは3になり, 各種プロセスが正しく立ち上がる
...どうです, バッドノウハウ感がたまらないでしょう? 個人的には, まるで「バッドノウハウのお手本」のような, なんかもうそういう感じのどうしようもない感じで非常に辛かったです(原因調査と, このバッドノウハウの発見だけで1日潰れましたし).
えー, というわけで, こういうバッドノウハウなどにも支えられつつ, DoX正式版は(恐らく)無事完成したので, 恐らく近日中に社内の各事業部に向けて提供することが出来ると思います.
調査
この症状について, チームで詳しく調べてみたところ, DoXベータ版と正式版でコンテナを動かすためのエンジンが異なるらしく(ベータ版はlxc, 正式版はlibcontainer), libcontaierで正常にreboot
できなかったイメージをlxc上でreboot
した所, 問題なく各種プロセスが立ち上がる形で再起動出来たとの事でした.
エンジンをlxcに変更する, という手もあるとはいえ, そうしてしまうとデバッグに便利な, Dockerコンテナ内に接続することが出来るdocker exec
コマンドが使えなくなってしまうというデメリットもあり, 今回はこのバッドノウハウを使いながら, libcontainerを使おうという方針になりました.
まとめ
というわけで, 会社で開発していたDoX正式版を作り上げる上で使わざるを得なかった, Dockerの超バッドノウハウを紹介しました.
こういうバッドノウハウは, どこかにまとめておかないと, 数カ月後の自分たちが「一体このコードは何なんだ...?」となることが多々あるので, これからも, 少なくともQiita:Teamにはしっかり書いていって, 経緯と記憶を後世に伝えていこうと思います...
有識者の反応
...なお, 本件に関する有識者の見解は以下のようなものでした.
@__papix__ それ使い方おかしいwww
— Daisuke Maki (@lestrrat) 2015, 2月 3
「それ使い方おかしいwww」
...現場からは以上です.