LXC容器简明教程

LXC(LinuX Containers)是一种操作系统层虚拟化技术,为Linux内核容器功能的一个用户空间接口。与Docker/Podman不同,LXC注重的是容器化的操作系统而不是应用。使用LXC就如同在裸机或虚拟机上运行了一个完整的Linux操作系统,这些容器一般基于一个干净的发布镜像并会长时间运行。

安装

1
2
3
4
# Debian
apt install lxc lxcfs lxc-templates [dnsmasq-base]
# RHEL
yum install lxc lxcfs lxc-templates [dnsmasq]

检查容器模板文件:ls /usr/share/lxc/templates

配置

LXC的服务配置文件位于/etc/defaults/lxc(Debian)或/etc/sysconfig/lxc(RHEL),内容如下:

1
2
3
4
5
6
7
8
9
10
# 是否启用开机启动,这一选项在systemd下没有意义
LXC_AUTO="true"
# 自动启动的容器组,默认为onboot组
BOOTGROUPS="onboot,"
# 等待容器关机的时间,默认5s
SHUTDOWNDELAY=5
# 开机服务选项,如果要在开机时自动启动所有容器,设为"-a -A"
OPTIONS=
# 停止服务选项,如果要直接杀死所有容器,添加-k
STOPOPTS="-a -A -s"

设置完成后,启动主服务,它掌管容器开机启动:
systemctl enable --now lxc

LXC的网络服务配置文件位于/etc/defaults/lxc-net(Debian)或/etc/sysconfig/lxc-net(RHEL),内容如下:

1
2
3
4
# 启用自动生成的lxcbr0网桥,这需要dnsmasq包
USE_LXC_BRIDGE="true"
# LXC的DHCP配置文件,可以设为/etc/dnsmasq.conf以应用主机配置
#LXC_DHCP_CONFILE=/etc/dnsmasq.conf

设置完成后,启动网络服务,它掌管网桥配置:
systemctl enable --now lxc-net

LXC的默认网桥为lxcbr0,宿主机侧的veth会桥接在该网桥上,LXC会自动设置nftables防火墙进行NAT,使得容器能够联通外网。
在旧版本的RHEL中,默认不创建此网桥,用户需要使用自己创建的网桥。
若要启用此网桥,先安装dnsmasq包(但是不必启动dnsmasq服务),然后修改/etc/sysconfig/lxc,将USE_LXC_BRIDGE=改为"true",然后重启LXC网络服务systemctl restart lxc-net

最后,启动lxcfs服务,它为容器提供了主机伪文件系统虚拟化的接口:
systemctl enable --now lxcfs

系统级容器配置文件位于/etc/lxc/default.conf(默认通用)和/var/lib/lxc/容器名/config(独立配置)。
用户级容器配置文件位于~/.config/lxc/default.conf(默认通用)和~/.config/lxc/容器名/config(独立配置)。
主要配置内容如下:

以下配置建议写入通用配置文件内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
lxc.include = /path
导入其他位置的配置文件。

lxc.net.0.type = veth
第 0 号网卡的类型,如果是第二块网卡,则为 lxc.net.1.type 可选类型如下:
none:完全拷贝主机的网络状态。注意对于非特权容器来说,必须挂载sysfs才能使用该选项。
empty:不创建网卡,容器仅有回环网卡。
veth:创建一个对等网卡,该网卡的一端分配给容器,另一端与 lxc.net.0.link 指定的网桥桥接。
macvlan:创建一个 MACVlan 接口,该接口和由 lxc.net.0.link 指定的网桥相连接。
vlan:创建一个由 lxc.net.0.link 指定的虚拟局域网接口分配给容器,VLan的标识符可由 lxc.net.0.vlan.id 指定。
phys:将 lxc.net.0.link 指定的物理网卡分配给容器,此时主机将不能使用该网卡。

lxc.net.0.name = 0
指定虚拟网卡接口的名称,默认是eth开头。

