いますぐ実践! Linuxシステム管理

いますぐ実践! Linux システム管理 / Vol.261 / 読者数:2624名

こんばんは、うすだです。

自分の国籍、家族構成、年齢、性別や職業には触れないで自己紹介をしてみましょう、 という演習が、とある本に書いてありました。

これは、自己理解(自分らしさを自分がわかっているかどうか) を確認するための問いなのだそうです。

他にも、今すぐ会社を辞めたらその会社から何が失われるかとか、 辞めて5年後に自分はどういう社員だったかをどう説明してもらいたいかとか、 聞かれると頭を抱えてしまいそうな演習がいくつか載っていました。

ちなみに、前者は会社における自分の存在意義、後者は会社にもたらした成果物、 を確認するための問いです。

最近、何も考えずに生きてきたツケが回ってきた感を、 ヒシヒシと感じているのですが、このあたりを真面目に考えると、 打開策が見えてくるかもしれない、と思ったりしています。

とはいえ、いざ書こうとすると、自己紹介は栗?、 後の2つはこれといって明記できるものが思いつかず… というていたらくです。

…いやいや、たぶん、すぐに思いつかないか、 きちんと考えて言葉にする方法がわからないだけで、 無価値な人間ではないはず…と思いたいです。
思うことにします。させてください。

迷える子羊状態のままですが、今回もはりきってまいりましょう。

今回のお題 - namespace をもう少し使ってみる (レベル:中級)

前回、namespace のさわりをご紹介しました。

Vol.260 - namespace を使ってみる
http://www.usupi.org/sysad/260.html

超さらっとなおさらいをしますと、Linux の namespace は、リソースを区切って、 それしかないように見せるためのものです。

で、前回は、mount、UTS、IPC、ネットワークをご紹介しました。

今回は、前回紹介できなかった、 プロセスID(PID)とユーザの namespace をご紹介します。あと、 実際に namespace を使った例もご紹介します。

PID namespace を新たに作る

というわけで、プロセスIDも、namespace を使って分離できます。

新たに PID namespace を作成すると、その後生成されるプロセスの PID は、 1番から割り振られます。
(PID namespace とプロセスの生成は同時なので、 そのプロセスに 1番が割り振られます。)

これは、おそらくコマンドでは確認できないため、 LWN.net の namespace の紹介ページに載っている、 下記のCのプログラムで試してみます。

pidns_init_sleep.c [LWN.net]
http://lwn.net/Articles/532741/

このプログラムは、clone システムコールで自分の分身となるプロセスを作ります。 その際、CLONE_NEWPID フラグを指定して、PID namespace を作成します。そして、 分身プロセスは、引数で指定されたディレクトリに procfs を mount し、 10分間 sleep します。
mount した procfs は、生成した PID namespace を参照するため、 元々の /proc とは違い、分身プロセスのプロセスしか存在しない、 というのを肌で感じることができます。

…ま、まあ、とにかく試してみましょう。

上記ページからソースコードの部分をコピペし、 pidns_init_sleep.c というファイル名で保存したら、 下記のようにコンパイルします。
(10分も寝てほしくなければ、 sleep を execlp しているところの 600 という数値(秒数)を小さめに変更してから、 下記をやりましょう。)

  $ gcc -O -o pidns_init_sleep pidns_init_sleep.c

namespace の作成には root の権限が必要ですので、 sudo経由か root になって実行します。引数には、 新しい namespace 用の procfs を mount するためのディレクトリを指定します。

  $ sudo ./pidns_init_sleep /mnt
  PID returned by clone(): 7033
  childFunc(): PID  = 1
  childFunc(): PPID = 0
  Mounting procfs at /mnt

実行すると、上記のように出力されます。自分の PID が 1、 親プロセスの PID が 0 であることがわかります。

ここですかさず [Ctrl]+[Z] を押してプロセスを止め、 procfs が mount されている /mnt を見てみると、 PID 1 のプロセスしか存在しないことが確認できます。

  $ ls -d /mnt/[0-9]*
  /mnt/1/

