一、虚拟化技术的简介
LXD 是目前市面上众多虚拟化的解决方案之一,在开始介绍 LXD 之前,需要对虚拟化技术进行简单的介绍。目前业界的虚拟化一般分为两种解决方案:内核虚拟化与硬件虚拟化。
1.1 内核虚拟化
硬件虚拟化字面义理解就是需要硬件设备来支持,需要在服务器上安装虚拟化管理软件(HyperVisor),然后通过 HyperVisor 提供的交互管理界面来创建虚拟机。市面上比较常见的硬件虚拟化如:VMware、SmartX等。
虚拟机在操作系统层面上和宿主机隔离,拥有自己独立的内核,使用上和普通的主机没有任何区别,拥有较高的安全性,但是因其独占式的资源使用方式,消耗的资源比较多,容易产生浪费。一般情况下分配的虚拟机资源不能超过宿主机的资源总量(例如CPU或者内存、磁盘容量),否则会引发宿主机崩溃。
硬件虚拟化需要在主板BOIS系统开启VE虚拟化支持(Intel主板为 Intel VT,AMD 主板为 AMD-V),可以通过执行以下的命令来检查是否已开启硬件虚拟化:
egrep -c '(vmx|svm)' /proc/cpuinfo
如果命令执行有输出,即代表支持硬件虚拟化。
注意,硬件的虚拟化技术分为 Type-1 和 Type-2:
- Type-1 HyperVisor:直接运行在裸金属设备上,在内核空间完成硬件的虚拟化,例如可以直接把 vCPU 绑定到物理 CPU 上进行分配,vCPU 的指令无需经过 CPU 虚拟化的代码进行模拟转换,例如 VMware ESXi、Citrix XenServer 等;
- Type-2 HyperVisor:运行在操作系统之上,在用户空间完成硬件的虚拟化,常见的如 QEMU、VirtualBox、VMwareWorkStation。在性能上相比 Type-1 较差,但是使用简单、方便。
1.2 内核虚拟化
内核虚拟化,顾名思义就是通过内核特性来完成虚拟化的操作,内核虚拟化的关键技术是 Cgroup 和 Namespace。Cgroup 负责对虚拟机使用资源的限制(虚拟机能用多少CPU、内存和磁盘容量、磁盘IO速率等),Namespace 负责对虚拟机的视图进行隔离(虚拟机能看到什么不能看到什么),例如 LXC Container、Docker 等就是常见的内核虚拟化解决方案。
- Cgroup 和 Namespace 技术细节较多,可以参考 Segmentfault 上的相关资料,例如:https://segmentfault.com/a/1190000040980305
- Container 和 Docker 不是同一个概念,Container 代表的是容器本身,而 Docker 则是一种实现容器的技术,业界有非常多实现容器的技术,例如 Kata Container、Containerd 等等,切不可把两者混为一谈。
内核虚拟化和硬件虚拟化相比:
- 轻量:容器的本质上是一个使用了 Cgroup 和 Namespace 限制的操作系统进程,进程的创建、销毁等效率非常高。
- 高效:内核虚拟化对资源的使用是共享式,可以对宿主机的资源(CPU、内存、磁盘容量、磁盘读写带宽等)进行充分的利用,特别是磁盘的 IO 性能,例如:硬件虚拟化对磁盘的性能损失是非常严重的,没有经过优化的 KVM 磁盘 IOPS 相比宿主机会下降几十倍到几百倍。
- 便携:容器镜像的大小一般就是操作系统内核+Shell相关程序的大小,几十MB到一百多MB左右,而虚拟机镜像大小动辄数十GB,难以在生产环境快速部署。
- 风险:内核虚拟化的隔离性相比虚拟机差,没有经过安全配置的容器进程,容易被入侵从而导致宿主机的操作系统被攻击引发安全事故。
可以看出,硬件虚拟化和内核虚拟化适用于不同的场景,在许多生产环境上,硬件虚拟化 + 内核虚拟化是搭配使用的,硬件虚拟化用于硬性隔离宿主机的资源,分配多个独立的环境用于多套业务系统使用;然后在虚拟机上,使用内核虚拟化技术的特性,快速部署、分发业务程序。
二、LXD 的简介
与市面上的 VMware、SmartX 等虚拟化解决方案一样,LXD 是一个开源的系统容器与虚拟机的 Linux 虚拟化管理平台。
LXD 可以同时支持对内核虚拟化和硬件虚拟化,内核虚拟化由 LXC Container 提供,硬件虚拟化目前只支持 KVM 虚拟化。
LXD 的主要功能如下:
- 镜像管理
- 实例管理
- 存储管理
- 网络管理
在开始使用 LXD 之前,需要对 LXD 进行安装与初始化。
2.1 初始化LXD
LXD 作为虚拟化管理服务,在宿主机上启动 LXD Daemon 和 LXCFS Daemon 2个核心进程:
- LXC Daemon:负责维护 LXD 平台下的所有虚拟化服务,包括实例管理、镜像管理、存储管理、网络管理等等;
- LXCFS Daemon:负责维护 LXC Container 的文件系统;
- lxc 命令行:负责与 LXD Daemon 进行通信,执行虚拟化平台的各项的管理操作。
本文基于 Ubuntu 22.04 系统,提供一个 LXD 安装的案例。
2.1.1 优化内核参数
编辑 /etc/sysctl.d/99-sysctl.conf 文件,添加以下的内容:
net.ipv4.ip_forward = 1
net.ipv4.tcp_tw_reuse = 1
## The following changes have been made for LXD ##
# fs.inotify.max_queued_events specifies an upper limit on the number of events that can be queued to the corresponding inotify instance - (default is 16384)
fs.inotify.max_queued_events = 1048576
# fs.inotify.max_user_instances This specifies an upper limit on the number of inotify instances that can be created per real user ID - (default value is 128)
fs.inotify.max_user_instances = 1048576
# fs.inotify.max_user_watches specifies an upper limit on the number of watches that can be created per real user ID - (default is 8192)
fs.inotify.max_user_watches = 1048576
# vm.max_map_count contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries - (default is 65530)
vm.max_map_count = 262144
# kernel.dmesg_restrict denies container access to the messages in the kernel ring buffer. Please note that this also will deny access to non-root users on the host system - (default is 0)
kernel.dmesg_restrict = 1
# This is the maximum number of entries in ARP table (IPv4). You should increase this if you create over 1024 containers.
net.ipv4.neigh.default.gc_thresh3 = 8192
# This is the maximum number of entries in ARP table (IPv6). You should increase this if you plan to create over 1024 containers.Not needed if not using IPv6, but...
net.ipv6.neigh.default.gc_thresh3 = 8192
# This is a limit on the size of eBPF JIT allocations which is usually set to PAGE_SIZE * 40000.
#net.core.bpf_jit_limit = 3000000000
# This is the maximum number of keys a non-root user can use, should be higher than the number of containers
kernel.keys.maxkeys = 2000
# This is the maximum size of the keyring non-root users can use
kernel.keys.maxbytes = 2000000
# This is the maximum number of concurrent async I/O operations. You might need to increase it further if you have a lot of workloads that use the AIO subsystem (e.g. MySQL)
fs.aio-max-nr = 524288
vm.swappiness = 0
vm.oom_kill_allocating_task = 1
vm.overcommit_memory = 0
vm.panic_on_oom = 0
执行以下的指令生效:sysctl -p
2.1.2 优化 LXD 系统资源使用上限
编辑 /etc/security/limits.conf 文件,添加以下内容:
* soft nofile 655360
* hard nofile 655360
root soft nofile unlimited
root hard nofile unlimited
2.1.3 更新操作系统软件版本
执行以下的指令,更新操作系统:
apt-get update
apt-get upgrade
# 可选,如果使用ZFS作为存储池,需要执行
apt install zfsutils-linux
2.1.4 修改 Cgroup 版本为 v1
目前许多较新版本的 Linux 发行版已经内置并且默认启用 CgroupV2 的版本,笔主在编写本文时 LXD 目前依然只能运行 CgroupV1 的 LXC Container,因此需要设置内核使用 v1 版本。
编辑 /etc/default/grub 文件,添加以下的参数:
GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0"
编辑完毕后,需要执行 update-grub
指令更新内核和 grub 引导器。
最后执行 reboot
重启操作系统,让所有配置生效。
2.1.5 (可选)关闭 swap 文件系统
执行命令 swapoff -a
关闭系统上的交换内存,尽可能引导操作系统使用物理内存,然后编辑 /etc/fstab 文件,把 swap 相关的文件系统注释掉,避免开机启动:
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
......
#/swap.img none swap sw 0 0
2.1.6 安装 LXD 服务
LXD 的服务已经被打包为 snap 软件包,可以很方便地通过 snap 进行软件安装和服务进程管理。
执行指令 snap install lxd
即可完成安装,安装完毕后执行指令 lxd init
进行 LXD 服务的初始化,该过程是交换式的,下面简要讲解一下:
# 启用 LXD 的集群模式,开发体验可选择 no,生产环境建议配置为 yes
Would you like to use LXD clustering? (yes/no) [default=no]: no
# Ubuntu 提供的裸金属服务,不使用时配置为 no
Would you like to connect to a MAAS server? (yes/no) [default=no]: no
# 创建一个新的网桥,作为宿主机和虚拟机的桥接通信网卡,开发体验配置为 yes
# 如果生产使用,一般使用已配置好的宿主机网桥,尽量不要自动创建
Would you like to create a new local network bridge? (yes/no) [default=yes]: yes
# 配置一个新的存储池给虚拟机使用,如果选择 yes,默认情况下会使用 zfs 初始化一个存储池,选择 no 的话可以初始化后,自行添加存储池(生产使用一般为no)
Do you want to configure a new storage pool? (yes/no) [default=yes]: yes
Name of the new storage pool [default=default]: default
Name of the storage backend to use (btrfs, dir, lvm, zfs) [default=zfs]: zfs
Create a new ZFS pool? (yes/no) [default=yes]: yes
Would you like to use an existing block device? (yes/no) [default=no]: yes
Path to the existing block device: /dev/disk/by-id/scsi-0DO_Volume_volume-fra1-01
# LXD Daemon 默认只提供 unix socket 的通信方式,如果希望通过 API 等进行管理,需要配置为 yes
Would you like LXD to be available over the network? (yes/no) [default=no]: no
# 自动更新本地镜像缓存,默认配置文件 yes
Would you like stale cached images to be updated automatically? (yes/no) [default=yes] yes
# 打印自动生成的 LXD 服务配置信息,可以选择 yes 进行查看
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]: no
初始化完毕后,执行以下指令创建一个 LXC 虚拟机来验证安装过程:
# 启动一个 LXC Container,名称叫 myServer
# CPU 配置为 1 核,256MiB 内存
lxc launch ubuntu:20.04 myServer -c limits.cpu=1 -c limits.memory=256MiB
# 查看虚拟机列表
lxc list
# 进入并使用 myServer 虚拟机
lxc exec myServer -- bash
2.2 对比 LXC Container 与 Docker
正如之前所提及的,容器实现的技术有很多种,LXC Container 和 Docker 都是容器的实现技术,而 LXD 支持则是 LXC Container。
相对于 Docker,LXC Container 的容器技术对大众来说并不常见,也较少在生产环境使用,其实两者师出同源:Docker 在 v0.9 版本开始,使用 libcontainer 来实现容器,而在此版本之前的所有版本,一直都是使用 LXC 容器技术来实现。
Docker 和 LXC 分道扬镳的根本原因在于应用场景的不一致:
- Docker 的目标是安全、快速、可靠的分发和部署应用程序,通过实现 Docker Engine,加强了应用程序在日志、存储、网络方面的使用能力;通过实现容器镜像、仓库机制以及联合文件系统,加强了自身的分发和部署能力。在设计上,Docker 容器默认情况下只允许允许主进程,如果主进程被杀死,那么容器本身也会被终止。
- LXC 的目标依然是对比虚拟机,在内核虚拟化上尽可能地完成对一个虚拟机的完整模拟,因此他本身利用了内核的非常多的特性来实现这个目标。例如使用 Process Namespace 隔离进程视图,使用 Mount Namespace 隔离文件系统视图等。
- 从安全性的角度看,LXC 的安全性要更高,LXC 的虚拟机默认启用 LXCFS 特性,并且 LXC Profile 有非常丰富的安全策略配置可供配置。由于 Docker 需要为容器管理网络、存储等关键的资源,所以 Docker Engine 在宿主机的权限特别高,容易被恶意软件识别和攻击;另外,在设计上目标是运行微服务,Docker 并没有像 LXC Container 那样,使用 LXCFS 文件系统来进行安全性方面的视图隔离,容器如果分配了较高的权限,容易引发生产安全事故。
所以,并不能直接把 Docker 看作是一种虚拟化能力,而是应用程序的生产编排解决方案。配合 Kubernetes 的编排技术,云原生应用已达到一个新的高度。
尽管比较小众,LXC 的使用场景依然集中虚拟化的解决方案上,LXD 对 Container 和 VM 的并行管理也是尽可能为生产使用提供更多的选择。
三、结语
本文从虚拟化技术的概念出发,引申出 LXD 平台基本能力介绍,以及 LXD 管理的内核虚拟化技术 LXC Container 和 Docker 的区别。
后续章节会针对 LXD 的各项功能,例如镜像管理、实例管理、网络和存储管理等进行详细的介绍。