[NFV] LXD

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

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

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

LXD

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

Linux Cgroup

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

Linux Namespace

namespace 是 Linux 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 的网络连接,通过虚拟网桥 lxcbr0 以 NAT 形式共享主机网络。

对模版的更多配置详见 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 可以查看全部镜像。