LXC 快速入门:在 Docker 和虚拟机之外,再备一套 Linux 环境

Kaku Lv4

只是想折腾一套 Linux,有必要开虚拟机吗

假设你想试一个服务。除了把程序跑起来,还打算研究一下它的配置文件、systemd unit、日志和开机启动。直接装在自己的机器上,试完又要清理;开一台虚拟机当然可以,但这次并不需要另一套内核,也不需要模拟硬件。

Docker 也能提供隔离环境。不过,如果日常操作是进入系统、安装软件、修改配置,再让几个相关服务长期运行,围绕 Dockerfile 构建和重建应用镜像的方式,未必是这次最顺手的选择。

这时可以考虑 LXC。

它能提供一套用起来接近普通 Linux 安装的环境。你可以进入 Shell,用包管理器装软件,用服务管理器启停服务。普通持久容器关掉再启动,安装的软件和修改的配置仍然在。

把它叫作“轻量虚拟机”很容易建立直觉,但要记住一个区别:LXC 是容器,与宿主机共享 Linux 内核。 它没有在里面再启动一个独立内核。

这也决定了它适合做什么,以及哪些事情仍然应该交给 VM。

像一套系统,但没有自己的内核

先看一张简化的结构图。这里只比较系统容器与常规虚拟机,不表示两者所有实现细节:

graph TB
    subgraph VM ["常规虚拟机"]
        V1["应用与用户空间 A"] --> K1["客户机内核 A"]
        V2["应用与用户空间 B"] --> K2["客户机内核 B"]
        K1 --> H["虚拟化层与宿主平台"]
        K2 --> H
    end
    subgraph CT ["LXC 系统容器"]
        C1["容器 A:init、服务、软件与配置"] --> K["同一个宿主机 Linux 内核"]
        C2["容器 B:init、服务、软件与配置"] --> K
    end

LXC 不需要为每个容器运行一份独立内核,也不走完整的虚拟硬件启动流程。这是它轻量的一个重要来源。不过,“轻量”不等于每个容器只占固定的几 MB。容器里开了哪些服务、用了多少缓存,仍然影响实际占用。

宿主机和容器可以使用不同的 Linux 发行版,因为发行版提供的 /etc、库、包管理器和各种程序属于用户空间。只要宿主内核、架构和所需功能兼容,Debian 宿主并不要求所有容器都只能装 Debian。

但这不等于可以随意换内核。容器里安装一个内核软件包,不会让它在下次启动时改用这个内核。测试自编译内核、启动 Windows,或者研究引导和虚拟硬件,仍然应该使用合适的虚拟机。

至于 Docker 与 LXC,区别更多体现在日常管理方式上:

需求我会优先考虑
构建应用镜像,按版本分发和重复部署Docker
保留一套 Linux 用户空间,练习安装、配置和服务管理LXC
使用独立内核、其他操作系统或虚拟硬件VM

这不是一张功能排他表。Docker 可以运行多个进程,LXC 也能运行应用容器;“一个容器一个进程”不能拿来当两者的技术分界。

本文只讨论 LXC 最有代表性的用法:系统容器

从大型机到容器:两条并行的路线

虚拟化并不是云服务器流行之后才出现的技术。把时间往前拨,它最初关心的问题就很实际:一台昂贵的计算机,怎样同时承载多套环境,又不必让它们挤在同一个系统里互相影响?

大型机时代,就已经在分出“多台机器”

20 世纪 60 年代,IBM 的 CP-40、CP-67 等项目已经在探索虚拟机技术。1972 年,IBM 发布 VM/370,把这条路线带到了 System/370 平台。

分时系统让多个用户共享计算资源,虚拟机则进一步提供机器级的运行环境,让不同操作系统实例可以共用底下的物理硬件。两者有关联,但不能把“多人同时登录一个系统”直接等同于“运行了多台虚拟机”。

今天我们在服务器上创建 VM,所面对的硬件、管理界面和使用规模已经变了,不过“同一台物理机器承载多个系统实例”的思路并不新。

虚拟机来到 PC 和 Linux 服务器

到 1999 年,VMware Workstation 1.0 已经把在一台 PC 上运行多个操作系统的体验带给桌面用户。开发和测试时,不必为了另一套系统专门准备一台电脑。

