Linux | 在LXD中运行containerd

2021 11 2, Tue

计划深入折腾折腾K8S,但是开虚拟机太费电了,所以打算用LXD容器作为Host跑K8S的Worker节点,里面运行containerd作为容器引擎。

虚拟化

虚拟化一般是指把各种资源隔离出一块,单独运行一套软件环境,相当于chroot的升级版,隔离资源、资源控制更加彻底。最常见的是虚拟机,除此之外Unix都有容器的概念(Windows也有容器的概念了,但是感觉没有什么用,因为太臃肿了),按照目的划分大致有两类容器:系统容器和应用容器。

虚拟机

虚拟机一般是模拟了部分或者全部的CPU指令,单独划分出一块硬盘和内存,模拟其他的硬件,以此运行一个软件构成的计算机,也因此得名虚拟机。

虚拟机特点是虚拟化程度高,资源隔离相对非常彻底,和物理机几乎不共享任何东西。但是相对的,即使有 动态内存 ( Memory ballooning ) ,虚拟机的资源占用和功耗也非常大。

系统容器:Jail / LXC

不特殊说明,容器指系统容器

在chroot和虚拟机之间,包括Linux在内的类/Unix系统,都用各种方法增加了一层系统级别的虚拟化叫容器。

容器和宿主之间共享内核,共享受限的运算和存储资源。由于不需要运行一整套硬件环境+独立的内核,而且运算资源和存储资源默认都是共享的,容器会非常的轻便。

在BSD上这样的容器叫jail,如果你用过FreeBSD或者FreeNAS,可能就有用过Jail。在Linux上则是通过命名空间和CGroups实现的LXC和OpenVZ(在国外买过虚拟机的小朋友是不是想起来什么了)。

应用容器:Docker以及衍生品

LXC大部分时候还是比较完美的,除了一种情况:要大量启动新容器并且部署应用的时候。

针对这种诉求,Kamel Founadi、Solomon Hykes和Sebastien Pahl开发了Docker,专门用来把一个应用打包成应用容器,启动容器就会直接启动应用,更加的轻量,容器里面连系统服务都不运行。后来从Docker由延伸出了Podman之类的实现,Docker本身也解耦分层、把容器的一层独立出来叫containerd。

应用容器的好处就是非常轻量,启动非常快,几乎没有额外耗费的无用的资源。但是相对缺点就是,应用容器并不是完整的系统,不能作为一个正常的linux使用,会受到宿主的限制、以及缺少服务管理工具的限制。

选择

上面提到我其实是想要部署K8S来玩,但是没有那么多钱花在服务器上,所以想用虚拟化的手段来部署K8S。但是虚拟机在NAS上性能太低,在笔记本上功耗太夸张,所以容器会是一个更好的选择。

由于K8S本身也需要运行应用容器,而目前绝大部分应用容器的实现都不允许在应用容器内运行另一个应用容器。把Docker的socket挂载到容器内也并不是一个安全的选择,更别提其他的容器实现不包含这样一个Unix socket。所以我们唯一的选择就是外面的虚拟化使用系统容器,想办法让系统容器允许嵌套容器。

系统容器在Linux上大部分选择都是LXC,我们也会用LXC和包装好的LXC的工具。应用容器理论上可以选择任意实现,我们在这里选择containerd,两方面原因:containerd是docker中分离的成熟的容器引擎,对k8s来说不需要额外的shim层。

LXC

LXC全名叫Linux container,是Linux下面大名鼎鼎的系统容器实现。docker也是借鉴了LXC的一部分实现,都是使用namespace和cgroup来隔离环境。

和应用容器不同的地方

但是LXC和Docker有很多不同之处:

  • 环境的完整程度不一样:这个不用说,LXC是系统容器,当然是运行了一个几乎完整的操作系统。Docker通常只运行一个应用。
  • rootfs不一样:LXC使用独立的目录作为根目录,也可以使用文件作为根目录使用的块设备。Docker这类应用容器通过overlayfs、aufs、btrfs或zfs把多个目录合并为一个目录,把合并的这个目录作为根目录。
  • 限制的规则不同:LXC会预先挂载好一些systemd和应用常用的虚拟文件系统,然后限制权限后运行一个degraded的init/systemd,在一定范围内我们还是可以在容器内使用一些系统涉及安全的特性(这也是为啥systemd依赖了这些东西但是还能在LXC中工作),比如说cgroup。应用容器通常不允许用户在容器内操作类似的东西。