また、前述の出力からもわかりますが、元々の namespace から見ると、 PID が 1 ではなく 7033 であることがわかります。

  $ ps -ax | grep sleep | grep -v grep
   7031 pts/10   T      0:00 sudo ./pidns_init_sleep /mnt
   7032 pts/10   T      0:00 ./pidns_init_sleep /mnt
   7033 pts/10   S      0:00 sleep 600

ひと通り確認したら、再度 foreground で実行を再開し、 プロセスが終了するまで約10分待ちます。
このプログラムでは、procfs の umount をしないので、プロセスが終了したら、 自分で umount しましょう。

  $ fg
  (10分間待ちましょう…)
  $ sudo umount /mnt

ちなみに、LWN.net には、PID namespace のネストの例もあります。 後の始末がさらに面倒(/proc[0-4] を umount して rmdir せねばなりません) ではありますが、興味のある方は、ぜひお試しください。

multi_pidns.c [LWN.net]
http://lwn.net/Articles/532745/

ユーザ namespace を新たに作る

それから、ユーザも namespace で分離できます。

新たに namespace を作って、 元の namespace とは別のユーザとして扱うことができます。
ただ、元の namespace から参照できるよう、 元の namespace のある範囲と対応させます。

ユーザID(UID)もグループID(GID)も、通常は、0〜4294967294 番が、 そのまま 0〜4294967294 番に割り振られています。

  $ cat /proc/self/uid_map
           0          0 4294967295
  $ cat /proc/self/guid_map
           0          0 4294967295

この namespace はみんなが使っているものですし、すでに割り振られているため、 変更はできません。

ですので、新たに namespace を作って変更してみましょう。
先ほどと同様、コマンドではできないようですので、 またまたプログラムで確認をしてみたいと思います。

ns_child_exec.c [LWN.net]
http://lwn.net/Articles/533492/

上記ページから、ソースコードの部分をコピペして、 ns_child_exec.c という名前で保存したら、下記のようにコンパイルして実行します。

  $ gcc -O -o ns_child_exec ns_child_exec.c
  $ sudo ./ns_child_exec -U bash
  sub$ 

引数に「-U」オプションをつけて、ユーザ namespace の作成のお願いをしています。 また、その次の引数に bash を指定して、bash をexecしてもらっています。
(その bash のプロンプトを、以降では「sub$」で示します。)

まずは、PIDとUID、GID を確認してみましょう。

  echo $$
  13741
  sub$ id
  uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

sudo 経由で root 権限で bash を実行したにも関わらず、 nobody さんになっているようです。というのも、

  sub$ cat /proc/self/[ug]id_map
  sub$ 

新たに作った namespace では、まだなにも割り振られていないため、 UID も GID も nobody にされてしまうようです。
(/proc/sys/kernel/overflowuid, overflowgid にされるようです。)

ですので、ここで、別のシェル(元の namespace のシェル)から、 以下のように割り振ってあげます。
(新たな namespace の0番(root)を、元の namespace の1000番に割り振っています。)

  $ sudo echo 0 1000 1 > /proc/13741/uid_map

すると、ちゃんと root として認識されるようになります。

  sub$ id
  uid=0(root) gid=65534(nogroup) groups=0(root),65534(nogroup)
  sub$ cat /proc/self/uid_map
           0       1000          1

同様に、GID も割り振ってあげます。

  $ sudo echo 0 1000 1 > /proc/13741/gid_map

GID も root に戻りました。

  sub$ id
  uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
  sub$ cat /proc/self/gid_map
           0       1000          1

ホームを隠蔽して Apache を起動する

最後に、応用っぽい例を示したいと思います。
前回紹介した、mount namespace を新たに作成し、/home を見えない状態にしてから、 apache を実行してみました。
(chroot で動かすほどではなく、とりあえず /home にアクセスされないようにしたい、 という超ニッチなニーズを想定しています。)

ちなみに、Ubuntu 14.04 で確認しております。諸般の事情により、 他の環境では確認できておりません。ご了承くださいませ。

namespace の作成は unshare コマンドに任せますが、 そこから /home を見えなくして apache を起動するための細工が必要です。
そこで、下記のようなスクリプトを用意しました。