后来,Intel VT、AMD-V 等处理器扩展为 x86 虚拟化提供了硬件辅助。这里容易产生一个误会:CPU 有了虚拟化扩展,并不意味着虚拟机从此不需要软件。虚拟机管理、设备模型和资源调度仍然需要相应的软件配合。

2007 年,KVM 的内核组件随 Linux 2.6.20 进入主线。它利用硬件虚拟化扩展,让 Linux 可以承担虚拟机运行的核心工作。

虽然 KVM 的名字里也有 Kernel,而且本身就在 Linux 内核中,它运行的客户机仍有自己的内核。这一点与 LXC 不同。判断是不是共享内核的容器,要看客户环境怎样运行,不能只看实现代码放在哪里。

另一条路线:不再启动一份内核

如果需求只是隔开几套应用和系统环境,有没有必要每次都提供完整的虚拟机器?操作系统级虚拟化走的是另一条路:保留同一个内核,在它上面划分运行环境。

Unix 的 chroot 可以改变进程看到的根目录,是理解这种思路的一个起点。但它本身不提供完整的进程、网络和权限隔离,也不能单独当作可靠的安全容器。

2000 年前后的 FreeBSD 4.x 已提供 jail,在目录之外进一步约束运行环境。Solaris 10 的传统 Zones 也允许在一个 Solaris 系统实例中创建多个隔离环境,非全局 zone 共享底下的内核。

这些技术不是“把虚拟机越做越小,最后变成容器”的连续升级,也不意味着它们之间都有直接的代码继承关系。虚拟机侧重提供独立的机器运行环境,操作系统级隔离则侧重在一个内核上划分环境。 两条路线有不同的能力和限制,可以长期并存。

Linux 上的 LXC,以及后来的 Docker

LXC 官方手册记载,项目自 2008 年开始活跃开发。它把 Linux 内核已有的隔离和资源管理能力组织起来,提供库和命令行工具,让创建系统容器不必从手工拼装这些机制开始。

早期 Docker 使用过 LXC。2014 年 3 月发布的 Docker 0.9 引入 libcontainer,并把它设为默认执行驱动,LXC 从那时起变成可选项。因此,看到旧文章说“Docker 基于 LXC”时,要看清它描述的年代,不能原样套到今天。

同样在 2014 年,LXC 1.0 引入了非特权容器支持。容器内 root 与宿主机 root 的关系,开始可以通过用户命名空间和 UID 映射分开处理。后面的实验会用到这部分。

所以,容器没有从 Docker 才开始,虚拟机也没有因为容器出现就失去用途。需要独立内核时使用 VM,需要隔开 Linux 用户空间时使用容器,甚至可以在 VM 内再运行容器。LXC 就处在共享内核这条路线上,提供接近普通 Linux 安装的系统环境。

共享内核,为什么还能像两套系统

LXC 本身不是另一种内核。官方把它描述为 Linux 内核容器隔离能力的用户空间接口。创建容器时,它负责配置环境,再在这个环境里启动进程。

其中有几块比较重要。

Namespaces:让进程看到不同的环境

进程列表、主机名、网卡和挂载,并不一定要让所有进程看到同一份。

例如,PID namespace 可以让容器中的 init 看起来是 PID 1,而从宿主机看,它只是另一个具有宿主 PID 的进程。Network namespace 则可以让容器拥有自己的网络设备、地址和路由表。

这种隔离不是把进程搬到另一台机器。进程仍由同一个内核调度,只是看到的资源视图不同。

Rootfs:把软件和配置分开

容器需要自己的根文件系统,也就是 rootfs。里面有 /usr/etc/var,以及程序、库和配置文件。

镜像模板准备这些文件,LXC 再结合挂载命名空间等机制,把它们组织成容器所见的 /

只有一个 rootfs 目录,并不能自动获得进程、网络和权限隔离。这也是为什么不能简单把容器理解成“换了根目录的 Shell”。

Cgroups:限制能用多少资源

看不到宿主机的其他进程,不代表不会把宿主机内存吃光。Namespaces 和资源配额解决的是不同问题。

Cgroups 用来组织进程,并通过相应控制器管理 CPU、内存、进程数量等资源。LXC 可以把这些限制写进容器配置。

