「第8回 コンテナ型仮想化の情報交換会@東京」の参加メモ

先日開催された第8回 コンテナ型仮想化の情報交換会@東京に参加してきました。 FreeBSD VPSに関するLT発表をさせていただいたのですが、他の方とLTの内容がかぶっているという事態が発生してしまいました...。とはいえ、同じくFreeBSD VPSについてLT発表された方からFreeBSD-10.1向けのVPSパッチを作成したよ!という有益な情報をいただけたので内心満足しています。

勉強会で聞いた内容を忘れないうちにメモしておこうと思います。公開されている発表スライドを参照しつつメモをまとめていますが、私の理解が間違っている可能性もありますのでご注意ください。

Linux Namespaces

発表者は@masami256さん。

LinuxのNamespacesに関する話で、調査対象のバージョンはkernel 4.1、glibc 2.21とのこと。

Namespaces概要

Linuxのコンテナで使われる技術はNamespaces,cgroup等の「プロセス・リソース管理」とbtrfs,overlayfs,aufs等の「ストレージバックエンド」の二つに大別される。Linux名前空間については@TenForwardさんの資料を読むのが確実。

Linux4.1でサポートしている名前空間として、System Vのプロセス間通信とPOSIXメッセージキューを扱う「IPC」、ネットワークデバイスやIPv4,6プロトコルスタック、ルーティングテーブルを扱う「Net」、マウントポイントを扱う「Mount」、プロセスIDを扱う「PID」、UID,GIDを扱う「User」、ホスト名を扱う「UTS」がある。これらの名前空間は昔からあまり変わっていない。

名前空間の機能として「リソースの管理」が挙げられる。ここでのリソースはメモリやCPU等のリソースとは異なる類のもので、IPCやネットワーク、ホスト名等を管理する仕組みのこと。この仕組みを入れ替えることで名前空間の分離が実現できる。名前空間を分離する時の挙動は、元の名前空間の複製(例:Mount名前空間)と、完全に新規の名前空間の作成(例:Net名前空間)という二種類がある。 ユーザランド上での名前空間は/proc//ns/[namespace name]のような形でファイルとして見える。

Napespacesの登場人物

Namespacesの登場人物として、UTS,Net等の「名前空間」、カーネル内で名前空間を管理する構造体である「NSProxy」、個々の名前空間の「参照カウンタ」(実装上はNSProxy構造体のメンバ変数)がある。

基本的に子プロセスは親プロセスと同じ名前空間に所属するが、プロセスの親子関係と名前空間の関係は別。これは単に名前空間のデフォルト値をどうするかという話。また、親プロセスの名前空間からの独立は(例外はあるものの)いつでも可能。

システムコールによる名前空間の操作

システムコールを通じて名前空間に対する「親プロセスの名前空間の共有」、「親プロセスの名前空間から分離」、「別プロセスへの名前空間移動」の操作が行える。

システムコールはclone(2),setns(2),unshare(2)がある。setns(2)は純粋に名前空間を操作するだけ。他の二つは名前空間「も」操作できる。

clone(2)はfork(2)の仲間で、子プロセスを作成する。fork(2)との違いは、フラグを色々設定して細かい制御が可能な点。スレッドを作る場合にも利用される(カーネル内の実装では、do_fork()がfork/cloneの共通処理になっている)。clone(2),unshare(2)で使用するフラグは、CLONE_NEWXXXというマクロ定数。

プロセス起動時から新しい名前空間で動かしたい場合はclone(2)を使う。CLONE_NEWXXXフラグで指定しなかった名前空間は親プロセスと共有される。

unshare(2)を使う場合は、PID名前空間は分離できないという制限がある。名前空間を完全に新しくしたい場合はclone(2)を使う必要がある。

setns(2)は所属したい名前空間のfdを使って、その名前空間に移動する。別のPID名前空間に所属させることはできるが、そのプロセス自身のPID名前空間は変わらないという制限がある。名前空間をまっさらにしたい場合は、unshare(2)の場合と同じくclone(2)する必要がある。

基本的に各名前空間はデータ的に独立しているが、User Namespaceは例外。User名前空間以外の構造体は、User Namespaceへのポインタを持っている。これは名前空間を使用する際にケーパビリティの有無をチェックするために使用される。というわけで、User名前空間だけは他の名前空間と独立ではない。

Hadoopのコンテナのはなし

発表者は@oza_x86さん。

データの処理基盤におけるコンテナのスケジューリングに関する話。

有象無象の生データをDBに入れる際の前処理をETL(Extract/Transform/Load)と呼ぶ。大量の生データを処理する際のプログラミングモデルとしてMapreduceがある(実装としてHadoopがある)。これはMap関数、Reduce関数を記述することで、対故障性を持った処理が行えるというもので、これらの関数はMapスロット、Reduceスロットという形でスケジューラが割り当てを決める。

Hadoopの計算プラットフォームとしてYARN(Yet Another Resource Negotiator)がある。読み方は「ヤーン」。

YARNのスケジューラには、入ってきたジョブから順にスケジューリングする「FIFO Scheduler」(一番簡単なスケジューラ)、ユーザ数で割った分でスケジューリングする「Fair Scheduler」、各人に割り当てられたキャパシティの中でスケジューリングする「Capacity Scheduler」がある。

コンテナのスケジューリングを考える際、異なるサイズのメモリや異なるプロセッサ数のCPUリソースが混在する場合に何をもって"Fairness"とするべきか。

cgroupsの場合はCPU上限を決定してしまうため、計算機資源を使い切れない。その代わり他のタスクを邪魔しない利点がある。これはSLA(Service Level Agreement)が必要な場合に有用かもしれない。 Unixプロセスベースの場合はCPU上限がないので計算機資源は使い切りやすいが、CPUを使いまくる場合は他のジョブの進行を阻害する危険がある。

"Fairness"を実現するため、Apache Mesosに出てくる概念である、Dominant Resource Fairness(DRF)を利用する。

DataCenter OSレベルのisolationになってくると、コンテナ技術単体だけでなくクラスタ単位のスケジューリング技術が必要であり、これにはFacebookによるBistroというスケジューラが提案されている。

SmartOS入門

発表者は@nslopeさん。

コンテナ用OSであるSmartOSに関する話。

SmartOSはJoyentが開発しているillumosベースのクラウド用OS。コンテナ、仮想マシンに特化している。SmartOSで使用できる仮想環境は、Naticve Zone(通常のコンテナ)、LX Baranded Zone(Linuxコンテナ)、KVMの3種類。SmartOSではデフォルトでdtrace機能が利用できる、ただし、カーネルの中の情報を取得するのには制限がある。

【個人的な感想】メモをとり忘れていたので発表の動画が公開されたら改めてメモにまとめてみます...。

Docker Swarm入門

発表者は@zembutsuさん。

Dockerはクライアント・サーバ型で、dockerデーモンとコマンドラインツールから構成される。Docker EngineはDockerの中核となるプログラムでコンテナの制御を行い、他のツールと連携してオーケストレーション機能を実現している。

Dockerとコンテナのオーケストレーションはなぜ必要か?これはクラウドやType1仮想化で複数のホストを管理するのと同じことをコンテナでも実現したいため。Type1仮想化における、VM+ゲストOS相当の箇所がDocker Clusterの管理レイヤにあたる。加えて、クラウド・Type1仮想化の両方をシームレスに管理するのはもう少し先の話かもしれない。

【個人的な感想】クラウド・Type1仮想化の両方をシームレスに管理するユースケースがうまく思いつかない。素人考えで思いつくのはType1仮想化の上でゲストOSを動かしたけど、IOまわりのオーバーヘッドが予想よりも大きかったのでコンテナで動かしたい、というような場合なのかなと思います。

(Dockerだけに限る話ではないが)「インフラの抽象化」は開発者の視点では開発・テスト・リリースの各プロセスにおいて一貫したインフラ環境を利用できる、という開発効率とアプリケーションのポータビリティが良いという利点がある。 ただし、Docker自体が何かするのではなく、あくまでも利用者の利便性向上のツール(またはプラットホーム)という点がポイント。

Docker動作ホスト環境を自動作成するツールにDocker Machineがある。自動でTLSを有効にしたDocker動作環境を構築し、仮想サーバの起動とDockerデーモンのプロビジョニングを行う。ツールの位置付けとしては、boot2dockerの置き換え。また、ソースコードでDockerの環境を管理できるDocker Composeがある。複数のコンテナを定義可能でイメージとしてはDockerfileを複数のコンテナ向けに拡張したもの。構成情報はYAML形式のファイルで指定する。

Docker Swarm(ドッカースウォーム)はDockerクラスタの管理ツール複数OS上のDocker環境を一つのリソースプールとして扱える。コマンドラインからdocker-machineでDockerホスト環境を作成後、docker run swarm createでコンテナクラスタを作成する。Docker Machineとの合わせ技でDockerクラスタを管理する。

Docker Swarmの概念として、Dockerデーモンの代わりにコマンドを受け付ける「マネージャ」(というプログラム)、ノード等のリソース情報をマネージャへの登録を行う「ディスカバリ」、コンテナの自動配置方針を決定する「ストラテジ」、コンテナ配置条件を指定する「フィルタ」がある。

スケジューリングのストラテジには、コンテナ稼働数でランク付けした値を用いる「Spred」(デフォルトのストラテジ)、コンテナをノードに集約する「Binpack」、ランダムに配置する「Random」がある。

Docker SwarmはDockerのAPIと互換性があり、docker ps,run等のコマンドをDocker Swarmで作成したクラスタ全体に対して適用可能。クラスタ群へのAPI実行はマネージャが行う。

フィルタはどのDockerホスト上でコンテナを起動するかを指定する機能。指定したフィルタ条件はストラテジよりも優先される。フィルタの種類として、Constraint,Affinity,Port,Dependency,Healthがある。

MINCS - Container in the shell script

発表者は@mhiramatさん。

コンテナの実装にはDocker以外にもLXC,Runc,OpenVZ等の様々な実装がある。Dockerは多くの機能を提供しているが、すこし規模が大きく、個々の機能を試しにくい。そこでUnix哲学の"Keep It Simple, Stupid"という思想に倣い、MINCS(Minimum Container Shell-scripts)というコマンド群をシェルスクリプトで実装されたとのこと。MINCSはPOSIX shell script(bashスクリプトではない)で作成されているためポータビリティがあり、busybox shell,dash等でも動作可能。

最小限の実行環境分離を実現するため、名前空間の利用、デバイスファイルのバインド、Chroot/pivot_rootによるrootfsの変更、CapabilitiesとCPUSETを利用する。ファイルシステムのレイヤリングにおいては、Linux-3.18以降でOverlayfsが利用可能であり、これを利用してコンテナイメージの管理も試してみたとのこと。

