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事情
- 発表者は@hiro_kamezawaさん
- 発表資料は以下のURLにて公開されています
私が遅れて到着してしまったため、最初の部分は聞き逃してしまいました……。
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の話
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
- 発表者は@isaoshimizuさん
mixiのモンストスタジオに所属されている方で、「OpenStackとLXCを導入した話」を元にした発表でした。
mixiにおける仮想化環境
当初はKVM(Kernel-based Virtual Machine)だった。用途は開発・ステージング環境。構築は自作のシェルスクリプトでbridge I/F、Cobblerとの連系でホスト名の連番化やIPの重複防止、virt-install,Kickstartをやっていた。しかし、基本手作業で面倒であったとのこと。
次に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向けに用意してあるイメージ
- ベースイメージ
- Reverse Proxy(mod_proxy)
- Varnish
- Q4M(Job Queue)
- Application Server(mod_perl)
- Tokyo Tyrant
- memcached
これらを起動すると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回 コンテナ型仮想化の情報交換会@東京)
- 発表者は@tukiyo3さん
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は便利とのことです。
- boot2dockerで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コンテナのクラスタリング
- 発表者は@yujiodさん
- PukiWikiの開発者さん(!)とのことです
- 発表スライドは以下で公開されています
- CoreOSによるDockerコンテナのクラスタリング
- http://www.slideshare.net/yujiod/coreosdocker
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
- ファイルシステムはread only
- アプリケーションはDockerで起動する必要がある
- クラスタリング機能が標準搭載
- ノードの自動構成
- コンテナのプロセス情報
- key-vlaueストア
- 分散デプロイ、フェイルオーバーの管理
- すべてGo言語で開発されている
- 自動フェイルオーバー
- 事前に「このロールのノードはn台」と定義しておく
CoreOSの構成要素
- locksmith
- OSアップデートのためのノードとupdate_engintプロセスの監視
- systemd
- コンテナを一つのUnitとして動作させられる
- 発表スライドの15枚目にsystemd unitの記述例があります
- X-Fleetというセクションが特別
- etcd
- fleet
- 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のデータは外部ディスクに保存。ロードバランサーへの自動組込が必要との説明がありました。
- Dynamic Docker links with an ambassador powered by etcd
最近の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環境へのインストールは試したことがありますか?
質問. Linuxコンテナ内のログをFluentd等で管理したい場合、どうしたら良いですか?
- 回答. Fluentdでの設定方法は未調査。Fluentdにこだわってログ管理しなくても良いかも。
コンテナ仮想化とはなんだったのか?
- 発表者は@m_birdさん
- 発表資料は以下のURLで公開されています
仮想化の概念を振り返りつつ、完全仮想化とコンテナ型仮想化(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を動作させる方法が紹介されていました。
- Running CentOS 5.5 in a Jail
質疑応答
- 質問. 完成版の資料はいつかどこかで見られる(笑)?
回答. 予定は未定です(笑)
回答. 別のカーネルを動かして別のコンテナを動かすことは技術的には可能、Dragonfly BSDでそうったことをやっているはず。ただ、それはコンテナ型仮想化とは異なる別の概念の仮想化になるかと思う。(参加者からの補足→)カーネルレベルでは無理だが、エミュレーションレベルだとOKかも。例えば、FreeBSD 9の上でFreeBSD 8バイナリを動かすことは可能。
質問. jailってOS Xでも使える?
- 回答. カーネルが別物なので利用できないと思う。
Oracle Solaris Zones -Oracle Solarisのコンテナ技術-
- 発表者は@satokazさん
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種類に分類されます。
- 大域ゾーン(global zone)
- → オペレーティングシステムの実体(ホストOS)
- 非大域ゾーン(non-global zone)
ネットワークについても以下の2種類に分類されます。
- 共有IPゾーン(shared IP zone)
- →デフォルトのネットワーク
- 排他的IPゾーン(shared IP zone)
- →非大域ゾーン専用の物理ネットワーク
これらのゾーン間でのアクセス可否は以下のようになっています。
アクセス元ゾーン | アクセス先ゾーン | アクセス可否 |
---|---|---|
大域ゾーン | 非大域ゾーン | 可能 |
非大域ゾーン | 大域ゾーン | 可能 |
非大域ゾーン | 非大域ゾーン | 不可 |
Oracle Solaris Zonesでのリソースは「資源プール」として管理されており、以下のリソースがあります。
- プロセッサセット(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の参考情報として、以下が紹介されていました。
- Solaris Zones: Operating System Support for Consolidating Commercial Workloads
- Oracle Solaris 11.2 Information Library
- Introduction to Oracle Solaris Zones
- http://docs.oracle.com/cd/E36784_01/html/E36848/index.html
- 日本語版はもう少しで公開とのことです
質疑応答
- 質問. Zoneをいくら作ってもお値段同じですか?
回答. 残念ながら……「無料」です。(というワケで、リソースの許す限りZoneを作れます)
質問. トワイライトゾーンについて聞きたいです。
- 回答. ゾーン毎に作成される(ゾーンに紐づいている)。ネイティブゾーンにはトワイライトゾーンが存在しない。kzprocessが裏でちょっとしたゾーンを作っている。
ニフティのクラウドへの取り組み
- 発表者は@ysaotomeさん
- 発表資料は以下のURLにて公開されています
ニフティクラウド→エンタープライズ向けで時間貸しのクラウド、が、法人向けのクラウド。そこで、個人でも使えるサービスを始めた。「ニフティクラウド C4SA」。15日間は無料で利用できる。
コンテナを「キャンバス」という概念で示し、Webブラウザから操作する形。コントロールパネルにsshっぽい画面がある。リソースは内部からデータを取ってお客さんに見せている。cgroupsの機能はバリバリ利用している。
管理、権限、課金ノードについてはセキュリティの観点から互いを信用せず、相互に監視しつづけるアーキテクチャになっている(例えば課金ノードの情報が不正に書き換えられても他のノードはそれを検知できる、ということ?)。
LXC最新情報
- 発表者は@ten_forwardさん
- 発表資料は以下のURLで公開されています。
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は以下です。
- LXC Linux Containers
まとめ
第4回 コンテナ型仮想化の情報交換会@東京に参加し、勉強会メモをまとめてみました。6時間近く開催された内容をまとめるのは大変でしたが、放っておくと頭から内容が揮発して行くので忘れない内に文章にしておくのが良さそうです。
MagicPointのスライドをPDFに変換する際にハマった話
MagicPointのスライドをPDFに変換する際にハマった話
私のスライド作成環境がMagicPointという一風変わった環境のせいもあるのですが、勉強会での発表スライドをWeb上で公開する際、少し古めかしい方法(スライド画像を単に列挙するだけ)になっていました。
MagicPointのスライドはPDFに変換でき、PDFであればSlideShareにアップロードできるようなので、作業手順をまとめてみました。
MagicPointとは?
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
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.svgをInkscapeで開き、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を提供しています。
- listings(MyTeXpert)
参考までに、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()
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でコマンドが実行されるまでの流れをソースコードレベルで追いかけてみました。今回は単純なコマンド実行だけでしたが、パイプやリダイレクトと組み合わせたコマンド実行の場合についても調べてみたいと思います。
「技評×オングスこんなシェルスクリプトは書いちゃダメだ!」に参加してきました
「技評×オングスこんなシェルスクリプトは書いちゃダメだ!」に参加してきました
技評×オングスこんなシェルスクリプトは書いちゃダメだ!に参加してきました。FreeBSD勉強会を開催している@daichigotoさんが主催されています。FreeBSD勉強会は若干お堅い内容なので、もう少しカジュアルな内容の勉強会にしたいとのことです。
シェルスクリプトの3つの側面
シェルスクリプトには以下の3つの側面があるとのことです。
- システムを組み上げるためのソフトウェア
- ユーザが操作するインタフェース
- 業務システムを組み上げるためのソフトウェア
シェルスクリプトはエンジニアの腕前の差がはっきりと出るものであり、学ぶことによって効率のよいシェルスクリプトが書けるようになるということ、また、シェルとカーネルをよく知るエンジニアが書くスクリプトは効率がよい処理になるという説明がありました。
ポイントとしては"at a glance"(一瞥してわかる)な書き方が重要とのことです。
(私の英語力はまったくダメダメなので思わず辞書を引いてしまいました……)
ダメなスクリプトの例
ダメなスクリプトの例として以下が挙げられていました。
手続き型的な発想のシェルスクリプト
手続き型的なプログラミングでは、データに対する順次処理を書いてしまいがちですが、「まず最初に処理するデータを減らす」ことが重要とのことで、悪い書き方と良い書き方として、以下のような例が示されていました。
- (悪い書き方)データレコードの各フィールドを都度if文でチェックしてから集計する方法
- (良い書き方)awkでマッチする行を抜き出してから集計する方法
前者と後者では1000倍くらい速度が違うという結果を示しながらの説明となっていました。料理でも下ごしらえが重要だったりするので、データ処理においても下ごしらえに相当する処理をどうするかは重要なのだなと思いました。
変数を多用するシェルスクリプト
シェルスクリプトの変数展開は遅い処理であるという説明がありました。例として文字列の連結をシェル変数上で行った場合とファイルに追加出力した場合の結果が示されていました。
私の環境でもちょっと試してみました。26Kbyteほどのテキストファイルに対して先の手順を実施してみます。
$ w3m -dump http://www.aozora.gr.jp/cards/000081/files/4601_11978.html > furandon.txt $ ls -hl furandon.txt -rw-r--r-- 1 fpig users 26K Sep 2 04:49 furandon.txt
結果は以下となりました。データサイズが小さいとはいえ、すでに10倍程度の速度差が出ています。数メガ、数ギガ単位のデータであれば、見過ごせないほどの速度差になってくるはずです。
$ # シェル変数上で文字列連結した場合 $ time (cat furandon.txt | while read i; do a=$a$i; done) real 0m1.348s user 0m1.047s sys 0m0.031s $ # ファイルに追加出力した場合 $ time (cat furandon.txt | while read i; do echo $i >> out.txt; done) real 0m0.124s user 0m0.040s sys 0m0.053s
関数(ただし、これは場合によりけりのようです)
これはシェル変数のスコープに関連してくる話で、シェルスクリプト関数の実態はグルーピングした処理であるため、文脈によってグローバルな変数を書き換えてしまう、という話でした。ただ、これは必ずしも悪手というわけではなく、シェルスクリプトの振る舞いをよく理解せずにコードを書くとハマる可能性があるよ、という意味での説明のようでした。
なぜこんな仕組みになっているのか?
シェルスクリプトのダメな例/良い例の話があった後、なぜこんな仕組みになっているのかの説明がありました。
シェルスクリプトの深い部分を理解しようと考えた場合、それはOSレイヤに踏み込んで行く形になります。取っ掛かりとして、シェルと結びつきが強い以下のシステムコールがどういう振る舞いをするか把握するとよいとのことでした。
- fork(2)
- execve(2)
- pipe(2)
- wait3(2)
ちょっと興味があったので、別エントリでNetBSDでの場合を調べてみました。
シェルによるシステム開発方式
一通り「こんなシェルスクリプトは書いちゃダメだ!」という説明があった後、株式会社オングスさんで実践しているttt開発方法を例にしたシェルによるシステム開発方式の説明がありました。
どのシステム開発にも言えることだと思いますが、シェルによるシステム開発でもうまく行くパターンと失敗するパターンがあるようです。
うまく行くパターンとしては、開発するシステムに対する業務フロー図やER図がちゃんと記述できること(記述できないということは、どんなシステムにするかを明確化できていない)、データの配置が事前に考えられているころ、UIにはHTML5/CSS3アダプティブデザインなど、最新の技術が活用されていることや、バックエンド側は長くても数百行程度のシェルスクリプトになっていること等が挙げられていました。
上記のパターンと逆の状態になっているケースが、いわゆる失敗するパターンとして説明されていました。
スキルアップのための書籍やサイト
これからシェルスクリプトやシェルによる開発を始めてみようという人向けに、以下の情報源が提示されていました。
- USP MAGAZINE
- UEC unicage engineers' community site
次回の勉強会について
今回の勉強会は満員ということもあり、次回の開催も決定しています!とのことです。
また、FreeBSD勉強会が今月中頃に開催されます。
- 第32回 FreeBSD勉強会 /etc/の下の設定ファイルを知ろう!
参加者からの質問
参加者から出た質問については、シェルスクリプトのデバッグ手法に関する内容が多かった気がします。また、BSD系OSをこれから使ってみようと考えている方もおられたようで、BSD人気を増やす向きの勉強会はありがたいものです。
シェルのデバッグ手法としては、シェバンに"/bin/sh -exv"を指定する方法、パイプの途中をteeで眺める方法などが紹介されていました。
他にもシェル変数の意味(「$$」など)の意味をどう調べたらよいか(man shに説明があるようですが、もう少しカジュアル(?)な変数の意味リストが欲しいかと……)、といった質問や、jsonやpdf等のデータをシェルスクリプトで扱う方法(オングスさんではズバリjsonというコマンドを用意しているそうです)といった質問が出ていました。
まとめ
技評×オングスこんなシェルスクリプトは書いちゃダメだ!に参加してきました。シェルスクリプトは日々の小さな作業をパッとこなすために使っており、大きなシステムを作ったことはなかったのですが、この勉強会の内容を元に比較的大きなシステムを手早く作れるようになれれば良いなと思います。
第20回「ネットワーク パケットを読む会(仮)」に参加してきました
第20回「ネットワーク パケットを読む会(仮)」に参加してきました
第20回「ネットワーク パケットを読む会(仮)」に参加し、tcpdumpの出力からネットワークパケットを読むための取っ掛かりとなる手順を発表してきました。
発表してきた内容
発表資料は以下のURLにて公開しています。ネットワークパケットをtcpdumpで出力し、その16進ダンプ内容をetherヘッダからICMP echo/replyまで読み解いてみようという内容です。
- tcpdumpの16進ダンプからはじめるネットワークパケット解読入門
発表スライドはMagicPointで作成しています。発表スライドのソースファイル(.mgpファイル)についてもgist上で公開しています。
- pakeana_20th.mgp
勉強会メモ
以下は勉強会のメモです。自分なりにまとめた内容なので、間違って理解している箇所があるかもしれませんのでご注意ください。
hpingで作るパケット
- 発表者は@takahoyoさん。
- 発表資料は以下のURLにて公開されています。
「Hentaiなping」であるhpingに関する発表で、hpingから設定可能なIP,ICMP,TCP,UDPのオプションについて解説されていました。発表者はCTF for Beginnersの問題作成用にhpingを利用してみましたとのことです。
ポートスキャンやパケットジェネレータの機能を併せ持つツールのようで、Scapyやpkttoolsと似たツールのようです。ただし、hping自体は10年ほど前くらいから更新されていないようです……。
512K問題をビッグデータ解析した先にあるもの
- 発表者はととろさん
つい先ごろ話題になった、BGPの512K問題に関する発表でした。私自身はBGPは名前くらいしか聞いたことがなく、512K問題の具体的な内容を知りませんでした。
BGPはAS番号を元に、接続先に最もホップ数が少ない経路を自動で選択する仕組みで、BGPオペレーションはごく限られた人しか行わない(行う必要がない)ようです。12K問題はIPv4に関する経路データの増加が引き金とのことですが、IPv6でも経路情報が爆発したら発生する可能性があるようです。
今回の512K問題は、2014/08/12前後から17にかけて発生しており、米ベライゾンが使用するCISCOルータで発生しました。ルータの物理メモリが少ないことで発生しており、経路数が512,000を越えると発生することから「512K問題」と呼ばれています。2011年以前製造のCISCOルータが原因であると特定されており、回避configの適用で対応加能です。一番の対策はルータを買い換えることのようですが、お値段が数千万円単位(!)のものらしく、おいそれと購入はできなさそうです。
BGP,512K問題については、以下のURLにて詳しく解説されています(BGPルータのメモリがDRAMと異なる仕組みだとは知りませんでした……)。
上記記事の中から、よく理解できていないキーワードを(自分用に)がーっと列挙しておきます。
- AS(Autonomous System)
- フルルート(Full Route:BGPが収集した世界中のASネットワークへの全経路情報)
- BGP version 4(RFC4271)
- EGP(Exterior Gateway Protocol)
- インターネット上で組織間の経路情報をやり取りする経路制御プロトコル
- BGPはEGPに分類される
- IGP(Interior Gateway Protocol)
- RIR(Regional Internet Registry;地域インターネットレジストリ)
- NIR(National Internet Registry;国別インターネットレジストリ)
- LIR(Local Internel Registry;ローカルインターネットレジストリ)
- (AS番号→RIRやJPNIC等のNIRから割り当てを受けることができる)
- (AS番号、原則として2バイトのサイズだが、AS番号数が不足してきたため、最近は4バイト化が進んでいる)
- iGP(内部BGP)
- eBGP(外部BGP)
- パンチングホール(経路数の増大を招く一因?)
- IRR(Internet Routing Registry)
- S-BGP(Secure BGP)
- soBGP(Secure Origin BGP)
- BFD(Bidirectional Forwarding Detection)
- TCAM(Ternary Content-Addressable Memory)
- CAM(Content-Addressable Memory)
- FIB(Forward Information Base)
- RIB(Routing Information Base)
- コンフェデレーション
- route reflector
- route server
- BGP経路のアグリゲーション
あと、これも知らなかったのですが、BGPMONというネットワークモニタリングサービスがあるようです。
- BGPMON
Fiddler使ってる?
- 発表者はTakagiさん
WebデバッグツールのFiddlerに関する発表でした。私はFiddlerを全く知らなかったので、ただ聞くだけになっていました。
FiddlerはIE開発リーダー格のひとりであったエリック・ローレンスによって作成されたツールで、Webのデバッグや、パフォーマンステスト、HTTP/HTTPSのトラフィックレコーディングやセッション操作、セキュリティテストといった、Web開発(の後の一連のテスト)に役立つ機能が提供されているようです(公式サイトからの受け売りですが……)。発表者の方もおっしゃっていましたが、「攻撃される前に自分でテスト」というのは重要だと感じました。
QUIC
発表者は@solomo83さん
発表資料は以下のURLにて公開されています。
QUIC(Quick UDP Internet Connections)に関する発表でした。QUICはSPDYに関連し、HTTP->SPDY->QUIC->UDP->IPの形で置き換えようとするプロトコルのようです。
The Chromium Blogの以下のエントリでQUICの解説と設計ドキュメントが公開されています(が、それ以降QUICに関するエントリが無いのがちょっと気になります……)。
Expermenting with QUIC(The Chromium Blog)
QUIC: Design Document and Specification Rationale
上記のブログと設計ドキュメントを読まないとダメかな……と思っていたら、既にブログにまとめていた方がいました。
なるほど、と思いながら読んでいたところ、以下のキーワードについては自分のなかでちゃんと理解できていなかったので、別途調べておこうと思いました。
Fiddler Add-on
- 発表者は@yukiyuukyさん
FiddlerのAdd-onに関する発表でした。Fiddlerを動作させる際は、.NETのフル版でないとハマる場合があるとのことです。また、.NETのバージョンにも注意する必要があるようです。atmarkITさんにてバージョン確認用のバッチファイルが公開されているので、これを利用すると良いとのことでした。
FiddlerのAdd-onは.NETで開発したdllで提供されており、Add-onの例として、Burp-like InspectorとSessionDecoratorが紹介されていました。
- "Burp-like Inspector" [Fiddler2 Extension] by yamagata21
- SessionDecorator
また、実践Fiddlerという書籍があるようです。
まとめ
第20回「ネットワーク パケットを読む会(仮)」にて発表してきました。次回のネッ トワークパケットを読む会は9/22(月)を予定しているとのことです。
FiddlerやQUICなど、(私が情報収集をサボっているだけですが)知らなかった技術キーワードを補足することができ、勉強になりました。知らないキーワードなどは少しずつ調べておこうと思います。
ラブライブ!OP曲を(株)白ヤギさんのテキスト要約にかけてみた
ラブライブ!OP曲を(株)白ヤギさんのテキスト要約にかけてみた
つい先日、株式会社 白ヤギコーポレーションさんにて「自動要約サービス」が公開されました。
Webスクレイピング勉強会への参加で、自然言語処理に興味が湧いてきており、さっそくこのサービスを試してみました。
- 自動要約サービス(株式会社 白ヤギコーポレーション)
ラブライブ!OP曲をテキスト要約してみる
要約の元になるテキストとして、アニメ版ラブライブ!のOP曲を用いてみました(いずれも名曲です)。
結果は以下となりました。要約結果を自分のなかで予想したものと並べてみます。
「僕らは今のなかで」の要約結果
予想 |
|
結果 | (歌詞が丸ごと返ってきた) |
「それは僕たちの奇跡」の要約結果
予想 |
|
結果 | (歌詞が丸ごと返ってきた) |
歌詞が丸ごと要約結果として返されます。これは当たり前の結果であり、『要約』は文書内の重要な部分を抽出するものであるからして、ラブライブ!の歌詞は一字一句無駄は無いと言えるのです!(キリッ
……ではなく、少し考えてみると、歌詞というものは想いのたけをそのまま文字にしているので、論理的な構成を見つけ出して抽出する要約には不向きな文章と言えるのだと思います。
「フランドン農学校の豚」を要約してみる
今度は歌詞ではなく、宮沢賢治の「フランドン農学校の豚」を要約してみましょう。
要約結果は以下となります。みごとに要約されています。
- フランドン農学校の豚宮沢賢治〔冒頭原稿一枚?なし〕以外の物質は、みなすべて、よくこれを摂取(せっしゅ)して、脂肪(しぼう)若(もし)くは蛋白質(たんぱくしつ)となし、その体内に蓄積(ちくせき)す。
- 」とこう書いてあったから、農学校の畜産(ちくさん)の、助手や又(また)小使などは金石でないものならばどんなものでも片(かた)っ端(ぱし)から、持って来てほうり出したのだ。
- というわけはその晩方、化学を習った一年生の、生徒が、自分の前に来ていかにも不思議そうにして、豚のからだを眺(なが)めて居た。
- 豚の方でも時々は、あの小さなそら豆形(まめがた)の怒(おこ)ったような眼(め)をあげて、そちらをちらちら見ていたのだ。
- ところが次の日のこと、畜産学の教師が又やって来て例の、水色の上着を着た、顔の赤い助手といつものするどい眼付して、じっと豚の頭から、耳から背中から尻尾(しっぽ)まで、まるでまるで食い込むように眺めてから、尖(とが)った指を一本立てて、「毎日阿麻仁(あまに)をやってあるかね。
まとめ
ラブライブ!OP曲をテキスト要約にかけてみました。あまり論理的な構成になっているとは言えない歌の歌詞のようなものは、機械的な要約に向かないようです。これを逆手に取って、自分の書いた文章の要約結果から文章の良し悪しを判定できたりもするのかなと思いました。