不设置限制时,不能假定系统已经自动替每个容器分好了额度。本文会在第一次启动前给实验容器设置基本上限。

容器里的 root,映射成宿主机上的谁

特权容器中,容器 UID 0 映射到宿主机 UID 0。虽然还会有 namespaces、capabilities、安全策略等约束,LXC 上游仍明确认为它不适合把容器 root 交给不可信的人。

非特权容器则可以建立这样的映射。以下数字只是示意:

1
2
3
容器 UID 0       → 宿主机 UID 100000
容器 UID 1 → 宿主机 UID 100001
容器 UID 65535 → 宿主机 UID 165535

容器内的管理员仍能管理容器自己的软件和文件,但在宿主机看来,这些进程并不是 UID 0。

这里有个容易混淆的地方:由宿主机 root 创建和启动的容器,也可以是非特权容器。 判断依据是容器的用户命名空间和映射,不是命令前面有没有 sudo

非特权映射也不是绝对安全保证。内核仍然共享,宿主机要及时更新;LXC 使用的 seccomp、AppArmor 和 capabilities 等限制,也不应该为了消除报错就随手关闭。

先认清工具,再固定环境

搜索 LXC 教程时,常会看到几套长得很像的名字:

名称本文需要分清的关系
LXC底层容器运行时、库,以及 lxc-createlxc-start 等工具
LXD更高层的容器与虚拟机管理工具,常见命令入口是 lxc
Incus起源于 LXD 分叉的管理工具,命令入口是 incus

lxc launch 并不是把 lxc-create 换个写法。它们属于不同工具的接口。这篇使用原生 LXC,不混用 LXD 或 Incus 的命令。

本文的目标环境固定为:

1
2
3
4
5
6
宿主机:Debian 13(trixie),amd64,systemd
LXC:Debian 仓库中的 6.0.4 系列,安全修订号随更新变化
容器:Linux Containers 镜像服务器的 Debian trixie / amd64 镜像
权限:宿主机 root 管理,容器使用非特权 UID/GID 映射
网络:lxc-net 管理的私有网桥,本文实验只使用 IPv4
资源控制:cgroup v2

验证范围说明: 以下步骤依据官方文档、Debian 打包源码和镜像索引整理,核对日期为 2026-09-08;尚未在上述环境完整实机执行,不提供虚构的运行输出。镜像会更新,实际网络、防火墙和安全策略也可能不同,首次操作建议放在一台可丢弃的 Debian 虚拟机中,而不是正在承载业务的服务器上。

下文默认宿主机账号可以使用 sudo。Debian 如果没有安装 sudo,可用 su - 进入宿主机 root Shell 后执行相应命令,去掉 sudo 前缀。标为“容器内”的命令,则只能在进入容器后执行。

安装 LXC,准备私有网络

安装会增加软件包,并可能启动 lxc-net、创建网桥和调整转发/NAT 规则。已有 Docker、VPN 或自定义防火墙的机器,先检查网段和网络策略;不要把安装步骤当作完全不影响宿主网络的操作。

在宿主机安装工具:

1
2
sudo apt update
sudo apt install lxc uidmap dnsmasq-base wget xz-utils ca-certificates curl

uidmap 提供 UID/GID 映射辅助工具;dnsmasq-base 供 lxc-net 提供 DHCP 等服务,不需要另外启动一个全局 dnsmasq 服务。Debian 的 liblxc-common 随依赖安装,其中包含本文使用的 download 模板。

检查安装版本、内核支持和 cgroup 挂载:

1
2
3
lxc-start --version
sudo lxc-checkconfig
findmnt -n -o FSTYPE /sys/fs/cgroup

最后一条应识别为 cgroup2,才适用后面的 lxc.cgroup2.* 配置。lxc-checkconfig 是功能检查,不是要求把每个可选功能都变成 enabled;缺项要结合具体用途判断。

Debian 13 的 LXC 包提供 lxc-net.service。它的常见默认 IPv4 配置是 lxcbr010.0.3.0/24。先看当前网络与配置,确认没有和已有 LAN、VPN 或容器网段重叠:

1
2
3
ip -4 address
ip -4 route show table all
sudo cat /etc/default/lxc-net