大致来说用LXC就是下载好容器镜像,解压以后写好配置文件,lxc-create并且lxc-start就可以正常用了,lxc-attach就是LXC容器的TTY,lxc-exec就是直接在容器里面执行命令。

LXD

但是说实话LXC太难用了,要自己拷贝文件系统啊、要配置很多配置文件什么的。说实话大佬们通常都是懒人(懒人推动世界发展),所以又造了个轮子叫LXD。

LXD是LXC的一个前端,其实包含了一套命令行工具和一套网站。命令行工具是一个go写的二进制叫lxc,不管是什么LXC操作,拉镜像、跑容器都用这一个lxc命令。云服务托管了各种镜像的rootfs和默认配置。我们大部分情况配置其实都可以用默认的,默认的就已经足够好了。镜像都可以从云服务直接下载,云服务会定时更新镜像列表,有一些更新很快的镜像甚至可以做到日更。

除此之外lxc还可以管理多个项目,类似于K8S的命名空间,命名空间可以有独立的一套资源和默认配置。可以使用多个Linux宿主作为LXC集群。

暂时没搞清楚是feature还是bug:

  • profile不区分项目
  • 非默认project中的容器只能使用默认网络

比如说我要部署k8s,那么我就可以

  • 创建project
  • 创建网络
  • 复制、修改默认配置
    • 添加选项、设备
    • 使用cloud-init配置容器
  • 用指定配置执行容器
# 创建project
lxc project create kubernetes-cluster -c features.images=false -c features.profiles=false

# 切换到project
lxc project switch kubernetes-cluster

# 创建网络
lxc network create kubebr0 --type bridge

# 复制配置
lxc profile show default
# config: {}
# description: Default LXD profile
# devices:
#   eth0:
#     name: eth0
#     network: lxdbr0
#     type: nic
#   root:
#     path: /
#     pool: default
#     type: disk
# name: default
# used_by: []

# 复制一份配置
lxc profile copy default ubuntu-containerd

# 编辑配置
lxc profile edit ubuntu-containerd
# config:
#   security.nesting: "true"
#   user.user-data: |
#     apt:
#       primary:
#         - uri: http://cn.archive.ubuntu.com/ubuntu/
#           arches:
#           - default
#     package_upgrade: true
#     packages:
#       - build-essential
#       - openssh-server
#       - containerd
#     locale: en_US.UTF-8
#     timezone: Asia/Shanghai
# description: Ubuntu + Containerd LXD profile
# devices:
#   eth0:
#     name: eth0
#     network: lxdbr0
#     type: nic
#   root:
#     path: /
#     pool: default
#     type: disk
# name: default
# used_by: []

# 创建容器
lxc launch images:ubuntu/focal/cloud $NODE_NAME --project kubernetes-cluster --network kubebr0  --profile ubuntu-containerd

# 获得一个interactive shell。LXC中执行命令会自动判断分配不分配pty
lxc exec --project kubernetes-cluster $NODE_NAME bash
# 在容器内想做什么做什么
tail -f /var/log/cloud-init-output.log

containerd

containerd是docker中分离出来的容器引擎。在Ubuntu中直接安装就好了。这个没有什么花里胡哨的。

apt install -y containerd

ctr images pull docker.io/library/redis:alpine
ctr containers create docker.io/library/redis:alpine redis
ctr containers ls
ctr tasks kill redis
ctr containers delete redis

LXC中运行containerd

我在Arch上的Arch容器中直接启动docker容器没有遇到什么问题。但是如果运行不了,提示权限被拒绝,可以像上面的配置中,设置security.nesting: true。

配置过程都是使用cloud-init,非常简单。初始化过程在launch之后,可以lxc exec进去用ps aux查看,或者tail -f /var/log/cloud-init-output.log检查cloud-init中命令的输出。