完全仮想環境でもコンテナ
KVM (qemu) で作成したゲスト OS の中にコンテナを作成します。
劇中劇なら VM 方式を同一の入れ子にしたほうがインストール手順が単純化しますが、
完全仮想環境同士で多層化すると資源を多く消費することになります。
LXC (Linux Containers) なら、OS の設計やデバイスの用意等はすでに構築済みのホストの資源 (今回はゲストの資源) をそのまま使うので、
レイヤーを増やした仮想環境について資源の配分をもう一度考える必要が無くなります。
(… というよりも、限られた資源の中でやりくりしなければならない。最近の計算機は、いろいろ余裕があるからたぶん大丈夫)
インストール時間やストレージの専有領域が少なくて済むので Web や DB に特化したサーバ等、省資源のサーバを作成するのに向いています。
完全仮想環境の中に LXC を作成すると、グループ化したコンテナ群のバックアップやリストアも楽にできるようになります。
コンテナは、ソフトウェアだけを対象としている仮想化技術なので、ハードウェアを直接に制御することは出来ません。
「Solaris コンテナ」の場合はハードウェアを厳密に定義するアプリケーションが多いため裏技を駆使する必要がありますが (Oracle のメモリ確保とかやめてほしい)、
「Linux コンテナ」の場合はハードウェアとの結合が薄いアプリケーションが多いので、導入のハードルが比較的低くなっているのが特徴です。
0. 前提
KVM でゲスト OS「guest1」が構築済みであること。
cont2 の説明は cont1 と同じになるので省略している。
種別 ホスト名 内容 IP アドレス 備考 コンテナ cont1 Rocky Linux release 9.4
(Blue Onyx)10.0.3.11 親 OS (guest1) は RedHat 系だが、デスクトップなしなら Ubuntu も OK。
ネットワークはデフォルトのブリッジを使うcont2 10.0.3.12 コンテナ環境 - LXC 4.0.12 Release 1.el9 10.0.3.1/24
(リゾルバも 10.0.3.1)本稿記述時の最新版
IP アドレスはデフォルトのものを使うゲスト OS guest1 AlmaLinux release 9.4
(Seafoam Ocelot)192.168.11.10 KVM (QEMU) による完全仮想環境
今回はホストと同じ OS にしたが、同じである必要はないホスト OS host1 AlmaLinux release 9.4
(Seafoam Ocelot)192.168.11.2 本稿記述時の最新版
ホスト OS に関して、本稿では触れていない。(ゲスト OS の構築が前提なので)
1. LXC のインストール
guest1$ su
guest1# dnf install -y epel-release
guest1# dnf install -y lxc lxc-templates
guest1# LANG=C dnf info lxc | egrep 'Name|Version|Release'
Name : lxc Version : 4.0.12 Release : 1.el9
2. ネットワークの設定
2-1. DNS ポートを許可
コンテナからの DNS リクエストを受けられるようにしておく。2-2. DHCP の通信許可と DHCP サーバの起動
guest1# firewall-cmd --permanent --add-port="53/udp" --add-port="53/tcp"
guest1# firewall-cmd --reload
guest1# firewall-cmd --list-ports
53/tcp 53/udp
guest1# firewall-cmd --permanent --add-service=dhcp2-3. lxc 設定ファイルの確認
guest1# firewall-cmd --reload
guest1# firewall-cmd --list-service
dhcp
guest1# cat /etc/sysconfig/lxc | grep -v ^# | grep -v ^$2-4. lxc-net の編集
確認だけ。特に変更作業はない。
LXC_AUTO="true" BOOTGROUPS="onboot," SHUTDOWNDELAY=5 OPTIONS= STOPOPTS="-a -A -s" USE_LXC_BRIDGE="false" # overridden in lxc-net [ ! -f /etc/sysconfig/lxc-net ] || . /etc/sysconfig/lxc-net
guest1# vim /etc/sysconfig/lxc-net2-5. コンテナ用に DHCP のアドレスを定義
# Leave USE_LXC_BRIDGE as "true" if you want to use lxcbr0 for your # containers. Set to "false" if you'll use virbr0 or another existing # bridge, or macvlan to your host's NIC. USE_LXC_BRIDGE="true" # <-- true を確認する # If you change the LXC_BRIDGE to something other than lxcbr0, then # you will also need to update your /etc/lxc/default.conf as well as the # configuration (/var/lib/lxc//config) for any containers # already created using the default config to reflect the new bridge # name. # If you have the dnsmasq daemon installed, you'll also have to update # /etc/dnsmasq.d/lxc and restart the system wide dnsmasq daemon. LXC_BRIDGE="lxcbr0" # <-- コメントを外して有効にする LXC_BRIDGE_MAC="00:16:3e:00:00:00" # … LXC_ADDR="10.0.3.1" # … LXC_NETMASK="255.255.255.0" # … LXC_NETWORK="10.0.3.0/24" # … LXC_DHCP_RANGE="10.0.3.2,10.0.3.254" # … LXC_DHCP_MAX="253" # … # Uncomment the next line if you'd like to use a conf-file for the lxcbr0 # dnsmasq. For instance, you can use 'dhcp-host=mail1,10.0.3.100' to have # container 'mail1' always get ip address 10.0.3.100. LXC_DHCP_CONFILE=/etc/lxc/dnsmasq.conf # <-- コメントを外して有効にする # Uncomment the next line if you want lxcbr0's dnsmasq to resolve the .lxc # domain. You can then add "server=/lxc/10.0.3.1' (or your actual $LXC_ADDR) # to /etc/dnsmasq.conf, after which 'container1.lxc' will resolve on your # host. LXC_DOMAIN="lxc" # <-- コメントを外して有効にする
guest1# touch /etc/lxc/dnsmasq.conf2-6. lxc-net を起動
guest1# vim /etc/lxc/dnsmasq.conf
各コンテナに割り当てる IP アドレス。コンテナ起動時に配布される。
運用中にこれを書き換えたら lxc-net を再起動させる。
dhcp-host=cont1,10.0.3.11 dhcp-host=cont2,10.0.3.12
guest1# systemctl enable dnsmasq guest1# systemctl enable lxc-net
guest1# systemctl start dnsmasq guest1# systemctl start lxc-net
3. コンテナの作成
guest1# lxc-create cont1 -t download -- --dist rockylinux
作成可能なコンテナのリストはここにある。guest1# ls -l /var/lib/lxc/cont1/
*1この選択だとキャッシュは以下のディレクトリになる。
Downloading the image index --- DIST RELEASE ARCH VARIANT BUILD --- rockylinux 8 amd64 default 20241025_02:06 rockylinux 8 arm64 default 20241025_02:06 rockylinux 9 amd64 default 20241025_02:06 ← これを選択 rockylinux 9 arm64 default 20241025_02:06 --- Release: 9 ⏎ Architecture: amd64 ⏎ Downloading the image index ← 初回はダウンロードになる*1 Downloading the rootfs (次回からは今回のキャッシュが使われる) Downloading the metadata The image cache is now ready Unpacking the rootfs --- You just created a Rockylinux 9 x86_64 (20241025_02:06) container.
/var/cache/lxc/download/rockylinux/9/amd64/default/
guest1# ls -l /var/lib/lxc/cont1/config # <-- コンテナの定義ファイル
guest1# ls -l /var/lib/lxc/cont1/rootfs/ # <-- コンテナのファイルシステム (ここをルートとして動作する)
作成したコンテナのネットワーク設定を確認する。
guest1# cat /var/lib/lxc/cont1/config | grep -v ^# | grep -v ^$
guest1# lxc-ls
lxc.include = /usr/share/lxc/config/common.conf lxc.arch = x86_64 lxc.rootfs.path = dir:/var/lib/lxc/cont1/rootfs lxc.uts.name = cont1 lxc.net.0.type = veth lxc.net.0.link = lxcbr0 lxc.net.0.flags = up lxc.net.0.hwaddr = 00:16:3e:57:74:92 ← ここは作成毎に異なる
guest1# lxc-info cont1
cont1
コンテナを破棄する場合はこちら。
Name: cont1 State: STOPPED
guest1# lxc-destroy cont1
4. コンテナの起動
guest1# lxc-start cont1
guest1# lxc-info cont1
guest1# lxc-ls cont1 -f
Name: cont1 State: RUNNING PID: 4314 IP: 10.0.3.11 ← DHCP で割り当てられたアドレス Link: vethqFSTwD TX bytes: 1.98 KiB RX bytes: 3.84 KiB Total bytes: 5.81 KiB
コンテナを停止する場合はこちら。( lxc-attach cont1 -- shutdown -h now を実行しても良い)
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED cont1 RUNNING 0 - 10.0.3.11 - false
guest1# lxc-stop cont1
5. 初期設定
最低限の設定をする。
guest1# lxc-attach cont1
- タイムゾーンを設定する
- ネットワークの確認・設定ができるようにする
- 通常のログインができるようにする
- ファイアウォールを設定する
- 管理者用アカウントを作成する
• タイムゾーンを Asia/Tokyo にする。
cont1# timedatectl set-timezone Asia/Tokyo
cont1# ls -l /etc/localtime ; date
• dnf を使うため、DNS 解決の確認をする。
lrwxrwxrwx. 1 root root 32 Nov 18 01:58 /etc/localtime -> ../usr/share/zoneinfo/Asia/Tokyo Mon Nov 18 02:01:05 AM JST 2024
cont1# cat /etc/resolv.conf
• ネットワークの確認に必要なアプリケーションをインストールする。
search lxc nameserver 10.0.3.1
cont1# dnf install -y bind-utils telnet
cont1# dnf install -y which vim less
• 通常ログイン用に sshd をインストールする。
cont1# dnf install -y openssh-server
cont1# cat /etc/ssh/sshd_config | egrep -i 'Port|ListenAddress'
今回はデフォルトのまま (変更しない)。lxc-net を使わない場合は変更が必要。cont1# systemctl enable sshd
# If you want to change the port on a SELinux system, you have to tell # semanage port -a -t ssh_port_t -p tcp #PORTNUMBER #Port 22 #ListenAddress 0.0.0.0 #ListenAddress :: # WARNING: 'UsePAM no' is not supported in RHEL and may cause several #GatewayPorts no
cont1# systemctl start sshd
• ファイアウォールをインストールする。
cont1# dnf install -y firewalld
cont1# systemctl enable firewalld
cont1# systemctl start firewalld
• ファイアウォールの初期状態を確認する。
cont1# firewall-cmd --get-default-zone
cont1# firewall-cmd --list-all | grep services:
public
• 管理者用アカウントを作成する。(sudo を使えるユーザ)sshd はデフォルトで許可されている。
services: cockpit dhcpv6-client ssh
cont1# useradd admin
cont1# echo 'password' | passwd --stdin admin # password をこのまま使わないように。
cont1# usermod -G wheel admin
cont1# exit
• コンテナの容量を確認する
guest1# du -sh /var/lib/lxc/cont1
guest1# exitここまでのインストールで約 506MB。「lxc-create …」の選択によっても異なる。
506M /var/lib/lxc/cont1
guest1$
6. おまけ (1)
親 OS のポートへ接続してきたら、コンテナのポートへ転送する。
ここでは、以下を想定している。
(覚えやすいようにポート番号の頭 2 桁をコンテナの第 4 オクテットと同じにしてあるが、これに従う必要はない)
親 OS (guest1) で実施する。(コンテナとの行き来を繰り返すと間違えやすい)
- ssh -p 1122 admin@192.168.11.10 → cont1:22
- curl http://192.168.11.10:1180/ → cont1:80
guest1$ su
guest1# firewall-cmd --permanent --add-forward-port=port=1122:proto=tcp:toport=22:toaddr=10.0.3.11
guest1# firewall-cmd --permanent --add-forward-port=port=1180:proto=tcp:toport=80:toaddr=10.0.3.11
guest1# firewall-cmd --reload
guest1# firewall-cmd --list-forward-ports
guest1# exit
port=1122:proto=tcp:toport=22:toaddr=10.0.3.11 port=1180:proto=tcp:toport=80:toaddr=10.0.3.11
guest1$
7. おまけ (2)
Web サーバ (Apache) をインストールしてみる
guest1$ ssh admin@10.0.3.11
cont1$ sudo dnf install -y httpd mod_ssl
cont1$ sudo systemctl enable httpd
cont1$ sudo systemctl start httpd
cont1$ sudo firewall-cmd --permanent --add-service=http --add-service=https
cont1$ sudo firewall-cmd --reload
cont1$ exit
guest1$ curl -s http://10.0.3.11/ | grep '<title>'
guest1$
<title>HTTP Server Test Page powered by: Rocky Linux</title>
8. おまけ (3)
実験環境にするなら以下を実施する。
(外部への 80, 443 を使用不能にするので、インストールがすべて完了してから)
firewall を操作するので、なるべく lxc-attach から。
guest1$ su
guest1# lxc-attach cont1
インバウンドを許可する。
· 入ってくる通信はすべて許可する。
cont1# firewall-cmd --permanent --add-rich-rule='rule family=ipv4 destination address="0.0.0.0/0" accept'
アウトバウンドを制限する。
· 親 OS への通信は DNS を許可して他はすべて禁止する。
· 自分のネットワークへの通信はすべて許可する。
· 外部への通信はシステムポートを禁止する。(← うっかり対策*2)
cont1# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 1 -d 10.0.3.1 -p udp --dport 53 -j ACCEPT
cont1# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 2 -d 10.0.3.1 -p tcp -m state --state NEW --dport 53 -j ACCEPT
cont1# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 3 -d 10.0.3.1 -p tcp -m state --state NEW --dport 0:1023 -j DROP
cont1# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 4 -d 10.0.3.0/24 -j ACCEPT
cont1# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 5 -p tcp -m state --state NEW --dport 0:1023 -j DROP
cont1# firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 6 -p udp --dport 0:1023 -j DROP
cont1# firewall-cmd --reload
インバウンドルールを確認する。
cont1# firewall-cmd --list-rich
アウトバウンドルールを確認する。
rule family="ipv4" destination address="0.0.0.0/0" accept
cont1# firewall-cmd --direct --get-all-rules
cont1# exit
ipv4 filter OUTPUT 1 -d 10.0.3.1 -p udp --dport 53 -j ACCEPT ipv4 filter OUTPUT 2 -d 10.0.3.1 -p tcp -m state --state NEW --dport 53 -j ACCEPT ipv4 filter OUTPUT 3 -d 10.0.3.1 -p tcp -m state --state NEW --dport 0:1023 -j DROP ipv4 filter OUTPUT 4 -d 10.0.3.0/24 -j ACCEPT ipv4 filter OUTPUT 5 -p tcp -m state --state NEW --dport 0:1023 -j DROP ipv4 filter OUTPUT 6 -p udp --dport 0:1023 -j DROP
guest1# exit
guest1$
*2 何かを監視したいならこれ↓
guest1# tcpdump -i lxcbr0 -q -l "tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) = 0" | logger
• メンテナンスをするには以下の手順が必要。
ファイアウォールを一時的に停止するため、一般ユーザを退出させてから作業する。
ssh ログインでもできるけど、なるべく lxc-attach を使う。
以下は screen コマンドを追加インストールする例。(EPEL からインストール)
cont1# systemctl stop sshd # 新規ログインの受付終了 cont1# w | grep -v admin # 居残りユーザの割り出し --> pts/N (複数かも) cont1# ps -dN | grep pts/N # 居残りユーザのプロセスの割り出し --> xxxxx cont1# kill -KILL xxxxx # 居残りユーザの強制終了 cont1# systemctl stop firewalld # ファイアウォールの停止
cont1# dnf install -y epel-release # インストール作業 1 cont1# dnf install -y screen # インストール作業 2
cont1# systemctl start firewalld # ファイアウォールの開始 cont1# systemctl start sshd # 新規ログインの受付開始