下面只针对新的实验宿主机。停止 lxc-net 会影响使用该网桥的现有容器。 要在旧配置仍有效时先停止服务,让脚本按旧参数清理网络,再备份和编辑;先改配置再重启,可能遗留原网段或 IPv6 的 NAT 规则。

1
2
3
sudo systemctl stop lxc-net.service
sudo cp -a --backup=numbered /etc/default/lxc-net /etc/default/lxc-net.bak
sudoedit /etc/default/lxc-net

将以下项设置为对应值。已有同名项就修改,不要反复追加。这是修改配置文件的内容,不是 Shell 命令:

1
2
3
4
5
6
7
8
9
10
11
USE_LXC_BRIDGE="true"
LXC_BRIDGE="lxcbr0"
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_IPV6_ENABLE="false"
LXC_IPV6_ADDR=""
LXC_IPV6_MASK=""
LXC_IPV6_NETWORK=""
LXC_IPV6_NAT="false"

这里不让 lxc-net 配置 IPv6 ULA 地址、路由通告和 IPv6 NAT,避免把双栈网络也塞进第一次上手。这不禁用宿主机或网桥接口的 IPv6 协议栈,仍可能出现链路本地地址。如果 10.0.3.0/24 已被使用,需要选择空闲私有网段,并同步修改地址、网络和 DHCP 范围。

保存后启动服务并检查:

1
2
3
sudo systemctl enable --now lxc-net.service
sudo systemctl status lxc-net.service --no-pager
ip -4 address show dev lxcbr0

这是一个 oneshot 服务,active (exited) 并不表示失败。需要同时确认网桥存在、IPv4 地址符合配置。失败时先看日志:

1
sudo journalctl -u lxc-net.service -b --no-pager

本文不会把物理网卡加入网桥,也不改宿主机默认路由。远程连接服务器时,没必要为了第一次创建容器就冒险重做物理网络。

先配映射,再创建容器

下面为容器 lxc-demo 准备一份独立配置,不修改所有新容器共用的 /etc/lxc/default.conf

先在宿主机查看已有的从属 ID 分配:

1
2
sudo cat /etc/subuid
sudo cat /etc/subgid

记录格式是 账号:起始ID:数量。例如 100000:65536 表示从 100000 到 165535,不是到 65536。

接下来用 100000–165535 作为示例。只有确认这段 UID 和 GID 范围没有分给其他账号、没有被实际用户或其他容器映射占用时,才能采用它。 已有 root 的合适分配可以复用授权,但不应让不互信的容器共享同一段映射。非全新机器需要结合 getent passwdgetent group 以及现有容器配置一起核对。

修改前备份两个文件;以下添加授权只需执行一次,已有同样授权时跳过 usermod

1
2
3
sudo cp -a --backup=numbered /etc/subuid /etc/subuid.bak
sudo cp -a --backup=numbered /etc/subgid /etc/subgid.bak
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 root

这不会把 root 自己的 UID 改掉,只是给它增加可使用的从属 ID 范围。若采用别的空闲范围,下面配置中的宿主起始 ID 也必须一起替换。

确认 /etc/lxc/lxc-demo.conf 不存在后,用编辑器新建:

1
sudoedit /etc/lxc/lxc-demo.conf

写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lxc.include = /usr/share/lxc/config/common.conf
lxc.include = /usr/share/lxc/config/userns.conf

lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.name = eth0

lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536

lxc.cgroup2.memory.max = 1073741824
lxc.cgroup2.memory.swap.max = 0
lxc.cgroup2.cpu.max = 100000 100000
lxc.cgroup2.pids.max = 1024

common.confuserns.conf 沿用 LXC 提供的基础配置。两条 lxc.idmap 分别映射用户和组,字段顺序为“类型、容器起始 ID、宿主起始 ID、数量”。

最后四项给实验设置上限:1 GiB 内存、禁止使用 swap、通常调度任务约一个逻辑 CPU 的总时间配额,以及 1024 个任务的上限。pids 控制器也计入线程;CPU 配额不等于绑定一颗核心。这些限制用于实验,不是适合所有服务的推荐规格,内存不足仍可能触发容器内的 OOM。

创建前确认没有同名容器:

1
sudo lxc-ls --fancy

然后创建 Debian 容器。此操作会下载并解包镜像,在宿主机创建容器数据:

1
2
3
4
5
sudo lxc-create \
-n lxc-demo \
-f /etc/lxc/lxc-demo.conf \
-t download -- \
-d debian -r trixie -a amd64

-- 前是 lxc-create 的参数,后面传给 download 模板;-d-r-a 分别选择发行版、版本代号和架构。本文固定 amd64,不应在 ARM 宿主上照抄这个架构值。

镜像来自 Linux Containers 社区镜像服务器,不是 Debian 官方直接发布的容器镜像。本次核对时索引中存在这个组合,但可用镜像和构建内容会变化。下载失败先确认镜像列表、网络和系统时间,不要用关闭 TLS 校验的方式绕过去。

创建完成后检查 /var/lib/lxc/lxc-demo/config,确认映射、网络配置和四项 cgroup2 限制仍在。download 模板会合并镜像默认配置,最终生成的文件才是后续启动使用的配置。

映射要在创建 rootfs 前准备好。 已经创建过特权容器后,再补两行 lxc.idmap,并不等于完成了非特权转换,文件属主也需要对应。第一次实验用正确配置重新创建一个新名字,通常比修补混乱的旧 rootfs 更清楚。

启动,进去看看

在宿主机启动并检查容器:

1
2
3
sudo lxc-start -n lxc-demo
sudo lxc-info -n lxc-demo
sudo lxc-ls --fancy

确认状态为 RUNNINGlxc-ls --fancyUNPRIVILEGED 列应为 true;如果不是,先停下来核对映射,不要继续把它当非特权环境使用。

容器运行与网络就绪不是同一件事。刚启动时还没有 IP 地址,可以稍后再检查;持续没有地址则需要排查 DHCP 和容器网络。

进入容器 Shell:

1
sudo lxc-attach -n lxc-demo --clear-env

从这里开始,以下命令在容器内执行:

1
2
3
4
5
cat /etc/os-release
uname -r
ps -p 1 -o pid,comm,args
ip -4 address
ip -4 route

/etc/os-release 描述容器用户空间的发行版;uname -r 显示的仍是宿主机内核版本。本文选择的是带 init 的 Debian 系统容器,PID 1 应对应其初始化系统,后面的服务管理以 systemd 为前提。

如果选了别的精简镜像,不能假定 systemd 一定存在。先看 PID 1,再决定使用哪个服务管理工具。

输入 exit 返回宿主机:

1
exit

退出这个 Shell 不会关闭容器。lxc-attach 也不是 SSH,不要求容器先装 SSH 服务或具备可用网络。

想从宿主机观察映射,可以取得容器 init 的宿主 PID,再读取它的用户命名空间映射:

1
2
3
4
PID=$(sudo lxc-info -n lxc-demo -pH)
sudo cat "/proc/$PID/uid_map"
sudo cat "/proc/$PID/gid_map"
sudo ps -p "$PID" -o pid,uid,gid,comm

容器必须保持运行,且 PID 非空。按前面的配置,应能看到容器起始 ID 0 对应宿主起始 ID 100000,长度 65536;init 在宿主机上的数值 UID/GID 也应体现这段映射,而不是 0。

装一个 Nginx,把服务跑起来

再次进入容器:

1
sudo lxc-attach -n lxc-demo --clear-env

以下都在容器内执行。先安装服务和验证工具,再检查配置并启动:

1
2
3
4
5
6
apt update
apt install nginx curl
nginx -t
systemctl enable --now nginx
systemctl status nginx --no-pager
curl --fail http://127.0.0.1/

这里的 127.0.0.1 属于容器自己的网络命名空间,不是宿主机。先从容器内请求,是为了把“服务是否启动”与“跨容器网络是否正常”分开检查。

在新的 Debian Nginx 安装中,先检查默认站点的 rootindex,确认网页目录是 /var/www/html,且 index.html 会被优先使用:

1
cat /etc/nginx/sites-enabled/default

然后写入一页实验内容。下面会覆盖容器内的 /var/www/html/index.html;若文件已有内容,先备份,不能对现有业务站点直接执行。

