Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Linux Namespaces

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Linux Namespaces

Avatar for Masami Ichikawa

Masami Ichikawa

September 26, 2015
Tweet

More Decks by Masami Ichikawa

Other Decks in Programming

Transcript

  1. @masami256 • Linuxカーネルのメモリーリークを直したり ◦ ackされたりされなかったり • Linuxカーネルもくもく会を開催したり ◦ at 秋葉原

    • ラズパイ向けのdockerイメージ作ってたり ◦ Arch Linuxのラズパイ • Arch LinuxのAURにPKGBUILDを公開したり • Fedora ProjectでQAをやったり ◦ [email protected] • きらら、きららミラク、きららキャラット、きらら MAXは欠かさず購入 ◦ ゆゆ式は哲学(`・ω・´)キリッ • 大家さんは思春期!も良いですね(*´ω`*) ◦ まんがタイム・まんがタイムファミリーで連載中 ◦ アニメ化企画進行中ヽ(=´▽`=)ノ
  2. Readme • kernelとlibcの実装は以下のバージョンで確認Linux kernel version 4.1 ▪ http://lxr.free-electrons.com/?v=4.1 ◦ glibc

    version 2.21 ▪ http://sourceware.org/git/?p=glibc.git;a=commit; h=4e42b5b8f89f0e288e68be7ad70f9525aebc2cff • Man ◦ man 7 namespace
  3. Linux 4.1でサポートしている名前空間 名前空間 概要 IPC System Vのプロセス間通信、POSIXメッセー ジキュー Net ネットワークデバイス、

    IPv4・IPv6プロトコルス タック、ルーティングテーブル等々 Mount マウントポイント PID PID User UID、GID UTS ホスト名
  4. 主な登場人物 • 名前空間 ◦ UTS、Net名前空間等 • NSProxy ◦ カーネルで各名前空間を管理している構造体 •

    参照カウンタ ◦ 名前空間(個々の名前空間)の参照カウンタ ◦ NSProxyの参照カウンタ
  5. 名前空間の概要:UTS名前空間での例 pid:1001 ppid:1000 NSProxy uts ns pid:1002 ppid:1000 nodename:foo pid:1003

    ppid:1000 NSProxy uts ns nodename: bar pid:1000 ppid:900 pid1001と1002がuname -nするとfooが返る pid1003がuname -nするとbarが返る pid1000の名前空間は この図では省略 名前空間 名前空間 struct new_utsname struct new_utsname
  6. 名前空間のユーザ空間への見せ方 • 名前空間はファイルとして見える ◦ /proc/<pid>/ns/[namespace name] masami@saga:~$ ls -la /proc/self/ns

    total 0 dr-x--x--x. 2 masami masami 0 Jul 15 23:39 ./ dr-xr-xr-x. 9 masami masami 0 Jul 15 23:39 ../ lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 ipc -> ipc:[4026531839] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 mnt -> mnt:[4026531840] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 net -> net:[4026531969] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 pid -> pid:[4026531836] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 user -> user:[4026531837] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 uts -> uts:[4026531838]
  7. 名前空間に関連するシステムコール • 3つあります ◦ clone(2) ◦ setns(2) ◦ unshare(2) •

    setns(2)は純粋に名前空間操作用 • その他の2関数は名前空間も操作できる
  8. CLONE_NEWXXXフラグ 名前空間 フラグ IPC Namespace CLONE_NEWIPC Net Namespace CLONE_NEWNET Mount

    Namespace CLONE_NEWNS PID Namespace CLONE_NEWPID User Namespace CLONE_NEWUSER UTS Namespace CLONE_NEWUTS • システムコールで名前空間を操作するためのフラグ ◦ clone(2)、unshare(2)で使用
  9. clone(2) ちょっとした問題 • clone(2)に渡すflagsは符号ありの32bit整数 ◦ 新規にNamespaceを作ろうとした場合、使えるフラグに空きがない ▪ 唯一の空きはこれ(/usr/include/linux/sched.h) ◦ 別件でclone4(2)を提案した人はいたけどmergeはされていない ▪

    Attaching file descriptors to processes with CLONE_FD • http://lwn.net/Articles/638613/ int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ ); /* 0x02000000 was previously the unused CLONE_STOPPED (Start in stopped state) and is now available for re-use. */
  10. Linuxのプロセス作成(ざっくりと) • ざっくりと言ってしまうと、fork(2)を実行したプロセスのコピーを作 る ◦ ファイルディスクリプタやメモリ空間などはコピーではなくて同 じデータを共有します ▪ Copy on

    Write ▪ 詳しく知りたいなら以下の本が良いと思います ▪ なるほどUnixプロセス ― Rubyで学ぶUnixの基礎 • http://tatsu-zine.com/books/naruhounix • このデータのコピー/共有というところで、clone(2)よる共有の設 定、unshare(2)による分離が必要に
  11. setns(2)? • 所属したい名前空間のfdを使って、その名前空 間に移動する ◦ fdは/proc/<pid>/nsにあるファイルのfd masami@saga:~$ ls -la /proc/self/ns

    total 0 dr-x--x--x. 2 masami masami 0 Jul 15 23:39 ./ dr-xr-xr-x. 9 masami masami 0 Jul 15 23:39 ../ lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 ipc -> ipc:[4026531839] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 mnt -> mnt:[4026531840] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 net -> net:[4026531969] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 pid -> pid:[4026531836] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 user -> user:[4026531837] lrwxrwxrwx. 1 masami masami 0 Jul 15 23:39 uts -> uts:[4026531838]
  12. PID名前空間の仕様 • clone(2)でプロセス生成時のみ、名前空間変更対象のプロセスは新しいPID名 前空間に所属できる • unshare(2)によるPID名前空間の分離は非サポート ◦ 一時期サポートされていたが「pidns: Don't have

    unshare (CLONE_NEWPID) imply CLONE_THREAD」で外された • https://github. com/torvalds/linux/commit/6e556ce209b09528dbf1931cbfd5d 323e1345926 • setns(2)による名前空間の移動 ◦ 移動したプロセスのPID名前空間は変わらない ◦ このプロセスがfork()等で子プロセスを作ると、その子プロセスから移動し た名前空間に所属する
  13. プロセスと名前空間 fork(2)実行時 pid: 1234 ppid: 784 pid: 1192 ppid: 784

    pid: 1326 ppid: 1192 pid ns mount ns net ns ipc ns user ns pid ns mount ns net ns ipc ns user ns fork(2)では親プロセスと名 前空間を共有 pid:1192がforkを実行し、pid:1326 が子プロセス
  14. プロセスと名前空間 clone(2)実行時 pid: 1234 ppid: 784 pid: 1192 ppid: 784

    pid: 1326 ppid: 1192 pid ns mount ns net ns ipc ns user ns pid ns mount ns net ns ipc ns user ns flagsでCLONE_NEWNETを指定した場合、 Net Namespace以外は親プロセスと共有 net ns
  15. プロセスと名前空間 setns(2)実行時 pid: 1234 ppid: 784 pid: 1192 ppid: 784

    pid: 1326 ppid: 1192 pid ns mount ns net ns ipc ns user ns pid ns mount ns net ns ipc ns user ns setns(2)でNet Namespaceをpid 1234 のnamespaceに所属させた場合
  16. プロセスと名前空間 unshare(2)実行時 pid: 1234 ppid: 784 pid: 1192 ppid: 784

    pid: 1326 ppid: 1192 pid ns mount ns net ns ipc ns user ns pid ns mount ns net ns ipc ns user ns fork(2)で親プロセスと名前空間を共有していた 状態から、Network namespaceだけを分離し た状態 net ns
  17. Linux Kernelでの実装 • nsproxy構造体 ◦ 名前空間の一元管理 ▪ User名前空間は除く • cred構造体

    ◦ User名前空間を管理 • nsfs ◦ 各名前空間をユーザ空間にエクスポート ▪ /proc/<pid>/ns配下のファイル
  18. NSProxy • include/linux/nsproxy.h ◦ NSProxy自体の参照数と各名前空間へのポインタを保 持 struct nsproxy { atomic_t

    count; struct uts_namespace *uts_ns; struct ipc_namespace *ipc_ns; struct mnt_namespace *mnt_ns; struct pid_namespace *pid_ns_for_children; struct net *net_ns; }; このnsproxyの参照数
  19. 各名前空間の構造体 Namespace Name File UTS uts_namespace include/linux/utsname.h IPC ipc_namespace include/linux/ipc_namespace.h

    Mount mnt_namespace fs/mount.h PID pid_namespace include/linux/pid_namespace.h Net net include/net/net_namespace.h User user_namespace include/linux/user_namespace.h
  20. 各名前空間が持つ基本的なデータ • 参照カウンタ ◦ 名前空間の分離をしない場合は参照数を増やして、プロセス間で共有する ため ▪ カウントするのは参照しているNSProxyの数 • struct

    ns_common構造体 ◦ procfsに関連するデータを持つ ▪ inode ▪ struct proc_ns_operations • setns(2)が/proc/<pid>/ns以下のファイルを使用するの覚えてま すよね(・∀・) • struct user_namespace ◦ User Namespace以外の名前空間はuser_namespaceのポインタをデー タとして持つ
  21. unshare -u /bin/bashしたとき Namespace inode(before) count(before) inode(after) inode(after) Net 4026531957

    : 91 4026531957 93 UTS 4026531838 91 4026531838 92 N/A N/A 4026532111 1 IPC 4026531839 91 4026531839 93 PID 4026531836 91 4026531836 93 User 4026531837 91 4026531837 93 Mount 4026531840 4026531857 4026532110 4026532124 88 1 1 1 4026531840 4026531857 4026532110 4026532124 90 1 1 1 新規に作られた
  22. nsenter --target $PID --utsしたとき Namespace inode(before) count(before) inode(after) inode(after) Net

    4026531957 : 94 4026531957 96 UTS 4026531838 93 4026531838 94 4026532111 1 4026532111 2 IPC 4026531839 94 4026531839 96 PID 4026531836 94 4026531836 96 User 4026531837 94 4026531837 96 Mount 4026531840 4026531857 4026532110 4026532124 91 1 1 1 4026531840 4026531857 4026532110 4026532124 93 1 1 1 ここに移動する 増えた
  23. struct ns_common • 主なデータ ◦ proc_ns_operation構造体 ◦ /proc/<pid>/<namespace>のinode番号 • 使用目的

    ◦ ユーザ空間にexportしたファイルのinodeから名前空間 の構造体にアクセスする ▪ container_ofマクロを使うとできる • 各名前空間はns_common構造体のポインタをメンバ変数 に持っている
  24. 補足: container_ofの仕組み #define container_of(ptr, type, member) ({ \ const typeof(

    ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) struct foo *p = &x; struct bar *ptr = p->b; struct foo *fp = container_of(ptr, struct bar, b); struct foo { int n; char s[4]; struct bar *b; int x; }; typeof(((struct bar *)0)->b) __mptr = ptr これで、struct bar * __mptr = ptrとなる offsetof(struct bar, b)でstruct fooのメンバ変数bの先頭からのオ フセットが取れる 最後に__mptrのアドレスからオフセットを引くと struct fooのインス タンスのアドレス(== pのアドレス)が取れる ↓はなんとなくの例
  25. proc_ns_operations構造体の関数 • get() ◦ 参照数を増やす • put() ◦ 参照数を減らす •

    install() ◦ 名前空間に参加する memo Linuxカーネルでget/putという名称は参照数を増や す/減らすという意味で使うことが多いです。
  26. 各名前空間の初期化タイミング Namespace 初期化タイミング 初期化時の変数 User コンパイル時 init_user_ns UTS コンパイル時 init_uts_ns

    IPC コンパイル時 init_ipc_ns Net コンパイル時 init_net_ns PID コンパイル時 init_pid_ns Mount init_mount_tree() NULL
  27. init_nsproxy struct nsproxy init_nsproxy = { .count = ATOMIC_INIT(1), .uts_ns

    = &init_uts_ns, #if defined(CONFIG_POSIX_MQUEUE) || defined (CONFIG_SYSVIPC) .ipc_ns = &init_ipc_ns, #endif .mnt_ns = NULL, .pid_ns_for_children = &init_pid_ns, #ifdef CONFIG_NET .net_ns = &init_net, #endif }; Mount Namespaceはコンパイル時に決まら ないので最初はNULL
  28. init_cred struct cred init_cred = { .usage = ATOMIC_INIT(4), ~略~

    .fsuid = GLOBAL_ROOT_UID, .fsgid = GLOBAL_ROOT_GID, .securebits = SECUREBITS_DEFAULT, .cap_inheritable = CAP_EMPTY_SET, .cap_permitted = CAP_FULL_SET, .cap_effective = CAP_FULL_SET, .cap_bset = CAP_FULL_SET, .user = INIT_USER, .user_ns = &init_user_ns, .group_info = &init_groups, };
  29. mnt_nsの初期化 start_kernel() -> vfs_cache_init() -> mnt_init() -> init_mount_tree() -> create_mnt_ns()

    rootfsのvfsmount構造体取得 mnt_ns構造体をallocして、mount 構造体のmnt_nsに設定 カーネルの初期化関数
  30. clone(2)の処理 • 名前空間に関する部分は2点 ◦ User名前空間の処理 ▪ User名前空間の参照カウンタを増やすか新規に作 る ◦ NSProxyの処理

    ▪ NProxyの参照カウンタを増やすか新規に作る ▪ 各名前空間の処理 • 対象の名前空間の参照カウンタを増やすか新規 に作る
  31. clone時のUser Namespaceの処理 • User Namespaceは認証情報のコピー処理の中で実施 do_fork() -> copy_process() -> copy_creds()

    -> create_user_ns() ->set_cred_user_ns() prepare_cred()で親プロセスのuser_nsを共有 CLONE_NEWUSERがセットされている場合は、こ こで親プロセスのuser_nsと分離 作成中プロセスのstruct credにあるuser_nsを create_user_ns()で作ったものに置き換え
  32. copy_namespaces()の処理 • CLONE_XXXのチェック ◦ 設定が無けれればNSProxyの参照を増やして終了 • 必要なケーパビリティがあるかチェック • CLONE_NEWIPCが設定されている場合 ◦

    CLONE_SYSVSEMが設定されていたらエラーとする ▪ http://linuxjm.osdn.jp/html/LDP_man-pages/man2/clone.2. html • create_new_namespaces()でNSProxyの作成と、各名前空間の処理 をする • 作成中のtask_structのnsproxyをcreate_new_namespaces()で作成し たものに置き換える
  33. craete_new_namespaces()の処理 • NSProxy構造体を新規に作成 ◦ 作成はcreate_nsproxy()で実施 ◦ 主な処理 ▪ kmem_cache_alloc()でメモリを確保 ▪

    参照数を1に設定 ▪ 各名前空間を処理 • 各名前空間の関数を呼び出していく ◦ copy_xxxという名前
  34. nsfs • linux 3.19より追加 ◦ 3.19からは1つのfile systemとなった ▪ 以前も同様の処理はしていた ◦

    take the targets of /proc/*/ns/* symlinks to separate fs ▪ https://github. com/torvalds/linux/commit/e149ed2b805fefdccf7c cdfc19eca22fdd4514ac
  35. inodeからtask_structの取得 • get_proc_task()を使うことで取得できる ◦ inodeからstruct proc_inodeの取得 ▪ この構造体の先頭要素はstruct pid ▪

    pid構造体のtasks変数の要素はtask_struct構造体のpidsメン バ変数の要素を指す ▪ 後はoffset_ofでメンバ変数のオフセット位置を引けば task_struct構造体の先頭アドレスを取得できる ◦ 細かい挙動は「Linux: inodeからtask_struct構造体を取得」を参照 してください ▪ http://kernhack.hatenablog.com/entry/2015/06/13/003928
  36. Mount名前空間 テスト環境 • Raspberry Pi B++ ◦ Linux saturn 4.1.4-1-ARCH

    #1 PREEMPT Thu Aug 6 21:07:48 MDT 2015 armv6l GNU/Linux • SDカードはなんだっけ・・? [masami@saturn ~]$ zgrep "CONFIG_[A-Z]*_NS" /proc/config.gz CONFIG_UTS_NS=y CONFIG_IPC_NS=y CONFIG_USER_NS=y CONFIG_PID_NS=y CONFIG_NET_NS=y
  37. Mount名前空間のテスト内容 • ツール ◦ bonnie++ ▪ bonnie++ -u 1000:1000 -d

    ${test_dir} -n 256:0:0:1 • ファイル数256、最大・最小ファイルサイズ0、ディレクトリ数1 ▪ 上記を5回1セットにして5セット実施 • unshareした場合としない場合の2パターン • ファイルシステム ◦ ext4 • 結果はRandom CreateのDelete処理の部分をグラフ化 ◦ delete(unlink)はmount namespaceを見ているので
  38. unlinkの場合の名前空間へのアクセス sys_unlink() ソースコード上は SYSCALL_DEFINE1(unlink, ~ -> do_unlinkat() -> vfs_unlink() ->

    is_local_mountpoint() -> __is_local_mountpoint() 削除対象のdentryを取得 dentryが現在のmount namespaceのものか調べる ↓のようにリストを辿るので名前空間がたくさんあればその分のオーバーヘッドはある list_for_each_entry(mnt, &ns->list, mnt_list) { is_covered = (mnt->mnt_mountpoint == dentry); if (is_covered) break; }
  39. Net名前空間 テスト環境 • Arch Linux on KVM ◦ Linux nstest

    4.1.6-1-ARCH #1 SMP PREEMPT Mon Aug 17 08:52:28 CEST 2015 x86_64 GNU/Linux [root@nstest masami]# zgrep "CONFIG_[A-Z]*_NS" /proc/config.gz CONFIG_UTS_NS=y CONFIG_IPC_NS=y # CONFIG_USER_NS is not set CONFIG_PID_NS=y CONFIG_NET_NS=y
  40. Net名前空間 テスト環境 iperf対抗機 192.168.11.7/24 KVMホスト enp9s0: 192.168.11.6/24 virbr0: 192.168.122.1/24 KVMゲスト&コンテナホスト

    名前空間:設定なし( init_net_ns) br0: 192.168.122.33/24 host-veth: アドレスなし コンテナゲスト 名前空間:testns guest-veth: 192.168.122.150/24
  41. kvmゲスト環境のネットワーク [root@nstest masami]# ip a 3: br0@NONE: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500

    qdisc noqueue state UP group default link/ether 86:dc:5e:95:d6:b3 brd ff:ff:ff:ff:ff:ff inet 192.168.122.33/24 brd 192.168.122.255 scope global dynamic br0 valid_lft 3067sec preferred_lft 3067sec inet6 fe80::84dc:5eff:fe95:d6b3/64 scope link valid_lft forever preferred_lft forever 5: host-veth@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br0 state UP group default qlen 1000 link/ether 7e:37:ac:ec:1c:25 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::7c37:acff:feec:1c25/64 scope link valid_lft forever preferred_lft forever [root@nstest masami]# ip a 4: guest-veth@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 82:27:59:38:88:7a brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.122.150/24 scope global guest-veth valid_lft forever preferred_lft forever inet6 fe80::8027:59ff:fe38:887a/64 scope link valid_lft forever preferred_lft forever コンテナのホスト側 lo、ethは省略 コンテナのゲスト側
  42. socket(2)での名前空間へのアクセス sys_socket() -> sock_create() return __sock_create(current->nsproxy->net_ns, family, type, protocol, res,

    0); これと言った処理はなくて、普通に net名前空間にアクセス。 Mount名前空間と違い、ピンポイントでデータにアクセスできるので カーネルとしてはオーバーヘッドは無いはず
  43. References • Professional Linux Kernel Architecture ◦ http://www.amazon.co.jp/dp/0470343435 • コードリーディングのめもはblogに書いてます

    ◦ http://kernhack.hatenablog.com/ • man 7 namespaces ◦ http://linuxjm.osdn.jp/html/LDP_man-pages/man7/namespaces.7.html ◦ 関連するmanページもここから • Linuxファイルシステムベンチマーク第1回 ◦ http://hesonogoma.com/linux/FileSystemBenchmarkResults-01.html
  44. まとめ • 名前空間はカーネルのリソースを管理 • 名前空間の操作 ◦ 親プロセスと共有 ◦ 親プロセスから分離 ◦

    他の名前空間に所属 • 名前空間分離時の挙動 ◦ 元の名前空間のコピーを作る ◦ データが全く無い空の状態 • オーバーヘッドはものによりけり ◦ ポインタの差し替えで済むもの ◦ リストを辿るもの