MINCSはフロントエンドとバックエンドの構成でコマンドが用意されている。フロントエンドコマンドは基本的にパラメタのパースのみを行い、パラメタを環境変数に変換してバックエンドを呼び出す役割になっている。

フロントエンドのコマンドにはminc,marten,plecatがある。mincは指定したコマンドをコンテナ内部で動かすもので、chrootやdocker runコマンドのようなもの。デフォルトでは名前空間分離とoverleyfsによる作業空間分離を行い、ネットワークはそのまま見える。martenはマルチレイヤのコンテナイメージの管理する。polecatは自己実行形式のシングルバイナリコンテナアプリを生成する。バックエンドのコマンドにはminc-exec,minc-coat,minc-farm,minc-trapperがある。

mincコマンドはパラメタのパース後、バックエンドコマンドのminc-execを実行する。minc-execの中では以下の処理が行われる。

  • netnsとcpumaskの設定
    • MINC_NETNSで指定された名前のnetnsをip netnsで作る。これは終了時にtrapコマンドで削除される。併せてtasksetコマンドで実行するCPUを指定する
  • 新しい名前空間への移行
  • PIDの保存とutsの設定
    • 後でコンテナ外からPIDを知るために保存しておく
  • コンテナ用rootfsのセットアップ
  • デバイスファイルのバインド
  • 不要なマウントポイントの削除
    • 不要なマウントポイントを残しておくとchroot後にも見えてしまう。umountできないものがあるのでpivot_rootdで処理を行う
  • 新しいrootfsへの移行とcapabilitiesの設定
    • capshコマンドでLinuxケーパビリティを変更する。単にchrootだけを実行した場合、ケーバビリティは変更されない

FreeBSD Jail/VIMAGEの始め方

発表者は@BsdHackerさん。

jailとはchroot(8)の発展系でroot directoryを変更する機能。プロセスの実行環境を分離できる。 Linux emulator機能を利用することでjailでlinux環境を作成できる(ただし現状32bit環境のみ)。

VIMAGEとはFreeBSD-9.0-RELEASEから利用可能なjail毎に異なるネットワークスタックを作成可能にする拡張。ただし、カーネルの再構築が必要。まだ対応できていないネットワークドライバ/スタックもあるが、Jail内にネットワークインタフェースを自由に配置できる。

LXD入門

発表者は@ten_forwardさん。

lxdはGo言語で書かれた、REST APIを提供するコンテナ管理デーモン。lxdと通信し、コンテナを操作するコマンドラインクライアントがlxc。また、nova-compute-lxdというOpenStack Novaプラグインもある。

lxdの特徴として、セキュアであること(デフォルトでは非特権コンテナ)、イメージベースであることとシンプルなAPIコマンドラインの提供、ライブマイグレーションがある。

Ubuntu 15.04ならlxdのインストールは簡単(パッケージが用意されている)。しかし、lxdは頻繁に更新されるため、ubuntu-lxc/lxd-stableリポジトリをadd-apt-repositoryしておいてupdateするとよい。もちろんソースからビルドするのもあり。

コンテナイメージのインポートは、lxc image importコマンドで行う。イメージサーバを登録するとリモートイメージからコンテナを直接起動できる。リモートサーバのコンテナはデフォルトだとUnix domain socket経由での接続のみであるため、必要に応じてリモート接続設定を行う必要がある。

ライブマイグレーションはlxc moveコマンドをリモート間で実行する(ただし残念なことに現時点のバージョン0.18(2015年9月現在)ではライブマイグレーションは行えない)。ローカルで実行すると単にコンテナのリネームになる。ライブマイグレーション機能は内部的にCRIUを利用している。lxdはデフォルトで非特権コンテナであり、CRIUは非特権コンテナに対応していないため、ライブマイグレーションを行う際には特権コンテナにする必要がある。

LT

メインセッションのメモをまとめた段階で力尽きてしまいました。今はこれが精一杯...。

まとめ

第8回 コンテナ型仮想化の情報交換会@東京の参加メモをまとめてみました。後から自分で見返しやすいようにメモを小さくまとめたつもりなのですが、思っていたよりも長いメモになってしまいました...。

それでもコンテナ型仮想化に関する現状とノウハウについて把握できてきたので、次回のコンテナ型仮想化の情報交換会を楽しみにしつつ、メモを見返すようにしようと思います。

【C88】トークイベント「TPPの著作権条項を考える ~非親告罪化、保護期間延長、そして法定賠償金~」の参加メモ

コミックマーケット88の1日目に行われたトークイベント、TPPの著作権条項を考える ~非親告罪化、保護期間延長、そして法定賠償金~に参加してきました。

TPPの著作権条項により、コミケ等の創作活動に影響が出るのでは、という話は以前から聞いていたのですが、具体的に何がどう問題なのか、加えて、現状どういう議論になっているのかが分からない状態のままになっていました。ちょうど良い機会なので、このトークイベントで把握できた内容をメモにまとめておこうと思います。

自分で分かるようにメモをまとめただけなので、議論の順番が前後していたり、(自分の理解が足りず)内容が間違っていたりする可能性もありますのでご注意ください。

また、トークイベントの内容はニコニコ生放送タイムシフト再生で観ることができるようです。

このトークイベントについて

トークイベントのタイトルにもある通り、TPPの著作権条項として議論されている以下の3項目について、パネラーの方々を交えて意見をやりとりするトークセッションでした。

これらの項目について、TPPの現状と、どう問題意識を持っているか、どう考えるべきか、そして今後どう変わって行くかについてパネルディスカッションが繰り広げられました。

モデレーターは香月啓佑さんで、パネラーは以下の方々。

トークイベントはモデレーター、パネラーの方々の自己紹介を兼ねた一言コメントから始まり、TPPの現状、今後どう変わってゆくか、どう考えているかの話から始まりました。

  • パロディや権利者のお目こぼしによるファン活動が今までのように続けられなくなるのではないか、同人活動を萎縮させずに続けるにはどうするか、という点について問題意識を持っている。
  • TPPが妥結したら、次は国内法の整備という段階になる。その段階になると、TPP条文の拡大解釈を防止して表現の自由を守れるよう、国内の偉い人を説得する必要が出てくる。

TPPの著作権条項の各項目に関するディスカッション

次に、TPPの著作権条項で挙げられている各項目についてのディスカッションに入って行きました。

著作権の保護期間の延長について

  • 現状の日本国内における著作権の保護期間は、著者者の生前50年+死後50年の計100年間
  • 日本では2006年に著作権の保護期間の延長に関する議論があったが、2009年に保護期間の延長は一旦見送る形になっている
    • 著作権の保護期間延長について、日本はもともと延長反対の立場を採っている

著作権非親告罪化について

  • TPPのメニューに著作権侵害については「非親告罪化せよ」という項目がある
    • TPPは条約なので、法律よりも優先される
  • 非親告罪の対象になるのは、「故意に商業的規模で経済的利益を得るような」場合
    • そういう観点では、同人誌の頒布は対象になりそう
    • ただし、著作権非親告罪化については「著作権者の利用能力に影響を与える場合」に限って非親告罪を適用するという「セーフガード」を日本は要求している
    • 国内法でどういうセーフガードにするか、という議論は別途必要

法定賠償金制度について

  • TPP議論内容のリーク文書によると、法定賠償金制度については反対もなく取り込まれている模様
  • 賠償金の算出については、被害額を充分に保証できるくらいの額で計算されそう
  • 賠償金が高額になる傾向があるため、米国では「コピーライト・トロール」が問題になっている

同人活動からTPPの著作権条項に関連するあれこれを考える

トークイベントはコミックマーケット内のイベントとして開催されたこともあり、同人活動から見たTPPの著作権条項に関連するあれこれのディスカッションが行われました。

コスプレは著作権侵害に該当する?

コスプレで逮捕された事例は今までには無い。衣装の販売で逮捕された人はいるけれど、これは別の話。

コスプレが私的複製の範囲内かと問われると、大勢に見てもらうような場合のコスプレは私的複製の範囲から外れるかも。コスプレの元になった作品には著作権があり、これを衣服にした場合、著作権侵害なのかどうかの判断は「オリジナリティがあるかどうか」がポイントになる。

【個人的な感想】オリジナリティの有無が要点になるのは理解できますが、「何を持ってオリジナリティがあるか」と判断できる、あるいは主張できるかが個人的に理解しきれていないです。布団にくるまって「ぷよぷよのコスプレ」とした場合は、オリジナリティがあるのだろうか(キラリと光るセンスはありそうだけど...)。

中川さんがコスプレを見て回った際の感想としては、著作権侵害にならないものが多い、という印象を受けたとのこと。

二次創作について

現状と著作権非親告罪化について

現状の同人活動を考えると、グレーゾーンな部分がある。ただし、著作権者が二次創作についてOKを出しているような場合は、検察が起訴するようなことは無いはずで、その観点では非親告罪化についてはあまり心配する必要はなさそう(著作権者がOK出してるワケですから)。

ただ、怖いのは第三者による通報のような「嫌がらせ」を目的とした行為が行われる可能性があるという点。行政はクレームに弱い傾向があるらしく、数百件くらいのクレームで折れてしまう(イベントを中止したり等)ことがあり、こういった嫌がらせ目的の通報等に対抗できるような明文化は必要かもしれない。

明文化といっても、著作権的にセーフかアウトか、という話に留めるべきで、同人誌そのものがセーフ、アウトか、という議論は混ぜるべきではない。

【個人的な感想】言われてみれば当たり前なのですが、著作権的な話と同人誌の是非に関する話は分けて考えるべし、というのは盲点でした。議論の際はこの分けて考える認識がないと、話がかみ合わないことがあるかも。

著作権者が訴えた例としては、任天堂コナミのケースがある(ポケモンときメモの話?)。とはいえ、赤松さんの弁によると、著作権者が訴えるのは考えにくいとのこと。そもそも描いてくれたら嬉しいので、ファンを攻撃するようなこと(訴える等)はしないよね、という話。とはいえ、「著作権を相続した遺族の方から訴えられるかも」という意見もでていました。

【個人的な感想】赤松さんの「描いてくれたらうれしいもん!」という熱い一言はグッとくるものがありました。著作権を相続した遺族が訴えるかも、という話はたしかに懸念事項ですね。著作権著作者人格権(譲渡できない)と財産的な権利(譲渡できる)に分けられるはずで、遺族が著作権を相続する場合は「財産的な権利」のみになるのかなと思います(ここらへんちゃんと理解できてない...)。そうすると先にあげた「『著作権者の利用能力に影響を与える場合』に限って著作権非親告罪を適用」というケースに当てはまりそうな心配がありますね。

現状での著作線侵害のセーフ、アウトの線引きはどこ?