lxc.net.0.link = lxcbr0
指定虚拟网卡连接到的网桥。

lxc.net.0.ipv4[|ipv6].address = 10.0.3.102
容器中网卡的IP。

lxc.net.0.ipv4[|ipv6].gateway = 10.0.3.1
容器中网卡的网关。

lxc.net.0.flags = up
指定网卡的状态,up激活接口,down关闭接口。

lxc.net.0.hwaddr = 00:16:3e:bc:27:d1
指定虚拟网卡的MAC地址,默认情况该值会自动分配。

lxc.arch = amd64
指定容器架构,可选x86, i686, x86_64, amd64。

lxc.autodev = 1
在启动容器时自动生成最小化的/dev目录,默认启用。

lxc.init.cmd = /sbin/init
启动容器时的init程序。

lxc.apparmor.profile = unchanged|unconfigured|generated
容器内的AppArmor规则。
unchanged:继承宿主机规则。
unconfigured:空白规则。
generated:使用临时生成的规则。

lxc.apparmor.allow_incomplete = 0|1
是否允许AppArmor不完整。

lxc.apparmor.allow_nesting = 0|1
是否允许嵌套容器。

lxc.selinux.context = unconfined_t
关闭容器内的SELinux效果,容器并不会修改SELinux标签。

lxc.tty.max = 2
容器中可用的最大终端数。

lxc.idmap = u 0 1000 25565
映射宿主机用户,从左到右依次为:
映射用户或用户组,可选u, g。
容器中的用户UID|GID。
宿主机中的用户UID|GID。
映射的用户数量,用于其他用户,必须确保用户有子UID|GID。

lxc.cgroup.relative = 1
确保LXC不会脱离cgroup,这在systemd下特别有用。

lxc.cgroup2.devices.allow = a rw
允许容器访问宿主机物理设备。
默认情况下,LXC允许容器访问所有物理设备。
也可以手动指定,语法左到右依次为:
设备类型,可选a(全部设备),c(字符设备|硬件设备),b(块设备|存储设备),可以执行ls -l /dev,然后查看第一个字母获得。
设备位置,可以执行ls -l /dev,然后查看第五列内容获得,然后使用:连接表示,如1, 2即为1:2,可以使用通配符。
访问权限,可选r(访问),w(写入),m(创建节点)。

lxc.cgroup2.devices.deny = c 226:0
禁止容器访问宿主机物理设备。
语法左到右依次为:
设备类型,可选a(全部设备),c(字符设备|硬件设备),b(块设备|存储设备),可以执行ls -l /dev,然后查看第一个字母获得。
设备位置,可以执行ls -l /dev,然后查看第五列内容获得,然后使用:连接表示,如1, 2即为1:2,可以使用通配符。

lxc.cgroup2.memory.max = 2G
容器最大内存大小。

lxc.cgroup2.memory.swap.max = 2G
容器最大交换空间大小。

lxc.cgroup2.cpuset.cpus = 0,1,2,3,4,5,6,7
容器可用的CPU核心,这里表示八核的所有核心。

lxc.log.level = 2
日志等级。

lxc.log.file = /var/log/lxc.log
日志文件路径。

以下内容建议写入容器特定的配置文件内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
lxc.tty.dir = lxc
保存容器tty设备文件的目录,会显示在/dev目录下。

lxc.cgroup.dir = lxcgroup
保存容器的cgroup信息的相对目录,会显示在/sys/fs/cgroup目录下。

lxc.group = onboot
加入自动启动组,onboot是默认组。

lxc.start.auto = 1
启用自动启动。

lxc.start.delay = 10
自动启动延迟。

lxc.rootfs.path =
设置容器根文件系统。可以是目录,镜像文件或块设备,特殊格式如下:
overlayfs:/lower:/upper 使用将upper:rw覆盖在lower:ro之上的overlayfs
loop:/file 挂载loopback文件系统

