LXDを使っていると非特権コンテナであってもホストとコンテナ間で気軽にファイルを共有したいことがよくあります。今回はその手順をまとめておきましょう。
LXDと非特権コンテナ
LXDはかんたんにシステムコンテナを構築できるツールです。「コンテナ型仮想化」と言えばDockerが有名ですが、これは主に「1コンテナ1サービス」というアプリコンテナとしての使われ方が主流です。それに対してLXDは「普通のLinuxシステム」をひとつのコンテナの中に構築します。どちらかと言うとKVMやVirtualBox、VMWareの代わりに使うタイプのソフトウェアです。
用途の違いからDockerとLXDは排他的ではなく、適材適所に使い分けることができます。もちろんLXDの上でDockerを動かすことも可能です。DockerとLXDの違いについては第459回にまとめていますので、そちらも参照してください。
LXDでは原則として「非特権コンテナ」を構築します。非特権コンテナではユーザー名前空間を用いて、コンテナとホストの間でUIDやGIDのマッピングを行います[1]。このためコンテナ上のroot(UID=0)はホスト上ではUID=0以外のUID、つまりは一般ユーザーとして見えるのです。またそれ以外のコンテナの一般ユーザーはIDは別の値にマッピングされてシステムが起動することになります。
ホスト上では、root(UID=0)がUID=165536、ubuntu(UID=1000)がUID=166536になっていることがわかります。また、プロセス名前空間も使っているためPIDもコンテナとホストでずれていることがわかりますね。このようにコンテナの内部のrootを含めたユーザーはホストから隔離されています[2]。
実際にどの値にマッピングされるかは、デスクトップ版にあとからLXDをインストールするか、最初からLXDがインストールされているサーバー版を使うかで微妙に異なります。
- デスクトップ版:コンテナ内部のUID/GIDの0から65535は、ホスト上では165536から231071にマッピングされる
- サーバー版:コンテナ内部のUID/GIDの0から65535は、ホスト上では100000から165535にマッピングされる
本連載でも第475回や第416回などで、ソースコードが明らかでないデスクトップ向けアプリのバイナリを隔離環境で動かす際に利用していました。ただしこのままだとコンテナ内部のアプリが作ったデータをホストに移動するのは大変ですし、逆も同じです。特に一般ユーザーのUIDとGIDが異なるため、単純に移動してもそのままでは変更できないことがあります。そこで今回はホストとゲストでシームレスにファイルのやり取りが行える仕組みを考えます。
LXDコンテナ環境の準備
まずはLXD環境を構築しましょう。LXDはUbuntu 16.04 LTS以降であれば簡単に構築できます。Ubuntu 16.04 LTSの公式リポジトリであれば2.0系、それより新しいリリースであればより新しいLXD 2.xがリポジトリに用意されているからです。また16.04のバックポートリポジトリから最新版をインストールできます。2.xのほうがいろいろと機能追加されていて便利なのですが、バージョンも頻繁にアップデートされるため、Ubuntuのサポートの範囲内で安定的に運用するなら2.0を使うことをおすすめします。今回の記事に関しては、2.0でも2.x系のUbuntuリポジトリ上の最新版である2.14でも動くように記述しています。
最初にLXDをインストールします。サーバー版のUbuntu 16.04 LTSであれば、最初からインストールされているはずです。明示的にインストールする場合は、次のコマンドを実行してください。
インストールと同時にlxdグループが作成され、sudo
を実行できるユーザーは自動的にlxdグループに所属します。グループの変更を反映するために、一度ログインしなおしましょう。lxdグループに所属すると、sudo
なしにlxc
コマンドを実行できるようになります。
次にLXDの初期設定を行います。LXDは「sudo lxd init
」によって、全体的な初期設定を行えます。設定するのはストレージのバックエンド(コンテナのルートファイルシステムをどのように保存するのか)とコンテナのネットワーク設定です。というわけで、インストール直後に一回だけ実行します。
基本的に既定の設定をそのままOKしていくだけです。詳しいことはUbuntu Weekly Recipeの第459回の2ページ目を参照してください。
次にUbuntu 16.04 LTSベースのコンテナ(コンテナ名:sample)を作ります。
これでコンテナは完成です。Ubuntuコンテナは最初からOpenSSHサーバーが動いています。ただしubuntuユーザーのパスワードがロックされているためそのままではログインできません。そこでssh-import-id
コマンドで、コンテナ内部のubuntuユーザーのホームディレクトリにLaunchpadから公開鍵をダウンロードしています。
lxc fileコマンドを使ったファイルの受け渡し
ホストから特定のコンテナとのファイルを受け渡ししたいのであれば、lxc file
コマンドが使えます。
気をつけなければいけないのは、コンテナ側のファイル名は「コンテナ名/フルパス」の書式であるということです。sampleコンテナの/etc/environment
なら「sample/etc/environment
」になります。「-r
」オプションをつけると、再帰的に受け渡しします。またcp
コマンドなどと同じく送信元のファイル(=最後の引数を除いたファイル)は複数指定可能です。その場合、保存先はディレクトリになります。
lxc file push
の場合、「-p
」オプションを付けることで保存先のパスに合わせてディレクトリを作成します。また「--uid
」「--gid
」「--mode
」オプションによって保存先のファイルオーナーやモードを変更できます。
非特権コンテナであってもlxc file
コマンドを使う限り、UID等の変更は「うまく」扱ってくれます。コンテナ上のUID=1000(ホスト上のUID=166536)がオーナーのファイルをホストにpullしたら、ホスト上のUID=1000がオーナーになりますし、その逆もまた然りです。コンテナとホストの間を手動で受け渡しすれば事足りるのであれば、lxc file
だけで十分です。特に別ホストにいるコンテナ(リモートコンテナ)にも同じコマンドで送ることができるというメリットがあります。
ちなみにlxc file
にはファイルを削除するlxc file delete
やホスト上のテキストエディターでコンテナのファイルを編集できるlxc file edit
コマンドもあります。特に後者は、ちょっとコンテナの設定を書き換えたいときに真価を発揮します。
ホストのディレクトリーツリーをbind mountする
名前空間による制限により、コンテナからホストのルートファイルシステムを直接見ることはできません。またAppArmorにより、コンテナ内部からのブロックファイルのmountは禁止されています。もしコンテナとホストの間、もしくは同じホスト上のコンテナ間でファイルやディレクトリを共有したい場合は、コンテナの設定でbind mountする方法が便利です。
上記のコマンドはホストの/srv/shared
(source=
オプション)を、コンテナ内部の/srv/shared
(path=
オプション)としてbind mountします。つまりホストとコンテナで/srv/shared
を共有できるというわけです。
lxc config
による設定は永続的に反映されますので、コンテナの再起動を行ってもそのまま残っています。
この方法のメリットは、ホスト側のファイルシステムに関係なく同じ方法でコンテナと共有できることです。たとえばコンテナの内部からNFSディレクトリをmountする場合も、一度ホスト側でNFSディレクトリをmountしておけば、それをそのままコンテナにbind mountできるのです。よって同じホスト上のコンテナ間だけでなく、ネットワーク越しのコンテナ間ともファイルやディレクトリを共有できます。
ただしlxc file
と異なり、UID等のマッピングの変更は行いません。よってコンテナの一般ユーザーで作ったファイルやディレクトリのオーナーは、ホストから見るととても大きなUIDを持っていることになります。
ホストとコンテナでUID等を一致させる
bind mountによる共有ディレクトリを使う場合、ホストとコンテナで一般ユーザーのUID等を一致させたほうが便利です。つまりコンテナのUID=1000はホストでもUID=1000にします。もちろんrootを一致させるとそれはとどのつまり「ほぼ特権コンテナ」となりますので、非特権コンテナにしておく意味が薄れます。あくまで「普段使うユーザー」のみUIDを一致させるのです。理想的にはそのユーザーはsudoグループに入っていないほうがいいでしょう。
UID等のマッピングは/etc/subuid
、/etc/subgid
ファイルで管理しています。UID=1000、GID=1000のユーザーとグループはホストとコンテナで一致したい場合は次のように設定します。
このうちroot
は今回設定したUID等のマッピングを許可するユーザーです。LXDの場合はrootがコンテナ起動時にマッピングを行うのでroot
にしています。書式は「USER:START:COUNT
」です。上記設定の場合は、「ID=1000から1つ」になります。もちろん「1
」を「100
」にして複数のUIDをマッピングすることも可能です。
コンテナ内部のマッピングに関する設定は、コンテナごとに行います。
「both
」はUIDとGID両方に同じ値を設定するパラメーターです。書式は「both HOST_ID CONTAINER_ID
」となります。HOST_ID
にはホスト上のUID/GIDを、CONTAINER_ID
にはHOST_ID
をコンテナの中にマップした時のIDを記述します。言い換えると「ホスト上のUID/GID 1000をコンテナ上のUID/GID 1000として扱う」ということです。
もしUID/GIDを異なる値にしたい場合はboth
のかわりにuid
やgid
を使います。
uid
とgid
を同時に設定したい場合は、改行でつなぎます。しかしながらlxc configコマンドは直接改行を扱えません。そこで設定内容を標準入力から受け付けるようにして、そこにパイプで設定内容を流し込みます。
ハイフンをつないで範囲を指定することも可能です。この場合、マッピング元とマッピング先の個数が同じになるようにしてください。
変更した設定を反映するには、一度そのコンテナを再起動します。冒頭と同じようにプロセスのUIDをコンテナとホストで比べたら、UID=0はマッピングされているものの、UID=1000はコンテナとホストで一致していることがわかるはずです。
これらを組み合わせれば、ホスト・コンテナ間のファイルやディレクトリのアクセス権をそれなりにコントロールできることでしょう。