[NFV] LXD

近期接触到了 NFV (Network Function Virtulization),简单来说就是通过虚拟化技术,在服务器、云主机上构建虚拟的网络设备(交换机、路由器等),从灵活性、可扩展性、维护成本这些方面,是要优于专有网络硬件设备的。

NFV 主要依托KVMLXD等虚拟化技术,在 VM 中利用 QuaggaOpen vSwitch 等实现相应的功能,当然,基于容器 (Docker) 或者公有云私有云 (如 OpenStack 等) 的应用也是可以的。

本文主要内容:LXD,使用 LXD 构建虚拟路由器。

LXD

lxd 其实是 lxc 迭代的版本,我们都知道 docker 技术最初是基于 lxc 实现的,这些技术的共同点,其实都基于 Linux 内核功能实现,如 cgroups namespaces(ipc, network, user, pid, mount)

Linux Cgroup

cgroupsLinux kernel 功能,用于在系统中对进程组进行统一的资源监控和限制 (如 CPU 时间、系统内存、网络带宽等或者这些资源的组合)

Linux Namespace

namespaceLinux kernel 用来隔离内核资源的方式,同样是以组来管理进程,与上述区别 cgroup 在于 namespace 更多是对系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源。

上述三者通过创建非特权容器,来实现像 selinux 这样的功能,即最大限度地减小进程可访问的资源 (也被称为最小权限原则) 。就我个人理解,容器也好虚拟机也好,核心的意义应该是隔离,至于镜像、接口甚至编排,都是为了让整个系统更加易用。

Privileged & Unprivileged

通常我们认为容器可以分为两种类型,对于 Linux 内核而言并不存在容器的概念,只是向用户空间提供相应的借口,所谓的特权容器 (Privileged Container)是指容器内部的 root 用户同样也是容器之外,主机之内的 root 用户 (UID 0);相反,非特权容器的 UID 0 映射到容器之外并不会是 主机的 UID 0

显而易见我们应该尽量避免使用特权容器,但是不管是 Docker 还是 Kubernetes 中都运行着大量的特权容器,为了易用性在安全性上有些妥协,不过通过鉴权、RBAC等一定程度上保证了容器不会被恶意侵入。

LSMs

Linux Security Module,顾名思义。

Linux Capabilities

早期 Unix/Linux 的权限控制比较粗糙,只有超级用户/普通用户的概念,有一个概念 SUID (Set User ID on execution) ,如将文件 /bin/passwd 设置了 SUID 的 flag,在普通用户运行 passwd 命令时,将会以 passwd 所属者,即 root 用户的身份运行

Capabilities 机制细化了权限的粒度,在进行特权操作的时候,不再是使用 SUID 以 root 的身份执行操作,而是通过 EUID 判断是否为 root 身份,检查该特权操作所对应的 Capabilities,并以此为依据决定是否执行操作。

chroot

在 linux 系统中系统默认的目录结构都是以 / 开始的,chroot 可以将制定的位置作为系统目录结构的跟 / 。通过 chroot 可以构建新根,与原系统的目录结构隔离开,限制用户访问一些特定的文件;在引导 Linux 系统启动和紧急救助上也能提供一定的便利。

LXD 与 LXC

  • lxc: 主要为 Linux 用户空间提供内核管理接口,包括 Kernel namespaces, Apparmor (访问权限控制) 和 SELinux 配置, chroot 及其他内核能力。

  • lxd: 作为一个容器的 hypervisor ,由 daemon (lxd) , CLI (lxc,注意不是前面那个 lxc) 等组成,相较于 lxc 更加灵活易用,引入了包括配置文件、镜像库、多种存储后端、集群模式、丰富的设备管理功能。

Docker

lxc/lxd 提供完整的虚拟机容器,而 docker 更多是应用级别的容器,docker 引擎用一个独立的文件系统来包裹应用,而不是 userspace ,更多的还是希望把容器当作一个单独的进程来用。Docker 曾使用 lxc 用来在底层与内核通讯,现在使用的是自研的 libcontainer


安装 LXD

1
$ sudo snap install lxd

初始化:

1
$ sudo lxd init

