lxc.container.conf(5) | lxc.container.conf(5) |
lxc.container.conf - LXC コンテナ設定ファイル
LXC は良く知られた、多くのテストが行われた Linux コンテナのランタイムです。LXC は、2008 年以来アクティブに開発されており、世界中の重要な本番環境で実証されています。開発への貢献者の中には、Linux カーネル内の良く知られた様々なコンテナ機能の実装に貢献した人と同じ人もいます。
LXC は主にシステムコンテナにフォーカスを当てています。つまり、VM で得られる環境と可能な限り近い環境を提供を提供するにも関わらず、別々のカーネルを実行したり、ハードウェアをすべてシミュレートしたりすることによるオーバーヘッドがないコンテナのことです。
このような環境は、名前空間 (namespace)、強制アクセスコントロール、cgroup といったカーネルのセキュリティ機能の組み合わせで実現しています。
LXC は非特権コンテナをサポートしています。非特権コンテナは、いかなる特権も持たずに実行するコンテナです。非特権コンテナの実行には、コンテナを実行しているカーネルにユーザ名前空間 (user namespace) のサポートが必要です。LXC は、ユーザ名前空間がメインラインカーネルにマージされてから、初めて非特権コンテナをサポートしたランタイムです。
本質的には、ユーザ名前空間は与えられた UID、GID の組を隔離します。ユーザ名前空間は、ホスト上の UID、GID のある範囲を、それとは異なるコンテナ上の UID、GID の範囲へマッピングすることで実現します。カーネルは、ホスト上では実際には UID、GID は特権を持たないにも関わらず、コンテナ内ではすべての UID、GID が期待されるように見えるように変換を行います。 例えば、コンテナ内では UID、GID が 0 として実行中のプロセスは、ホスト上では UID、GID が 100000 として見えるでしょう。実装と動作の詳細は、ユーザ名前空間の man ページから得られます。UID と GID のマッピングは lxc.idmap を使って定義できます。
Linux コンテナは、簡単な設定ファイルで定義します。設定ファイル中のオプションは key = value の形で一行で表します。'#' は、その行はコメントであることを示します。ケーパビリティや cgroup のオプションのような、リスト形式で指定するオプションでは、value がない形式で指定でき、そのように使うと、それ以前に定義した値をすべてクリアします。
LXC は、シングルドットを使って設定キーの名前空間を表します。lxc.net.0 のような複雑な設定キーは、lxc.net.0.type、lxc.net.0.link、lxc.net.0.ipv6.address や、さらに細分化された設定向けの色々なサブキーを持つことを意味します。
複数の関係するコンテナの管理を容易にするために、コンテナの設定ファイルに別のファイルをロードすることが可能です。 例えば、ネットワークの設定を、複数のコンテナから include させるように 1 つのファイルに定義することが可能です。 その場合、コンテナが他のホストに移動すると、そのファイルだけを更新する必要があるかもしれません。
コンテナに対してアーキテクチャを設定することが可能です。 例えば、64 ビットのホスト上で 32 ビットのバイナリを動かすために 32 ビットアーキテクチャを設定することが可能です。 この設定を行うことにより、パッケージのダウンロードを行うなどの作業のうち、アーキテクチャ名に依存するような作業を行うコンテナスクリプトの修正を行います。
有効なオプションには以下のようなものがあります。 x86, i686, x86_64, amd64
utsname セクションは、コンテナに設定されるホスト名を定義します。 コンテナは、システムのホスト名を変えることなく、自身のホスト名を持つ事が可能です。 このことにより、ホスト名はコンテナ専用となります。
コンテナをクリーンにシャットダウンするためにコンテナの init プロセスに送るシグナル名か番号を指定できます。init システムによって、クリーンなシャットダウンを行うために使うシグナルは異なります。このオプションではシグナルとして kill(1) で使う形式を指定できます。 例えば SIGKILL, SIGRTMIN+14, SIGRTMAX-10 のような形式、もしくは数字を指定します。デフォルトのシグナルは SIGPWR です。
コンテナをリブートするために送るシグナル名か番号を指定できます。このオプションではシグナルとして kill(1) で使う形式を指定できます。 例えば SIGKILL, SIGRTMIN+14, SIGRTMAX-10 のような形式、もしくは数字を指定します。デフォルトのシグナルは SIGINT です。
コンテナを強制的にシャットダウンするために送るシグナル名か番号を指定できます。このオプションではシグナルとして kill(1) で使う形式を指定できます。 例えば SIGKILL, SIGRTMIN+14, SIGRTMAX-10 のような形式、もしくは数字を指定します。デフォルトのシグナルは SIGKILL です。
コンテナの init として使うコマンドを設定します。
コンテナのワーキングディレクトリとして、コンテナ内の絶対パスを設定します。LXC は init を実行する前に、このディレクトリに移動します。
init と後続のコマンドが使う UID/GID を設定します。システムコンテナを起動するのに非 root な UID を使うと、特権がないために動作しないでしょう。UID/GID の指定は、通常はアプリケーションコンテナの動作の際に役に立ちます。 デフォルト値: UID(0)、GID(0)
コアスケジューリングは、コンテナのペイロードが同じコアでスケジュール可能であるとマークするかどうかを指定します。 これによりカーネルスケジューラーは、同じグループに属さないタスクが同一コア上で同時に実行されないようにします。 これは、コンテナペイロードがクロスハイパースレッド攻撃を受けることを防ぐための、追加のセキュリティ対策として機能させることができます。
コンテナ内の proc ファイルシステムで設定できるパラメータを設定します。
lxc.proc.oom_score_adj = 10
シャットダウン後にコンテナを削除するかどうかを指定できます。
ネットワークセクションは、コンテナ内でどのようにネットワークを仮想化するかを定義します。 ネットワークの仮想化はレイヤー 2 で作動します。 ネットワークの仮想化を使用するためには、コンテナのネットワークインターフェースを定義しなければなりません。 いくつかの仮想インターフェースをアサインすることができます。 そして、仮に物理ネットワークインターフェースが一つしかなくても、コンテナ内でいくつもの仮想インターフェースを使うことができます。
none: ホストのネットワーク名前空間を共有します。 これにより、ホストのネットワークデバイスをコンテナ内で使うことが可能になります。 もしコンテナもホストも init として upstart を使っている場合、(例えば) コンテナ内で 'halt' を実行すると、ホストがシャットダウンしてしまうことにもなります。 非特権コンテナでは、sysfs をマウントできないので、この設定は動作しません。この問題に対する回避策は、ホストの sysfs を bind マウントすることです。ただしこの回避策は安全ではありません。
empty: ループバックインターフェースだけを作成します。
veth: 一方がコンテナに、もう一方がホストに接続されるペアの仮想イーサネットデバイスを作成します。 lxc.net.[i].veth.mode は、veth の親(ホスト側)がホスト上で使うモードを指定します。 指定できるモードは bridge と router です。 指定しない場合のデフォルトのモードは bridge です。 bridge モードでは、ホスト側は、lxc.net.[i].link オプションで指定したブリッジに接続されます。 もし、ブリッジが指定されていない場合、veth ペアデバイスは作成されますが、ブリッジには接続されません。 ブリッジはコンテナが開始する前にシステムで事前に設定しておく必要があります。 lxc はコンテナ外の設定を扱うことはありません。 router モードでは、ホスト側の veth インターフェースを指すコンテナの IP アドレスに対して、ホスト上でスタティックルートが作成されます。 加えて、コンテナがホストに到達できるために、コンテナ内で定義されたゲートウェイの IP アドレスに対して、Proxy ARP と Proxy NDP エントリがホスト側の veth インターフェースに追加されます。 デフォルトでは、lxc がコンテナの外部に属するネットワークデバイスに対する名前を決定します。 しかし、もしこの名前を自分で指定したい場合、lxc.net.[i].veth.pair オプションを使って名前を設定し、lxc に対して指定をすることができます (非特権コンテナの場合をのぞきます。セキュリティ上の理由からこのオプションは無視されます)。 lxc.net.[i].veth.ipv4.route、lxc.net.[i].veth.ipv6.route オプションを使って、静的ルーティングをコンテナを指し示すホスト上に追加できます。 複数のルートがある場合は複数の設定を指定します。 ルートは x.y.z.t/m の形式です。例: 192.168.1.0/24 bridge モードでは、タグなし VLAN は lxc.net.[i].veth.vlan.id で設定できます。このオプションでは、コンテナポートをブリッジのデフォルトのタグなし VLAN から削除するための特別な値 'none' が指定できます。コンテナのブリッジポートを複数のタグ付き VLAN に所属させるために、lxc.net.[i].veth.vlan.tagged.id を複数回指定できます。
vlan: vlan インターフェースは lxc.net.[i].link で指定されたインターフェースとリンクし、コンテナに割り当てられます。 vlan の指定は lxc.net.[i].vlan.id オプションで指定します。
macvlan: macvlan インターフェースは lxc.net.[i].link により指定されるインターフェースとリンクし、コンテナに割り当てられます。 lxc.net.[i].macvlan.mode でモードを指定すると、その macvlan の指定を、同じ上位デバイスで異なる macvlan 間の通信をする時に使います。 指定できるモードは private、vepa、bridge、passthru のいずれかです。 private モードの場合、デバイスは同じ上位デバイスの他のデバイスとの通信を行いません (デフォルト)。 新しい仮想イーサネットポート集約モード (Virtual Ethernet Port Aggregator (VEPA)) である vepa モードの場合、隣接したポートが、ソースとデスティネーションの両方が macvlan ポートに対してローカルであるフレームを全て返すと仮定します。 すなわち、ブリッジが reflective relay として設定されているということです。 上位デバイスから入ってくるブロードキャストフレームは、VEPA モードである全ての macvlan インターフェースに送りつけられます。 ローカルのフレームはローカルには配送されません。 bridge モードの場合、同じポートの異なる macvlan インターフェースの間のシンプルなブリッジとして動作します。 あるインターフェースから他のインターフェースへのフレームは、直接配送され、外部には送出されません。 ブロードキャストフレームは、全ての他のブリッジと外部のインターフェースに対して送られます。 しかし、reflective relay からフレームが返ってきたときは、再度それを配送することはしません。 全ての MAC アドレスを知っているので、ブリッジモジュールのように、macvlan ブリッジモードは学習や STP の必要はありません。 passthru モードの場合、物理インターフェースで受け取った全てのフレームは macvlan インターフェースに転送されます。passthru モードの場合、ひとつの macvlan インターフェースだけが、ひとつの物理インターフェースに対して設定できます。
ipvlan: ipvlan インターフェースは lxc.net.[i].link により指定されるインターフェースとリンクし、コンテナに割り当てられます。 lxc.net.[i].ipvlan.mode でモードを指定すると、その ipvlan の指定を、同じ上位デバイスで異なる ipvlan 間の通信をする時に使います。 指定できるモードは l3、l3s、l2 で、デフォルトは l3 モードです。 l3 モードでは、L3 までの TX (送信) 処理はスレーブデバイスにアタッチされたスタックインスタンス上で行われます。 そしてパケットは、L2 処理のためにマスターデバイスのスタックインスタンスにスイッチされます。このインスタンスからのルーティングは、発信デバイス上でキューに入る前に使われます。 このモードでは、スレーブはマルチキャスト・ブロードキャストのトラフィックを受信しませんし、送信もできません。 l3s モードは、TX (送信) 処理は L3 モードと非常に似ていますが、iptables (conn-tracking) がこのモードで動作します。 それゆえに L3対称 (symmetric) (L3s) です。このモードは若干パフォーマンスが低下しますが、conn-tracking (接続追跡) が動作するように、普通の L3 モードの代わりにこのモードを選んでいるので問題にはならないはずです。 l2 モードでは、TX (送信) 処理はスレーブデバイスにアタッチされたスタックインスンタンス上で行われます。 パケットは送信のため、マスターデバイスにスイッチされ、マスターデバイス上でキューに入ります。このモードでは、スレーブはマルチキャストも(該当する場合)ブロードキャストも RX/TX (送受信) 処理します。 lxc.net.[i].ipvlan.isolation は隔離モードを指定します。隔離モードには bridge、private、vepa が指定できます。デフォルトは bridge モードです。 bridge 隔離モードでは、スレーブはマスターデバイス経由の通信とは別に、スレーブ同士で通信できます。 private 隔離モードでは、ポートはプライベートに設定されます。つまり、スレーブ間の通信はできません。 vepa 隔離モードでは、ポートは VEPA モードに設定されます。つまり、802.1Qbg で説明されているように、ポートはスイッチング機能を外部エンティティにオフロードします。
phys: lxc.net.[i].link で指定された、すでに存在しているインターフェースがコンテナに割り当てられます。
up: インターフェースを起動させます。
すべてのフックで追加の情報が使えます。以下の情報がスクリプトに提供されます:
この情報が環境変数の形で提供されるか、スクリプトへの引数の形で提供されるかは lxc.hook.version の値によって決まります。もし lxc.hook.version が 1 に設定されている場合は、環境変数の形で提供されます。もし 0 が設定されている場合は、スクリプトへの引数として提供されます。
スクリプトからの標準出力は debug レベルでロギングされます。標準エラー出力はロギングされません。しかし、フックの標準エラー出力を標準出力にリダイレクトすることにより保存することは可能です。
すべてのフックで追加の情報が使えます。以下の情報がスクリプトに提供されます:
この情報が環境変数の形で提供されるか、スクリプトへの引数の形で提供されるかは lxc.hook.version の値によって決まります。もし lxc.hook.version が 1 に設定されている場合は、環境変数の形で提供されます。もし 0 が設定されている場合は、スクリプトへの引数として提供されます。
スクリプトからの標準出力は debug レベルでロギングされます。標準エラー出力はロギングされません。しかし、フックの標準エラー出力を標準出力にリダイレクトすることにより保存することは可能です。
さらに厳しい隔離のために、コンテナは自身のプライベートな pseudo tty (擬似端末) を持つことが可能です。
コンテナでルートファイルシステムを持つように設定されており、inittab ファイルでコンソールの使用が設定されている場合、このコンソールの出力がどこになされるのかを指定したいと思うでしょう。
このオプションはコンテナが root ファイルシステムを持つように設定されており、inittab ファイルで tty 上に getty の起動が設定されている場合に役に立ちます。 このオプションはコンテナで利用できる tty の数を指定します。 inittab ファイルに設定する getty の数は、このオプションの指定する tty の数より大きくしてはいけません。 さもなければ、超過した分の getty セッションはコンソールか /var/log/messages にうっとうしいメッセージを生死を表示しながら、永久に生死を繰り返すでしょう。
LXC のコンソールはホストによって作られ、コンテナ内で要求されたデバイスに bind マウントされた Unix98 PTY 経由で提供されます。 デフォルトでは /dev/console と /dev/ttyN に bind マウントされます。 これはゲスト内でのパッケージのアップグレードを妨げる可能性があります。 なので /dev 以下のディレクトリを指定することができます。 LXC はこのディレクトリ以下にファイルを作成し、これらのファイルを bind マウントします。 そして、これらの (作成された) ファイルは /dev/console と /dev/ttyN にシンボリックリンクされます。 シンボリックリンクを消去したり置き換えたりすることは可能ですから、パッケージのアップグレードは成功します。
デフォルトでは、lxc はコンテナの /dev 以下に fd, stdin, stdout, stderr のシンボリックリンクを作成しますが、自動的にはデバイスノードのエントリは作成しません。 これは、コンテナの rootfs で必要な設定を行えるようにするものです。 lxc.autodev が 1 に設定されている場合、コンテナの rootfs をマウントした後、LXC は新しい tmpfs を /dev 以下にマウントします (デフォルトでは 500k 制限でマウント、lxc.autodev.tmpfs.size で指定可能)。 そして初期デバイスの最小限のセットを作成します。 これは、"systemd" ベースの "init" 環境のコンテナを起動する時に通常必要ですが、他の環境の場合はオプショナルなものです。 コンテナの /dev ディレクトリ内の追加デバイスは lxc.hook.autodev フックを使用して作成されます。
マウントポイントセクションは、マウントするための区別された場所を指定します。 これらのマウントポイントは、コンテナだけに見え、コンテナ外で実行されるプロセスから見えることはありません。 例えば、/etc や /var や /home をマウントするときに役に立つでしょう。
注意: 通常 LXC は、マウント対象と相対パス指定のバインドマウントを、適切にコンテナルート以下に閉じ込めます。 これは、ホストのディレクトリやファイルに対して重ね合わせを行うようなマウントによる攻撃を防ぎます。(絶対パス指定のマウントソース中の各パスがシンボリックリンクである場合は無視されます。) しかし、もしコンテナの設定が最初に、/home/joe のようなコンテナユーザのコントロール配下にあるディレクトリを、コンテナ中のある path にマウントし、その後 path 以下でマウントが行われるような場合、コンテナユーザがタイミングを見計らって自身のホームディレクトリ以下でシンボリックリンクを操作するような TOCTTOU 攻撃が成立する可能性があります。
proc proc proc nodev,noexec,nosuid 0 0
この例は、root ファイルシステムがどこにあっても、コンテナの /proc 以下に proc ファイルシステムをマウントします。これは、ブロックデバイスがバックエンドのファイルシステムだけでなく、コンテナのクローンにも柔軟に対応できます。
ファイルシステムがイメージファイルやブロックデバイスからマウントされている場合、3 つ目のフィールド (fs_vfstype) は mount(8) のように auto を指定することはできず、明確に指定しなければいけません。
dev/null proc/kcore none bind,relative 0 0
は dev/null を ${LXC_ROOTFS_MOUNT}/dev/null と展開し、コンテナ内の proc/kcore にマウントします。
cgroup 名前空間が有効の場合、cgroup の自動マウントの指定はどれも無視されます。これは、コンテナが自身でファイルシステムをマウントするため、自動マウントがコンテナの init を混乱させる可能性があるためです。
cgroup ファイルシステムの自動マウントが有効の場合、/sys/fs/cgroup 以下の tmpfs は常に読み書き可能でマウントされることに注意が必要です (しかし :mixed と :ro の場合は、個々の階層の /sys/fs/cgroup/$hierarchy は読み込み専用となるでしょう)。これは Ubuntu の mountall(8) コマンドの特異な動きに対処するためのものです。特異な動きとは、/sys/fs/cgroup が読み込み専用でマウントされた状態で、コンテナが CAP_SYS_ADMIN を持たない場合、/sys/fs/cgroup を読み書き可能で再マウントしようとしてできないため、コンテナのブート時にユーザからの入力を待ってしまうというものです。
例:
lxc.mount.auto = proc sys cgroup
lxc.mount.auto = proc:rw sys:rw cgroup-full:rw
コンテナのルートファイルシステムは、ホストのルートファイルシステムと異なるようにすることも可能です。
ディレクトリ、単純なブロックデバイスのバックエンドを持つコンテナの場合、パス名を使うことができます。 もし rootfs が nbd デバイスの場合、nbd:file:1 という指定は file を nbd デバイスとして使用し、その 1 番目のパーティションが rootfs としてマウントされます。 nbd:file という指定は、nbd デバイス自身をマウントします。 overlayfs:/lower:/upper という指定は、rootfs は /lower という読み込み専用でマウントされるディレクトリの上に、/upper というディレクトリを読み書き可能で重ね合わせてマウントします。 overlayfs は、複数の /lower ディレクトリを指定できます。 loop:/file は /file を loop デバイスとして使用し、loop デバイスをマウントします。
CONTROL GROUP セクションは、(lxc とは) 別のサブシステムの設定を含みます。 lxc は、このサブシステム名の正しさはチェックしません。 実行時のエラーを検出するのに不便ですが、別の将来のサブシステムをサポート出来るという有利な点もあります。
カーネルにおける cgroup 実装は長年にわたって大きく変化してきました。 Linux 4.5 で新しい cgroup ファイルシステムのサポートが追加されました。通常は "cgroup2" や "unified hierarchy"(単一階層構造) と呼ばれています。 それ以来、通常は古い cgroup ファイルシステムは "cgroup1" や "legacy hierarchies"(レガシー階層構造)と呼ばれています。 この 2 つのバージョンの違いについての詳細な説明は、cgroup のマニュアルページをご覧ください。
LXC は cgroup1(レガシー階層構造)と cgroup2(単一階層構造)に対する設定を、異なる設定プレフィックスを使って区別しています。 cgroup1 に対する設定を変更するには lxc.cgroup. というプレフィックスを使う必要があり、cgroup2 の設定を変更するには lxc.cgroup2. を使う必要があります。 LXC は、cgroup2 だけが使われているシステム上の lxc.cgroup. を無視します。逆に cgroup1 だけが使われているシステム上の lxc.cgroup2. を無視します。
cgroup 階層の本質は、プロセスを階層的に構造化する方法です。通常は、cgroup 階層では 1 つ以上の「コントローラー」が有効になっています。 通常、cgroup 階層の「コントローラー」は階層に従って特定のタイプのシステムリソースを分配する役割を果たします。 コントローラーには "pids" コントローラー、"cpu" コントローラー、"memory" コントローラーなどがあります。 しかし、システムリソースの分配するという役割に該当しないコントローラーもあります。このようなコントローラーは「ユーティリティー」コントローラーと呼ばれたりします。 ユーティリティーコントローラーの 1 つにデバイスコントローラーがあります。このコントローラーはシステムリソースを分配する代わりにデバイスへのアクセスを管理できます。
cgroup1 では、デバイスコントローラーは他の多くのコントローラーと同様に、書き込みできるファイルのセットとして実装されていました。 これらのファイルは "devices.allow" と "devices.deny" という名前のファイルでした。レガシーデバイスコントローラーは「許可リスト(allowlists)」と「拒否リスト(denylists)」の両方を実装できました。
許可リスト(allowlist)とは、すべてのデバイスへのアクセスをブロックするデバイスプログラムです。特定のデバイスへのアクセスを行うには、特定のデバイスもしくはデバイスクラスに対する「許可ルール(allow rules)」を指定する必要があります。 一方、拒否リスト(denylist)はデフォルトですべてのデバイスへのアクセスを許可するデバイスプログラムです。特定のデバイスへのアクセスを拒否するには、特定のデバイスもしくはデバイスクラスに対する「拒否ルール(deny rules)」を指定する必要があります。
cgroup2 では、デバイスコントローラーの実装が完全に変わりました。読み書きするファイルの代わりに、BPF_PROG_TYPE_CGROUP_DEVICE の eBPF プログラムを cgroup にアタッチできます。 カーネルの実装が完全に変わったのにもかかわらず、LXC は cgroup1 のデバイスコントローラーと cgroup2 の eBPF ベースのデバイスコントローラーで同じセマンティクスに従えるようにしています。 このあとの段落では、cgroup2 の eBPF デバイスコントローラーに対するセマンティクスを説明します。
先に述べたように、cgroup2 の eBPF ベースのデバイスコントローラーに対するデバイスルールを指定するフォーマットは、cgroup1 のデバイスコントローラーと同じです。ただし、設定キーのプレフィックスは変更されています。 具体的には、cgroup1 のデバイスコントローラーに対するデバイスルールは lxc.cgroup.devices.allow と lxc.cgroup.devices.deny を使って指定します。一方、cgroup2 の eBPF ベースのコントローラーでは lxc.cgroup2.devices.allow と lxc.cgroup2.devices.deny を使わなければなりません。
lxc.cgroup2.devices.deny = a
は、カーネルに対してデフォルトですべてのデバイスへのアクセスをブロックするように LXC が指示します。 デバイスへのアクセスを許可するには、デバイスに対する許可ルールを lxc.cgroup2.devices.allow を使って追加する必要があります。これは「許可リスト」デバイスプログラムとして参照されます。
lxc.cgroup2.devices.allow = a
は、カーネルに対してすべてのデバイスへのアクセスをデフォルトで許可するように LXC が指示します。 デバイスへのアクセスを拒否するには、デバイスに対する拒否ルールを lxc.cgroup2.devices.deny を使って追加する必要があります。これは「拒否リスト」デバイスプログラムとして参照されます。
例えば、次のようなルールの組
lxc.cgroup2.devices.deny = a
lxc.cgroup2.devices.allow = c *:* m
lxc.cgroup2.devices.allow = b *:* m
lxc.cgroup2.devices.allow = c 1:3 rwm
は、許可リスト(allowlist)デバイスプログラムを実装します。つまり、カーネルはこのリストで許可されるように設定されていないすべてのデバイスへのアクセスをブロックします。 このプログラムでは、すべてのキャラクターデバイスとブロックデバイスが作成できますが、読み書きは /dev/null に対してしか行なえません。
代わりに先のルールから次のようなルールの組に変更したとすると、
lxc.cgroup2.devices.allow = a
lxc.cgroup2.devices.deny = c *:* m
lxc.cgroup2.devices.deny = b *:* m
lxc.cgroup2.devices.deny = c 1:3 rwm
LXC はカーネルに拒否リスト(denylist)の実装を指示します。つまりカーネルはこのリストで拒否を指定していないすべてのデバイスへのアクセスを許可します。 このプログラムでは、キャラクターデバイスとブロックデバイスは作成できません。そして /dev/null の読み書きと作成は許可されません。
ここで、同じプログラムでも、前述のようにデバイスのプログラムタイプを決定するような「グローバルルール」が続いている場合を考えてみましょう。
lxc.cgroup2.devices.allow = a
lxc.cgroup2.devices.deny = c *:* m
lxc.cgroup2.devices.deny = b *:* m
lxc.cgroup2.devices.deny = c 1:3 rwm
lxc.cgroup2.devices.allow = a
最後の行は、デバイスプログラムのタイプを変更せずに、LXC がデバイスリストをリセットしてしまいます。
次のように指定した場合、
lxc.cgroup2.devices.allow = a
lxc.cgroup2.devices.deny = c *:* m
lxc.cgroup2.devices.deny = b *:* m
lxc.cgroup2.devices.deny = c 1:3 rwm
lxc.cgroup2.devices.deny = a
前の例と違って最後の行によって、LXC はデバイスリストをリセットし、許可リスト(allowlist)から拒否リスト(denylist)にプログラムを変更してしまいます。
コンテナが root 権限で実行されていても、コンテナ内ではケーパビリティ (capabilities) を削除する事は可能です。
名前空間は clone したり (lxc.namespace.clone)、keep したり (lxc.namespace.keep)、share したり (lxc.namespace.share.[namespace identifier]) できます。
新しいマウント、ネット、IPC 名前空間を作る場合は lxc.namespace.clone=mount net ipc と指定します。
ネットワーク、ユーザ、IPC 名前空間を元のプロセスの名前空間のままで実行したい場合は lxc.namespace.keep=user net ipc と指定します。
PID 名前空間を共有すると、ほとんどの init で動作しない可能性があることに注意してください。
コンテナが新しいユーザ名前空間をリクエストし、そのコンテナがネットワーク名前空間は継承したい場合は、ユーザ名前空間も同様に継承する必要があることに注意してください。
他のプロセスから名前空間を継承するには、lxc.namespace.share.[namespace identifier] の値をプロセスの PID に設定します。例えば lxc.namespace.share.net=42 のようになります。
他のコンテナから名前空間を継承するには、lxc.namespace.share.[namespace identifier] の値をコンテナ名に設定します。例えば lxc.namespace.share.pid=c3 のようになります。
標準の liblxc のパスとは異なるコンテナパスに存在する他のコンテナから名前空間を継承するには、lxc.namespace.share.[namespace identifier] をそのコンテナのフルパスで指定します。例えば lxc.namespace.share.user=/opt/c3 のようになります。
名前空間を継承するためには、呼び出し元が継承元のプロセスまたはコンテナに対して十分な権限を持っている必要があります。
システムコンテナ間での PID 名前空間の共有は、ほとんどの init システムではうまく動作しない可能性があることに注意が必要です。
ふたつのプロセスが異なるユーザ名前空間に存在し、そのうちのひとつが他のネットワーク名前空間を継承したい場合、通常はユーザ名前空間も同様に継承する必要があることに注意が必要です。
LSM で慎重に設定を追加しないで、タスクでユーザ + PID 名前空間を共有すると、そのタスクは liblxc を呼び出したタスクの権限に昇格できることに注意が必要です。
コンテナに対するソフトもしくはハードリミットを変更できます。非特権コンテナでは、制限を下げることしかできません。明示的に指定されていないリソースは継承されます。
コンテナ用のカーネルパラメータを設定します。
lxc が apparmor サポートでコンパイルされ、インストールされている場合で、ホストで apparmor が有効な場合、コンテナが従って動くべき apparmor プロファイルは、コンテナの設定で指定することが可能です。 デフォルトは、ホストのカーネルで cgroup 名前空間が使える場合は lxc-container-default-cgnsです。使えない場合は lxc-container-default です。
lxc.apparmor.profile = unconfined
もし apparmor プロファイルが変更されないままでなくてはならない場合 (ネストしたコンテナである場合や、すでに confined されている場合) は以下のように設定します。
lxc.apparmor.profile = unchanged
もし LXC に AppArmor プロファイルを生成するように指示するには次のように設定します。
lxc.apparmor.profile = generated
このフラグが 0 の場合 (デフォルト)、カーネルが apparmor のマウント機能をサポートしていない場合にコンテナが起動しません。これはカーネルを更新した後に機能が退行したことが検出できるようにするためです。 不完全な apparmor の保護の下でコンテナを起動するためには、このフラグを 1 に設定してください。
lxc が SELinux サポートでコンパイルされ、インストールされている場合で、ホストで SELinux が有効な場合、コンテナが従って動くべき SELinux コンテキストは、コンテナの設定で指定することが可能です。 デフォルトは unconfined_t であり、これは lxc がコンテキストを変えないという意味になります。 ポリシーの例と追加の情報は /usr/share/lxc/selinux/lxc.te ファイルを参照してください。
lxc.selinux.context = system_u:system_r:lxc_t:s0:c22
lxc.selinux.context.keyring = system_u:system_r:lxc_t:s0:c22
Linux キーリング機能は、さまざまなカーネルコンポーネントが、セキュリティーデータ、認証キー、暗号化キー、その他のデータをカーネルに保持またはキャッシュするための機能です。 デフォルトでは、LXC は開始したアプリケーションのために、新しいセッションキーリングを作成します。
lxc.keyring.session = 0
コンテナは、起動時に seccomp プロファイルをロードすることで、利用可能なシステムコールを減らして起動することが可能です。 seccomp の設定ファイルは、1 行目がバージョン番号、2 行目がポリシーのタイプで始まる必要があり、その後に設定を書きます。
現時点では、バージョン番号は 1 と 2 をサポートしています。バージョン 1 では、ポリシーはシンプルなホワイトリストですので、2 行目は "allowlist" でなければなりません。 そして残りの行には 1 行に 1 つずつ、システムコール番号を書きます。各行のシステムコール番号がホワイトリスト化され、リストにない番号は、そのコンテナではブラックリストに入ります。
バージョン 2 では、ポリシーはブラックリストもしくはホワイトリストで表され、ルールごとのアクションと、ポリシーごとのデフォルトのアクションを設定できます。そして、アーキテクチャごとの設定と、テキストで書かれたシステムコール名での設定が可能です。
以下にブラックリストのポリシーの例を示します。これは mknod 以外の全てのシステムコールが許可され、mknod が呼ばれると、何もせずに単に 0(成功) を返します。
2
denylist
mknod errno 0
ioctl notify
アクションとして "errno" を指定すると、LXC は seccomp フィルタを登録します。これにより、指定した errno を呼び出し元に返します。 errno の値は "errno" という単語の後に指定します。
アクションとして "notify" を指定すると、LXC は seccomp リスナーを登録し、カーネルからリスナーのファイルディスクリプタを取得します。 "notify" として指定しているシステムコールが作成されると、カーネルは poll イベントを生成し、ファイルディスクリプタを通してメッセージを送信します。 呼び出し元はこのメッセージを読み、引数を含めてシステムコールを調査できます。 呼び出し元はこの情報に基づき、どのアクションを取るべきかをカーネルに知らせるメッセージを送り返すことが期待されます。 このメッセージが送られるまで、カーネルは呼び出し元のプロセスをブロックします。読み書きするメッセージのフォーマットは seccomp 自身に記述されています。
PR_SET_NO_NEW_PRIVS を付与すると、対象の execve() は、execve() の呼び出しなしでは実行できなかったことに対する特権を許可しなくなります (例えば、set-user-ID、set-group-ID 許可ビットや、ファイルケーパビリティが動作しなくなります)。 一度設定されると、このビットは解除できません。このビットの設定は fork() や clone() で生成される子プロセスにも継承され、execve() の前後で保持されます。 PR_SET_NO_NEW_PRIVS は、コンテナに適用しようとする AppArmor プロファイルもしくは SELinux コンテキストへの変更がなされたあとに適用されます。
コンテナは、ユーザとグループの id のマッピングを持った専用のユーザ名前空間で起動することが可能です。 たとえば、コンテナ内のユーザ id 0 を、ホストのユーザ id 200000 にマッピングすることが可能です。 コンテナの root ユーザはコンテナ内では特権を持ちますが、ホストでは特権を持ちません。 通常は、システムコンテナは id の範囲を要求し、それをマッピングします。 例えば、コンテナ内のユーザとグループの id 0 から 20,000 を 200,000 から 220,000 にマッピングします。
コンテナのフックは、コンテナの存続期間の色々な場面で実行することのできるプログラムやスクリプトです。
コンテナフックが実行されるとき、追加の情報が渡されます。追加の引数がコマンドライン引数で渡されるか、環境変数経由で渡されるかを判断するのに、lxc.hook.version が使えます。引数は:
次の環境変数がセットされます。
スクリプトからの標準出力は debug レベルでロギングされます。 標準エラー出力はロギングされません。 しかし、フックの標準エラー出力を標準出力にリダイレクトすることにより保存することは可能です。
起動時のフックに設定情報を提供し、フックの機能を助けるための環境変数がいくつか利用可能です。 全ての変数が全てのコンテキストで利用可能なわけではありません。 具体的には、全てのパスはホストシステム上のパスであり、そのため、lxc.hook.start フックの時点では使用できません。
ロギングはコンテナごとに設定することが可能です。 デフォルトでは、lxc パッケージのコンパイル条件に依存し、コンテナのスタートアップは ERROR レベルでのみロギングされ、コンテナのパス以下か、/var/log/lxc 以下のどちらかにコンテナ名 (の後に '.log' が付与される) をもとにした名前でロギングされます。
デフォルトのログレベルとログファイルは両方とも、コンテナの設定ファイル内で指定され、デフォルトの値を上書きします。 同様に、設定ファイルのエントリは lxc-start のコマンドラインオプションで上書きすることも可能です。
(フックスクリプトやネットワークインターフェースの起動、停止時のスクリプトのような) スクリプトが呼ばれた時、スクリプトの標準出力は level 1 の debug でロギングされます。
自動起動オプションでは、自動起動させるコンテナと順番の設定が可能です。 このオプションは LXC ツールが直接使用するか、ディストリビューションが提供する外部ツールが使用するかもしれません。
コンテナはいくつでもグループに属することができ、全く属さないことも可能です。特別なグループが 2 つ存在します。1 つは NULL グループです。これはどのグループにも属さないコンテナです。もう 1 つは "onboot" グループです。
LXC サービスが有効になった状態でシステムがブートすると、最初に "onboot" グループのメンバーである lxc.start.auto == 1 が設定されたコンテナを起動しようとします。起動は lxc.start.order の順に起動します。 lxc.start.delay が指定されている場合、現在対象となっているコンテナに初期化の時間を与え、ホストシステムの負荷を低減するために、次のコンテナを開始させるまでに遅延時間を与えます。 "onboot" グループのメンバーが開始した後、LXC システムは lxc.start.auto == 1 が設定された、どのグループのメンバーでもない (NULL グループの) コンテナのブートを onboot グループのコンテナと同様に開始します。
コンテナに環境変数を渡したい場合 (環境変数はコンテナの init とその子孫全てで利用可能です)、lxc.environment パラメータがその用途に使えます。 機微 (センシティブ) な情報を渡さないように注意が必要です。そのような情報を持たないコンテナ内のプロセスでこれらの環境変数が利用可能になってしまいます。環境変数は常に /proc/PID/environ 経由で利用可能になります。
この設定項目は、設定したい環境変数ごとに 1 度ずつ、何度でも指定できます。
lxc.environment = APP_ENV=production
lxc.environment = SYSLOG_SERVER=192.0.2.42
以下に紹介するいくつかの例に加えて、他の設定例が /usr/share/doc/lxc/examples にあります。
この設定は、片方をブリッジである br0 と接続される veth ペアデバイスを使うコンテナを設定します (ブリッジは管理者によりあらかじめシステム上に設定済みである必要があります)。 仮想ネットワークデバイスは、コンテナ内では eth0 とリネームされます。
lxc.uts.name = myhostname
lxc.net.0.type = veth
lxc.net.0.flags = up
lxc.net.0.link = br0
lxc.net.0.name = eth0
lxc.net.0.hwaddr = 4a:49:43:49:79:bf
lxc.net.0.ipv4.address = 1.2.3.5/24 1.2.3.255
lxc.net.0.ipv6.address = 2003:db8:1:0:214:1234:fe0b:3597
この設定は、コンテナ内のユーザとグループ両方の id 0-9999 の範囲を、ホスト上の 100000-109999 へマッピングします。
lxc.idmap = u 0 100000 10000
lxc.idmap = g 0 100000 10000
この設定は、アプリケーションのための control group をいくつか設定します。 cpuset.cpus は定義された cpu のみ使用できるように制限します。 cpus.share は、control group の (cpu) 優先度を指定します。 devices.allow は、特定のデバイスを使用可能にします。
lxc.cgroup.cpuset.cpus = 0,1
lxc.cgroup.cpu.shares = 1234
lxc.cgroup.devices.deny = a
lxc.cgroup.devices.allow = c 1:3 rw
lxc.cgroup.devices.allow = b 8:0 rw
この例は、control group を使って、複雑なネットワークスタックを作成し、新しいホスト名を指定し、いくつかの場所をマウントし、ルートファイルシステムを変更するような複雑な設定を示します。
lxc.uts.name = complex
lxc.net.0.type = veth
lxc.net.0.flags = up
lxc.net.0.link = br0
lxc.net.0.hwaddr = 4a:49:43:49:79:bf
lxc.net.0.ipv4.address = 10.2.3.5/24 10.2.3.255
lxc.net.0.ipv6.address = 2003:db8:1:0:214:1234:fe0b:3597
lxc.net.0.ipv6.address = 2003:db8:1:0:214:5432:feab:3588
lxc.net.1.type = macvlan
lxc.net.1.flags = up
lxc.net.1.link = eth0
lxc.net.1.hwaddr = 4a:49:43:49:79:bd
lxc.net.1.ipv4.address = 10.2.3.4/24
lxc.net.1.ipv4.address = 192.168.10.125/24
lxc.net.1.ipv6.address = 2003:db8:1:0:214:1234:fe0b:3596
lxc.net.2.type = phys
lxc.net.2.flags = up
lxc.net.2.link = random0
lxc.net.2.hwaddr = 4a:49:43:49:79:ff
lxc.net.2.ipv4.address = 10.2.3.6/24
lxc.net.2.ipv6.address = 2003:db8:1:0:214:1234:fe0b:3297
lxc.cgroup.cpuset.cpus = 0,1
lxc.cgroup.cpu.shares = 1234
lxc.cgroup.devices.deny = a
lxc.cgroup.devices.allow = c 1:3 rw
lxc.cgroup.devices.allow = b 8:0 rw
lxc.mount.fstab = /etc/fstab.complex
lxc.mount.entry = /lib /root/myrootfs/lib none ro,bind 0 0
lxc.rootfs.path = dir:/mnt/rootfs.complex
lxc.rootfs.options = idmap=container
lxc.cap.drop = sys_module mknod setuid net_raw
lxc.cap.drop = mac_override
lxc(7), lxc-create(1), lxc-copy(1), lxc-destroy(1), lxc-start(1), lxc-stop(1), lxc-execute(1), lxc-console(1), lxc-monitor(1), lxc-wait(1), lxc-cgroup(1), lxc-ls(1), lxc-info(1), lxc-freeze(1), lxc-unfreeze(1), lxc-attach(1), lxc.conf(5)
Daniel Lezcano <daniel.lezcano@free.fr>
2023-11-30 |