1
2
3
4
5
if [ -e /var/www/html/index.html ]; then
cp -a --backup=numbered /var/www/html/index.html /var/www/html/index.html.bak
fi
printf '%s\n' '<h1>Hello from LXC</h1>' > /var/www/html/index.html
curl --fail http://127.0.0.1/

这次只修改静态文件,不需要重启 Nginx。如果修改的是 Nginx 配置,则先 nginx -t,通过后再用 systemctl reload nginx 加载。

退出容器,在宿主机查看它的 IPv4 地址:

1
2
exit
sudo lxc-info -n lxc-demo -iH

将实际地址填入下面变量。10.0.3.100 只是示例地址,不是保证会分配给你的地址

1
2
CONTAINER_IP='10.0.3.100'
curl --fail --connect-timeout 5 "http://${CONTAINER_IP}/"

应收到刚才写入的 HTML。至此,容器内的服务可以从宿主机访问,并不需要另做 Docker 风格的端口发布。

再做一次持久性检查。以下停止操作会中断实验服务:

1
2
3
sudo lxc-stop -n lxc-demo
sudo lxc-start -n lxc-demo
sudo lxc-info -n lxc-demo

等待容器网络就绪,重新查询 IP,再请求页面。不要依赖前一次 DHCP 地址永远不变。systemctl enable 负责 Nginx 随容器内部的系统启动,写入 rootfs 的网页则仍然保留。

这并没有设置“宿主机开机时自动启动容器”。后者属于 LXC 的自动启动配置,是另一层启动关系。

能访问外网,为什么局域网电脑打不开

本文使用的网络大致是这样:

flowchart LR
    A[容器 eth0] --- V[veth 对]
    V --- B[宿主机 lxcbr0]
    B --> R[宿主机路由与防火墙]
    R --> N[出站 NAT]
    N --> E[外部网络]
    H[宿主机程序] --> B

veth 把容器连接到网桥;DHCP 给它分配地址;宿主机路由、转发规则和 NAT 配合,让私有地址的容器能访问外网。这些工作不是一个“有网桥”就全部完成了。

宿主机本身连接着 lxcbr0,通常知道如何到达这个网段。局域网里的另一台电脑却未必知道 10.0.3.0/24 在哪里,更不会因为出站 NAT 已经启用,就自动获得到容器服务的入站通路。

因此这三件事要分开:

  • 容器能够访问外网;
  • 宿主机能够访问容器;
  • 局域网或公网客户端能够访问容器。

需要对外提供服务时,可以再选择宿主机反向代理、明确配置端口转发,或者部署适合环境的路由/桥接方案。本文先不修改这部分,也不把 NAT 当成完整安全边界。网桥上的其他容器同样可能访问服务,仍要考虑防火墙和认证。

连不通时,可以按现象缩小范围:

现象先检查
容器没有 IPv4 地址lxc-net 日志、veth/网桥连接、容器 DHCP 客户端
有地址,但包管理器连不上仓库容器默认路由、DNS、宿主转发和防火墙
容器内访问 localhost 失败Nginx 状态、配置和监听端口
容器内成功,宿主访问失败实际 IP、服务监听地址、双方防火墙
宿主成功,局域网电脑失败到容器网段的路由或对外发布方式

例如在容器内查看监听、解析和服务日志:

1
2
3
ss -lntp
getent hosts deb.debian.org
journalctl -u nginx -b --no-pager

不要一看到网络不通,就清空宿主机 nftables/iptables 规则或关闭整个防火墙。那可能顺手破坏 Docker、VPN 和其他服务的网络边界。

数据、限制和清理,别只记住启动命令

配置文件和 rootfs 在哪里

本文使用默认容器存放路径、默认目录存储方式,创建后的配置位于:

1
/var/lib/lxc/lxc-demo/config

rootfs 的位置由其中的 lxc.rootfs.path 指定,本文这类目录容器通常位于 /var/lib/lxc/lxc-demo/rootfs。其他存储后端可能不同,不应只凭目录名判断。

前面的 /etc/lxc/lxc-demo.conf 是创建时传入的配置。容器创建完成后,后续修改应针对 /var/lib/lxc/lxc-demo/config,不要以为修改最初的文件就会自动同步。

