LXD General Command
LXD
介绍
lxd是lxc容器管理的一种。LXC(Linux Containers)是一种轻量级的虚拟化技术,它允许在 Linux 系统上创建和管理容器。每个容器都有自己的文件系统、进程空间和网络栈,但共享宿主机的内核。
之前用过docker,podman和kubernetes,但LXD更偏向于虚拟机,简化 LXC 容器的管理,更接近于完整系统隔离的容器环境,docker和podman更像一种应用发布,重点在于快速、轻量级的应用程序部署,kubernetes和docker compose则是容器编排工具。
关于docker和podman的区别,命令大体一样,只需将docker和podman互换。而docker因为只有root用户才能运行docker daemon,可能有安全风险,podman是无根用户模式,直接运行在用户空间,普通用户也能使用。podman与kubernetes的基本单元相同,都是pod,而docker是以容器为基本单元
环境
Ubuntu 22.04
snappy
安装
ubuntu
1 | 安装snapp,zfs和bridge |
centos7
1 | yum install epel-release yum-plugin-copr -y |
基本操作
创建容器
1 | 创建并启动容器 |
容器信息
1 | 列出所有的容器 |
容器的基本信息
1 | lxc info <container> |
启动
1 | lxc start <container> |
停止
1 | lxc stop <container> |
重启
1 | lxc restart <container> |
暂停
容器任务将会被发送相同的 SIGSTOP 信号,这也意味着它们将仍然是可见的,并且仍然会占用内存,但是它们不会从调度程序中得到任何的 CPU 时间片
1 | lxc pause <container> |
删除
1 | lxc delete <container> |
容器的配置
设备
- 磁盘 既可以是一块物理磁盘,也可以只是一个被挂挂载到容器上的分区,还可以是一个来自主机的绑定挂载路径。
- 网络接口卡 一块网卡。它可以是一块桥接的虚拟网卡,或者是一块点对点设备,还可以是一块以太局域网设备或者一块已经被连接到容器的真实物理接口。
- unix 块设备 一个 UNIX 块设备,比如 /dev/sda
- unix 字符设备 一个 UNIX 字符设备,比如 /dev/kvm
- none 这种特殊类型被用来隐藏那种可以通过配置文件被继承的设备。
配置 profile 文件
1 | 所有可用的配置文件列表 |
本地配置
有些配置是某个容器特定的,你并不想将它放到配置文件中,修改容器配置
1 | lxc config edit <container> |
添加设备
1 | lxc config device add my-container kvm unix-char path=/dev/kvm # 将会为名为“my-container”的容器设置一个 /dev/kvm 项 |
读取配置
1 | # 读取容器的本地配置 |
配置值和设备项的设置都会对容器实时发生影响。这意味着在不重启正在运行的容器的情况下,你可以添加和移除某些设备或者修改安全配置文件。
执行环境
与 LXD 在容器内执行命令的方式相比,有一点是不同的,那就是 shell 并不是在容器中运行。这也意味着容器不知道使用的是什么样的 shell,以及设置了什么样的环境变量和你的家目录在哪里。
通过 LXD 来执行命令总是使用最小的路径环境变量设置,并且 HOME 环境变量必定为 /root,以容器的超级用户身份来执行(即 uid 为 0,gid 为 0)。
其他的环境变量可以通过命令行来设置,或者在“environment.”配置中设置成永久环境变量。
1 | 进入shell |
文件管理
从容器中获得一个文件
1 | lxc file pull <container>/<path> <dest> |
向容器发送一个文件
1 | lxc file push <source> <container>/<path> |
直接编辑一个文件
1 | lxc file edit <container>/<path> |
快照管理
LXD 允许你对容器执行快照功能并恢复它。快照包括了容器在某一时刻的完整状态(如果-stateful被使用的话将会包括运行状态),这也意味着所有的容器配置,容器设备和容器文件系统也会被保存。
创建快照
1 | lxc snapshot <container> # 命令执行完成之后将会生成名为snapX(X 为一个自动增长的数)的记录。 |
列出所有的快照
一个容器的所有快照的数量可以使用lxc list来得到
1 | lxc info <container> |
恢复快照
1 | lxc restore <container> <snapshot name> |
快照重命名
1 | lxc move <container>/<snapshot name> <container>/<new snapshot name> |
从快照中创建一个新的容器
新的容器除了一些可变的信息将会被重置之外(例如 MAC 地址)其余所有信息都将和快照完全相同
1 | lxc copy <source container>/<snapshot name> <destination container> |
删除快照
1 | lxc delete <container>/<snapshot name> |
克隆并重命名
复制容器
目标容器在所有方面将会完全和源容器等同。除了新的容器没有任何源容器的快照以及一些可变值将会被重置之外(例如 MAC 地址)
1 | lxc copy <source container> <destination container> |
移动快照
“move”命令将会被用作给容器重命名。
唯一的要求就是当容器应该被停止,容器内的任何事情都会被保存成它本来的样子,包括可变化的信息(类似 MAC 地址等)。
1 | lxc move <old name> <new name> |
可用资源限制
资源限制可以在容器运行时动态更改。某些可能无法启用,例如,如果设置的内存值小于当前内存用量,但 LXD 将会试着设置并且报告失败。
所有的限制也可以通过配置文件继承,在这种情况下每个受影响的容器将受到该限制的约束。也就是说,如果在默认配置文件中设置 limits.memory=256MB,则使用默认配置文件(通常是全都使用)的每个容器的内存限制为 256MB。
不支持资源限制池,将其中的限制由一组容器共享
除了网络限制是通过较旧但是良好的“tc”实现的,上述大多数限制是通过 Linux 内核的 cgroup API 来实现的。
LXD 在启动时会检测你在内核中启用了哪些 cgroup,并且将只应用你的内核支持的限制。如果你缺少一些 cgroup,守护进程会输出警告,接着你的 init 系统将会记录这些。
在 Ubuntu 16.04 上,默认情况下除了内存交换审计外将会启用所有限制,内存交换审计需要你通过swapaccount = 1这个内核引导参数来启用。
1 | 容器范围的限制 |
磁盘
Linux 没有基于路径的配额,而大多数文件系统只有基于用户和组的配额
使用 ZFS 或 btrfs 存储后端,这意味着现在 LXD 只能支持磁盘限制
可能为 LVM 实现此功能,但这取决于与它一起使用的文件系统,并且如果结合实时更新那会变得棘手起来,因为并不是所有的文件系统都允许在线增长,而几乎没有一个允许在线收缩
不像 CPU 和内存,磁盘和 I/O 限制是直接作用在实际的设备上的,因此你需要编辑原始设备或者屏蔽某个具体的设备。
1 | 设置磁盘限制(需要 btrfs 或者 ZFS) |
CPU
-
X 个 CPU 核心
在这种模式下,你让 LXD 为你选择一组核心,然后为更多的容器和 CPU 的上线/下线提供负载均衡。
容器只看到这个数量的 CPU 核心。 -
一组特定的 CPU 核心(例如,核心1、3 和 5)
类似于第一种模式,但是不会做负载均衡,你会被限制在那些核心上,无论它们有多忙。 -
拥有的 20% 处理能力
在这种模式下,你可以看到所有的 CPU,但调度程序将限制你使用 20% 的 CPU 时间,但这只有在负载状态才会这样!所以如果系统不忙,你的容器可以跑得很欢。而当其他的容器也开始使用 CPU 时,它会被限制用量。 -
每测量 200ms,给我 50ms(并且不超过)
此模式与上一个模式类似,你可以看到所有的 CPU,但这一次,无论系统可能是多么空闲,你只能使用你设置的极限时间下的尽可能多的 CPU 时间。在没有过量使用的系统上,这可使你可以非常整齐地分割 CPU,并确保这些容器的持续性能。
还可以将前两个中的一个与最后两个之一相结合,即请求一组 CPU,然后进一步限制这些 CPU 的 CPU 时间
除此之外,我们还有一个通用的优先级调节方式,可以告诉调度器当你处于负载状态时,两个争夺资源的容器谁会取得胜利。
1 | 限制使用任意两个 CPU 核心 |
注意,为了避免完全混淆用户空间,lxcfs 会重排 /proc/cpuinfo 中的条目,以便没有错误
1 | 限制容器使用 10% 的 CPU 时间 |
内存
支持这种限制以及基于百分比的请求
可以选择在每个容器上打开或者关闭 swap,如果打开,还可以设置优先级,以便你可以选择哪些容器先将内存交换到磁盘
内存限制默认是“hard”。 也就是说,当内存耗尽时,内核将会开始杀掉你的那些进程。
或者,你可以将强制策略设置为“soft”,在这种情况下,只要没有别的进程的情况下,你将被允许使用尽可能多的内存。一旦别的进程想要这块内存,你将无法分配任何内存,直到你低于你的限制或者主机内存再次有空余。
1 | 内存限制 支持的后缀是 KB、MB、GB、TB、PB、EB |
网络 I/O
支持两种限制。
第一个是对网络接口的速率限制。你可以设置入口和出口的限制,或者只是设置“最大”限制然后应用到出口和入口。这个只支持“桥接”和“p2p”类型接口。
第二种是全局网络 I/O 优先级,仅当你的网络接口趋于饱和的时候再使用。
只要机制可用,网络 I/O 基本等同于块 I/O。
1 | 将一个千兆网的连接速度限制到仅仅 100Mbit/s |
块 I/O
可以直接设置磁盘的读写 IO 的频率和速率,并且有一个全局的块 I/O 优先级,它会通知 I/O 调度程序更倾向哪个
实现这些功能的底层使用的是完整的块设备。这意味着不能为每个路径设置每个分区的 I/O 限制。
当使用可以支持多个块设备映射到指定的路径(带或者不带 RAID)的 ZFS 或 btrfs 时,并不知道这个路径是哪个块设备提供的
完全有可能,实际上确实有可能,容器使用的多个磁盘挂载点(绑定挂载或直接挂载)可能来自于同一个物理磁盘
为了使限制生效,LXD 具有猜测给定路径所对应块设备的逻辑,这其中包括询问 ZFS 和 btrfs 工具,甚至可以在发现一个文件系统中循环挂载的文件时递归地找出它们
这个逻辑虽然不完美,但通常会找到一组应该应用限制的块设备。LXD 接着记录并移动到下一个路径。当遍历完所有的路径,然后到了非常奇怪的部分。它会平均你为相应块设备设置的限制,然后应用这些。
这意味着你将在容器中“平均”地获得正确的速度,但这也意味着你不能对来自同一个物理磁盘的“/fast”和一个“/slow”目录应用不同的速度限制。 LXD 允许你设置它,但最后,它会给你这两个值的平均值。
不像 CPU 和内存,磁盘和 I/O 限制是直接作用在实际的设备上的,因此你需要编辑原始设备或者屏蔽某个具体的设备。
1 | 限制速度 |
获取当前资源使用率
LXD API 可以导出目前容器资源使用情况的一点信息,你可以得到:
- 内存:当前、峰值、目前内存交换和峰值内存交换
- 磁盘:当前磁盘使用率
- 网络:每个接口传输的字节和包数。
1 | lxc info |
镜像
LXC 1.0 “下载”模板,它允许用户下载预先打包的容器镜像,用模板脚本在中央服务器上生成,接着高度压缩、签名并通过 https 分发。很多用户从旧版的容器生成方式切换到了使用这种新的、更快更可靠的创建容器的方式。
所有容器都是从镜像创建的,在 LXD 中具有高级镜像缓存和预加载支持,以使镜像存储保持最新。
所有的容器都是由镜像创建的。镜像可以来自一台远程服务器并使用它的完整 hash、短 hash 或者别名拉取下来,但是最终每个 LXD 容器都是创建自一个本地镜像
1 | lxc launch ubuntu:14.04 c1 |
所有这些引用相同的远程镜像,在第一次运行这些命令其中之一时,远程镜像将作为缓存镜像导入本地 LXD 镜像存储,接着从其创建容器。
下一次运行其中一个命令时,LXD 将只检查镜像是否仍然是最新的(当不是由指纹引用时),如果是,它将创建容器而不下载任何东西。
现在镜像被缓存在本地镜像存储中,你也可以从那里启动它,甚至不检查它是否是最新的:
1 | lxc launch 75182b1241be c4 |
手动导入镜像
如果你想复制远程的某个镜像到你本地镜像存储,但不立即从它创建一个容器,你可以使用lxc image copy命令。它可以让你调整一些镜像标志
1 | lxc image copy ubuntu:14.04 local: |
通过比记住其指纹更容易的方式来记住你引用的镜像副本,则可以在复制时添加别名
1 | lxc image copy ubuntu:12.04 local: --alias old-ubuntu |
使用源服务器上设置的别名,你可以要求 LXD 复制下来
1 | lxc image copy ubuntu:15.10 local: --copy-aliases |
上面的副本都是一次性拷贝,也就是复制远程镜像的当前版本到本地镜像存储中。如果你想要 LXD 保持镜像最新,就像它在缓存中存储的那样,你需要使用 –auto-update 标志:
1 | lxc image copy images:gentoo/current/amd64 local: --alias gentoo --auto-update |
导入 tarball
提供了一个单独的 tarball
1 | lxc image import <tarball> |
导入时设置一个别名
1 | lxc image import <tarball> --alias random-image |
给了两个 tarball,要识别哪个是含有 LXD 元数据的。通常可以通过 tarball 的名称来识别,如果不行就选择最小的那个,元数据 tarball 包是很小的。 然后将它们一起导入
1 | lxc image import <metadata tarball> <rootfs tarball> |
从 URL 中导入
如果你的一台 https Web 服务器的某个路径中有 LXD-Image-URL 和 LXD-Image-Hash 的标头设置,那么 LXD 就会把这个镜像拉到镜像存储中。
1 | lxc image import https://dl.stgraber.org/lxd --alias busybox-amd64 |
当拉取镜像时,LXD 还会设置一些标头,远程服务器可以检查它们以返回适当的镜像。 它们是 LXD-Server-Architectures 和 LXD-Server-Version。
这相当于一个简陋的镜像服务器。 它可以通过任何静态 Web 服务器提供一中用户友好的导入镜像的方式。
管理本地镜像存储
列出镜像
1 | lxc image list |
以通过别名或者指纹来过滤
1 | lxc image list amd64 |
指定一个镜像属性中的键值对来过滤
1 | lxc image list os=ubuntu |
了解镜像的所有信息
1 | lxc image info ubuntu |
编辑镜像
1 | lxc image edit <alias or fingerprint> |
删除镜像,注意你不必移除缓存对象,它们会在过期后被 LXD 自动移除(默认上,在最后一次使用的 10 天后)。
1 | lxc image delete <alias or fingerprint> |
导出镜像 得到目前镜像的 tarball
1 | lxc image export old-ubuntu . |
镜像格式
LXD 现在支持两种镜像布局,unified 或者 split。这两者都是有效的 LXD 格式,虽然后者在与其他容器或虚拟机一起运行时更容易重用其文件系统。
不支持任何应用程序容器的“标准”镜像格式,有关镜像格式的最新详细信息,请参阅此文档。
unified 镜像(一个 tarball)
unified 镜像格式是 LXD 在生成镜像时使用的格式。它们是一个单独的大型 tarball,包含 rootfs 目录下的容器文件系统,在 tarball 根目录下有 metadata.yaml 文件,任何模板都放到 templates 目录。
tarball 可以用任何方式压缩(或者不压缩)。镜像散列是压缩后的 tarball 的 sha256 。
Split 镜像(两个 tarball)
这种格式最常用于滚动更新镜像并已经有了一个压缩文件系统 tarball 时。
它们由两个不同的 tarball 组成,第一个只包含 LXD 使用的元数据, metadata.yaml 文件在根目录,任何模板都在 templates 目录。
第二个 tarball 只包含直接位于其根目录下的容器文件系统。大多数发行版已经有这样的 tarball,因为它们常用于引导新机器。 此镜像格式允许不经修改就重用。
两个 tarball 都可以压缩(或者不压缩),它们可以使用不同的压缩算法。 镜像散列是元数据的 tarball 和 rootfs 的 tarball 结合的 sha256。
镜像元数据
metadata.yaml
1 | architecture: "x86_64" |
两个唯一的必填字段是 creation date(UNIX 纪元时间)和 architecture。 其他都可以保持未设置,镜像就可以正常地导入。
额外的属性主要是帮助用户弄清楚镜像是什么。 例如 description 属性是在 lxc image list 中可见的。 用户可以使用其它属性的键/值对来搜索特定镜像。
相反,这些属性用户可以通过 lxc image edit来编辑,creation date 和 architecture 字段是不可变的。
模板机制允许在容器生命周期中的某一点生成或重新生成容器中的一些文件。
使用 pongo2 模板引擎来做这些,将所有我们知道的容器信息都导出到模板。 这样,你可以使用用户定义的容器属性或常规 LXD 属性来自定义镜像,从而更改某些特定文件的内容。
正如你在上面的例子中看到的,我们使用在 Ubuntu 中使用它们来进行 cloud-init 并关闭一些 init 脚本。
将容器变成镜像
1 | lxc publish my-container --alias my-new-image |
容器过去的快照变成镜像
1 | lxc publish my-container/some-snapshot --alias some-image |
手动构建镜像
构建你自己的镜像也很简单。
- 生成容器文件系统。这完全取决于你使用的发行版。对于 Ubuntu 和 Debian,它将用于启动。
- 配置容器中该发行版正常工作所需的任何东西(如果需要任何东西)。
- 制作该容器文件系统的 tarball,可选择压缩它。
- 根据上面描述的内容写一个新的
metadata.yaml文件。 - 创建另一个包含
metadata.yaml文件的 tarball。 - 用下面的命令导入这两个 tarball 作为 LXD 镜像:
lxc image import <metadata tarball> <rootfs tarball> --alias some-name
在一切都正常工作前你可能需要经历几次这样的工作,调整这里或那里,可能会添加一些模板和属性。
所有 LXD 守护程序都充当镜像服务器。除非另有说明,否则加载到镜像存储中的所有镜像都会被标记为私有,因此只有受信任的客户端可以检索这些镜像,但是如果要创建公共镜像服务器,你需要做的是将一些镜像标记为公开,并确保你的 LXD 守护进程监听网络。
只运行 LXD 公共服务器
最简单的共享镜像的方式是运行一个公共的 LXD 守护进程。
1 | lxc config set core.https_address "[::]:8443" |
远程用户就可以添加你的服务器作为公共服务器:
1 | lxc remote add <some name> <IP or DNS> --public |
他们就可以像使用任何默认的镜像服务器一样使用它们。 由于远程服务器添加了 -public 选项,因此不需要身份验证,并且客户端仅限于使用已标记为 public 的镜像。
要将镜像设置成公共的,只需使用 lxc image edit 编辑它们,并将 public 标志设置为 true。
远程主机及容器迁移
XD 2.0 支持两种协议:
- LXD 1.0 API:这是在客户端和 LXD 守护进程之间使用的 REST API,以及在 LXD 守护进程间复制/移动镜像和容器时使用的 REST API。
- Simplestreams:Simplestreams 协议是 LXD 客户端和守护进程使用的只读、仅针对镜像的协议,用于客户端和 LXD 守护进程获取镜像信息以及从一些公共镜像服务器(如 Ubuntu 镜像)导入镜像。
以下所有内容都将使用这两个协议中的第一个。
LXD API 的验证是通过客户端证书在 TLS 1.2 上使用最近的密钥验证的。 当两个 LXD 守护进程必须直接交换信息时,源守护程序生成一个临时令牌,并通过客户端传输到目标守护程序。 此令牌仅可用于访问特定流,并且会被立即撤销,因此不能重新使用。
为了避免中间人攻击,客户端工具还将源服务器的证书发送到目标服务器。这意味着对于特定的下载操作,目标服务器会被提供源服务器的 URL、所需资源的一次性访问令牌以及服务器应该使用的证书。 这可以防止中间人攻击,并且只允许临时访问所传输的对象。
LXD 2.0 使用这样一种模型,某个操作的目标(接收端)直接连接到源以获取数据。
这意味着你必须确保目标服务器可以直接连接到源、可以更新任何所需的防火墙。
有个允许反向连接的计划,允许通过客户端代理本身以应对那些严格的防火墙阻止两台主机之间通信的罕见情况。
与远程主机交互
默认情况下,唯一真正的 LXD 远程配置是 local:,这也是默认的远程(所以你不必输入它的名称)。这个本地(local:)远程使用 LXD REST API 通过 unix 套接字与本地守护进程通信。
假设你已经有两台装有 LXD 的机器:你的本机以及远程那台我们称为“foo”的主机。
首先你需要确保“foo”正在监听网络,并设置了一个密码,以便得到一个远程 shell,运行:
1 | lxc config set core.https_address [::]:8443 |
在你本地 LXD 上,你需要使它对网络可见,这样我们可以从它传输容器和镜像:
1 | lxc config set core.https_address [::]:8443 |
现在已经在两端完成了守护进程的配置,你可以添加“foo”到你的本地客户端,(将 1.2.3.4 替换成你的 IP 或者 FQDN)
1 | lxc remote add foo 1.2.3.4 |
远程列表
1 | lxc remote list |
远程创建
1 | lxc launch ubuntu:14.04 foo:c1 |
列出远程主机正在运行的容器
1 | lxc list foo: |
需要在远程主机上同时指定镜像和容器。因此如果你在“foo”上有一个“my-image”的镜像,并且希望从它创建一个“c2”的容器,你需要运行:
1 | lxc launch foo:my-image foo:c2 |
得到一个远程容器的 shell
1 | lxc exec foo:c1 bash |
复制容器
1 | lxc copy foo:c1 c2 |
移动容器
除非你在做实时迁移,不然你需要在移动前先停止容器
1 | lxc stop foo:c1 |
远程容器的交互时 LXD 使用的 REST API 并不是通过本地 Unix 套接字,而是通过 HTTPS 传输
当两个守护程序之间交互时会变得有些棘手,如复制和移动的情况。在这种情况下会发生:
- 用户运行
lxc move foo:c1 c1。 - 客户端联系
local:远程以检查是否现有“c1”容器。 - 客户端从“foo”获取容器信息。
- 客户端从源“foo”守护程序请求迁移令牌。
- 客户端将迁移令牌以及源 URL 和“foo”的证书发送到本地 LXD 守护程序以及容器配置和周围设备。
- 然后本地 LXD 守护程序使用提供的令牌直接连接到“foo” a) 它连接到第一个控制 websocket b) 它协商文件系统传输协议(zfs 发送/接收,btrfs 发送/接收或者纯 rsync) c) 如果在本地可用,它会解压用于创建源容器的镜像。这是为了避免不必要的数据传输。 d) 然后它会将容器及其任何快照作为增量传输。
- 如果成功,客户端会命令“foo”删除源容器。
容器检查点和恢复
检查点/恢复意味着正在运行的容器状态可以被序列化到磁盘,要么可以作为同一主机上的有状态快照,要么放到另一主机上相当于实时迁移
要求
要使用容器实时迁移和有状态快照,你需要以下条件:
- 一个非常新的 Linux 内核,4.4 或更高版本。
- CRIU 2.0,可能需要一些 cherry-pick 的提交,具体取决于你确切的内核配置。
- 直接在主机上运行 LXD。 不能在容器嵌套下使用这些功能。
- 对于迁移,目标主机必须至少实现源主机的指令集,目标主机内核必须至少提供与源主机相同的系统调用,并且在源主机上挂载的任何内核文件系统也必须可挂载到目标主机上。
Ubuntu 16.04 LTS 已经提供了所有需要的依赖,在这种情况下,您只需要安装 CRIU 本身:
1 | add-apt-repository ppa:criu/ppa |
使用 CRIU
有状态快照
测试24.04无法执行,有嵌套空间的错误,但Ubuntu 16.04可以正常执行
1 | lxc snapshot c1 second --stateful |
意味着所有容器运行时状态都被序列化到磁盘并且作为了快照的一部分。可以像你还原无状态快照那样还原一个有状态快照
1 | lxc restore c1 second |
有状态快照的停止/启动
由于升级内核或者其他类似的维护而需要重启机器。与其等待重启后启动所有的容器
1 | lxc stop c1 --stateful |
容器状态将会写入到磁盘,会在下次启动时读取。
还原容器
1 | lxc start c1 |
实时迁移
实时迁移基本上与上面的有状态快照的停止/启动相同,除了容器目录和配置被移动到另一台机器上。
1 | stgraber@dakara:~$ lxc list c1 |
引用
1.LXC(Linux Containers)介绍、安装、使用及与Docker的区别与联系-CSDN博客

