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

Linux Namespaces

Linux Namespaces

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. まとめ • 名前空間はカーネルのリソースを管理 • 名前空間の操作 ◦ 親プロセスと共有 ◦ 親プロセスから分離 ◦

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