#!/bin/sh
mnt_init=$(readlink /proc/1/ns/mnt)
mnt_self=$(readlink /proc/self/ns/mnt)
if [ "$mnt_init" = "$mnt_self" ]; then
    echo "You must exec via unshare --mount." >&2
    exit 2
fi
if [ $# -eq 0 ]; then
    echo "Usage: $0 cmds..."
    exit 1
fi
mount -t tmpfs tmpfs /home
exec $*

すごいことをやっていそうですが、まったくそんなことはありません。

最初の、2〜7行目は、unshare 経由かどうか確認するためのものです。
具体的には、 init と同じ mount namespace であればunshareしていないと判断しています。
次の、8〜11行目は、 実行するコマンドを引数に指定されているかどうかを確認するためのものです。
で、最後から2番目で、/home に tmpfs を mount して、 もとの /home を見えなくしています。
そして最後に、引数で指定されたコマンドを exec しています。

…つまり、肝は最後の2行だけ、ということになります。

さて、これを、/usr/local/sbin/unshare_hide_home.sh というファイル名で保存し、 下記のように(いつものように)実行権をつけておきます。

  $ sudo chmod +x /usr/local/sbin/unshare_hide_home.sh

念のため、unshare 経由でないと実行されないことを確認します。
下記のようにエラーになれば問題ありません。

  $ sudo unshare_hide_home.sh 
  You must exec via unshare --mount.

次に、起動スクリプトである「/etc/init.d/apache2」の中の、

  $APACHE2CTL start

と書かれている行を、下記に差し替えます。

  unshare --mount /usr/local/sbin/unshare_hide_home.sh $APACHE2CTL start

そして、apache を再起動します。

  $ sudo service apache2 restart

これで、/home が tmpfs になっている状態で、 apache が起動するようになった…はずです。

もし、UserDir public_html などの設定があれば、 個人のページに閲覧ができないことで、/home の隠蔽を確認できます。
(あるいは、Alias やシンボリックリンクなどでご確認ください。)

ただ、この方法、少なくとも一つ、問題があります。
/home の tmpfs を umount していませんので、起動や再起動のたびに、 /etc/mtab のエントリが増えていきます。

  $ df
  Filesystem      1K-blocks       Used  Available Use% Mounted on
  ...中略...
  tmpfs            1920656K         0K   1920656K   0% /home
  tmpfs            1920656K         0K   1920656K   0% /home

実際には mount されていない(umount するとないよ! と言われます)ですし、 起動スクリプトなどで安直に umount するのはコワイため、 対処するのは諦めました。
ですが、/home が独立したパーティションでなければ、 安直に umount を入れても問題ないかもしれません。

おわりに

以上、プロセスIDとユーザの namespace と、応用例をご紹介しました。

いずれも、コンパイルして実行したり、 スクリプトを作ったり差し替えが必要だったりと、 初心者の方にとっては敷居が高かったかもしれません。
ですが、丁度いい機会だと思って、ぜひやってみてください。

今回も、元ネタの大半は、LWN.net の下記ページの情報です。
もう少し踏み込んで理解したいという方は、下記を参考にしてください。

Namespaces in operation, part 1: namespaces overview [LWN.net]
http://lwn.net/Articles/531114/

宿題の答え

前回の宿題は、

  unshare で作られた namespace は、unshare 終了後どうなるのでしょうか?

でした。

実際にコードを見たほうが早そうなので、そうしてみました。
まず、unshareコマンドは、「util-linux」に含まれます。

  $ dpkg -S `which unshare`
  util-linux: /usr/bin/unshare

ですので、util-linuxのソースを、どーんと入手します。

  $ apt-get source util-linux
  $ ls
  util-linux-2.20.1/
  util-linux_2.20.1-5.1ubuntu20.1.diff.gz
  util-linux_2.20.1-5.1ubuntu20.1.dsc
  util-linux_2.20.1.orig.tar.gz

といっても、 見るソースは「util-linux-2.20.1/sys-utils/unshare.c」だけなのですが…。

さて、眺めてみますと、指定されたオプションに従ってフラグを求めて、 unshareシステムコールを呼んでいるだけでした。
そして、execvpでコマンドをexecしたらおしまいです。

  if(-1 == unshare(unshare_flags))
      err(EXIT_FAILURE, _("unshare failed"));

  ...中略...

  execvp(argv[optind], argv + optind);
  err(EXIT_FAILURE, _("exec %s failed"), argv[optind]);

ちなみに、execvpが成功したら戻ってこないので、 最後のerrは失敗したときしか実行されません。

じゃあ、カーネル側はどうしているかと言いますと、 「kernel/fork.c」で処理をされています。

kernel/git/stable/linux-stable.git - Linux kernel stable tree
https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/kernel/fork.c?id=refs/tags/v3.15.3

unshareシステムコールの処理は、下記関数で行っています。
話を単純にするため、以降では、フラグ「CLONE_FS」に限定して、 処理を追っかけたいと思います。
で、CLONE_FS に関する処理は、unshare_fs()で行っています。

  SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags) {
      ...中略...
      err = unshare_fs(unshare_flags, &new_fs);

具体的には、フラグに「CLONE_FS」が立っていたら、 新たに fs をコピーして作ります。具体的には下記の通りです。

  static int unshare_fs(unsigned long unshare_flags, struct fs_struct **new_fsp) {
      struct fs_struct *fs = current->fs;

      if (!(unshare_flags & CLONE_FS) || !fs) // フラグのチェック
          return 0;

      if (fs->users == 1) // 自分だけならコピーしない
          return 0;

      *new_fsp = copy_fs_struct(fs);

clone_fs() を呼び出した後、新たに作られた fs が new_fs にあれば、 current(実行中のプロセス)の fs に設定します。

  if (new_fs) {
      fs = current->fs;
      spin_lock(&fs->lock);
      current->fs = new_fs;
      ...中略...
      spin_unlock(&fs->lock);
  }

…あ、そもそも、プロセス終了時にどうなるのかというのが問題でした。
その処理は、「kernel/exit.c」のdo_exit()で行っています。
ここでは、fs の状態に関係なく、「fs/fs_struct.c」の exit_fs() で、 下記のように fs の開放処理を試みます。

  void exit_fs(struct task_struct *tsk) {
      ...中略...
      task_lock(tsk);
      spin_lock(&fs->lock);
      tsk->fs = NULL;
      kill = !--fs->users;  // 自分だけの fs なら kill は真
      spin_unlock(&fs->lock);
      task_unlock(tsk);
      if (kill)
          free_fs_struct(fs);  // kill が真なら fs を開放

ここでは、unshare で作られた fs だろうがそうでなかろうが、 他に使用している人がいなければちゃんと開放されます。

というわけで、大方の予想通り、unshare終了後はちゃんと開放される、 というのが正解でした。

カーネルのコードを書く際は、いろいろ考えることがありますが、 処理をざっくり理解するだけなら、上記のようにサラサラっと眺めるだけです。

あまり身構えないで、まずは中を見てみることをお勧めします。

今回の宿題

今回の宿題は、

  新たに作った mount namespace で mount したら、namespace 開放時に
  umount されるのでしょうか。

です。

応用例で、/home に tmpfs を mount しましたが、 よく考えたら umount をしていません。どんどん mount しっぱなしになっていたら、 そのうち大変なことになったりするかもしれません。

前回の宿題の続きみたいな感じですが、これをよい機会だと思って、 問題ないかどうかはっきりさせたいと思います。

あとがき

最近、「スチュワード」という言葉を、よく目にします。

たとえば、今はやりのビッグデータな書籍には、 「データスチュワード」なる役割が出てきます。
経営・業務部門が担当する「データガバナンス」 (方針を決めてデータを扱っていく仕組み)と、 システム部門が担当する「データマネジメント」の橋渡しをする人材のこと、 だそうです。

すべてわかるビッグデータ大全
http://www.amazon.co.jp/exec/obidos/ASIN/482226291X/usupiorg-22

また、ここ数日の日経新聞には、「スチュワードシップコード」という、 機関投資家向けの行動規範を指す言葉が載っていました。
投資家は利益を求めるだけでなく、企業の成長や経済の発展を考慮して、 行動規範に従った積極的な参加や行動を行うべきではないか、 ということのようです。

スチュワードシップコード | 証券用語解説集 | 野村證券
https://www.nomura.co.jp/terms/japan/su/A02233.html
スチュワードシップコード - 日本経済新聞
http://www.nikkei.com/money/investment/toushiyougo.aspx?g=DGXNASFZ25038_25072013K10600

「steward」はもともと、執事、財産管理人、 乗客係(スチュワーデスって言いますよね)といった意味です。
個人的には、スチュワードという言葉自体からは、受動的なニュアンスを感じます。
ですが、前述のデータスチュワードなどからは、全体を考えて積極的に! という能動的な、力強さのようなものを感じます。

ある技術を深く掘り下げて理解していくことは、 技術者として必要なことだと思います。ですが、いま必要とされているのは、 複数の分野を束ねて積極的に活用していける人材なのではないか、という気がしました。

 

…といいつつ、当メルマガは、重箱の隅にあるような(失礼!)技術を取り上げ、 何に役立つかという視点ではなく、こうしたら動いたとか、 こんなふうになっています的なことばかりを書いてきました。

そして、今後も、狭い視野で微妙な技術の方法論に注目していきたい… と思っております。どう役立てればいいかは、自己責任でおねがいします!

 

今回も、ここまで読んでいただき、誠にありがとうございました。
次回は、7月20日(日) 未明にお会いしましょう!

 

「いますぐ実践! Linux システム管理」の解除は、以下からできます。
http://www.usupi.org/sysad/ (まぐまぐ ID:149633)

バックナンバーは、こちらにほぼ全部そろっています。
http://www.usupi.org/sysad/backno.html

「栗日記」- 地味に、毎日栗の絵を描いております。
http://www.usupi.org/kuri/ (まぐまぐ ID:126454)
http://usupi.seesaa.net/ (栗日記ブログ)
http://usupi.org/k/ (モバイル栗日記)
http://twitter.com/kuriking/ (栗つぶやき)
http://facebook.com/kuriking3 (栗顔本)


[バックナンバーのトップへ] [Linux システム管理のトップへ]

トップ

バックナンバー
    [日付順] [目的別]

プロフィール

▼ リンク

独学Linux
Linuxデスクトップ環境に関する情報が満載です。 メルマガもありますよ。
Server World
CentOS 6をサーバとしたときの設定例が、これでもかというくらいたくさん載っています。 CentOS以外のディストリビューション(Fedora, Ubuntu)も充実しています。
LINUXで自宅サーバーを構築・導入(Fedora9)
Fedora9のインストールの仕方から管理方法まで、詳しく載っています。 SearchManには情報がもりだくさんです。
マロンくん.NET
〜サーバ管理者への道〜
Linuxをサーバとして使用するための、いろいろな設定方法が載っています。 マロンくんもかわいいです。 なんといっても、マロンくんという名前がいいですね!!
日経Linux
今や数少なくなってしまったLinuxの雑誌。ニュースやガイドもあります。
Linux Square − @IT
@ITが提供する、Linux の情報が満載。 載っていない設定方法はないんじゃないでしょうか。
gihyo.jp…技術評論社
Linuxに限らず様々な技術情報が満載のサイト。 SoftwareDesign誌も、 ソフトウェア技術者は必見です。
SourceForge.JP Magazine
Linux に限らず、オープンソース関連の記事が網羅されています。
ITmediaエンタープライズ:Linux Tips 一覧
Tips というより FAQ 集でしょうか。わからないことがあれば覗きましょう。
IBM developerWorks : Linux
開発者向けですが、勉強になりますよ。
Yahoo!ニュース - Linux
Yahoo!のLinuxに関するニュース一覧です。
栗日記
システム管理とかと全然関係ありませんが、毎日栗の絵を描いています。
システム管理につかれちゃったとき、癒されたいときに、ご覧ください。:-)
WEB RANKING - PC関連
ランキングに参加してみました。押してやってください。

▼ 作ってみました

Add to Google

▼ せんでん




▼ 最近読んだ本

▼ 気に入ってる本