由于我们需要自定义网络,所以注意初始化中的以下选项:

1
2
Would you like LXD to be available over the network? (yes/no) [default=no]: yes
Would you like stale cached images to be updated automatically? (yes/no)[default=yes] no

用户组:

1
2
$ sudo usermod -a -G lxd <username>
$ newgrp lxd

创建容器

创建容器需要两样东西:镜像与模版。

镜像,顾名思义:

1
2
$ sudo lxc remote add tuna-images https://mirrors.tuna.tsinghua.edu.cn/lxc-images/ --protocol=simplestreams --public
$ sudo lxc image copy tuna-images:ubuntu/16.04 local: --alias ubuntu/16.04 --copy-aliases --public

模版中包括了对容器的一些初始化配置,如有需要,可以修改模版:

1
$ sudo lxc profile edit default

Optional: 比如取消容器自动创建网卡,可以将默认网卡 etho0 部分删除:

1
2
3
4
5
6
7
8
9
10
11
12
config: {}
description: Default LXD profile
devices:
root:
path: /
pool: local
type: disk
name: default
used_by:
- /1.0/containers/H1
- /1.0/containers/H2
- /1.0/containers/H3

注意:

此处移除网卡会导致基于该模版建立的容器无法联网,需要重新建立与 lxcbr0 的网络连接,通过虚拟网桥 lxcbr0NAT 形式共享主机网络。

对模版的更多配置详见 Instance configuration .

接下来创建容器:

1
$ sudo lxc init ubuntu/16.04 R1 -p default

并启动容器:

1
$ sudo lxc start R1

虚拟路由器

Quagga 基于 Zebra 实现,支持RIP、OSPF、BGP等路由协议的动态路由软件,现在我们要做的,就是将 Quagga 安装到刚刚运行起来的容器中。

下载 Quagga

下载 quagga-0.99.19.tar.gz 到宿主机硬盘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 向容器发送文件到根目录

$ sudo lxc file push quagga-0.99.19.tar.gz R1/

# 进入容器

$ sudo lxc exec R1 bash

# 解压文件

$ tar -zxvf quagga-0.99.19.tar.gz

# 进入目录

$ cd quagga-0.99.19

# 配置

$ ./configure --enable-vtysh --enable-usr=root --enable-group=root --enable-vty-group=root

编译安装

在 Quagga 路径下,执行编译指令,如果有缺失依赖,逐项安装即可:

1
2
3
$ make

$ make install

配置 Quagga

检查文件/etc/services ,是否有如下内容,没有的要添加。

1
2
3
4
5
6
7
8
9
zebrasrv        2600/tcp                        # zebra service
zebra 2601/tcp # zebra vty
ripd 2602/tcp # ripd vty (zebra)
ripngd 2603/tcp # ripngd vty (zebra)
ospfd 2604/tcp # ospfd vty (zebra)
bgpd 2605/tcp # bgpd vty (zebra)
ospf6d 2606/tcp # ospf6d vty (zebra)
ospfapi 2607/tcp # OSPF-API
isisd 2608/tcp # ISISd vty (zebra)

在quagga文件夹下 $ cp /usr/local/etc/zebra.conf.sample zebra.conf

启动 Quagga

进入 Quagga 配置文件所在路径中,(Quagga 相关执行文件在 /usr/local/sbin 中)

1
2
3
$ cd /usr/local/etc
# 启动 zebra
$ zebra -u root -g root -d

注意此处可能会碰到权限问题,需要将当前用户加入到 quagga 用户组中。

通过 $ ps aux | grep zebra 确定 zebra 进程已经正常运行。

1
2
3
4
5
# 查看启动密码
$ cat /usr/local/etc/zebra.conf
hostname Router
password zebra
enable password zebra

构建路由镜像

将容器打包成镜像:

1
2
3
4
5
6
7
8
9
$ sudo lxc stop R1

$ sudo lxc publish R1 --alias router --public

$ sudo lxc image list //可以查看到本地镜像router是否创建成功

$ sudo lxc init router R2 -p default

$ sudo lxc init router R3 -p default

通过 $ sudo lxc list 可以查看全部镜像。