lxc.mount.auto = proc:mixed sys:mixed
设置映射哪些宿主机上的内核文件系统,这可能在某些情况下相当有用。包括以下几个可选项:
proc:映射/proc
sys:映射/sys
cgroup:映射 /sys/fs/cgroup/容器名 到容器中的/sys/fs/cgroup目录
cgroup-full:映射真实的/sys/fs/cgroup
默认情况下以读写模式映射,可以添加挂载选项,有以下几个选项:
:mixed 以混合模式映射,大部分目录保持读写,部分敏感目录为只读权限
:ro 以只读模式映射
:rw 以读写模式映射,这不安全
:force 无论如何都强制映射

lxc.mount.fstab = /xxx
指定一个映射配置文件,格式如下:
主机目录 去掉/的容器目录 文件系统 挂载选项 0 0
挂载选项optional会使目录不存在时也不会失败。
挂载选项create=dir会自动创建挂载目录。

lxc.mount.entry = /mnt mnt none bind 0 0
映射宿主机的/mnt目录。

使用物理网卡

将物理网卡分配给容器而不影响主机使用的一种方法是创建子网卡。
首先创建子网卡ip addr add 192.168.8.222/24 label enp?s?:? dev enp?s?
然后编辑容器配置:

1
2
lxc.net.1.type = phys
lxc.net.1.link = enp?s?:?

使用自建网桥

用户自建网桥后,可以将主机方的Veth桥接到自己创建的虚拟网桥上,对于自建的网桥,LXC不会进行NAT,用户可以将主机网卡也桥接到网桥上,实现二层交换
首先创建虚拟网桥:
brctl addbr br?
brctl addif br? enp?s?

ip link add br? type bridge
ip link set enp?s? master br?
给网桥分配IP:
ip addr add 192.168.?.?/24 dev br?
当然,使用自动化的网络管理工具创建网桥也是可以的。

然后编辑容器配置:

1
2
3
lxc.net.0.type = veth
lxc.net.0.link = br?
lxc.net.0.flags = up

设置非root容器

无特权容器不能直接挂载主机上的网络存储,也不能运行嵌套容器。
对于存储的挂载,用户应当在宿主机上进行挂载,然后设置目录映射。
首先给用户添加子UID和子GID:
usermod 用户名[root] -v|--add-subuids UID段[100000-165535] 添加子UID
usermod 用户名[root] -V|--del-subuids UID段[100000-165535] 删除子UID
usermod 用户名[root] -w|--add-subgids GID段[100000-165535] 添加子GID
usermod 用户名[root] -W|--del-subgids GID段[100000-165535] 删除子GID
如果使用普通用户创建容器,还需要设置内核参数:kernel.unprivileged_userns_clone=1,写入/etc/sysctl.conf,然后执行sysctl -p即可。
然后编辑容器设置,给容器配置用户映射:

1
2
3
# 从左到右依次为:用户或组,容器内ID起点,宿主机ID起点,映射数量
lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536

执行lxc-create创建容器,然后执行lxc-attach -n 容器名 passwd(需要先启动)修改密码。
由于非特权容器的特殊性,对于某些目录的挂载是必然失败的,这其中的典型包括sys-kernel-debug.mountsys-kernel-config.mount,还有某些服务也可能会出错,比如systemd-journald-audit.socketapparmor.service;对于这些服务,可以考虑删除/etc/fstab中的挂载点,或者直接systemctl mask掉。

容器性能限制

使用cgroup v2实现容器的性能限制,编辑配置文件添加:lxc.cgroup2.配置组.配置项 = 大小
例如:

1
2
3
4
5
lxc.cgroup2.cpu.share = 1024    # CPU占用比例
lxc.cgroup2.cpuset.cpus = 0,1 # CPU可用核心
lxc.cgroup2.memory.max = 2G # 最大可用内存大小
lxc.cgroup2.memory.swap.max = 2G # 最大可用交换空间大小
lxc.cgroup2.devices.allow = a # 允许访问所有设备

