Linux のコンテナ仮想化を構成する要素の 1 つに、カーネルの Namespace (名前空間) という機能がある。 Namespace には色々とあるけど、今回はホスト名と NIS (Network Information Service) 1 ドメイン名を隔離する仕組みを提供している UTS Namespace について扱ってみる。
具体的には、unshare(1) と unshare(2) を使ってホスト名が隔離される様子を観察する。 unshare(2) というのは Namespace を操作するためのシステムコール。 そして、unshare(1) は同名のシステムコールを利用したコマンドラインツールになっている。
使った環境は次のとおり。
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=20.04 DISTRIB_CODENAME=focal DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS" $ uname -rm 5.4.0-92-generic aarch64 $ gcc --version gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
もくじ
下準備
まずは、事前に必要なパッケージをインストールしておく。
unshare(1) を使うために util-linux
を、C のコードをビルドするために build-essential
を入れておく。
$ sudo apt-get update $ sudo apt-get install -y util-linux build-essential
unshare(1) を使って UTS Namespace を使ってみる
まずは unshare(1) から UTS Namespace を使ってみよう。 あらかじめ、システムのホスト名と NIS ドメイン名を確認しておく。
$ hostname focal $ domainname (none)
上記から、ホスト名は focal
で、NIS ドメイン名は設定されていないことが分かる。
現在の Namespace の情報も確認しておこう。
procfs の内容から、今は 4026531838
という識別子の UTS Namespace であることが確認できる。
$ file /proc/$$/ns/uts /proc/949/ns/uts: symbolic link to uts:[4026531838]
続いて、unshare(1) を使って新しく UTS Namespace を作って bash を立ち上げる。
$ sudo unshare --uts bash
procfs の内容から、UTS Namespace の識別子が 4026532128
に変わったことが分かる。
# file /proc/$$/ns/uts /proc/1068/ns/uts: symbolic link to uts:[4026532128]
ここで hostname(1) と domainname(1) を使ってホスト名と NIS ドメイン名を変更してみよう。
# hostname host.example.com # domainname example
次のとおり、ちゃんと変更された。
# hostname host.example.com # domainname example
しかし、上記の変更はあくまで新しく作られた UTS Namespace 上での操作に過ぎないはず。 exit して、元のシェルに戻ってみよう。
# exit exit
UTS Namespace の識別子は、もちろん元に戻る。
$ file /proc/$$/ns/uts /proc/949/ns/uts: symbolic link to uts:[4026531838]
ホスト名と NIS ドメイン名も元に戻っている。 つまり、ホスト名と NIS ドメイン名は、ちゃんと UTS Namespace によって隔離されていた。
$ hostname focal $ domainname (none)
unshare(2) を使って UTS Namespace を使ってみる
続いては unshare(2) を使って UTS Namespace を使ってみよう。 今度は C のコードを使うことになるけど、長くなるのでホスト名とドメイン名で分けることにする。 といっても、両者は呼び出すシステムコールが {set,get}hostname(2) になるか {set,get}domainname(2) になるか位しか違いはない。
ホスト名を変更する
まずはホスト名から。 コメントに処理の説明は書いてあるけど、unshare(1) で UTS Namespace を新しく作ってから sethostname(2) と gethostname(2) を発行している。 そして、最終的には bash を起動している。
#define _GNU_SOURCE #include <sched.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { // UTS Namespace を作成する if (unshare(CLONE_NEWUTS) != 0) { fprintf(stderr, "Failed to create a new UTS namespace: %s\n", strerror(errno)); exit(EXIT_FAILURE); } // sethostname(2) でホスト名を変更する char const hostname[] = "host.example.com"; if (sethostname(hostname, strlen(hostname)) != 0) { fprintf(stderr, "Failed to set hostname: %s\n", strerror(errno)); exit(EXIT_FAILURE); } // gethostname(2) でホスト名を確認する char hostname_buf[254]; if (gethostname(hostname_buf, sizeof(hostname_buf) / sizeof(hostname_buf[0])) != 0) { fprintf(stderr, "Failed to get hostname: %s\n", strerror(errno)); exit(EXIT_FAILURE); } printf("gethostname: %s\n", hostname_buf); // execvp(3) でシェルを起動する char* const args[] = {"bash", NULL}; if (execvp(args[0], args) != 0) { fprintf(stderr, "Failed to exec \"%s\": %s\n", args[0], strerror(errno)); exit(EXIT_FAILURE); } return EXIT_SUCCESS; }
上記をコンパイルする。
$ gcc -Wall example.c
できたバイナリを実行する。
$ sudo ./a.out gethostname: host.example.com
sethostname(2) で変更されたホスト名が gethostname(2) で取得できている。
また、立ち上がったシェルを hostname(1) を発行しても、ちゃんとホスト名が変更されていることがわかる。
# hostname
host.example.com
procfs で確認できる UTS Namespace の識別子もちゃんと変わっている。 どうも識別子は使い回されてるっぽいけど。
# file /proc/$$/ns/uts /proc/1288/ns/uts: symbolic link to uts:[4026532128]
もちろん、シェルから抜ければホスト名は元に戻る。
# exit exit $ hostname focal
NIS ドメイン名を変更する
同様に NIS ドメイン名でも試してみる。 基本的にさっきのコードで発行するシステムコールが setdomainname(2) と getdomainname(2) に変わっただけ。
#define _GNU_SOURCE #include <sched.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { // UTS Namespace を作成する if (unshare(CLONE_NEWUTS) != 0) { fprintf(stderr, "Failed to create a new UTS namespace: %s\n", strerror(errno)); exit(EXIT_FAILURE); } // setdomainname(2) でNIS ドメイン名を変更する char const hostname[] = "example"; if (setdomainname(hostname, strlen(hostname)) != 0) { fprintf(stderr, "Failed to set NIS domain name: %s\n", strerror(errno)); exit(EXIT_FAILURE); } // getdomainname(2) で NIS ドメイン名を確認する char hostname_buf[64]; if (getdomainname(hostname_buf, sizeof(hostname_buf) / sizeof(hostname_buf[0])) != 0) { fprintf(stderr, "Failed to get NIS domain name: %s\n", strerror(errno)); exit(EXIT_FAILURE); } printf("getdomainname: %s\n", hostname_buf); // execvp(3) でシェルを起動する char* const args[] = {"bash", NULL}; if (execvp(args[0], args) != 0) { fprintf(stderr, "Failed to exec \"%s\": %s\n", args[0], strerror(errno)); exit(EXIT_FAILURE); } return EXIT_SUCCESS; }
上記をコンパイルする。
$ gcc -Wall example.c
できたバイナリを実行する。
$ sudo ./a.out getdomainname: example
ちゃんと NIS ドメイン名が変更されている。
立ち上がったシェルで domainnname(1) を実行しても、変更されていることがわかる。
# domainname
example
procfs で確認できる UTS Namespace の識別子もちゃんと変わっている。
# file /proc/$$/ns/uts /proc/1338/ns/uts: symbolic link to uts:[4026532128]
もちろん、シェルから抜ければホスト名は元に戻る。
# exit exit $ domainname (none)
ばっちり。
まとめ
今回は UTS Namespace を使ってホスト名と NIS ドメイン名の変更が隔離される様子を観察した。
-
使ったことがないけどディレクトリサービスの一種らしい↩