VPS for FreeBSDの内部実装 -構造体-
VPS for FreeBSD
よくクラウドとかレンタルサーバーで聞くVPS(Virtual Private Server)とは違います。
Virtual Private System for FreeBSD (link)です。 VPS for FreeBSDは EuroBSDCon2010 で Klaus P. Ohrhallinger が発表したものです。 FreeBSDのコンテナであるJailをライブマイグレーション可能にしたシステムと言われています。 カーネルに手が加えられていますが、競合はあまり起こらないように実装されているらしいです。
今回はコードから見たJailとVPSの違いを、構造体に着目して書いていきたいと思います。 結論から言いますと、VPSはJail実装をベースとして拡張したものです。 Jailに似た部分が多いので、Jailの実装を見た後にVPSを見て行きます。
途中から説明が雑になっていると思います。いい言い回しとか、そもそも間違っているとか、意味わからないとかあったら遠慮なくコメントしてください。
FreeBSD Jailの用語
FreeBSD Jailはjailとかprisonとかいろんな用語をつかいます。 linkから用語をそのまま引用します。
- jail [監獄]
- hosted
- jailシステムで隔離されたシステム、プロセス、サービスを表す。
- host
- jailシステムが動いているシステム、プロセス、サービスを表す。名前の通りの意味。
- prisoner [囚人]
- hostedに同じ
- jailer [看守]
- hostに同じ
コンテナ内部を呼ぶときはhosted/prisoner、コンテナ外部を呼ぶときはhost/jailerと呼びます。 また別の視点(公式のドキュメント)から見ると、 ユーザ空間からコンテナをJailと呼び、カーネル空間からコンテナをPrisonと呼んでいます。 実装レベルでも同じように分けて実装されています。
FreeBSD Jailの実装
まずは、Jailの構造体を見たいと思います。
/* sys/sys/jail.h */ struct jail { uint32_t version; char *path; char *hostname; char *jailname; uint32_t ip4s; uint32_t ip6s; struct in_addr *ip4; struct in6_addr *ip6; };
非常にシンプルですね。jail構造体には、jlsをした時に見える情報、Jailに関する基本的な情報が格納されています。 基本的にはユーザ空間とカーネル空間のインタフェースのような扱いで使われます。 例えば、jail(2)の実行時にカーネルで呼ばれるsys_jailで使われます。
/* sys/kern/kern_jail.c */ int sys_jail(struct thread *td, struct jail_args *uap) { uint32_t version; int error; struct jail j; error = copyin(uap->jail, &version, sizeof(uint32_t)); if (error) return (error); switch (version) { case 0: { struct jail_v0 j0; /* FreeBSD single IPv4 jails. */ bzero(&j, sizeof(struct jail)); error = copyin(uap->jail, &j0, sizeof(struct jail_v0)); if (error) return (error); j.version = j0.version; j.path = j0.path; j.hostname = j0.hostname; j.ip4s = htonl(j0.ip_number); /* jail_v0 is host order */ break; } case 1: /* * Version 1 was used by multi-IPv4 jail implementations * that never made it into the official kernel. */ return (EINVAL); case 2: /* JAIL_API_VERSION */ /* FreeBSD multi-IPv4/IPv6,noIP jails. */ error = copyin(uap->jail, &j, sizeof(struct jail)); if (error) return (error); break; default: /* Sci-Fi jails are not supported, sorry. */ return (EINVAL); } return (kern_jail(td, &j)); }
この関数では、jail(2)の引数をjail構造体にコピーして使っています。 上の方でインタフェースと表現したように、これはコンテナの実体ではありません。
次に、FreeBSDにおけるコンテナの実体であるprison構造体を見ます。
/* sys/sys/jail.h */ struct prison { TAILQ_ENTRY(prison) pr_list; /* (a) all prisons */ int pr_id; /* (c) prison id */ int pr_ref; /* (p) refcount */ int pr_uref; /* (p) user (alive) refcount */ unsigned pr_flags; /* (p) PR_* flags */ LIST_HEAD(, prison) pr_children; /* (a) list of child jails */ LIST_ENTRY(prison) pr_sibling; /* (a) next in parent's list */ struct prison *pr_parent; /* (c) containing jail */ struct mtx pr_mtx; struct task pr_task; /* (c) destroy task */ struct osd pr_osd; /* (p) additional data */ struct cpuset *pr_cpuset; /* (p) cpuset */ struct vnet *pr_vnet; /* (c) network stack */ struct vnode *pr_root; /* (c) vnode to rdir */ int pr_ip4s; /* (p) number of v4 IPs */ int pr_ip6s; /* (p) number of v6 IPs */ struct in_addr *pr_ip4; /* (p) v4 IPs of jail */ struct in6_addr *pr_ip6; /* (p) v6 IPs of jail */ struct prison_racct *pr_prison_racct; /* (c) racct jail proxy */ void *pr_sparep[3]; int pr_childcount; /* (a) number of child jails */ int pr_childmax; /* (p) maximum child jails */ unsigned pr_allow; /* (p) PR_ALLOW_* flags */ int pr_securelevel; /* (p) securelevel */ int pr_enforce_statfs; /* (p) statfs permission */ int pr_devfs_rsnum; /* (p) devfs ruleset */ int pr_spare[3]; int pr_osreldate; /* (c) kern.osreldate value */ unsigned long pr_hostid; /* (p) jail hostid */ char pr_name[MAXHOSTNAMELEN]; /* (p) admin jail name */ char pr_path[MAXPATHLEN]; /* (c) chroot path */ char pr_hostname[MAXHOSTNAMELEN]; /* (p) jail hostname */ char pr_domainname[MAXHOSTNAMELEN]; /* (p) jail domainname */ char pr_hostuuid[HOSTUUIDLEN]; /* (p) jail hostuuid */ char pr_osrelease[OSRELEASELEN]; /* (c) kern.osrelease value */ };
大きめな構造体になっていますね。prison構造体はリストになっています。
pr_childcountやpr_childmaxといったコンテナのネスト数なども管理してます。
メンバ変数のpr_prison_racctは、hosted/prisonerが使っているリソース(cpuやメモリなど)の情報を含んでおり、主にRACCT/RCTLで使われます。
prison構造体は、スレッドが保持するucred構造体に格納され、td->td_ucred->pr_prison
という使われ方をします。
host/jailer(コンテナホスト)のスレッドのpr_prisonには特別な値prison0が代入されます。prison0はゼロ値(っぽい値)が入ったprison構造体です。
/* sys/sys/kern_jail.c */ struct prison prison0 = { .pr_id = 0, .pr_name = "0", .pr_ref = 1, .pr_uref = 1, .pr_path = "/", .pr_securelevel = -1, .pr_devfs_rsnum = 0, .pr_childmax = JAIL_MAX, .pr_hostuuid = DEFAULT_HOSTUUID, .pr_children = LIST_HEAD_INITIALIZER(prison0.pr_children), #ifdef VIMAGE .pr_flags = PR_HOST|PR_VNET|_PR_IP_SADDRSEL, #else .pr_flags = PR_HOST|_PR_IP_SADDRSEL, #endif .pr_allow = PR_ALLOW_ALL_STATIC, };
ここまでのFreeBSD Jailの情報をまとめてみます。
- jail構造体はユーザ空間とカーネル空間とのインタフェースのような扱い
- コンテナの実体はprison
- スレッドごとにprison構造体を持つ
- コンテナホストのスレッドはprison0が代入される
FreeBSD VPSの実装
まず、vps構造体を見ましょう。
/* sys/vps/vps2.h */ struct vps { struct vnet *vnet; LIST_ENTRY(vps) vps_all; LIST_ENTRY(vps) vps_sibling; LIST_HEAD(, vps) vps_child_head; struct vps *vps_parent; struct sx vps_lock; char *vps_lock_name; u_int vps_id; char vps_name[MAXHOSTNAMELEN]; u_char vps_status; u_int vps_refcnt; struct mtx vps_refcnt_lock; #ifdef INVARIANTS TAILQ_HEAD(, vps_ref) vps_ref_head; #endif struct timeout_task vps_task; u_char priv_allow_set[PRIV_SET_SIZE]; u_char priv_impl_set[PRIV_SET_SIZE]; struct vps_arg_ip4 *vps_ip4; struct vps_arg_ip6 *vps_ip6; u_int16_t vps_ip4_cnt; u_int16_t vps_ip6_cnt; u_int vps_flags; int restore_count; int64_t suspend_time; struct vps_acc *vps_acc; /* XXX do inline */ struct vnode *consolelog; struct tty *console_tty; struct file *console_fp_ma; int consolelog_refcnt; int console_flags; struct ucred *vps_ucred; struct devfs_rule *devfs_ruleset; struct vnode *_rootvnode; char _rootpath[MAXPATHLEN]; };
prison構造体と似た作りになっていますが、違う情報も扱っていることがわかります。 prison構造体とは全く関係がないのかというとそうではなくて、ucred構造体であるvps_ucredを持っています。 上のprison構造体の説明で書きましたが、ucred構造体はprison構造体を含んでいます。 vps構造体はprison構造体を含んでいる。つまり、vps構造体はprison構造体の拡張になります。
/* sys/vps/vps_core.c */ struct vps * vps_alloc(struct vps *vps_parent, struct vps_param *vps_pr, char *vps_name, int *errorval) { struct vps *vps; struct vps *vps_save; struct nameidata nd; char *path; int error; char *tmpstr = NULL; struct thread *td = curthread; ~~~~~~~~~中略~~~~~~~~~~ vps_prison_alloc(vps_parent, vps); prison_hold(VPS_VPS(vps, prison0)); prison_free(vps->vps_ucred->cr_prison); vps->vps_ucred->cr_prison = VPS_VPS(vps, prison0); ~~~~~~~~~~~~~~~~~~~~~~ } static int vps_prison_alloc(struct vps *vps_parent, struct vps *vps) { struct prison *pp, *np; ~~~~~~~~~中略~~~~~~~~~~ VPS_VPS(vps, prison0) = malloc(sizeof(struct prison), M_VPS_CORE, M_WAITOK|M_ZERO); np = VPS_VPS(vps, prison0); pp = VPS_VPS(vps_parent, prison0); ~~~~~~~~~~~~~~~~~~~~~~ }
vps_alloc関数はvpsctlでコンテナを実行した後、vps構造体を作る関数です。 vpsを作る中で、vps_prison_alloc関数を呼び、prison構造体を作成しています。
/* sys/vps/vps_core.c */ static void vps_init(void *unused) { LIST_INIT(&vps_head); /* init all the global locks here */ sx_init(&vps_all_lock, "lock of all vps instances"); sx_xlock(&vps_all_lock); vps0 = vps_alloc(NULL, NULL, "", NULL); sx_xunlock(&vps_all_lock); curthread->td_vps = vps0; vps_console_init(); DBGCORE("WARNING: VPS VIRTUAL PRIVATE SYSTEMS ENABLED. " "HIGHLY EXPERIMENTAL!\n"); }
OS起動時にはhost/jailerに対して、ゼロ値で初期化されたvps構造体を作成しています。
prison0も一緒に作成されますが、VPSの場合はtd->td_vps->vps_ucred->cr_prison
に代入されます。
ここまでのVPS for FreeBSDの情報をまとめてみます。
まとめ
FreeBSD JailとVPS for FreeBSDでは、似ている部分としてprisonを使ってコンテナを作成していることが挙げられます。 しかし、jailはユーザ空間とカーネル空間のインタフェース的な存在なのに対し、vpsはprisonそのもののような役割を持っています。