中川さんがコミケの島めぐりをして、どの辺りが危ないかについての解説。一見してアウト、セーフが判別できるものがあり、判断基準は「著作権者の絵をそのまま使っているか」。「絵が似ているか」で、例えば、TVのキャプチャをそのまま使っているのはマズいとのこと。絵が似ているかについては、似ていなかったらそもそも著作権侵害が成り立たないよね、という話。つまるところ絵のウマい・ヘタ論になってしまうも、著作権侵害だ!と言われた場合に、その判断(絵が似ている似ていない)を警察等の機関が正しく行えるかは疑問が残る。

【個人的な感想】確かに、絵が似ている、似ていないを第三者が判断する状況下で正しく判断してもらえるかは心配の種として残りますね。例えば、リゼ(ご注文はうさぎですか?)と胡桃(がっこうぐらし!)は同じキャラでしょ、と言われるとどう説明したものやら...。

二次創作小説について

二次創作小説については、マンガと比べて著作権侵害と判断されにくい。作品のキャラクターやアイディアを借りてくるだけであれば著作権侵害ではない、しかし、作品内のセリフをそのまま使うとアウト。「引用」の範囲であると判断できそうだけれど、先の例にあった本の表紙にTVのキャプチャを使うのはマズい。

ガイドラインもの」について

初音ミク艦これ等に見られる二次利用許諾の形態、いわゆる「ガイドラインもの」については、(当然ではあるが)ガイドラインに従って同人作品を作るのはなんら問題無い。このガイドラインものに加え、著作者への確認の制度をルール化するのが良い方法かもしれない。

【個人的な感想】ガイドラインによる二次利用許諾という方法は賛成ですね。すでに同人マークで二次創作と同人誌の配布の許諾意思を原作者が明示的に示すという流れがあります。ただ、原作者が二次創作OKとしていても、出版社が許可しない、という状況もありえるのだろうか?という疑問が新たに湧いてきてしまいました。このあたりは、「原作者、出版社、許諾形態」の形で星取り表があったりすると分かりやすいのかなと思います。

我々はどうするべきなのか

TPPで著作権の扱いが変わることによる、「(創作活動の)萎縮への対策」と「第三者の悪意ある通報への対策」を考えないといけないという話。TPP著作権条項にまつわる問題点については、政府側でも問題は共有されつつあるとのこと。

【個人的な感想】国会中継とかはあまり見たりしない(逆に国会中継を欠かさず観る!という人はいるのだろうか...)のですが、このメモをまとめるにあたり、国会議事録検索システムの存在を知りました。定期的に議事録内を「著作権 TPP」といったキーワードで検索することで、議論の状況を追跡できそうです。

この話をしている途中で、山田太郎さんから飛び入りでの補足があり、TPPの非親告罪化については大丈夫だと思う、政府が本当に規制したいのはエログロやゲームの暴力表現である、という補足説明があった。

【個人的な感想】SFCストIIで2コンのスタートボタンを押したのか!と思うくらいの飛び入りっぷりでした。

我々はどうすべきなのかについては、TPPの著作権条項により、即座にコミケ潰れるようなことはないが、安心しすぎるのも良くない。また、ひとつのジャンルに限らず見て行くのが大事という話がされていました。加えて、若い人も政治に関心をもった方がいいよね、という(良い最終回だった的な)話の締めくくりになっていました。

【個人的な感想】個人的に政治に関心をという話は大仰かなと思うところもありますが、著作権まわりの現状とこれからについては、同人活動に関わるなら少なくとも自分の意見を持てるくらいじゃないと足元が危ういという気がしています。そのためには現状の理解が必要で、こういったトークイベントはとても勉強になります。

まとめ

このトークイベントにより、TPPの著作権条項の議論のポイント、現状についてある程度把握できました。これを踏まえて今後の議論や法律面から著作権の話、創作界隈にどう影響してくるのかを掘り下げて理解して行ければ良いなと思います。

参考URL

「ポケットミクちゃんに何かいろいろさせる会(その8)」開催レポート

「ポケットミクちゃんに何かいろいろさせる会(その8)」開催レポート

ポケットミクちゃんに何かいろいろさせる会(その8)を開催しました。定期的に開催しているにも関わらず、ぜんぜん開催レポートをまとめていませんでしたが、得られた知見をまとめておかないとすぐ忘れてしまうので今回から開催レポートを書いてみようと思います。

リモート開催してみた

ポケミク会は都内のコワーキングスペースで開催しているのですが、都内は距離的に参加がキビしいという声もあり、今回からリモート接続しつつポケミク会、というのを試してみました。

えいやでSkypeを使ってみたところ、リモートから接続はできるのですが、以下のようなハマり所がありました。

  • リモートから同時に接続できるのは1人だけ
    • 後から接続した人の電話を取ると、既に接続していた人は保留状態になってしまう
  • Webカメラの性能が良すぎる(HD画質など)と、ネットワークの帯域を圧迫してしまう
    • 画質の低いWebカメラの方が良さそうです...。

特に同時接続1人だけという制限はキツいため、次回以降はGoogleハングアウトで試す予定です。こちらは同時8人での通話が可能とのことです(次回に向けのての準備はそのあたりの確認からですね)。

ポケミクで歌わせる際のTips

今回のポケミク会では初参加の方がいて、ポケミクを演奏する際の疑問がいくつか出ていました。すっかり忘れていた部分もあったので、備忘録を兼ねてメモしてみます。音を伸ばしたりする歌わせ方の時のノウハウです。

「ばーい」と歌わせたい場合

  • 歌詞は「ばい」と入力する
  • リボンを「ばー」の所で引っ張る
    • リボンの上側を通してから次の音に入れる
    • リボンの上側が滑らかに音が変化するので自然(?)に聞こえる
      • ただし、音が離れすぎているような場合はもう少し工夫が必要そう

「とぉーく」と歌わせたい場合

  • 「ぉ」を入力しなくてもOK
  • 「とぉー」でスタイラスをタッチ、リボンの上を次の音まで滑らせて「く」で(ダブルクリックっぽく)軽くタップする

Firefox + WebMIDI API

Web MIDI APIのサポート状況が厚いこともあり、ポケミクアプリはGoogle Chromeのみでの動作となっています。他のブラウザでのWeb MIDI APIのサポート(実装)状況はどうなんでしょうね?という話があり、ざっとググってみたところ、FirefoxでWeb MIDI APIを実装しようぜ!的な話はあるみたいです。が、「でもまあ、優先度は低いよね」というちょっと残念な流れのようです。

MIDI規格周りの話

MIDI規格周りの話題も出ていて、なんでもBluetoothを利用してMIDIデータをやり取りする規格があるとのことです。ちょっとまだよく分かってないので、とりあえずはググって得られたリンク集を貼っておきます...。

まとめ

ポケットミクちゃんに何かいろいろさせる会(その8)を開催してみました。開催の記録を残しておかないと、せっかくの知見が頭から揮発してしまうので、次回以降はせっせとメモを取るようにしようと思います。

Linuxカーネルもくもく会#6に参加してきました

Linuxカーネルもくもく会#6に参加してきました。この会は各自Linuxソースコードの気になる箇所を読み進めたり、Linuxに関する何かをもくもくと進める会です。

cgroup周りのソースコードを読み進めてみているので、読んでみた内容のメモをまとめてみます。linux-3.17-rc3のソースコードを対象にしています。

start_kernel()からcgroup_init_early()を読んでみる

まずはLinuxカーネルのエントリポイントであるinit/main.c:start_kernel()から読み進めてみます。

init/main.c:
 500 asmlinkage __visible void __init start_kernel(void)
 501 {
 ...
 519         cgroup_init_early();
 ...

cgroup_*()な関数群で最初に呼ばれているのはkernel/cgroup.c:cgroup_init_early()です。関数コメントによると、OS起動時にcgroupの初期化とearly initが必要なサブシステムの初期化を行うようです。

kernel/cgroup.c:
4895 /**
4896  * cgroup_init_early - cgroup initialization at system boot
4897  *
4898  * Initialize cgroups at system boot, and initialize any
4899  * subsystems that request early init.
4900  */
4901 int __init cgroup_init_early(void)
4902 {
4903         static struct cgroup_sb_opts __initdata opts;
4904         struct cgroup_subsys *ss;
4905         int i;
4906
4907         init_cgroup_root(&cgrp_dfl_root, &opts);
4908         cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;
4909
4910         RCU_INIT_POINTER(init_task.cgroups, &init_css_set);
4911
4912         for_each_subsys(ss, i) {
4913                 WARN(!ss->css_alloc || !ss->css_free || ss->name || ss->id,
4914                      "invalid cgroup_subsys %d:%s css_alloc=%p css_free=%p name:id=%d:%s\n",
4915                      i, cgroup_subsys_name[i], ss->css_alloc, ss->css_free,
4916                      ss->id, ss->name);
4917                 WARN(strlen(cgroup_subsys_name[i]) > MAX_CGROUP_TYPE_NAMELEN,
4918                      "cgroup_subsys_name %s too long\n", cgroup_subsys_name[i]);
4919
4920                 ss->id = i;
4921                 ss->name = cgroup_subsys_name[i];
4922
4923                 if (ss->early_init)
4924                         cgroup_init_subsys(ss, true);
4925         }
4926         return 0;
4927 }

引数cgrp_dfl_rootはグローバル変数で同じソースファイル内で宣言されています。

kernel/cgroup.c:
 139 /*
 140  * The default hierarchy, reserved for the subsystems that are otherwise
 141  * unattached - it never has more than a single cgroup, and all tasks are
 142  * part of that cgroup.
 143  */
 144 struct cgroup_root cgrp_dfl_root;

変数cgrp_dfl_rootのデータ型である、struct cgroup_rootを見てみます。

include/linux/cgroup.h:
264 /*
265  * A cgroup_root represents the root of a cgroup hierarchy, and may be
266  * associated with a kernfs_root to form an active hierarchy.  This is
267  * internal to cgroup core.  Don't access directly from controllers.
268  */
269 struct cgroup_root {
270         struct kernfs_root *kf_root;
271
272         /* The bitmask of subsystems attached to this hierarchy */
273         unsigned int subsys_mask;
274
275         /* Unique id for this hierarchy. */
276         int hierarchy_id;
277
278         /* The root cgroup.  Root is destroyed on its release. */
279         struct cgroup cgrp;
280
281         /* Number of cgroups in the hierarchy, used only for /proc/cgroups */
282         atomic_t nr_cgrps;
283
284         /* A list running through the active hierarchies */
285         struct list_head root_list;
286
287         /* Hierarchy-specific flags */
288         unsigned int flags;
289
290         /* IDs for cgroups in this hierarchy */
291         struct idr cgroup_idr;
292
293         /* The path to use for release notifications. */
294         char release_agent_path[PATH_MAX];
295
296         /* The name for this hierarchy - may be empty */
297         char name[MAX_CGROUP_ROOT_NAMELEN];
298 };

kernel/cgroup.c:init_cgroup_root()は必要なデータ構造の初期化を行っているだけのようなので、ざっくりと眺める感じで次の処理を読み進めてみます。

kernel/cgroup.c:
1596 static void init_cgroup_root(struct cgroup_root *root,
1597                              struct cgroup_sb_opts *opts)
1598 {
1599         struct cgroup *cgrp = &root->cgrp;
1600
1601         INIT_LIST_HEAD(&root->root_list);
1602         atomic_set(&root->nr_cgrps, 1);
1603         cgrp->root = root;
1604         init_cgroup_housekeeping(cgrp);
1605         idr_init(&root->cgroup_idr);
1606
1607         root->flags = opts->flags;
1608         if (opts->release_agent)
1609                 strcpy(root->release_agent_path, opts->release_agent);
1610         if (opts->name)
1611                 strcpy(root->name, opts->name);
1612         if (opts->cpuset_clone_children)
1613                 set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->cgrp.flags);
1614 }

