转载
LXC Linux系统容器 | 某科学的最后之作 (yunfwe.cn)
简介
LXC 是 Linux Container 的简写。Linux Container 是一种内核虚拟化技术,可以提供轻量级的虚拟化以便隔离进程和资源。大名鼎鼎的 Docker 在早期版本使用的底层容器引擎便是 LXC,不过 Docker 的目标是创建应用级容器,而 LXC 的目标是创建系统级容器,所以使用 LXC 更容易获得接近虚拟机的体验。
环境
LXC 也是利用了 Linux 内核的 cgroup 和 namespace 特性来实现容器的资源隔离,因此也对 Linux 的内核版本有较高的要求。这里使用 Ubuntu 18.04 Server
作为容器的宿主机,内核版本为 4.15.0-58-generic
教程
安装
安装命令行工具
安装非常简单,在 Ubuntu 上使用 apt-get install lxc
即可。lxc
包里包含了所有的命令,之后就可以看到系统多了很多 lxc-
开头的命令。
root@localhost:~# lxc-
lxc-attach lxc-checkpoint lxc-create lxc-freeze lxc-snapshot lxc-unfreeze lxc-wait
lxc-autostart lxc-config lxc-destroy lxc-info lxc-start lxc-unshare
lxc-cgroup lxc-console lxc-device lxc-ls lxc-stop lxc-update-config
lxc-checkconfig lxc-copy lxc-execute lxc-monitor lxc-top lxc-usernsexec
LXC 安装成功后会自动创建一个叫 lxcbr0 的网桥,如下:
lxcbr0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 10.0.2.1 netmask 255.255.255.0 broadcast 0.0.0.0
ether 00:16:3e:00:00:00 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
之后运行的容器的虚拟网卡都会接入到这个网桥了。LXC 还为这个网桥配置了一个 DHCP 服务,配置文件在 /etc/dnsmasq.d/lxc
。这样容器支持 DHCP 的话就可以直接获取到可用的 IP 地址了。
快速开始
创建容器
lxc
安装后 在 /usr/share/lxc/templates
为我们提供了几个容器安装的模板:
lxc-download
:通过网络获取容器镜像来创建容器。lxc-local
:通过本地已有的镜像来创建容器。lxc-busybox
:使用 busybox 来创建最基本的容器。lxc-oci
:通过 oci 标准的镜像来创建容器。
创建容器通过使用 lxc-create
命令来完成,此命令会调用指定的模板来完成容器的创建,我们先通过 lxc-busybox
这个模板创建一个最简单的容器:
1 | lxc-create -t busybox -n busybox |
lxc-create
命令通过 -t
参数指定要使用的模板,-n
参数指定创建的容器的名称。之后使用 lxc-ls
可以看到所有已经创建的容器,所有的容器默认都保存在 /var/lib/lxc
中。
root@localhost:~# ls /var/lib/lxc/
busybox
root@localhost:~# ls /var/lib/lxc/busybox/
config rootfs
root@localhost:~# ls /var/lib/lxc/busybox/rootfs/
bin dev home lib64 null ram0 sbin sys tty tty1 urandom var
console etc lib mnt proc root selinux tmp tty0 tty5 usr zero
root@localhost:~#
每个容器对应 /var/lib/lxc
下的每个目录,目录内存放着这个容器的配置文件和根文件系统。lxc-create
还可以指定使用 lvm
、Ceph RBD
、zfs
、loop
文件系统来作为容器的根目录,默认使用的是 dir
的方式与宿主机共享文件系统。目录的方式胜在简单,但是却无法支持其他文件系统带来的高级特性,比如磁盘配额、快照等。
启动容器
使用 lxc-info
命令可以查看指定容器的状态,使用 lxc-start
来启动容器:
root@localhost:~# lxc-info busybox
Name: busybox
State: STOPPED
root@localhost:~# lxc-start busybox
root@localhost:~# lxc-info busybox
Name: busybox
State: RUNNING
PID: 5288
CPU use: 0.01 seconds
BlkIO use: 0 bytes
Memory use: 1.77 MiB
KMem use: 1.40 MiB
Link: veth7F3KFW
TX bytes: 1.51 KiB
RX bytes: 1.87 KiB
Total bytes: 3.37 KiB
root@localhost:~#
容器启动后的第一个进程的 ID 是 5288,查看此进程的父进程可以看到是一个内核进程:
root@localhost:~# cat /proc/5288/status |grep PPid
PPid: 5281
root@localhost:~# ps 5281
PID TTY STAT TIME COMMAND
5281 ? Ss 0:00 [lxc monitor] /var/lib/lxc busybox
root@localhost:~#
连接到容器
容器启动后,我们可以通过 lxc-attach
命令来运行容器内的命令,默认会运行容器内的 shell 来供我们操作容器。
root@localhost:~# lxc-attach busybox
BusyBox v1.27.2 (Ubuntu 1:1.27.2-2ubuntu3.2) built-in shell (ash)
Enter 'help' for a list of built-in commands.
~ # ifconfig
eth0 Link encap:Ethernet HWaddr 00:16:3E:88:E3:4A
inet6 addr: fe80::216:3eff:fe88:e34a/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:25 errors:0 dropped:0 overruns:0 frame:0
TX packets:15 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2526 (2.4 KiB) TX bytes:1962 (1.9 KiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
~ #
进入容器后就可以执行容器内的所有命令了,可以看到,容器默认分配了一个 eth0
的网卡,但是并没有获取到 IP 地址。LXC 容器会尽量让一个容器就像是一个虚拟机一样,因此在容器内可以自主的配置 IP,甚至使用 reboot 命令重启容器都可以。接下来给容器分配一个 IP 地址,然后看看是否可以与主机互访。
容器内操作:
~ # ifconfig eth0 10.0.2.100
~ # ip ro add default via 10.0.2.1
~ # ping 180.76.76.76
PING 180.76.76.76 (180.76.76.76): 56 data bytes
64 bytes from 180.76.76.76: seq=0 ttl=127 time=4.649 ms
64 bytes from 180.76.76.76: seq=1 ttl=127 time=7.460 ms
64 bytes from 180.76.76.76: seq=2 ttl=127 time=4.998 ms
64 bytes from 180.76.76.76: seq=3 ttl=127 time=4.852 ms
--- 180.76.76.76 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 4.649/5.489/7.460 ms
~ #
需要注意的是,给容器分配的 IP 地址要和宿主机的 lxcbr0
网桥的地址在同一网段,并且容器内将默认网关设置为宿主机网桥的 IP 即可实现容器内联网。接着执行 telnetd
命令允许在外部通过 telnet
程序远程连接到此容器。
宿主机已经可以 ping
通容器:
root@localhost:~# ping -c 4 10.0.2.100
PING 10.0.2.100 (10.0.2.100) 56(84) bytes of data.
64 bytes from 10.0.2.100: icmp_seq=1 ttl=64 time=0.046 ms
64 bytes from 10.0.2.100: icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from 10.0.2.100: icmp_seq=3 ttl=64 time=0.047 ms
64 bytes from 10.0.2.100: icmp_seq=4 ttl=64 time=0.045 ms
--- 10.0.2.100 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3070ms
rtt min/avg/max/mdev = 0.045/0.049/0.058/0.005 ms
root@localhost:~#
除了使用 lxc-attach
还可以使用 lxc-console
来登陆容器,此命令会连接到容器内 Linux 的控制台,所以必须提供必要的用户名和密码才可以登陆进容器内部。
更改容器内密码:
~ # passwd
passwd: no record of root in /etc/shadow, using /etc/passwd
Changing password for root
New password:
Bad password: too weak
Retype password:
passwd: password for root changed by root
~ #
宿主机使用 lxc-console
来登陆容器:
root@localhost:~# lxc-console busybox
Connected to tty 1
Type <Ctrl+a q> to exit the console, <Ctrl+a Ctrl+a> to enter Ctrl+a itself
busybox login: root
Password:
BusyBox v1.27.2 (Ubuntu 1:1.27.2-2ubuntu3.2) built-in shell (ash)
Enter 'help' for a list of built-in commands.
~ #
可以看到,成功输入用户名和密码后也进入了容器。
停止并删除容器
使用 lxc-stop
来停止容器,使用 lxc-destroy
来彻底删除容器。
root@localhost:~# lxc-stop busybox
root@localhost:~# lxc-destroy busybox
lxc-destroy: busybox: tools/lxc_destroy.c: main: 271 Destroyed container busybox
root@localhost:~#
需要注意的是,如果想直接删除一个正在运行的容器,可以使用 lxc-destroy -f
来强制删除。
容器管理
在快速开始一章中,已经体验了 LXC 的基本用法,下面的章节将会讲解如何对容器进行更高级的管理,让容器运行的更像是虚拟机一样。
创建容器
容器的创建分两种,一种是由 root 用户创建的特权容器,一种是由普通用户创建的非特权容器。非特权容器有一些限制,比如无法创建设备节点等。而在特权模式下,让 Docker 运行在 LXC 中,甚至 LXC 嵌套 LXC 运行都成了可能。下面所有的例子创建的都是特权容器。
获取镜像模板
busybox 只提供了一个极小的更适合用于嵌入式 Linux 的基本文件系统,下面就看看如何使用 LXC 运行 CentOS、Ubuntu 等完整的 Linux 发行版吧!
例如,想要运行一个基于 CentOS 发行版的容器,首先需要下载 CentOS 的容器镜像模板,之后需要运行容器时,会自动从下载好的模板解压出容器的根文件系统到指定的 lvm
、zfs
、loop
或者 dir
等存储设备,并启动容器。
LXC 提供了 lxc-download
模板来通过网络获取容器镜像:
root@localhost:~# lxc-create -t download -n ubuntu-1604
Setting up the GPG keyring
Downloading the image index
---
DIST RELEASE ARCH VARIANT BUILD
---
alpine 3.10 amd64 default 20190923_13:00
alpine 3.10 arm64 default 20190923_13:00
.
.
.
---
Distribution:
ubuntu
Release:
xenial
Architecture:
amd64
Downloading the image index
Downloading the rootfs
Downloading the metadata
The image cache is now ready
Unpacking the rootfs
---
You just created an Ubuntu xenial amd64 (20190923_07:42) container.
To enable SSH, run: apt install openssh-server
No default root or user password are set by LXC.
命令执行后会列出所有镜像的索引,接着会让你输入要下载的发行版、版本号、架构信息。经过片刻等待,Ubuntu 16.04 的容器就创建成功了。所有下载的镜像都缓存在 /var/cache/lxc/download
中,下一次创建此镜像模板的容器时就不会再次通过网络下载了。
也可以使用 lxc-create -t download --help
来查看模板支持的一些命令,例如安装 CentOS 7 过程可以省略为 lxc-create -t download -n centos7 -- -d centos -r 7 -a amd64
。需要注意的是命令行当中的 --
参数,此参数之后的参数才会会当作模板参数传递给模板。
启动容器
接着启动此容器,并验证是否是 Ubuntu 16.04 的发行版:
root@localhost:~# lxc-start ubuntu-1604
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# python3 -m platform
Linux-4.15.0-58-generic-x86_64-with-Ubuntu-16.04-xenial
root@ubuntu-1604:~#
lxc-start
默认在后台启动容器,如果想切换至前端启动,可以使用 -F
参数,同时容器启动过程中的一些控制台输出也会被打印出来了。
远程连接容器
LXC 没有提供远程连接容器的方式,如果打算为 LXC 写一套 Web 管理工具,并想实现在 Web 端操作容器,可以采用将 lxc-attach
或者 lxc-console
的输入输出通过 WebSocket
的方式与前端通信。
或者通过 SSH 的方式远程连接到容器,这样就需要在每个容器内配置 SSH 服务。例如在刚才创建好的容器中安装 SSH 服务:
root@ubuntu-1604:~# apt-get install openssh-server -y
root@ubuntu-1604:~# netstat -anpt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 610/sshd
tcp6 0 0 :::22 :::* LISTEN 610/sshd
root@ubuntu-1604:~#
由于新启动的容器还是空密码,所以还需要设置一个密码才可以登录:
root@ubuntu-1604:~# passwd
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root@ubuntu-1604:~#
允许使用 root 用户登录并重启服务:
sed -i 's/^PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config
service ssh restart
这样就可以在宿主机上通过 ssh 连接到容器了,如果想在局域网内也可以 ssh 连接到容器,可以将 lxcbr0
与物理网卡桥接起来,并配置局域网的 IP 地址。
容器资源限制
对于虚拟机来说,在创建虚拟机时为虚拟机分配的内存大小 就是对虚拟机能使用的内存的限制。对于容器而言,限制容器可使用的硬件资源 都是通过 cgroup 来实现的。
LXC 提供了 lxc-cgroup
命令来操作容器的控制组,默认容器是没有任何限制的。
限制内存使用
在容器内使用 free -m
命令查看内存使用情况:
root@ubuntu-1604:~# free -m
total used free shared buff/cache available
Mem: 3921 18 3802 8 99 3902
Swap: 3920 0 3920
root@ubuntu-1604:~#
可以看到总内存是和宿主机内存相同的,接下来使用 lxc-cgroup
来限制内存的使用:
宿主机上执行:
root@ubuntu-1604:~# lxc-cgroup ubuntu-1604 memory.limit_in_bytes 256M
容器内查看:
root@ubuntu-1604:~# free -m
total used free shared buff/cache available
Mem: 256 18 137 8 99 237
Swap: 3920 0 3920
root@ubuntu-1604:~#
可以看到允许使用的最大内存已经变成了 256M。但是这样只是临时更改了容器允许使用的内存,在容器重启后限制就会消失,如果想在重启重启后也保持这样的限制,可以更改每个容器的配置文件。
例如容器 ubuntu-1604 的配置文件就在 /var/lib/lxc/ubuntu-1604/config
在文件的最后添加如下内容:
lxc.cgroup.memory.limit_in_bytes = 512M
接着使用 lxc-stop ubuntu-1604
和 lxc-start ubuntu-1604
停止并启动容器,直接在容器内使用 reboot
命令是无效的。
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# free -m
total used free shared buff/cache available
Mem: 512 11 492 8 8 500
Swap: 3920 0 3920
root@ubuntu-1604:~#
容器重启后可用内存变为了 512M。对于交换分区,我们也可以通过 cgroup 来限制其使用,其限制字段名为 memory.memsw.limit_in_bytes
,但是限制交换分区使用后通过 free
命令看到的依旧是宿主机的交换分区大小。
限制CPU使用
与内存限制的简单粗暴不同,对于 CPU 资源的限制复杂了一些,因为 CPU 有不同维度的限制条件,例如仅允许容器运行在 CPU 的某个核心上,或者仅允许容器在一段时间内获取多久的 CPU 执行时间片。这里只简单的限制容器可以在哪几个核心上使用。
宿主机上执行:
root@ubuntu-1604:~# lxc-cgroup ubuntu-1604 cpuset.cpus "0,1"
表示仅允许容器使用 CPU 的第 0 个和第 1 个核心。容器内查看:
root@ubuntu-1604:~# cat /proc/cpuinfo |grep processor
processor : 0
processor : 1
root@ubuntu-1604:~#
容器内只可以看到 CPU 的两个核心了。同样,如果想要在容器重启后限制依然生效,可以修改容器的配置文件 新增以下内容:
lxc.cgroup.cpuset.cpus = "0,1"
限制存储空间
如果容器的存储使用的是 dir
则与宿主机共享存储,如果使用了 lvm
、zfs
等文件系统则可以通过给容器分配独立的分区方式来限制容器可用的存储空间。这里使用 loop
设备模拟硬盘分区的方式来限制容器存储使用。
创建使用 loop
设备的容器并启动:
1 2 | lxc-create -B loop -t download -n ubuntu-1604-loop --fssize 1G -- -d ubuntu -r xenial -a amd64 lxc-start ubuntu-1604-loop |
可以看到,dir
和 loop
的区别如下:
root@localhost:~# ls /var/lib/lxc/ubuntu-1604
config rootfs
root@localhost:~# ls /var/lib/lxc/ubuntu-1604-loop/
config rootdev rootfs
root@localhost:~# cat /var/lib/lxc/ubuntu-1604/config |grep rootfs
lxc.rootfs.path = dir:/var/lib/lxc/ubuntu-1604/rootfs
root@localhost:~# cat /var/lib/lxc/ubuntu-1604-loop/config |grep rootfs
lxc.rootfs.path = loop:/var/lib/lxc/ubuntu-1604-loop/rootdev
root@localhost:~# ls /var/lib/lxc/ubuntu-1604-loop/rootfs
root@localhost:~# ls -lh /var/lib/lxc/ubuntu-1604-loop/rootdev
-rw------- 1 root root 1.1G Sep 25 03:36 /var/lib/lxc/ubuntu-1604-loop/rootdev
root@localhost:~#
虽然 ubuntu-1604-loop
容器依然保留了 rootfs
目录,但内容确是空的。容器内也可以看到根文件系统的总空间只有 1G 了:
root@localhost:~# lxc-attach ubuntu-1604-loop
root@ubuntu-1604-loop:~# df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/loop0 ext4 976M 372M 537M 41% /
none tmpfs 492K 0 492K 0% /dev
tmpfs tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs tmpfs 2.0G 8.1M 2.0G 1% /run
tmpfs tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
root@ubuntu-1604-loop:~#
网卡接口限速
对容器网卡流量进行限制使用了 Linux 内核的流量控制功能,通过 tc
命令进行管理。tc
命令的操作比较复杂,所幸有人封装好了对接口进行限速的脚本并开源到了 Github 上,我们只需要下载下来使用就可以了。
宿主机上安装 wondershaper
工具:
1 2 | git clone https://github.com/magnific0/wondershaper.git cp wondershaper/wondershaper /usr/local/sbin/ |
windershaper
工具使用非常简单,使用 -a
参数指定网卡接口名称,-c
清理所有规则,-d
限制下载速率,-u
限制上传速率,单位是 Kbps
。需要注意的是 Linux 并不能很准确的控制下载速度,而且对于提供服务的容器或虚拟机而言,一般需要限制的是上传速率。
root@localhost:~# wondershaper
USAGE: /usr/local/sbin/wondershaper [-hcs] [-a <adapter>] [-d <rate>] [-u <rate>]
Limit the bandwidth of an adapter
OPTIONS:
-h Show this message
-a <adapter> Set the adapter
-d <rate> Set maximum download rate (in Kbps) and/or
-u <rate> Set maximum upload rate (in Kbps)
-p Use presets in /etc/conf.d/wondershaper.conf
-c Clear the limits from adapter
-s Show the current status of adapter
-v Show the current version
MODES:
wondershaper -a <adapter> -d <rate> -u <rate>
wondershaper -c -a <adapter>
wondershaper -s -a <adapter>
EXAMPLES:
wondershaper -a eth0 -d 1024 -u 512
wondershaper -a eth0 -u 512
wondershaper -c -a eth0
root@localhost:~#
接着找到要限制流量的容器对应的网卡接口 并限制容器的上行速度为 1Mbps
:
root@localhost:~# lxc-info ubuntu-1604 | egrep "IP|Link"
IP: 10.0.2.238
Link: vethQFAM44
root@localhost:~# wondershaper -a vethQFAM44 -c
root@localhost:~# wondershaper -a vethQFAM44 -d 1024
root@localhost:~#
我也不清楚为什么设置容器的上行速率使用的是 -d
参数,实际测试发现此参数能控制容器的上传速率。
在容器内运行一个 Web 服务进行测试:
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# dd if=/dev/zere of=img bs=1M count=128
dd: failed to open '/dev/zere': No such file or directory
root@ubuntu-1604:~# dd if=/dev/zero of=img bs=1M count=128
128+0 records in
128+0 records out
134217728 bytes (134 MB, 128 MiB) copied, 0.0789097 s, 1.7 GB/s
root@ubuntu-1604:~# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 ...
宿主机上使用 wget
命令下载测试:
root@localhost:~# wget http://10.0.2.238/img
--2019-09-25 08:47:46-- http://10.0.2.238/img
Connecting to 10.0.2.238:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 134217728 (128M) [application/octet-stream]
Saving to: ‘img’
img 0%[ ] 1.04M 120KB/s eta 18m 4s
可以看到,速度保持在了 1Mbps
下。
挂起和恢复容器
正在运行中的容器可以随时暂停和恢复,使用 lxc-freeze
命令挂起一个容器,此时容器内所有正在运行的程序都将被强制挂起:
root@localhost:~# lxc-freeze ubuntu-1604
root@localhost:~# ps aux |grep python3
root 7921 0.0 0.4 55988 17520 pts/4 D+ 07:33 0:01 python3 -m http.server 80
root 8222 0.0 0.0 13136 1104 pts/2 S+ 09:07 0:00 grep --color=auto python3
root@localhost:~# cat /proc/7921/status |grep State
State: D (disk sleep)
root@localhost:~# kill -9 7921
root@localhost:~# cat /proc/7921/status |grep State
State: D (disk sleep)
root@localhost:~#
可以看到,刚才容器内运行的 python 进程已经是不可中断的休眠状态了,此时即使使用 kill -9
也依然无法杀掉此状态的进程。使用 lxc-unfreeze
解除容器的挂起状态:
root@localhost:~# lxc-unfreeze ubuntu-1604
root@localhost:~# cat /proc/7921/status |grep State
cat: /proc/7921/status: No such file or directory
root@localhost:~#
在容器内可以看到 python 进程已经退出,并显示 Killed
,看来 kill -9
可能会迟到,但永远不会缺席。
容器状态监控
LXC 有三个命令工具提供了容器状态监控,分别是 lxc-monitor
、lxc-top
、lxc-wait
。
lxc-monitor
这个命令可以监控单个或多个容器的状态变化,例如监听所有名称以 ubuntu
开头的容器状态:
1 | lxc-monitor ubuntu* |
接着打开另外一个终端,重启一个容器:
root@localhost:~# lxc-stop ubuntu-1604-loop
root@localhost:~# lxc-start ubuntu-1604-loop
root@localhost:~#
可以看到,lxc-monitor
输出了被监听的容器的状态变化:
root@localhost:~# lxc-monitor ubuntu*
'ubuntu-1604-loop' exited with status [0]
'ubuntu-1604-loop' changed state to [STOPPING]
'ubuntu-1604-loop' changed state to [STOPPED]
'ubuntu-1604-loop' changed state to [STARTING]
'ubuntu-1604-loop' changed state to [RUNNING]
lxc-wait
此命令是等待容器到达某一状态后便退出。
root@localhost:~# lxc-wait ubuntu-1604-loop -s RUNNING
root@localhost:~# lxc-wait ubuntu-1604-loop -s STOPPED
ubuntu-1604-loop
容器已经是运行状态,所以命令直接就退出了,而等待容器状态变化为 STOPPED
是,命令发送了阻塞。接着停止此容器,可以看到 lxc-wait
发现被监控容器是 STOPPED
状态后就退出了。
如果想同时监听多个状态,可以使用 lxc-wait ubuntu-1604-loop -s "RUNNING|STOPPED"
这种方式。
lxc-top
此命令可以查看所有容器的运行状态
Container CPU CPU CPU BlkIO Mem KMem
Name Used Sys User Total(Read/Write) Used Used
ubuntu-1604 0.39 0.25 0.10 4.00 KiB( 0.00 /4.00 KiB) 17.83 MiB 4.53 MiB
ubuntu-1604-loop 2.24 1.48 0.28 54.32 MiB(54.14 MiB/180.00 KiB) 72.16 MiB 6.16 MiB
TOTAL 2 of 2 2.63 1.73 0.38 54.32 MiB(54.14 MiB/184.00 KiB) 89.99 MiB 10.68 MiB
root@localhost:~#
网卡管理
网卡配置项
容器启动后默认只有一个可用的 eth0 网卡接口,查看容器的配置文件可以找到网卡相关的配置项:
cat /var/lib/lxc/ubuntu-1604/config
...
# Network configuration
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:bc:27:d1
...
网卡相关配置如下:
lxc.net.0.type
:第 0 块网卡的类型,如果是第二块网卡,则为lxc.net.1.type
可选类型如下:empty
:不创建网卡,容器仅有 lo 网卡veth
:创建一个对等网卡,该网卡的一端分配给容器,另一端与lxc.net.0.link
指定的网桥桥接vlan
:创建一个由lxc.net.0.link
指定的虚拟局域网接口分配给容器,vlan的标识符可由lxc.net.0.vlan.id
指定phys
:将lxc.net.0.link
指定的网卡接口分配给容器。macvlan
:创建一个macvlan
接口,该接口和由lxc.net.0.link
指定的接口相连接
lxc.net.0.name
:指定虚拟网卡接口的名称,默认是 eth 开头。lxc.net.0.link
:指定进行真实网络通信的网卡。lxc.net.0.flags
:指定网卡的状态,up
激活接口,down
关闭接口。lxc.net.0.hwaddr
:指定虚拟网卡的 MAC 地址,默认情况该值会自动分配。
分配物理网卡给容器
为容器新增一块虚拟网卡非常简单,只需要模范示例配置,将其中的 0 改为 1 就会新增一块 eth1
的网卡了,下面试试直接分配一块物理网卡给容器,需要先保证宿主机有一块额外的物理网卡,如果是虚拟机可以先为宿主机新增一块。
将以下内容追加到容器配置文件,将物理网卡分配给容器:
lxc.net.1.type = phys
lxc.net.1.link = ens35
lxc.net.1.flags = up
ens35
是宿主机上第二块物理网卡的名称,不同的 Linux 环境网卡接口名称也不尽相同。然后重启被修改的容器:
root@localhost:~# lxc-stop ubuntu-1604 && lxc-start ubuntu-1604
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# ifconfig
ens35 Link encap:Ethernet HWaddr 00:0c:29:95:b4:7b
inet6 addr: fe80::20c:29ff:fe95:b47b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:726 (726.0 B)
eth0 Link encap:Ethernet HWaddr 00:16:3e:bc:27:d1
inet addr:10.0.2.238 Bcast:10.0.2.255 Mask:255.255.255.0
inet6 addr: fe80::216:3eff:febc:27d1/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:13 errors:0 dropped:0 overruns:0 frame:0
TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1519 (1.5 KB) TX bytes:1452 (1.4 KB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
root@ubuntu-1604:~#
可以看到容器内部也可以使用这块网卡了,接着为此网卡配置一个静态 IP,看看从外部是否可以直接访问:
root@ubuntu-1604:~# ifconfig ens35 10.0.1.11/24
root@ubuntu-1604:~#
需要注意的是,这个 IP 必须是此物理网卡所在网段的 IP 才可以。在 Windows 下直接访问容器:
Microsoft Windows [版本 10.0.18362.356]
(c) 2019 Microsoft Corporation。保留所有权利。
C:\Users\yunfwe\Desktop>ssh root@10.0.1.11
The authenticity of host '10.0.1.11 (10.0.1.11)' can't be established.
ECDSA key fingerprint is SHA256:WGzRElNn4jypkZLIwVzDcqbDDi7vqCb29UyAPbC8Oqs.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.1.11' (ECDSA) to the list of known hosts.
root@10.0.1.11's password:
Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.15.0-58-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Last login: Wed Sep 25 01:58:44 2019
root@ubuntu-1604:~#
桥接物理网卡
直接分配物理网卡虽然实现了在外部直接访问宿主机内部的容器了,但是显然太浪费资源,每一个容器都要分配一个物理网卡。我们可以在宿主机创建一个网桥,然后将物理网卡接入到网桥内,之后再将容器的虚拟网卡也接入到网桥,这样就可以实现桥接模式了。
首先删除之前为容器分配物理网卡的配置项并重启容器,然后在宿主机上新增网桥,并将第二块物理网卡加入到网桥中。
宿主机执行:
root@localhost:~# brctl addbr netbr0
root@localhost:~# brctl addif netbr0 ens35
root@localhost:~# ifconfig netbr0 up
root@localhost:~# ifconfig ens35 up
root@localhost:~# brctl show
bridge name bridge id STP enabled interfaces
lxcbr0 8000.00163e000000 no vethA75TNV
vethWL2CSX
netbr0 8000.000c2995b47b no ens35
root@localhost:~#
lxcbr0 是 LXC 自动帮我们创建的网桥,网桥内已经有两个接口,对端便是两个容器的 eth0 网卡。接下来我们可以重新创建一个容器,然后将容器配置文件中的 lxc.net.0.link
改为我们新创建的网桥,或者在现有的容器中新增一块虚拟网卡,然后连接到网桥上。
修改容器配置文件,新增如下配置:
lxc.net.1.type = veth
lxc.net.1.link = netbr0
lxc.net.1.flags = up
重启此容器并查看 netbr0 网桥已经有多少个接口:
root@localhost:~# lxc-stop ubuntu-1604 && lxc-start ubuntu-1604
root@localhost:~# brctl show
bridge name bridge id STP enabled interfaces
lxcbr0 8000.00163e000000 no vethA75TNV
vethGHBNA6
netbr0 8000.000c2995b47b no ens35
veth479Q5D
root@localhost:~#
为容器内接口分配 IP 并远程连接:
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# ifconfig eth1 10.0.1.11/24
root@ubuntu-1604:~# ifconfig eth1
eth1 Link encap:Ethernet HWaddr 66:15:70:46:7e:bf
inet addr:10.0.1.11 Bcast:10.0.1.255 Mask:255.255.255.0
inet6 addr: fe80::6415:70ff:fe46:7ebf/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:44 errors:0 dropped:0 overruns:0 frame:0
TX packets:22 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:4625 (4.6 KB) TX bytes:1588 (1.5 KB)
root@ubuntu-1604:~#
Windows 上远程连接成功:
C:\Users\yunfwe\Desktop>ssh root@10.0.1.11
root@10.0.1.11's password:
Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.15.0-58-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Last login: Thu Sep 26 03:01:21 2019 from 10.0.1.1
root@ubuntu-1604:~#
当前网桥的配置是临时生效的,如果需要开机自动配置 还请查看你当前所使用的发行版的网络管理器如何配置网桥。
设备管理
LXC 提供了 lxc-device
命令将宿主机的硬件设备直接分配给容器。其实运用到的技术是 cgroup 的设备白名单和在容器内创建设备相应的设备文件。
lxc-device
命令的使用非常简单,使用 add
直接指定要将宿主机 /dev
目录下的哪个设备分配给容器就可以了,删除可以使用 del
分配硬盘分区到容器
宿主机执行:
1 | lxc-device ubuntu-1604 add /dev/sda2 |
容器内查看:
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# ls /dev/sda2
/dev/sda2
root@ubuntu-1604:~# mount /dev/sda2 /mnt/
root@ubuntu-1604:~# ls /mnt/
bin cdrom etc initrd.img lib lost+found mnt proc run snap swap.img tmp var vmlinuz.old
boot dev home initrd.img.old lib64 media opt root sbin srv sys usr vmlinuz
root@ubuntu-1604:~#
分配 tunnel 设备到容器
默认在容器内是没有 /dev/net/tun
设备文件的,因此一大部分的 VPN 应用都无法在容器内正常运行。利用 lxc-device
命令可以很方便的将 tunnel 设备分配给容器:lxc-device ubuntu-1604 add /dev/net/tun
,但是用这种方法在容器重启后分配的设备文件将失效。
我们可以在配置文件中指定容器允许的设备文件白名单,容器在启动过程中会默认分配一些设备文件,在容器的配置文件中有一行是 lxc.include = /usr/share/lxc/config/common.conf
打开此文件,可以看到这些默认会创建的设备文件:
## Allow specific devices
### /dev/null
lxc.cgroup.devices.allow = c 1:3 rwm
### /dev/zero
lxc.cgroup.devices.allow = c 1:5 rwm
### /dev/full
lxc.cgroup.devices.allow = c 1:7 rwm
### /dev/tty
lxc.cgroup.devices.allow = c 5:0 rwm
### /dev/console
lxc.cgroup.devices.allow = c 5:1 rwm
### /dev/ptmx
lxc.cgroup.devices.allow = c 5:2 rwm
### /dev/random
lxc.cgroup.devices.allow = c 1:8 rwm
### /dev/urandom
lxc.cgroup.devices.allow = c 1:9 rwm
### /dev/pts/*
lxc.cgroup.devices.allow = c 136:* rwm
### fuse
lxc.cgroup.devices.allow = c 10:229 rwm
Linux tunnel 设备号是 c 10 200
,如果我们需要在将来创建的容器都默认分配 tunnel 设备,可以直接修改 /usr/share/lxc/config/common.conf
,否则只修改容器的配置文件即可:
容器配置文件最后添加以下内容:
### tunnel
lxc.cgroup.devices.allow = c 10:200 rwm
现在只是允许了容器内使用 /dev/net/tun
设备,容器并不会自动创建此设备文件,我们可以在容器的 /etc/rc.local
中写入创建设备文件的命令来完成启动容器后自动创建。
编辑容器内的 /etc/rc.local
在最后的 exit 0
之前写入以下内容:
mkdir /dev/net -p
mknod /dev/net/tun c 10 200
然后重启容器:
root@localhost:~# lxc-stop ubuntu-1604 && lxc-start ubuntu-1604
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# ls /dev/net/tun
/dev/net/tun
root@ubuntu-1604:~#
目录挂载
如果想在多个容器内共享同一个目录,可以采用挂载宿主机同一目录的方式。
首先在宿主机创建一个目录用于容器见共享:
root@localhost:~# mkdir /data
root@localhost:~# echo hello > /data/world
root@localhost:~#
修改容器配置文件,新增如下内容:
lxc.mount.entry = /data data none rw,bind,create=dir 0 0
lxc.mount.entry
的写法跟 /etc/fstab
的写法一样,上面的内容就是将宿主机的 /data
目录挂载到容器内的 /data
目录,容器内的路径开头不可以写 /
否则会无法挂载。rw,bind,create=dir
是挂载选项,表示以读写方式挂载,如果容器内文件夹不存在则自动创建。
root@localhost:~# lxc-stop ubuntu-1604 && lxc-start ubuntu-1604
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# ls /data
world
root@ubuntu-1604:~# cat /data/world
hello
root@ubuntu-1604:~#
我们还可以利用挂载的特性,将设备文件直接挂载到容器内,比如上面的 tunnel 设备文件还可以用以下方式自动挂载:
lxc.mount.entry = /dev/net/tun dev/net/tun none rw,bind,create=file 0 0
容器权限
在 Linux2.2 内核开始将超级用户的权限分为若干独立的子权限,每个子权限可独立的禁止或者打开,注意,一旦剥夺了根用户的某一权限,那么除非重启系统,否则无法恢复该权限。下面对这些权限进行简单的介绍:
CAP_CHOWN
:允许改变文件的所有权CAP_DAC_OVERRIDE
:忽略对文件的所有 DAC 访问限制CAP_DAC_READ_SEARCH
:忽略所有对读、搜索操作的限制CAP_FOWNER
:如果文件属于进程的 UID,就取消对文件的限制CAP_FSETID
:允许设置 setuid 位CAP_KILL
:允许对不属于自己的进程发送信号CAP_SETGID
:允许改变组 IDCAP_SETUID
:允许改变用户 IDCAP_SETPCAP
:允许向其它进程转移能力以及删除其它进程的任意能力CAP_LINUX_IMMUTABLE
:允许修改文件的不可修改 (IMMUTABLE) 和只添加 (APPEND-ONLY) 属性CAP_NET_BIND_SERVICE
: 允许绑定到小于 1024 的端口CAP_NET_BROADCAST
:允许网络广播和多播访问CAP_NET_ADMIN
:允许执行网络管理任务:接口、防火墙和路由等。CAP_NET_RAW
:允许使用原始 (raw) 套接字CAP_IPC_LOCK
:允许锁定共享内存片段CAP_IPC_OWNER
: 忽略 IPC 所有权检查CAP_SYS_MODULE
: 插入和删除内核模块CAP_SYS_RAWIO
:允许对 ioperm/iopl 的访问CAP_SYS_CHROOT
:允许使用 chroot() 系统调用CAP_SYS_PTRACE
:允许跟踪任何进程CAP_SYS_PACCT
:允许配置进程记帐 (process accounting)CAP_SYS_ADMIN
:允许执行系统管理任务:加载/卸载文件系统、设置磁盘配额、开/关交换设备和文件等。CAP_SYS_BOOT
:允许重新启动系统CAP_SYS_NICE
:允许提升优先级,设置其它进程的优先级CAP_SYS_RESOURCE
:忽略资源限制CAP_SYS_TIME
:允许改变系统时钟CAP_SYS_TTY_CONFIG
:允许配置 TTY 设备CAP_MKNOD
:允许使用 mknod() 系统调用CAP_LEASE
:Allow taking of leases on files
容器的配置文件提供了 lxc.cap.drop
来允许我们运行的容器抛弃某些权限,例如我们要抛弃容器的创建设备文件和更改 IP 地址的权限,追加以下配置到容器的配置文件:
lxc.cap.drop = mknod net_admin
权限的名称不需要写开头的 CAP_
使用小写即可,接着重启容器:
root@localhost:~# lxc-stop ubuntu-1604 && lxc-start ubuntu-1604
root@localhost:~# lxc-attach ubuntu-1604
root@ubuntu-1604:~# ifconfig eth1 10.0.1.11/24
SIOCSIFADDR: Operation not permitted
SIOCSIFFLAGS: Operation not permitted
SIOCSIFNETMASK: Operation not permitted
root@ubuntu-1604:~# mknod test c 10 200
mknod: test: Operation not permitted
root@ubuntu-1604:~#
容器开机自启
容器默认在系统启动时不会自动启动,但是提供了 lxc-autostart
命令来帮我们启动所有设置了开机自启的容器。 我们可以在容器的配置文件中添加以下内容来让容器自启动:
lxc.start.auto = 1
lxc.start.delay = 10
lxc.group = onboot
lxc.start.auto
表示允许自启。lxc.start.delay
表示启动延迟,在处理互相依赖的容器中,启动延迟会比较有用。lxc.group
表示定义启动组,这样就可以通过 lxc-autostart -g onboot
来启动所有属于 onboot
组的容器了。
当设置好容器开机自启动后,将 lxc-autostart
命令添加到宿主机的 /etc/rc.local
中,这样就可以实现开机时自动启动容器了。
容器备份
可以在停掉或冻结要备份的容器后直接对 /var/lib/lxc/
下的容器目录进行归档备份,如果想克隆容器,直接将容器目录复制一份,然后改下复制后的容器配置文件中的网卡配置相关冲突的地方即可。
附录
LXC 工具集的使用上还是比较原始简单,还有个更好用的 LXC 管理工具:LXD。该工具类似于 Docker,启动一个守护进程,然后通过 lxc
命令对容器和镜像进行管理,并提供了更灵活的配置和更完善的官方文档。还有容器快照、热迁移等高级功能。