Dockerの実装確認 - namespace編 -


背景

cgroups編でも書いたがdockerとは以前にコンテナとは何か良く分からなかったので
体系的に学んでみました。今回は主要な機能でるカーネルの「namespace」を中心にまとめてみました。

ちなみにmanで見ることもできます
NAMESPACE(7)

namespaceとは

名前空間は、特定の ID 集合に範囲を絞る方法です。
名前空間を使うと、名前空間が異なれば同じ ID を何回も使用できます。
また、特定のプロセスに見える ID 集合を限定することもできます。

例えば、 Linux は、他にもありますが、ネットワークとプロセスの名前空間を提供しています。
プロセスがプロセス名前空間内で実行されている場合、そのプロセスには同じ名前空間内の他のプロセスだけが見え、通信できるのも名前空間内のプロセスだけです。
そのため、あるプロセス名前空間内のシェルで ps waux を実行すると、同じ名前空間内の他のプロセスだけが表示されます。

dockerにおけるnamespaceの役割は公式に下記のように記載されています

Docker は名前空間(ネームスペース)と呼ばれる技術を利用し、コンテナ (container) と呼ぶワークスペース(作業空間)の分離をもたらします。
Docker はコンテナ毎に 名前空間 の集まりを作成します。

dockerとカーネル

dockerからカーネルに関わる操作は可能です。しかしコンテナごとに異なる操作を行うことはできません。
カーネルの機能で実現している環境ですので,当然全コンテナから見えるカーネルは同一です。
したがって,コンテナから見えるデバイスやロードされているカーネルモジュールは同じになります。

名前空間API

名前空間 API として以下のシステムコールがある。

  • clone(2) - 新しいプロセスを作成
  • setns(2) - 呼び出したプロセスを既存の名前空間に参加させることができる
  • unshare(2) - 呼び出したプロセスを新しい名前空間に移動する

上記システムコールを用いてコンテナは独自の名前空間を作成し
コンテナとしている。

名前空間一覧

じゃあ具体的に何が分離されているのかを確認。

名前空間 定数 概要
IPC名前空間 CLONE_NEWIPC IPC(Inter-Process Communication:プロセス間通信)リソースであるSystem V IPCオブジェクト、POSIXメッセージキューを分離する。
マウント名前空間 CLONE_NEWNS ファイルシステムツリーを分離する
ネットワーク名前空間 CLONE_NEWNET ネットワークデバイスやIPアドレス、ルーティングテーブルなどのネットワークインタフェースを分離する
PID名前空間 CLONE_NEWPID PID(プロセスID)空間を分離する
ユーザー名前空間 CLONE_NEWUSER UID/GIDを分離する
UTS名前空間 CLONE_NEWUTS uname() システムコールから返される2つのシステム識別子(nodename および domainname)を分離する

ちなみに/proc/{pid}/ns/ 以下で名前空間の一覧が確認できます。

$ ls -l /proc/$$/ns/
合計 0
lrwxrwxrwx 1 root root 0 11月  8 16:54 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 11月  8 16:54 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 11月  8 16:54 net -> net:[4026531968]
lrwxrwxrwx 1 root root 0 11月  8 16:54 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 11月  8 16:54 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 11月  8 16:54 uts -> uts:[4026531838]

実際の実装方法は「include/kernel/nsproxy.h」で定義されている、
nsproxy 構造体で確認も出来る。

include/linux/nsproxy.h
/*
 * A structure to contain pointers to all per-process
 * namespaces - fs (mount), uts, network, sysvipc, etc.
 *
 * The pid namespace is an exception -- it's accessed using
 * task_active_pid_ns.  The pid namespace here is the
 * namespace that children will use.
 *
 * 'count' is the number of tasks holding a reference.
 * The count for each namespace, then, will be the number
 * of nsproxies pointing to it, not the number of tasks.
 *
 * The nsproxy is shared by tasks which share all namespaces.
 * As soon as a single namespace is cloned or unshared, the
 * nsproxy is copied.
 */
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;
    struct cgroup_namespace *cgroup_ns;
};
extern struct nsproxy init_nsproxy;

まとめ

Docker はコンテナごとに 名前空間 の集まりを作成し
作業空間を作成する。

階層型仮想化って言う人もいるけれどなんとなく意味が分かりました。

docckerはカーネルが持つ複数の機能を利用して実装している。

ホスト型/ハイパーバイザー型の仮想化を学んだあとに学習していくと
ここが混乱を招きそうですね。

参考リンク

公式ドキュメント
Linuxカーネル Docker関連 namespaceのメモ
原理原則で理解するDocker