init_cgroup_root()の直後で何やらフラグを設定しています。

kernel/cgroup.c:
4901 int __init cgroup_init_early(void)
4902 {
 ...
4907         init_cgroup_root(&cgrp_dfl_root, &opts);
4908         cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;

CSS_NO_REFはinclude/linux/cgroup.hでenum定義されており、このCSS(Cgroup Subsys State)には参照カウンタが無いことを示しているみたいです(「参照カウンタを持たない」という意味かも)。

include/linux/cgroup.h:
 96 /* bits in struct cgroup_subsys_state flags field */
 97 enum {
 98         CSS_NO_REF      = (1 << 0), /* no reference counting for this css */
 99         CSS_ONLINE      = (1 << 1), /* between ->css_online() and ->css_offline() */
100         CSS_RELEASED    = (1 << 2), /* refcnt reached zero, released */
101 };

cgroup_init_early()を読み進めて行きます。関数の後半にはfor_each_subsys()のループがあり、何やらsubsys的なものを順次処理しているようです。

kernel/cgroup.c:
4901 int __init cgroup_init_early(void)
4902 {
 ...
4912         for_each_subsys(ss, i) {
 ...
4920                 ss->id = i;
4921                 ss->name = cgroup_subsys_name[i];
4922
4923                 if (ss->early_init)
4924                         cgroup_init_subsys(ss, true);
4925         }
 ...

for_each_subsus()はfor文に展開されるマクロだと予想できるのですが、実際どうなっているか見てみます。

kernel/cgroup.c:
 376 /**
 377  * for_each_subsys - iterate all enabled cgroup subsystems
 378  * @ss: the iteration cursor
 379  * @ssid: the index of @ss, CGROUP_SUBSYS_COUNT after reaching the end
 380  */
 381 #define for_each_subsys(ss, ssid)                                       \
 382         for ((ssid) = 0; (ssid) < CGROUP_SUBSYS_COUNT &&                \
 383              (((ss) = cgroup_subsys[ssid]) || true); (ssid)++)

上記で参照しているCGROUP_SUBSYS_COUNT、cgroup_subsys_idはenum値なので、最後にCGROUP_SUBSYS_COUNTが付いています。

 43 /* define the enumeration of all cgroup subsystems */
 44 #define SUBSYS(_x) _x ## _cgrp_id,
 45 enum cgroup_subsys_id {
 46 #include <linux/cgroup_subsys.h>
 47         CGROUP_SUBSYS_COUNT,
 48 };
 49 #undef SUBSYS

cgroup_init_early()の4925行目のcgroup_init_subsys()、こちらはcgroup_subsys_name[]で配列になっています。

kernel/cgroup.c:
 125 /* generate an array of cgroup subsystem pointers */
 126 #define SUBSYS(_x) [_x ## _cgrp_id] = &_x ## _cgrp_subsys,
 127 static struct cgroup_subsys *cgroup_subsys[] = {
 128 #include <linux/cgroup_subsys.h>
 129 };
 130 #undef SUBSYS
 131
 132 /* array of cgroup subsystem names */
 133 #define SUBSYS(_x) [_x ## _cgrp_id] = #_x,
 134 static const char *cgroup_subsys_name[] = {
 135 #include <linux/cgroup_subsys.h>
 136 };
 137 #undef SUBSYS

linux/cgroup_subsys.hの中身を見てみます。

linux/cgroup_subsys.h:
  1 /*
  2  * List of cgroup subsystems.
  3  *
  4  * DO NOT ADD ANY SUBSYSTEM WITHOUT EXPLICIT ACKS FROM CGROUP MAINTAINERS.
  5  */
  6 #if IS_ENABLED(CONFIG_CPUSETS)
  7 SUBSYS(cpuset)
  8 #endif
  9
 10 #if IS_ENABLED(CONFIG_CGROUP_SCHED)
 11 SUBSYS(cpu)
 12 #endif
 ...

kernel/cgroup.c:SUBSYS()マクロで値が列挙されています。簡単なサンプルコードで展開結果を確認してみます。

$ cat tmp.c 
#define SUBSYS(_x) [_x ## _cgrp_id] = #_x,
SUBSYS(cpu)
$ gcc -E tmp.c 
...
[cpu_cgrp_id] = "cpu",

微妙にややこしいのですが、上記のcpu_cgrp_idはlinux/cgroup_sybsys.h(こっちはヘッダファイル)でenum宣言された値です。

linux/cgroup_subsys.hをテンプレートのように利用し、*cgroup_sybsys[]とenum cgroup_syubsys_idを再定義したSUBSYS()マクロで作るという方法になっています。cgroupサブシステムの列挙を一つのファイルで完結させたいという気持ちは分かるのですが、知らずに読むと混乱してしまいます...。

気を取り直してcgroup_init_early()の続きを読み進めてみます。

kernel/cgroup.c:
4901 int __init cgroup_init_early(void)
4902 {
 ...
4912         for_each_subsys(ss, i) {
 ...
4920                 ss->id = i;
4921                 ss->name = cgroup_subsys_name[i];
4922
4923                 if (ss->early_init)
4924                         cgroup_init_subsys(ss, true);
4925         }
4926         return 0;
4927 }

再度for_each_subsys()、変数ssに入るのはcgroup_sybsys[]ですね。

 381 #define for_each_subsys(ss, ssid)                                       \
 382         for ((ssid) = 0; (ssid) < CGROUP_SUBSYS_COUNT &&                \
 383              (((ss) = cgroup_subsys[ssid]) || true); (ssid)++)

cgroup_subsys[]の中身を見てみます。サブシステムごとに関数ポインタを渡せるみたいですが、この段階ではidとnameの設定とearly_initが真の場合にcgroup_init_subsys()を呼ぶだけですね。

include/cgroup/cgroup.h:
623 struct cgroup_subsys {
624         struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css);
625         int (*css_online)(struct cgroup_subsys_state *css);
626         void (*css_offline)(struct cgroup_subsys_state *css);
627         void (*css_free)(struct cgroup_subsys_state *css);
628         void (*css_reset)(struct cgroup_subsys_state *css);
629
630         int (*can_attach)(struct cgroup_subsys_state *css,
631                           struct cgroup_taskset *tset);
632         void (*cancel_attach)(struct cgroup_subsys_state *css,
633                               struct cgroup_taskset *tset);
634         void (*attach)(struct cgroup_subsys_state *css,
635                        struct cgroup_taskset *tset);
636         void (*fork)(struct task_struct *task);
637         void (*exit)(struct cgroup_subsys_state *css,
638                      struct cgroup_subsys_state *old_css,
639                      struct task_struct *task);
640         void (*bind)(struct cgroup_subsys_state *root_css);
641
642         int disabled;
643         int early_init;
 ...
660         /* the following two fields are initialized automtically during boot */
661         int id;
662 #define MAX_CGROUP_TYPE_NAMELEN 32
663         const char *name;

続けてcgroup_init_subsys()も読み進めようと思ったのですが、このあたりでLinuxもくもく会の終了時刻に。続きは次回ですね。

kernel/cgroup.c:
4845 static void __init cgroup_init_subsys(struct cgroup_subsys *ss, bool early)
4846 {
4847         struct cgroup_subsys_state *css;
4848
4849         printk(KERN_INFO "Initializing cgroup subsys %s\n", ss->name);
...

まとめ

Linuxカーネルもくもく会に参加し、cgroupソースコードを読み進めてみました。次回のもくもく会でもcgroup周りを読んでみようと思います。

「第4回 コンテナ型仮想化の情報交換会@東京」に参加してきました

「第4回 コンテナ型仮想化の情報交換会@東京」に参加してきました

第4回 コンテナ型仮想化の情報交換会@東京に参加してきました。

以下は勉強会のメモです。私の理解に間違い勘違いがあるかもしれませんのでご注意ください。

最新cgroup事情

私が遅れて到着してしまったため、最初の部分は聞き逃してしまいました……。

cgroup概要

(プロセス等の?)コントロールはタスク単位だが、リソースはタスク単位ではない。そのため、タスク単位でリソースを扱うのは正しいのか、という議論がある。

cgroupの自由度を高めた結果、カオスな状況になっている。そこでsane_behaviorオプションにより「制限を加えること」で正しい使い方を強制する。これはKernel3.19-3.20あたりでデフォルトで有効なオプションになるかも。

systemdの話

systemdは全てのcgroupを牛耳るというスタンス。systemdが有効なシステムでは、systemdでcgroupを作る。Unit file, DBUS APIで作る。libvirtのcgroupもsystemdを使うよう修正された。

Systemdに新たに加わったユニットタイプの一つにsliceがある。systemdの下には3つのsliceがあり、注意が必要なのはuser.sliceとsystem.slice。user.sliceの下にあるやつはsystemdが完全に掴んでいる。

systemd/src/shared/unit-name.h:
 33 enum UnitType {
 34         UNIT_SERVICE = 0,
 35         UNIT_SOCKET,
 36         UNIT_BUSNAME,
 37         UNIT_TARGET,
 38         UNIT_SNAPSHOT,
 39         UNIT_DEVICE,
 40         UNIT_MOUNT,
 41         UNIT_AUTOMOUNT,
 42         UNIT_SWAP,
 43         UNIT_TIMER,
 44         UNIT_PATH,
 45         UNIT_SLICE,
 46         UNIT_SCOPE,
 47         _UNIT_TYPE_MAX,
 48         _UNIT_TYPE_INVALID = -1
 49 };
systemd/src/shared/path-lookup.h:
 34 typedef enum SystemdRunningAs {
 35         SYSTEMD_SYSTEM,
 36         SYSTEMD_USER,
 37         _SYSTEMD_RUNNING_AS_MAX,
 38         _SYSTEMD_RUNNING_AS_INVALID = -1
 39 } SystemdRunningAs;

systemdから設定できるパラメータは限定的。CPUパラメータに"Share"は設定可能だが"Limit"は設定不可。また、Memoryの上限を設定できてもSwapの設定はできない。現状では設定可能なパラメータを増やす予定は無い。これはLinux Kernel側のcgroupが再設計中であるため、それが一段落して安定してからAPIの追加が行われるためとのこと。

Memory cgroupの話

  • 設定できるメモリ使用量の上限(1-3は連携している)
    1. メモリ使用量の上限(ユーザメモリとページ(ファイル)キャッシュ)
    2. メモリ+swap使用量の上限
    3. カーネルメモリ使用量の上限
    4. TCPのバッファ量

TCPバッファ量の設定機能は他のメモリ上限設定とは趣きが異なるが、これはNECのHPCのグループの要望によるものとのこと。メモリとスワップの上限設定があるのは、これらを制限すれば、グローバルなリソースを都度触らなくても管理できるよね、という考え方。また、swap上限の設定は、fork bomb等でswapを使いきってしまうのを防ぐ側面もある。

最近のcgroupの実装では、最初にメモリページを読んだ人に課金されている。が、Dockerの流行など考えると、ページキャッシュを占有しているとよかったね……という話も(最初にページを触った人に課金されてしまう)。

kernelメモリのアカウンティング

kernelメモリのアカウンティングは、以下の2つのケースで発生する。カーネルメモリ課金はfree()された時に減算。とはいえ、特定のmemory cgroup狙い撃ちでカーネルメモリを開放するルーチンは今のところ無い。

  • SLAB/SLUBアロケータからページを割り当てた場合
  • alloc_kmem_pages()を呼ぶ場合

TCP bufferのアカウンティング

TCP bufferのアカウンティング。元々システム全体でtcp bufferを制限するための仕組みがあり、これを流用している。Socketのdata用のメモリ領域をアロケートする所で判定する。

memory cgroupの面倒な所

タスクとメモリのライフサイクルが異なる場合がある。莫大な性能オーバーヘッドがあると信じられている。タスクに課金ではなくページメモリ課金なのでレースコンディションが多い。課金に関する点で見ると、ページメモリに大して課金する。race conditionを回避するためロックを使っていると性能に影響する

性能オーバーヘッドの改善方法のひとつとして、各CPU毎に課金の前借り情報を付与している。前借りなので、メモリのusageのカウント誤差を許容(memory cgroupは性能のためにカウント誤差を許容)する。cgroupの利用、BMと比較すると場合によっては3,4%性能が落ちる。メモリ解放処理はput_page()のバッチ処理の中で複数のページ文をまとめて開放する。LRUはmemory cgroup毎に持つ。システム全体のLRUは「存在しない」

今後の強化ポイント(予想)

  • kernel memory cgroyupのメモり回収処理を追加
  • Blkio cgroupと連動してのbufferd I/Oの制御
  • Page付帯情報を16byteから8byteにする
  • soft limitの再実装
    • kswapd per memory cgroup
  • 不揮発メモリの扱い?
    • 今後の議論になってゆくと思う

質疑応答

  • 質問. ドキュメントでcgroup,cgroupsの表記が揺らいでいる、理由は?
  • 回答. OSSの悪い所。特に理由は無い。cgroupsと表記する人が多い感じ。

  • 質問. cgroupのカーネルメモリも課金対象との話だが、これはプロセスに紐づいているもの?

  • 回答. プロセスには紐づいていない。i-node等も課金される。プロセスがいなくなっても課金は継続される。基本的にはkmalloc()を(最初に)使っている人に課金される。例えば、ファイルを複数のグループで使っている場合、最初に使った人に課金される。

Using LXC on Production

mixiのモンストスタジオに所属されている方で、「OpenStackとLXCを導入した話」を元にした発表でした。

mixiにおける仮想化環境

当初はKVM(Kernel-based Virtual Machine)だった。用途は開発・ステージング環境。構築は自作のシェルスクリプトでbridge I/F、Cobblerとの連系でホスト名の連番化やIPの重複防止、virt-install,Kickstartをやっていた。しかし、基本手作業で面倒であったとのこと。

  • KVMのメリット
    • 軽度の利用では十分なパフォーマンスが出る
    • ゲストOSを(ベアメタルな)マシンと同じ扱いができ、管理コスト低減に寄与する
    • KVMのノウハウはWeb上にたくさんあり、かつクラウド系のツールが充実している
  • KVMのデメリット
    • 仮想化によるボトルネックが大きい(とくにディスクI/O)
    • Disk容量を多く消費する
    • Intel VTやAMD-V等がBIOSレベルで無効化されている場合がある
      • 物理マシンをデータセンターに設置している場合に困ることがある
      • 誰かがデータセンターまで出向く必要が出てくる

次にOpenStackを使ってみた。用途は社内プロダクト向けのPaaS("Gizumo"という名称でたぶん水をかけると増える)。アプリサーバは独自のデプロイツールを利用。ミドルウェアの構成はChefでMySQL, Redis, Jenkins等を展開。後に個人の開発環境にも展開していった。シェルスクリプトKVMを使っていた時期よりも楽になった。

現在の運用はLXCを利用しているとのこと。

LXC概要

KVMのようなハードウェアエミュレーション上で仮想マシンを動作させるのはなく、Kernelの機能を利用してプロセスやネットワーク、ユーザ空間を分離し、仮想的な環境を提供する。KVMのケースのようなCPU、ディスクI/O等のパフォーマンス劣化がほとんど発生しない。KVMと比べて起動が速い。

同時期にDockerの人気が出始めたようで、AUFSが気にななったり、Docker Registryが便利そうだったり、Goのポータビリティは素晴らしかったり。IPマスカレードはちょっと面倒くさい(DockerはIPマスカレード)。バージョンアップが激しい。コンテナにIPを個別に振って、仮想マシンのように扱いたい(macvlan使いたい)。taggedVLANの環境でも問題なく使いたい。ネットワーク周りの要件の兼ね合いでDockerは見送ったらしい。

独自ツールtrailerの開発

LXCにかぶせる形での独自ツール"trailer"を開発。Rubyで記述されている。現段階ではオープンソースではない。LXCのラッパーとして動作。mixi内での運用に必要な機能に絞って実装。IP,MACアドレスの採番。コンテナイメージのダウンロードと展開。起動中のコンテナからイメージを作成する。Trailerfileと呼ばれるコンテナ定義。 リポジトリサーバへのイメージアップロード。

LXC向けに用意してあるイメージ

これらを起動するとIPとMACが振られた状態で起動する。アプリケーションサーバはさらにアプリのデプロイが必要。

コンテナを作るときの注意点

スレッド、PID数の上限に注意。ファイルディスクリプタ数やTCP/IP周りのKernelパラメータも用途に応じて調整する。インスタンス側では設定できないKernelパラメータがあったりするので注意。

その他に気をつける点として、利用リソースの予測と見積りを行い、他のコンテナに悪影響を及ぼさないようにする。ディスク容量については(LXCでは)容量制限ができない。モニタリングデータのグラフ化は重要(=監視は重要)。trailerではコンテナ単体とCPUとメモリの利用量、ホスト側でのメモリ量を取得できるようにしている。

発表者によるtrailerのデモ。以下のような感じでtrailerコマンドを使用していました。

$ trailer image-list
$ sudo trailer image-destroy fedora19
$ sudo trailer start --image fedora19-x86_64 --hostname container-test --dhcp --briged-interface eth0
Copying rootfs from fedora19-x86_64 ---done
Starting container container-test

trailerコマンドからLXCでコンテナが起動します。アタッチしたコンテナからはCtrl-q Ctrl-aで抜けられるようです。

$ ps ax | grep container-test
...中略...
lxc-start --name container-test --daemon --rcfile /data/lxc/container-test/config --lxcpath /data/lxc -o /data/lxc/container-test/log/lxc.log -l debug -p /data/lxc/container-test/container-test.pid -- /.trailerinit

trailerを利用して一つの物理マシン上にコンテナとしてサービスを集約。考え方として、複数のサービスを一つのマシンに集約するのではなく、mod_perl,memcached等のサービス毎に集約したコンテナを利用する。

質疑応答

  • 質問. trailerからLXCへのアドレス付与はどう行っている?
  • 回答. init起動した後にifconfig/ipコマンドを発行している。内部からIPを設定している。基本的には固定IPを(起動時に)動的に付与し、DHCPは使っていない。root fsの中にIPを記述した設定ファイルを置いておき、それを用いてIPを設定している。(補足:LXCの設定項目にIPアドレスの項目がある、とのこと)

  • 質問. ディスクイメージ内のFSとして、ZFSとか使ってみたりしてますか?

  • 回答. ディスクは気をつけていれば特に問題なく、こういったサービスでは不要かも。

  • 質問. LXCシステムコンテナの元になるイメージ、trailerではどうやっていいますか?

  • 回答. LXCに付属しているtemplatesを元にしてイメージ作成していますが、だいぶ変更しています。(補足:LXC 1.0でのtemplatesでだいぶ改善された)

  • 質問. リソース監視は外から行うのですか?

  • 回答. 外から取得している。snmpdを動かしている。コンテナ側の情報はextendと独自のスクリプトで取得している。

  • 質問. アプリケーションコンテナの場合、ログインする(sshが必ず動いている?)

  • 回答. sshdは全てのコンテナで立ち上げています。普通のマシンとして利用できることを想定したコンテナにしています。

  • 質問. trailerの1ホスト上にコンテナはいくつくらい立ち上げている?

  • 回答. 多くて6個か8個、少なくて2個。プロセスをたくさん立ち上げるやつはコンテナ数を少なくしている。

  • 質問. LXC 0.9から1.0への以降は考えている?

  • 回答. 考えている。lxc start以外のコマンドではうまく動かないものもあるので移行したい。が、今のところ移行の予定は無いです。

  • 質問. プロダクション運用する際、コンテナ周りで困ったことは?

  • 回答. スライドにもあったがPIDの問題(これは気づきやすい類の問題)。メモリ使用量は取れてもロードバランスは取りにくい等があり、監視・リソースのモニタリングは難しい。意外と大問題にはぶち当たっていない気はする。

  • 質問. KVMからLXCに移行する際、セキュリティはどう考えた?

  • 回答. 子の親殺しといった、(LXCの機能に起因する)セキュリティは意識していない。ホスティングサービスとは異なり、社内で利用するのであれば、あまり神経質になることは無いかもしれない。

LT資料 (第4回 コンテナ型仮想化の情報交換会@東京)

vagrantやLXC,Docker、OpenVZのproxmoxに関するTips集とDocker上でCentOS7のsystemctlを動作させる内容の発表でした。

個人的にはDockerでUbuntuを起動してみたことがあるだけで、Docker+CentOS 7でハマり所があるのは知りませんでした。

vagrant 1.6.5(2014/9/6)でCentOS 7ゲストに対応された。/etc/yum.confのautoupdateを無効化しておくとよい。vagrantcloud(vagrant init chef/centos-7.0)を用いたvagrant shareは便利とのことです。

Docker Hubはdockerhubにアカウントを作成後、"docker login", "docker push'するだけでお手軽に使い始められます、とのことです。ただ、ちょっと帯域が細く、apt-getとかが遅いようです。"docker search tukiyo3"で@tukiyo3さんのイメージが検索できます。

OpenVZのproxmoxについてはバックアップの方法が解説されており、proxmoxを使うとそのままホストOS、ゲストOS間で通信ができるため、定期的にバックアップが取れるとのことです。バックアップは一時的にOSをスリープさせているようだが、たまに復帰できない時があるという話が……。

他にもDockerのデータ永続化の方法として、cronで定期的にdocker commitを実行する方法が紹介されていました。

CoreOSによるDockerコンテナのクラスタリング

CoreOSはChorome OSベースのLinuxディストリビューション。単体で開発機として利用可能だが、クラスタリング構成で最も威力を発揮する。Google,Twitter,Mozilla,Suse,Cisco,Rackspace等のメンバーが開発に参加している。基本的に64bit CPUであれば動作する。IaaSではAmazon EC2,Google Compute Engine,Rackspace Cloudで動作、他にもさくらVPS,mac miniで動作する。

CoreOSの特徴

  • 省メモリ
    • 起動時で114MB(Dockerの動作のみに注力)
  • 自動OSアップデート
  • Docker専用OS
  • クラスタリング機能が標準搭載
    • ノードの自動構成
    • コンテナのプロセス情報
    • key-vlaueストア
    • 分散デプロイ、フェイルオーバーの管理
    • すべてGo言語で開発されている
  • 自動フェイルオーバー
    • 事前に「このロールのノードはn台」と定義しておく

CoreOSの構成要素

  • locksmith
    • OSアップデートのためのノードとupdate_engintプロセスの監視
  • systemd
    • コンテナを一つのUnitとして動作させられる
    • 発表スライドの15枚目にsystemd unitの記述例があります
      • X-Fleetというセクションが特別
  • etcd
  • fleet
    • クラスタノードを管理。分散デプロイ、フェイルオーバー、sshログイン、ジャーナルログの確認、unitの管理(systemctlのリモート実行)
    • systemdのUnitに[X-Fleet]セクションを追加し、デプロイ先をコントロールできる。ノードやリージョンを指定可能で。同一ノードやユニットが起動していないノードへのデプロイが可能。ノードの情報はetcdに格納する。
    • etcdで管理している情報を元にフェイルオーバーを実施する
  • cloud-config
    • クラスタ全体の構成管理を行う。YAMLで記述し、OSアップデートポリシーやプロセス、コンテナ管理(systemd)、他ファイルの書き込みやCoreOSのログイン(ssh)設定、hostsファイルの設定を行う
    • (cloud-configの例→写真参照)
    • 発表スライドの25,26枚目にデモ用のcloud-configの完全な例が提示されています。

CoreOS上のコンテナでの分散デプロイ、フェイルオーバーのデモ

以下のコマンドを投入し、CoreOS上での分散デプロイとフェイルオーバのデモがありました。

CoreOS$ # CoreOSにログインして以下を実行
CoreOS$ fleectl list-machine
CoreOS$ fleectl list-unit
CoreOS$ fleectl submit busybox\@{1,2}.service
CoreOS$ fleectl start busybox@*.service

デモの補足として、クラスタを組む際にはフェイルオーバーを前提にすること。RDB/NoSQLのデータは外部ディスクに保存。ロードバランサーへの自動組込が必要との説明がありました。

最近のCoreOS(2014/09-)に関する情報

CoreOS Managed Linuxという有償サポートも開始された。CoreUpdateというノード管理GUIが提供される。Premium Managed Linuxという上位プランにはプライベートDocker Hub Registryも提供される。

デバッグを目的としてFedoraの環境が利用できる。実態はCoreOSと同じ名前空間で起動するコンテナ。コンテナはDockerではなくsystemd-nspawnを利用しており、ファイルシステムは/media/rootにマウントされる。CoreOS自体の管理等にも利用できる。

質疑応答

  • 質問. EFIな環境にインストールする方法が分からないです。ベアメタル環境にCoreOSをインストールされたとの話ですが、EFI環境へのインストールは試したことがありますか?
  • 回答. mac miniで試しており、これにはEFIが入っている。普通にgrubが入っていた気がする。

  • 質問. Linuxコンテナ内のログをFluentd等で管理したい場合、どうしたら良いですか?

  • 回答. Fluentdでの設定方法は未調査。Fluentdにこだわってログ管理しなくても良いかも。

コンテナ仮想化とはなんだったのか?

仮想化の概念を振り返りつつ、完全仮想化とコンテナ型仮想化(OSレベル仮想化)についての比較と共にFreeBSDのjail機能を説明するという内容での発表でした。

仮想化の概念の話として、PopekとGoldbergの仮想化要件の説明がありました。まず、仮想マシンモニタ(VMM)の要件として等価性(Equivalence)、資源の管理(Resource Control)、効率性(Efficiency)があり、仮想マシンモニタを構築に際し、CPU命令セットを以下の3つに分類します。

  • 特権命令
  • 特権センシティブ命令
  • 動作センシティブ命令

ただし、これらは完全仮想化に関するものであり、コンテナ型仮想化の観点からこの要件と分類を比較する形で、仮想化「っぽい」要件とは何か、という説明がありました。いずれも「コンテナ型仮想化から見た場合」の話です。

  • 等価性→コンテナに分けた環境は同じ振る舞いをするので、「コンテナ環境」が等価性を満たす要件といえる。
  • 効率性→(後述)
  • 資源管理→コンテナはホストOSが完全に掌握しているので、「ホストOS」が資源管理を満たす要件といえる。

効率性については、完全仮想化の場合「大部分の機械の命令をVMMの介在無く実行できると」(Wikipediaから引用)とありますが、コンテナ型仮想化の場合はユーザ空間のレベルでコンテナが作られるので、機械の命令云々の話はそもそも出てこない、というワケです。

それでも完全仮想化とコンテナ型仮想化のI/O性能の比較について言及があり、完全仮想化ではI/O性能低下の要因としてホストOSとゲストOS間でのコンテキストスイッチの増加、その改善方法としてvirtio等の準仮想化ドライバでコンテキストスイッチを減らす方法とPlan9由来のプロトコルを使用し、ホストOSのファイルシステムを直接読み書きするvirtfsの仕組みが紹介されていました。

完全仮想化におけるI/O性能低下のデータも提示されており、実機上のI/O性能を100%とした場合に、virtfsで99%、準仮想化ドライバで81%、IDEブロックデバイスエミュレーションで41%の性能になるという結果になっていました。

FreeBSD jail

FreeBSD jailについては、jail(2)システムコールから見た説明となっていました。コンテナ型仮想化ではホストOS(コンテナホスト)でリソースを制限をしており、FreeBSDでは4.2BSD以降に追加されたgetrlimit/setrlimitや/etc/login.confでリソース制限を行います(Linuxでは/etc/security/limits.confとのことです)。

jailの設定は/etc/jail.confで行います。jailはOS標準の仕組みで構成されている。jailシステムコール自体にはリソース制限は存在しないため、FreeBSD 9よりRACCTL(カーネル内の資源量把握), RCTL(資源の制限を行う)が利用できますが、GENERICカーネルでは提供されていない機能なのでカーネルの再構築が必要とのことです。

その他、FreeBSD jailの面白い機能として、jail環境でCentOSを動作させる方法が紹介されていました。

質疑応答

  • 質問. 完成版の資料はいつかどこかで見られる(笑)?
  • 回答. 予定は未定です(笑)

  • 質問. chrootの進化系ということだが、コンテナ上でベースのOS上で異なるカーネルが動く?

  • 回答. 別のカーネルを動かして別のコンテナを動かすことは技術的には可能、Dragonfly BSDでそうったことをやっているはず。ただ、それはコンテナ型仮想化とは異なる別の概念の仮想化になるかと思う。(参加者からの補足→)カーネルレベルでは無理だが、エミュレーションレベルだとOKかも。例えば、FreeBSD 9の上でFreeBSD 8バイナリを動かすことは可能。

  • 質問. jailってOS Xでも使える?

  • 回答. カーネルが別物なので利用できないと思う。

Oracle Solaris Zones -Oracle Solarisのコンテナ技術-

Oracle Solaris Zoneに関する発表でした。Oracle Solarisは「研究及び開発目的であれば無償利用可能」とのことです。

Solaris Zone

2003年代のOracle Solaris Zonesの開発目標としては以下のが挙げられていた(当時からリソースの制御も要件に含めていたようです)。「粒度」はリソースを分配可能にするという話で、「透過性」はコンテナに分離する際にアプリケーションの移植を必要としない(させない)というものです。

  • セキュリティ(Security)
  • 隔離(Isolation)
  • 仮想化(Virtualization)
  • 粒度(Granularity)
  • 透過性(Transparency)

隔離(Isolation)のアプローチとしてchroot,FreeBSDのjail等があり、基本的にはjailの考え方に基づいて実装されたようです。

Oracle Solaris Zonesは単一のシステム上に複数の隔離されたSolarisインスタンスを提供する機能で、イメージとしてはjailやLXC,Docker等と同じです。ただし、Zonesは以下の2種類に分類されます。

ネットワークについても以下の2種類に分類されます。

  • 共有IPゾーン(shared IP zone)
    • →デフォルトのネットワーク
  • 排他的IPゾーン(shared IP zone)
    • →非大域ゾーン専用の物理ネットワーク

これらのゾーン間でのアクセス可否は以下のようになっています。

アクセス元ゾーンアクセス先ゾーンアクセス可否
大域ゾーン非大域ゾーン可能
非大域ゾーン大域ゾーン可能
非大域ゾーン非大域ゾーン不可

Oracle Solaris Zonesでのリソースは「資源プール」として管理されており、以下のリソースがあります。

  • プロセッサセット(CPU)
  • スケジューリングクラス
    • FSS(Fair Share Scheduler)
      • ゾーンに設定するCPUシェア数に基づいて資源プールを共有する
    • TS(Time Sharing)
      • Oracle Solarisの標準スケジューラ。CPU資源をプロセス毎に平等に配分する

Solaris Kernel Zones

Oracle Solaris 11.2から提供されるゾーンとして、"Solaris Kernel Zones"があります。物理ホストには以下の高いスペックが要求されます。

  • CPU
  • メモリは最小8GB必要
  • ZFS
    • ZFS ARC(Adaptive Replacement Cache)の上限値を搭載物理メモリの半分程度におさえる
    • これは重要なポイントで、これを忘れるとある日突然Kernel Zonesが起動しなくなる現象に見舞われるとのこと

Kernel Zonesの内部についても解説があり、kzhostプロセスとzvmmカーネルモジュールの説明がありました。kzhostプロセスはゲストOSに仮想CPUを利用させるためのもので、Kernel Zones毎に生成され、I/Oスレッドや各種管理、Zonesに割り当てられるメモリ管理を行います。zvmm(zone virtual machine monitor)カーネルモジュールは擬似ドライバでゲストOSに対して仮想ハードウェアとして振る舞います。

Oracle Solaris Zoneの参考情報として、以下が紹介されていました。

質疑応答

  • 質問. Zoneをいくら作ってもお値段同じですか?
  • 回答. 残念ながら……「無料」です。(というワケで、リソースの許す限りZoneを作れます)

  • 質問. トワイライトゾーンについて聞きたいです。

  • 回答. ゾーン毎に作成される(ゾーンに紐づいている)。ネイティブゾーンにはトワイライトゾーンが存在しない。kzprocessが裏でちょっとしたゾーンを作っている。

ニフティクラウドへの取り組み

ニフティクラウドエンタープライズ向けで時間貸しクラウド、が、法人向けのクラウド。そこで、個人でも使えるサービスを始めた。「ニフティクラウド C4SA」。15日間は無料で利用できる。

コンテナを「キャンバス」という概念で示し、Webブラウザから操作する形。コントロールパネルにsshっぽい画面がある。リソースは内部からデータを取ってお客さんに見せている。cgroupsの機能はバリバリ利用している。

管理、権限、課金ノードについてはセキュリティの観点から互いを信用せず、相互に監視しつづけるアーキテクチャになっている(例えば課金ノードの情報が不正に書き換えられても他のノードはそれを検知できる、ということ?)。

LXC最新情報

LXCの最新状況に関する発表でした。現在LXC 1.0.5がリリース(2014/07/14)がされており、LXC 1.0の新機能として、公式APIバインディング(Python2.7とPython3、lua,Go,ruby)、stableなliblxc1によるAPIの提供、非特権コンテナのサポートがあり、セキュリティに関しては、SELinuxとAppArmorをサポートし、seccompによるコンテナ毎のケーパビリティ指定が可能とのことです。

cgmanager(1)というコマンドが追加されており、DBusメッセージを送ることでcgroupを管理できるようです。

そしてこの度LXC日本語サイト作りました、とのこと。URLは以下です。

まとめ

第4回 コンテナ型仮想化の情報交換会@東京に参加し、勉強会メモをまとめてみました。6時間近く開催された内容をまとめるのは大変でしたが、放っておくと頭から内容が揮発して行くので忘れない内に文章にしておくのが良さそうです。

MagicPointのスライドをPDFに変換する際にハマった話

MagicPointのスライドをPDFに変換する際にハマった話

私のスライド作成環境がMagicPointという一風変わった環境のせいもあるのですが、勉強会での発表スライドをWeb上で公開する際、少し古めかしい方法(スライド画像を単に列挙するだけ)になっていました。

MagicPointのスライドはPDFに変換でき、PDFであればSlideShareにアップロードできるようなので、作業手順をまとめてみました。

MagicPointとは?

MagicPointはテキストベースのプレゼンテーションツールです。

例えば以下のような感じでスライドを作成します(これで一枚のスライドになります)。テキストエディタさえあればスライドが作成できる点はなかなか魅力的です。

%page

スライドタイトル

    BSDの種類
        FreeBSD
        NetBSD
        OpenBSD

MagicPointによるスライドのサンプル

スライドのサンプルを以下に示します。スライド中でも説明していますが、epsファイルの挿入が可能です。

PDFファイルへの変換

MagicPointのコマンド(mgpコマンド)にはHTMLにエクスポートするオプションがあり、今まではこれを利用していました。mgp2psというコマンドの存在を知り、PS→PDFに変換できるのではと思い、手順を調べてみました。

その際、かなり色々とハマってしまったので、自分への備忘録も兼ねて手順をまとめてみます。

mgpコマンドで画面上にスライドを表示する場合は問題ないのですが、背景色と前景色を指定し、かつepsファイルを取り込んでいるスライドをPS,PDFに変換する場合、epsファイル側に一工夫入れないとダメでした。

具体的な問題としては、挿入したepsの背景色でスライドの文字が上書き(塗りつぶされる)という現象が発生します。白背景に黒色の前景色だと問題ないかもしれませんが、白と黒色だけのスライドは味気ないので何とか対応方法を調べてみました。

今回は以下のeps出力ケースの場合での対応方法となります。また、スライドの背景色、前景色はそれぞれDarkBlue,snowという前提です(前述のスライドサンプル参照)。

  • tgifが出力したeps
  • Inkscapeが出力したeps
  • TeXから生成したeps

tgifが出力したeps

tgifで背景色等を変更せずに図を描いた場合、背景色は白、前景色は黒になります。このままではスライドの色と異なるため、見辛いものとなります(青色の背景に黒色の組み合わせは見辛いです)。

そこでtgifが出力したeps内の背景・前景色を以下のコマンドで修正します。生成されたepsではPostScriptのsetrgbcolorをRGというコマンドで定義しなおしている(C言語のdefineのような使い方)ので、この部分について色を置き換えます。

$ tgif -print -stdout -eps foo.obj > foo.tmp.eps
$ # 青背景(0 0 1 RG)に白描画(1 1 1 RG)に修正している
$ cat foo.tmp.eps \
    | sed -e "s/            0 SG$/            1 1 1 RG/g" \
          -e "s/L CP$/L CP 1 1 1 RG/g" \
          -e "s/ L$/ L 1 1 1 RG/g" \
          -e "s/   0 SG$/   1 1 1 RG/g" \
          -e "s/L CP 1 SG F/L CP 0 0 1 RG F/g" \
    > foo.eps

RGコマンド(setrgbcolor)は"R G B setrgbcolor"となっており、色成分は0.0から1.0までの間で指定するようです。

Inkscapeが出力したeps

Inkscapeについては、epsで保存し直す前のSVGファイルに対して修正を加えます。テキストとストロークの色情報はfill,stroke要素で定義されています。"fill:#RRGGBB"のような定義になっているので、RGBの部分を置き換えます。

$ cat foo.svg \
    | sed -e "s/fill:\#000000/fill:\#FFFAFA/g" \
          -e "s/stroke:\#000000/stroke:\#FFFAFA/g" \
    > foo2.svg

foo2.svgInkscapeで開き、epsで保存し直せば完了です。MagicPointにInkscapeで作成したepsを取り込む場合、拡大・縮小で線が潰れてしまうことがあるので、線の太さを3px等にしておくと良いです。

TeXから生成したeps

TeXから生成したepsの場合は、対応方法が少しややこしいです。手始めにcolorマクロを使用し、スライドの背景色と前景色に合わせたepsを出力します。実際はMagicPointの色はshowrgbコマンドで出力される色名になっており、TeXのcolorマクロで指定可能な色名と異なります。できるだけ近い色を指定してください。今回はDarkBlue(スライド)に対しblue(TeX)としています。

\documentclass{article}
\pagestyle{empty}

\usepackage{color}

\begin{document}
%% \pagecolor{blue}
\color{white}

...TeXの本文...
\end{document}

以下の手順でTeXからepsを生成します。TeXのcolorマクロを使用していると、生成されたepsには背景色を描画(塗りつぶす)する命令が含まれるようです。"f{P fill}..."の部分が当該箇所なので、この部分の描画処理を無効化します。

$ platex foo.tex
$ dvips foo.dvi
$ ps2eps -f foo.ps
$ eps2eps foo.eps foo.tmp.eps
$ # 背景色の描画を無効化する
$ cat foo.tmp.eps \
    | sed -e "s/^\/f{P fill}.*$/\/f{}!/" \
    > output.eps

補足

TeXのlistings環境でソースコードを見やすく整形できます。これをepsに変換してMagicPointのスライドで用いると見栄えの良いスライドに近づきます。

listings環境を提供するlistings.styは日本語に対応していないようで、有志の方が日本語対応したjlisting.styを提供しています。

参考までに、FreeBSD-10の環境でのjlisting.styのインストール手順を示します。

$ gzip -d jlisting.sty.bz2
$ sudo mkdir /usr/local/share/texmf-dist/tex/latex/jlisting
$ sudo mv jlisting.sty /usr/local/share/texmf-dist/tex/latex/jlisting/
$ sudo chown root:wheel /usr/local/share/texmf-dist/tex/latex/jlisting/jlisting.sty
$ sudo chmod go+rx /usr/local/share/texmf-dist/tex/latex/jlisting/
$ sudo chmod go+r /usr/local/share/texmf-dist/tex/latex/jlisting/jlisting.sty
$ sudo mktexlsr

まとめ

MagicPointのスライドをPDFに変換する手順をまとめてみました。背景色を指定しepsを含むスライドをPS,PDFに変換する場合はいろいろとハマり所があり苦労しました。しかし一通りの手順は把握できたので、これでMagicPointを活用する場面が増えそうです。

NetBSDの/bin/shでコマンドがexecve(2)されるまでの流れを追いかけてみた

NetBSD/bin/shでコマンドがexecve(2)されるまでの流れを追いかけてみた

技評×オングスこんなシェルスクリプトは書いちゃダメだ!にてシェル上でコマンドを実行する際のユーザランドからカーネルまでの流れが説明されていました。

ちょっと興味が出てきたので、NetBSD/bin/shでコマンドを実行した場合のコマンド入力からexecve(2)までの流れをソースコードレベルで追いかけてみました。

NetBSDソースコードにおける/bin/shの場所

NetBSDソースコードは、ユーザランド/カーネル共に/usr/srcの下に置かれます。ユーザランドのコマンドは/usr/src/<コマンドのパス>の位置に用意されており、/bin/shの場合は/usr/src/bin/shソースコードのあるディレクトリになります。

$ which sh
/bin/sh
$ cd /usr/src/bin/sh
$ ls
CVS/            error.h         jobs.h          mktokens        redir.h
Makefile        eval.c          machdep.h       myhistedit.h    sh.1
TOUR            eval.h          mail.c          mystring.c      shell.h
USD.doc/        exec.c          mail.h          mystring.h      show.c
alias.c         exec.h          main.c          nodes.c.pat     show.h
alias.h         expand.c        main.h          nodetypes       syntax.c
arith.y         expand.h        memalloc.c      options.c       syntax.h
arith_lex.l     funcs/          memalloc.h      options.h       trap.c
bltin/          histedit.c      miscbltin.c     output.c        trap.h
builtins.def    init.h          miscbltin.h     output.h        var.c
cd.c            input.c         mkbuiltins      parser.c        var.h
cd.h            input.h         mkinit.sh       parser.h
error.c         jobs.c          mknodes.sh      redir.c

main()からexecve(2)までの流れ

/bin/shを実行し、ユーザがコマンドを入力・実行する場合は、以下の関数を通過しています(細々とした関数は除き、主要な関数のみ抽出しています)。

  • /usr/src/bin/sh/main.c:main()
    • /usr/src/bin/sh/main.c:cmdloop()
      • /usr/src/bin/sh/eval.c:evaltree()
        • /usr/src/bin/sh/eval.c:evalcommand()
          • /usr/src/bin/sh/jobs.c:forkchild()
          • /usr/src/bin/sh/exec.c:shellexec()
            • /usr/src/bin/sh/exec.c:tryexec()
              • execve(2)

main()から順に追いかけてみます。

main()

何やら不穏なコメントがありますが、単純に/bin/shを起動してキーボードからコマンドを入力する場合は、cmdloop()に入って行くようです。

/usr/src/bin/sh/main.c:
 95 /*
 96  * Main routine.  We initialize things, parse the arguments, execute
 97  * profiles if we're a login shell, and then call cmdloop to execute
 98  * commands.  The setjmp call sets up the location to jump to when an
 99  * exception occurs.  When an exception occurs the variable "state"
100  * is used to figure out how far we had gotten.
101  */
102
103 int
104 main(int argc, char **argv)
105 {
...中略...
218         if (sflag || minusc == NULL) {
219 state4: /* XXX ??? - why isn't this before the "if" statement */
220                 cmdloop(1);
221         }

cmdloop()では、255行目のparsecmd()の戻り値からEOFの入力の有無を判定し、EOFでなければevaltree()を呼んでいます。

/usr/src/bin/sh/main.c:
230 /*
231  * Read and execute commands.  "Top" is nonzero for the top level command
232  * loop; it turns on prompting if the shell is interactive.
233  */
234
235 void
236 cmdloop(int top)
237 {
...中略...
245         for (;;) {
...中略...
255                 n = parsecmd(inter);
256                 /* showtree(n); DEBUG */
257                 if (n == NEOF) {
258                         if (!top || numeof >= 50)
259                                 break;
260                         if (!stoppedjobs()) {
261                                 if (!Iflag)
262                                         break;
263                                 out2str("\nUse \"exit\" to leave shell.\n");
264                         }
265                         numeof++;
266                 } else if (n != NULL && nflag == 0) {
267                         job_warning = (job_warning == 2) ? 1 : 0;
268                         numeof = 0;
269                         evaltree(n, 0);
270                 }

単純なコマンド実行の場合、evaltree()内の301行のcase文が実行されます。

/usr/src/bin/sh/eval.c:
 214 /*
 215  * Evaluate a parse tree.  The value is left in the global variable
 216  * exitstatus.
 217  */
 218
 219 void
 220 evaltree(union node *n, int flags)
 221 {
...中略...
 235         switch (n->type) {
...中略...
 293         case NNOT:
...中略...
 297         case NPIPE:
...中略...
 301         case NCMD:
 302                 evalcommand(n, flags, NULL);
 303                 do_etest = !(flags & EV_TESTED);
 304                 break;

マクロ定数NCMDはnodes.hで定義されています。nodes.hはmake時に生成され、元になる定義はファイルnodetypesにあり、NCMDについては"a simple command"というコメントがあります。

nodetypesにはNCMDの他に論理演算子やパイプに関する定義があり、evaltree()内のcase文でNNOTやNPIPEをチェックしています。

$ grep NCMD *.h
nodes.h:#define NCMD 1
$ grep -A1 'create.*nodes\.h' my_make.log
#    create  sh/nodes.h
AWK=awk  SED=sed /bin/sh mknodes.sh nodetypes nodes.c.pat /home/fpig/work/sh
$ grep ^N nodetypes
NSEMI nbinary                   # two commands separated by a semicolon
NCMD ncmd                       # a simple command
NPIPE npipe                     # a pipeline
NREDIR nredir                   # redirection (of a complex command)
NBACKGND nredir                 # run command in background
NSUBSHELL nredir                # run command in a subshell
NAND nbinary                    # the && operator
NOR nbinary                     # the || operator
NIF nif                         # the if statement.  Elif clauses are handled
NWHILE nbinary                  # the while statement.  First child is the test
NUNTIL nbinary                  # the until statement
NFOR nfor                       # the for statement
NCASE ncase                     # a case statement
NCLIST nclist                   # a case
NDEFUN narg                     # define a function.  The "next" field contains
NARG narg                       # represents a word
NTO nfile                       # fd> fname
NCLOBBER nfile                  # fd>| fname
NFROM nfile                     # fd< fname
NFROMTO nfile                   # fd<> fname
NAPPEND nfile                   # fd>> fname
NTOFD ndup                      # fd<&dupfd
NFROMFD ndup                    # fd>&dupfd
NHERE nhere                     # fd<<\!
NXHERE nhere                    # fd<<!
NNOT nnot                       # ! command  (actually pipeline)

NCMDの場合はevalcommand()が呼ばれます。この関数の中でプロセスのフォークが行われます(vfork()が呼ばれています)。実行されるコマンドを追いかけたいので、858行目からの子プロセス側の処理を追って行きます。

/usr/src/bin/sh/eval.c:
 672 /*
 673  * Execute a simple command.
 674  */
 675
 676 STATIC void
 677 evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
 678 {
...中略...
 845                 if (cmdentry.cmdtype == CMDNORMAL) {
 846                         pid_t   pid;
 847
 848                         savelocalvars = localvars;
 849                         localvars = NULL;
 850                         vforked = 1;
 851                         switch (pid = vfork()) {
 852                         case -1:
 853                                 TRACE(("Vfork failed, errno=%d\n", errno));
 854                                 INTON;
 855                                 error("Cannot vfork");
 856                                 break;
 857                         case 0:
 858                                 /* Make sure that exceptions only unwind to
 859                                  * after the vfork(2)
 860                                  */
 861                                 if (setjmp(jmploc.loc)) {
 862                                         if (exception == EXSHELLPROC) {
 863                                                 /* We can't progress with the vfork,
 864                                                  * so, set vforked = 2 so the parent
 865                                                  * knows, and _exit();
 866                                                  */
 867                                                 vforked = 2;
 868                                                 _exit(0);
 869                                         } else {
 870                                                 _exit(exerrno);
 871                                         }
 872                                 }
 873                                 savehandler = handler;
 874                                 handler = &jmploc;
 875                                 listmklocal(varlist.list, VEXPORT | VNOFUNC);
 876                                 forkchild(jp, cmd, mode, vforked);
 877                                 break;
 878                         default:
 879                                 handler = savehandler;  /* restore from vfork(2) */

一見すると、子プロセス側の処理はforkchild()側で完結する(別プロセスがexecされる)ように見えますが、forkchild()は生成したプロセスに対するシグナルハンドリング処理などの便利関数となっていました。なので、今回はforkchild()の中身についてはスキップします。

/usr/src/bin/sh/jobs.c:
 891 void
 892 forkchild(struct job *jp, union node *n, int mode, int vforked)
 893 {
...中略...
 953 }

forkchild()から戻ってきた子プロセス側の処理は、vfork()の戻り値による親・子プロセスの判別を抜け、evalcommand()の916行目以降の処理に入って行きます。

ここからの処理では、シェル関数、組み込みコマンド、通常コマンドのいずれかにより処理が分かれます。今回は通常コマンドなので、1051行目以降の処理となります。

/usr/src/bin/sh/eval.c:
 676 STATIC void
 677 evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
 678 {
...中略...
 876                                 forkchild(jp, cmd, mode, vforked);
 877                                 break;
...中略...
 916         /* This is the child process if a fork occurred. */
 917         /* Execute the command. */
 918         switch (cmdentry.cmdtype) {
 919         case CMDFUNCTION:
 920 #ifdef DEBUG
 921                 trputs("Shell function:  ");  trargs(argv);
 922 #endif
...中略...
 973 #ifdef DEBUG
 974                 trputs("builtin command:  ");  trargs(argv);
 975 #endif
...中略...
1051         default:
1052 #ifdef DEBUG
1053                 trputs("normal command:  ");  trargs(argv);
1054 #endif
1055                 clearredir(vforked);
1056                 redirect(cmd->ncmd.redirect, vforked ? REDIR_VFORK : 0);
1057                 if (!vforked)
1058                         for (sp = varlist.list ; sp ; sp = sp->next)
1059                                 setvareq(sp->text, VEXPORT|VSTACK);
1060                 envp = environment();
1061                 shellexec(argv, envp, path, cmdentry.u.index, vforked);
1062                 break;
1063         }
1064         goto out;
1065
1066 parent: /* parent process gets here (if we forked) */

通常コマンドの場合はshellexec()が呼ばれ、ここからtryexec()が呼ばれます。

/usr/src/bin/sh/exec.c:
 116 /*
 117  * Exec a program.  Never returns.  If you change this routine, you may
 118  * have to change the find_command routine as well.
 119  */
 120
 121 void
 122 shellexec(char **argv, char **envp, const char *path, int idx, int vforked)
 123 {
 124         char *cmdname;
 125         int e;
 126
 127         if (strchr(argv[0], '/') != NULL) {
 128                 tryexec(argv[0], argv, envp, vforked);
 129                 e = errno;
 130         } else {
 131                 e = ENOENT;
 132                 while ((cmdname = padvance(&path, argv[0])) != NULL) {
 133                         if (--idx < 0 && pathopt == NULL) {
 134                                 tryexec(cmdname, argv, envp, vforked);
 135                                 if (errno != ENOENT && errno != ENOTDIR)
 136                                         e = errno;
 137                         }
 138                         stunalloc(cmdname);
 139                 }
 140         }
 141
 142         /* Map to POSIX errors */
 143         switch (e) {
 144         case EACCES:
 145                 exerrno = 126;
 146                 break;
 147         case ENOENT:
 148                 exerrno = 127;
 149                 break;
 150         default:
 151                 exerrno = 2;
 152                 break;
 153         }
 154         TRACE(("shellexec failed for %s, errno %d, vforked %d, suppressint      %d\n",
 155                 argv[0], e, vforked, suppressint ));
 156         exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC));
 157         /* NOTREACHED */
 158 }

tryexec()内で晴れて(?)execve(2)が呼ばれる運びとなります。

/usr/src/bin/sh/exec.c:
 161 STATIC void
 162 tryexec(char *cmd, char **argv, char **envp, int vforked)
 163 {
...中略...
 169 #ifdef SYSV
 170         do {
 171                 execve(cmd, argv, envp);
 172         } while (errno == EINTR);
 173 #else
 174         execve(cmd, argv, envp);
 175 #endif

まとめ

NetBSD/bin/shでコマンドが実行されるまでの流れをソースコードレベルで追いかけてみました。今回は単純なコマンド実行だけでしたが、パイプやリダイレクトと組み合わせたコマンド実行の場合についても調べてみたいと思います。