KVM で LXC
〜 完全仮想環境からコンテナ 〜
2024-11-22 作成 福島
TOP > tips > kvm-lxc
[ TIPS | TOYS | OTAKU | LINK | MOVIE | CGI | AvTitle | ConfuTerm | HIST | AnSt | Asob | Shell ]

完全仮想環境でもコンテナ

KVM (qemu) で作成したゲスト OS の中にコンテナを作成します。
劇中劇なら VM 方式を同一の入れ子にしたほうがインストール手順が単純化しますが、
完全仮想環境同士で多層化すると資源を多く消費することになります。

LXC (Linux Containers) なら、OS の設計やデバイスの用意等はすでに構築済みのホストの資源 (今回はゲストの資源) をそのまま使うので、
レイヤーを増やした仮想環境について資源の配分をもう一度考える必要が無くなります。
(… というよりも、限られた資源の中でやりくりしなければならない。最近の計算機は、いろいろ余裕があるからたぶん大丈夫)

インストール時間やストレージの専有領域が少なくて済むので Web や DB に特化したサーバ等、省資源のサーバを作成するのに向いています。
完全仮想環境の中に LXC を作成すると、グループ化したコンテナ群のバックアップやリストアも楽にできるようになります。

コンテナは、ソフトウェアだけを対象としている仮想化技術なので、ハードウェアを直接に制御することは出来ません。
「Solaris コンテナ」の場合はハードウェアを厳密に定義するアプリケーションが多いため裏技を駆使する必要がありますが (Oracle のメモリ確保とかやめてほしい)、
「Linux コンテナ」の場合はハードウェアとの結合が薄いアプリケーションが多いので、導入のハードルが比較的低くなっているのが特徴です。


0. 前提

KVM でゲスト OS「guest1」が構築済みであること。
種別ホスト名内容IP アドレス備考
コンテナcont1Rocky Linux release 9.4
(Blue Onyx)
10.0.3.11親 OS (guest1) は RedHat 系だが、デスクトップなしなら Ubuntu も OK。
ネットワークはデフォルトのブリッジを使う
cont210.0.3.12
コンテナ環境-LXC 4.0.12 Release 1.el910.0.3.1/24
(リゾルバも 10.0.3.1)
本稿記述時の最新版
IP アドレスはデフォルトのものを使う
ゲスト OSguest1AlmaLinux release 9.4
(Seafoam Ocelot)
192.168.11.10KVM (QEMU) による完全仮想環境
今回はホストと同じ OS にしたが、同じである必要はない
ホスト OShost1AlmaLinux release 9.4
(Seafoam Ocelot)
192.168.11.2本稿記述時の最新版
ホスト OS に関して、本稿では触れていない。(ゲスト OS の構築が前提なので)
cont2 の説明は cont1 と同じになるので省略している。


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 リクエストを受けられるようにしておく。

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  
2-2. DHCP の通信許可と DHCP サーバの起動
guest1# firewall-cmd --permanent --add-service=dhcp
guest1# firewall-cmd --reload
guest1# firewall-cmd --list-service
dhcp           
2-3. lxc 設定ファイルの確認
guest1# cat /etc/sysconfig/lxc | grep -v ^# | grep -v ^$
確認だけ。特に変更作業はない。
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  
2-4. lxc-net の編集
guest1# vim /etc/sysconfig/lxc-net
# 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"             # <-- コメントを外して有効にする
2-5. コンテナ用に DHCP のアドレスを定義
guest1# touch /etc/lxc/dnsmasq.conf
guest1# vim /etc/lxc/dnsmasq.conf
各コンテナに割り当てる IP アドレス。コンテナ起動時に配布される。
dhcp-host=cont1,10.0.3.11  
dhcp-host=cont2,10.0.3.12
運用中にこれを書き換えたら lxc-net を再起動させる。
2-6. lxc-net を起動
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
作成可能なコンテナのリストはここにある。
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.
*1この選択だとキャッシュは以下のディレクトリになる。
/var/cache/lxc/download/rockylinux/9/amd64/default/
guest1# ls -l /var/lib/lxc/cont1/
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 ^$
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-ls
cont1  
guest1# lxc-info cont1
Name:           cont1
State:          STOPPED  
コンテナを破棄する場合はこちら。
guest1# lxc-destroy cont1


4. コンテナの起動

guest1# lxc-start cont1
guest1# lxc-info cont1
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
guest1# lxc-ls cont1 -f
NAME  STATE   AUTOSTART GROUPS IPV4      IPV6 UNPRIVILEGED  
cont1 RUNNING 0         -      10.0.3.11 -    false
コンテナを停止する場合はこちら。( lxc-attach cont1 -- shutdown -h now を実行しても良い)
guest1# lxc-stop cont1


5. 初期設定

最低限の設定をする。
  1. タイムゾーンを設定する
  2. ネットワークの確認・設定ができるようにする
  3. 通常のログインができるようにする
  4. ファイアウォールを設定する
  5. 管理者用アカウントを作成する
guest1# lxc-attach cont1

• タイムゾーンを Asia/Tokyo にする。
cont1# timedatectl set-timezone Asia/Tokyo
cont1# ls -l /etc/localtime ; date
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
• dnf を使うため、DNS 解決の確認をする。
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 を使わない場合は変更が必要。
# 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 enable sshd
cont1# systemctl start sshd

• ファイアウォールをインストールする。
cont1# dnf install -y firewalld
cont1# systemctl enable firewalld
cont1# systemctl start firewalld

• ファイアウォールの初期状態を確認する。
cont1# firewall-cmd --get-default-zone
public  
cont1# firewall-cmd --list-all | grep services:
  services: cockpit dhcpv6-client ssh  
sshd はデフォルトで許可されている。
• 管理者用アカウントを作成する。(sudo を使えるユーザ)
cont1# useradd admin
cont1# echo 'password' | passwd --stdin admin  # password をこのまま使わないように。
cont1# usermod -G wheel admin

cont1# exit

• コンテナの容量を確認する
guest1# du -sh /var/lib/lxc/cont1
506M    /var/lib/lxc/cont1  
ここまでのインストールで約 506MB。「lxc-create …」の選択によっても異なる。
guest1# exit
guest1$


6. おまけ (1)

親 OS のポートへ接続してきたら、コンテナのポートへ転送する。

ここでは、以下を想定している。
(覚えやすいようにポート番号の頭 2 桁をコンテナの第 4 オクテットと同じにしてあるが、これに従う必要はない)
  1. ssh -p 1122 admin@192.168.11.10 → cont1:22
  2. curl http://192.168.11.10:1180/ → cont1:80
親 OS (guest1) で実施する。(コンテナとの行き来を繰り返すと間違えやすい)

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
port=1122:proto=tcp:toport=22:toaddr=10.0.3.11  
port=1180:proto=tcp:toport=80:toaddr=10.0.3.11
guest1# exit
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>'
    <title>HTTP Server Test Page powered by: Rocky Linux</title>  
guest1$


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
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
cont1# exit
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 # 新規ログインの受付開始