注:LXC使用目录作为根文件系统,要限制容器根分区大小,请使用配额或子卷。

操作

检查配置情况
lxc-checkconfig

创建容器
lxc-create [-B best|dir|lvm|loop|btrfs|zfs|rbd] -t download -n 容器名 -- --server mirrors.tuna.tsinghua.edu.cn/lxc-images [--dist 发行版] [--release 版本号] [--arch 架构]
默认情况下使用dir即目录保存容器文件系统,用户也可以指定其他的存储方式,包括以下几种:

  • best,自动检测并尝试使用最佳的存储后端。
  • dir,使用普通的目录进行存储,这意味着容器的文件系统与宿主机的文件系统一致,而且大小没有限制。
  • loop,回环文件系统。会创建一个镜像文件rootdev进行存储,可以通过--fstype指定文件系统(默认情况下为ext4),--fssize指定镜像最大大小(默认情况下为1G)。
  • lvm,逻辑卷。默认情况下会创建卷组lxc,精简池lxc,1G的容器同名LV,ext4文件系统进行存储。可以通过--vgname指定VG名,--thinpool指定精简池名,--lvname指定LV名,--fstype指定文件系统,--fssize指定卷大小。
  • btrfs,Btrfs子卷。会创建容器的同名子卷进行存储。
  • rbd,远程块设备。不太常用。

一般选择download模板从远程进行下载,这里使用--server指定镜像源,--表示结束lxc-create的选项,将后面的选项传送给下载脚本。
如果没有指定,那么下载时会交互式选择系统,版本和架构。
创建的容器的根文件系统位于/var/lib/lxc/容器名/rootfs(系统容器)或~/.local/share/lxc/容器名/rootfs(用户容器),要修改容器初始root密码,执行lxc-attach -n 容器名 passwd(需要先启动)或者chroot /容器根文件系统 passwd

列出容器
lxc-ls [-f|--fancy]
-f|--fancy表示显示详细信息。

查看容器配置
lxc-config -l
查看所有可用配置项。

查看容器信息
lxc-info -n 容器名

启动容器
lxc-start [-F] -n 容器名
-F 启动时立刻进入前台,否则默认为-d,即后台启动

停止容器
lxc-stop [-W] [-k] [-r] -n 容器名
-W 并不等待容器停止
-k 强制杀死容器
-r 重启容器

在容器中执行命令
lxc-attach [-e] -n 容器名 [命令]
若没有命令,默认在容器中以当前用户身份启动Shell,这相当于lxc-console,但是不会进行登录(这意味着没有任何用户变量),而且总是会产生新会话。
-e 使用提升权限执行命令

进入容器
lxc-console -n 容器名 [-t TTY号]
这会进入容器的指定TTY并进行登录流程,当TTY号为0时,使用/dev/console
在LXC容器中,当进入容器时实际上是将容器中的/dev/ttyN映射到了主机当前所使用的TTY上,也就是说,如果映射了/dev/console(容器中不存在tty0,/dev/console没有依附的终端),那么可以在当前主机的TTY上看到容器系统的内部信息。

脱离容器
使用Ctrl+A+Q快捷键脱离容器终端。
这个快捷键在现在的版本上往往不能使用。强烈建议使用SSH进行连接。

删除容器
lxc-destroy -n 容器名 [-f] [-s]
-s 连同删除所有快照
-f 强制删除

拷贝容器
lxc-copy [-R] -n 容器名 -N 新容器名
-R 交换容器名和新容器名

制作快照
lxc-snapshot -n 容器名

列出快照
lxc-snapshot -n 容器名 -L|--list

恢复快照
lxc-snapshot -n 容器名 -r 快照名 [-N 新容器名]

分配物理设备
lxc-device add /dev/设备名 -n 容器名
lxc-device del /dev/设备名 -n 容器名

监控所有容器
lxc-top

监控单个容器状态
lxc-monitor -n 容器名

批量启动
lxc-autostart -g 组名[onboot]