安装软件和改应用配置,尽量通过 lxc-attach 在容器内完成。直接从宿主机往非特权 rootfs 写文件,容易留下映射不正确的属主。

绑定挂载则是另一回事:宿主目录映射到容器里后,双方操作的是同一批文件。容器看见了目录,不代表就有写权限,仍要核对宿主 UID/GID 与容器映射。不要为解决权限问题就把宿主机的 //etc 或整个用户目录挂进去,也不要直接 chmod -R 777

资源限制要能读回来

容器运行时,可以在宿主机查询前面设置的控制器值:

1
2
3
4
sudo lxc-cgroup -n lxc-demo memory.max
sudo lxc-cgroup -n lxc-demo memory.swap.max
sudo lxc-cgroup -n lxc-demo cpu.max
sudo lxc-cgroup -n lxc-demo pids.max

它们应对应创建配置中的值。若控制器不存在或访问失败,检查 cgroup 模式与 LXC 启动日志,不要把配置文件里“写了”当成已经生效。

要调整持久配置,先备份容器的 config 再编辑,并安排停启使其应用。不要把内存上限突然调到低于正在使用的量;这可能导致内存回收和 OOM。本文这些 CPU、内存、任务数量限制也不包含磁盘空间配额,容器写满宿主存储仍是需要单独处理的风险。

启动失败,先留下日志

确认容器已停止后,可以带调试日志尝试启动。此命令会启动容器,并在宿主机写入日志文件:

1
2
3
4
sudo lxc-start -n lxc-demo \
-o /var/log/lxc-demo-start.log \
-l DEBUG
sudo less /var/log/lxc-demo-start.log

重点看映射、挂载、网桥与 cgroup 相关错误。调试日志可能包含主机路径和配置信息,对外分享前先检查敏感内容。

不要把“改成特权模式”“关闭 AppArmor”“允许所有设备”当通用修复。报错消失了,隔离边界可能也一起消失了。

停止不是删除,删除也不是备份

只想暂时不用,执行停止即可,服务会中断,但本文普通持久容器的文件仍保留:

1
sudo lxc-stop -n lxc-demo

如果确认实验不再需要,先确认容器名、检查是否有要保存的文件,并在停止状态下销毁:

下面命令会删除 lxc-demo 的容器配置和由 LXC 管理的 rootfs 数据。安装的软件、网页及其他保存在其中的文件无法靠再次启动找回。务必先备份需要保留的内容。

1
sudo lxc-destroy -n lxc-demo

单独保存在 /etc/lxc/lxc-demo.conf 的创建配置不会因此自动删除,宿主机的从属 ID 授权、软件包和网桥也会保留。不要为了清理一个容器就直接停掉供其他容器使用的网桥,或撤销仍被其他实例使用的 ID 范围。

以后用上快照,也要区分快照与独立备份。快照能力和行为取决于存储方案;同一块存储损坏时,只留在上面的快照不一定能救回数据。

给工具箱多留一个选择

LXC 最吸引人的地方,是让一套 Linux 用户空间可以单独管理,又不必为它运行独立内核。装软件、改配置、启停服务,操作习惯和普通服务器很接近。

代价也清楚:内核共享,某些系统级操作受限,网络、文件权限和资源限制仍然需要理解。它不是一台缩小了但能力完全不变的虚拟机。

如果目标是按镜像版本交付应用,我会继续用 Docker;如果是保留一套 Linux 环境做配置实验或运行受信任的系统服务,LXC 很值得放进工具箱;需要另一个内核或操作系统时,就开 VM。

本文手动接触了 LXC 的配置和网络。如果后面需要频繁创建多个系统容器,希望统一管理镜像、网络、存储和快照,可以再看 Incus。但在换工具之前,先分清用户空间与内核、容器 root 与宿主 root,后面的操作会容易理解得多。

参考资料

  • 标题: LXC 快速入门:在 Docker 和虚拟机之外,再备一套 Linux 环境
  • 作者: Kaku
  • 创建于 : 2026-09-08 17:11:57
  • 更新于 : 2026-09-08 17:34:50
  • 链接: https://www.kakunet.top/2026/09/08/LXC-快速入门:在-Docker-和虚拟机之外,再备一套-Linux-环境/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论