4
29
2016
5

一个系统,两套网络

创建并配置一个新的网络命名空间

公司 VPN 用的网段是 192.168.1.0/24,而我家里的网络,就像大多数人的一样,也是 192.168.1.0/24。网段冲突了。当然啦,隔离得相当好的 lxc 可以解决这个问题。可是它隔离得太好了,我可不想再多维护一套环境。那么,就只隔离网络好了!

正好之前看到 iproute2 套件可以管理网络命名空间。那么,就用它啦。

首先,创建一个名为 vpn 的网络命名空间:

ip netns add vpn

现在如果进去看的话,会发现里边只有一个孤零零的 lo 网络接口,所以里边是一片与世隔绝的黑暗。我没有其它的网络接口给它用。桥接当然是可以尝试的,只是需要更改已有的网络配置。所以还是用另外的方案吧。弄根网线来,把里边和外边连接起来好了。

创建一对 veth 接口。这就是我们的「网线」~

ip link add vpn0 type veth peer name vpn1

一端留外边,另一端移到 vpn 里边去:

ip link set vpn1 netns vpn

接好网线的两端:

ip link set vpn0 up
ip netns exec vpn ip link set vpn1 up

好了,现在两个网络之间可以通信了~当然,只是链路层。为了使用 TCP/IP,我们得分配 IP 地址。在外边的这个就不需要 IP 地址了,因为我要把它接到一个网桥上,网桥自己有 IP 的。

brctl addif br0 vpn0

这里 br0 是我已有的网桥(给 lxc 用的那个)。如果你没有的话,就自己按以下方法弄一个。我用的是 bridge-utils 里的 brctl 命令。iproute2 也可以做的。

brctl addbr br0
ifconfig br0 192.168.57.1
iptables -t nat -A POSTROUTING -s 192.168.57.1/24 -j MASQUERADE

最后的 iptables 命令是做 NAT 啦。当然内核的 IPv4 转发功能还要开启的。

vpn0 这端弄好了,再给 vpn1 分配 IP,并设置相关路由表项:

ip netns exec vpn ip address add dev vpn1 192.168.57.101/24
ip netns exec vpn ip route add default via 192.168.57.1

现在就可以在里边连 VPN 啦~

ip netns exec vpn openvpn ...

当然也可以去里边开个 zsh 用

ip netns exec vpn zsh

不过要注意如果跑 GUI,或者连接 D-Bus 的话,需要手动把相关环境变量移进去。虽然是独立的网络命名空间,但是因为共享了文件系统,所以虽然看不到在监听的 UNIX 套接字,但是连起来还是没有问题的。X 和 D-Bus 都可以良好工作的。

export DISPLAY=:0 LANG=zh_CN.UTF-8 LANGUAGE=zh_CN:zh_TW
export GTK_IM_MODULE=fcitx QT_IM_MODULE=fcitx XMODIFIERS=@im=fcitx
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
# 如果在 tmux 里的话
export TMUX=1

再加上 mount 命名空间吧

我这里在里边得用 IP 地址,因为我的 DNS 是 127.0.0.1,在里边当然是用不了的。怎么办呢?我不想改 DNS 服务器地址,因为里外的文件系统是共享的。那就再加上 mount 命名空间吧,这样就可以 bind mount 一个修改过的文件到 /etc/resolv.conf 上了。

其实呢,ip netns 是通过把网络命名空间(/proc/<pid>/ns/net)bind mount 到文件来实现命名空间的持久化的(不然使用这个命名空间的进程都退出,该命名空间就销毁了)。其文件位于 /run/netns 下。对于 mount 命名空间我们可以手工这么做:

mkdir -p /var/run/ns
mount --bind /var/run/ns /var/run/ns
# 命名空间只能 bind mount 到 private 挂载的文件系统上
mount --make-private /var/run/ns
# 随意找个普通文件就行。一般是用空文件;我这样可以省一个文件~
cp /etc/resolv.conf /var/run/ns

然后用 unshare 建立新的 mount 命名空间,并进入之前的 vpn 网络命名空间(当然用 nsenter 进入也是可以的):

unshare --mount=/var/run/ns/resolv.conf ip netns exec vpn zsh

创建了之后就可以用 nsenter 进去玩儿了:

nsenter --mount=/var/run/ns/resolv.conf --net=/var/run/netns/vpn zsh

可以在里边各种 bind mount,不会影响外边的哦:

# 在新的命名空间里边
mount --bind /var/run/ns/resolv.conf /etc/resolv.conf
vim /etc/resolv.conf

组合更多的命名空间

当然也可以组合更多种的命名空间的。我还试过 pid 命名空间,不过 pid 命名空间比较特殊:它在 fork 后才生效,当 init 进程(pid=1 的进程)退出之后所有位于此 pid 命名空间的进程都会被杀死,并且再也进不去了。所以不是很好玩的啦。

创建与进入的命令如下:

unshare --mount-proc -f --pid=/var/run/ns/pid --mount=/var/run/ns/resolv.conf nsenter --net=/var/run/netns/vpn su - lilydjwg
# 进入时用 -t 选项,或者重新 bind mount 文件(不然新进程会在原 pid 命名空间)
nsenter -t 9416 --mount=/var/run/mountns/resolv.conf --net=/var/run/netns/vpn --pid su - lilydjwg

后记

除了可能偶尔连接公司 VPN 会用到这个技术之外,我又给其找到了一个更好的使用场合:Wine QQ!于是本地的 nginx 日志里终于不会再有 /srv/http/cgi/reccom 找不到的提示了,CGI 服务也不会被不必要地启动了。

2016年5月13日更新:自用的脚本放在 Gist 上了。

Category: Linux | Tags: linux 网络 lxc
6
21
2015
0

在用户命名空间中运行 LXC 虚拟机

不用 sudo 也可以跑 LXC 虚拟机啦。使用 root 权限的 LXC 虚拟机,里边的 root 权限就是真实的 root 权限,虽然不太能够跑出来。而利用用户命名空间来启动的普通权限的 LXC 虚拟机则只在那个虚拟机里有 root 权限,从外面看跟一普通用户一样的。

首先需要一枚启用了CONFIG_USER_NS的内核。使用以下命令查看:

zgrep USER_NS /proc/config.gz

部分发行版会默认禁用用户命名空间功能,需要手动启用,参见 vagga 的安装文档。而 Arch Linux 不喜欢给软件打补丁,而这个特性又被认为是不安全的,所以并没有启用。当然这并不妨碍自己编译一个启用了这个特性的内核啦,比如 linux-lily 从 4.0.1 开始启用此特性。

注意:这个特性被认为不安全的,会时不时地爆出个提权漏洞(比如前不久这个),请谨慎启用。

内核支持没问题的话就可以开始配置了。以下配置过程主要参考 Arch Linux 论坛里的这篇帖子

首先给自己配置一些子 UID 和子 GID,也就是自己的分身。我在/etc/subuid/etc/subgid内写下如下内容

lilydjwg:100000:65536

意思是说,我(lilydjwg)被授权使用从 100000 开始的 65536 个 UID 和 GID。这一步是需要 root 权限的。这个配置好之后就可以创建用户命名空间了,比如:

lxc-usernsexec -m u:0:100000:1 -m g:0:1000:1 -- /bin/zsh

此命令是说,创建一个用户命名空间,其中 UID 从 0 开始,实际对应于外边 100000 开始的 UID,总共分配一个;GID 从 0 开始,实际对应于外边 1000 开始的 GID,总共分配一个。执行之后可以看到新启动的 zsh 已经是 root 权限了。不过cat /etc/shadow就会发现还是没权限 :-D 在里边 touch 个文件的话,在外边看会是 UID 为 100000 的用户创建的。我之所以要指定 GID 的映射,是因为我的 HOME 目录外人读不了的。为了加载 zsh 的配置,就把自己的 GID 映射给它了。

当然我也可以把自己的真实 UID 映射过去,这样子除了被里边的进程自认为有 root 权限之外没什么别的差异。用户命名空间要配合别的命名空间一起用才有意思。

然后要配置一下 cgroup,不然 lxc 会报错的。这一步也是需要 root 权限的。

echo 1 | sudo tee /sys/fs/cgroup/cpuset/cgroup.clone_children

for d in /sys/fs/cgroup/*; do
    sudo mkdir $d/$USER
    sudo chown -R $USER: $d/$USER
done

用处后边再说。

虚拟机里的网络是分开的。默认是没有网络的。想要的话得先授权,向/etc/lxc/lxc-usernet文件里写入

lilydjwg veth br0 10

其中br0是桥接用的网络接口名。没有就自己建一个:

brctl addbr br0
ifconfig br0 192.168.57.1
iptables -t nat -A POSTROUTING -s 192.168.57.1/24 -j MASQUERADE

这些当然也是需要 root 权限的。

还要告诉 LXC 使用用户命名空间:在~/.config/lxc/default.conf写入:

 lxc.include = /etc/lxc/default.conf
 lxc.id_map = u 0 100000 65536
 lxc.id_map = g 0 100000 65536

然后,去弄一个 LXC 系统镜像吧:

lxc-create -t download -n lxcname

名字自己起。这个命令会让你选择你要的发行版和版本的。这一步不需要 root 权限了。镜像文件列表可以看这里

等它跑完之后新的 LXC 虚拟机的 root 文件系统已经就绪了。不过在启动它之前先去编辑一下它的配置文件,加入网络配置。默认它位于~/.local/share/lxc下与 LXC 虚拟机同名的目录下。

在配置文件里加上

lxc.network.type = veth
lxc.network.link = br0
lxc.network.flags = up
lxc.network.ipv4 = 192.168.57.4
lxc.network.name = eth0

在启动之前还要做一件事——将当前进程加入到之前创建的 cgroup 中:

for d in /sys/fs/cgroup/*; do echo $$ > $d/$USER/tasks; done

然后就可以启动 LXC 虚拟机啦。当然是不需要 root 权限的:

lxc-start -F -n lxcname

当然,得给里边的 root 用户设置一个密码,不然登录不了的。可以使用 lxc-usernsexec 来 chroot 过去:

lxc-usernsexec -- chroot rootfs /bin/bash
Category: Linux | Tags: lxc linux
11
8
2014
6

在 Arch Linux 下安装 openSUSE LXC 虚拟机

前边,我已经尝试过在 Arch Linux 下安装 Funtoo在 Arch 中安装 Arch 就更简单了。为了测试,我还通过 Aufs 来将我的 Arch Linux 在 LXC 里复制一份。至于安装个 Debian 或者 Ubuntu LXC,由于有在任何 Linux 下都可以跑的 deboostrap,安装起来也十分容易。

现在难题来了:在 LXC 里安装一个 openSUSE。LXC 自带了个 openSUSE 模板,但是它需要 zypper 等。虽然说 AUR 里就有 zypper 和 libzypp(还都是 git 版本的,下载很耗时的),不过还是不怎么够呢。经过尝试,我发现需要以下包来运行这个模板:

  • Arch 里有的:augeas
  • Arch 里没的:libsolv-tools build rpm libzypp zypper

有的就直接安装啦。没有的,可以从它的软件源下载。x86_64 架构的在这里,那个 build 是 noarch 的,在这边

把它们全部下回来,拿 7z 解压能够得到 cpio 档。然后建立个目录并 cd 过去,使用以下命令解开:

$ mkdir t
$ cd t
$ for f in ../*.cpio; do cpio -id < $f; done

这样就把它们解压到目录t里边的。我没有把它们解压到/,因为我不想弄乱我的系统,即使能够清理也是相当麻烦的,而且一不小心还可能删错文件。

所以,又该 Aufs 上场啦。当然在此之前还有件事:openSUSE 没有进行/usr合并。所以要手动去把binusr/sbinsbin等目录下的文件移动到usr/bin下,然后删掉那些目录;把usr/lib64下的文件移动到usr/lib下,并删掉usr/lib64

然后就可以将这个目录和我的 Arch Linux 合体啦:

$ mkdir root
$ sudo mount -t aufs -o br:$PWD/root=rw:$PWD/t=ro:/=ro aufs root

但是!这样子的话,新装好的 openSUSE LXC 会在这个root目录里呢。所以要把外边真实的 LXC 目录给 bind mount 过来。我使用了自定义的 LXC 路径,所以是这样子的:

$ sudo mkdir -p root/ldata/media/temp/lxc
$ sudo mount --bind /ldata/media/temp/lxc root/ldata/media/temp/lxc

然后编辑一下 openSUSE 的模板,搜索「http」把软件源的链接全部改到中国的镜像:

$ sudo vim root/usr/share/lxc/templates/lxc-opensuse

我使用的是中科大的源镜像。

一切就绪,开始安装~

$ sudo chroot root /usr/bin/lxc-create --lxcpath=/ldata/media/temp/lxc -n opensuse -t opensuse

耐心等待哦。最终安装完成根文件系统的大小是 333MiB。

安装完毕之后卸载刚刚挂载的那些东西:

$ sudo umount -R root

然后编辑一下自动生成的 LXC 配置文件,比如属改改网络什么的。以下是我改过的配置文件:

# Template used to create this container: /usr/share/lxc/templates/lxc-opensuse
# Parameters passed to the template:
# For additional config options, please look at lxc.container.conf(5)
lxc.rootfs = /ldata/media/temp/lxc/opensuse/rootfs
lxc.utsname = opensuse
lxc.autodev=1
lxc.tty = 4
lxc.pts = 1024
lxc.mount.entry = run run tmpfs rw 0 0
lxc.mount.entry = tmp tmp tmpfs rw 0 0
lxc.mount.auto = proc sys
lxc.cap.drop = sys_module mac_admin mac_override mknod sys_time
lxc.kmsg = 0

# When using LXC with apparmor, uncomment the next line to run unconfined:
#lxc.aa_profile = unconfined

#networking
lxc.network.type = veth
lxc.network.link = br0
lxc.network.flags = up
lxc.network.ipv4 = 192.168.57.6
lxc.network.name = eth0

lxc.cgroup.devices.deny = a
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
lxc.cgroup.devices.allow = c 4:0 rwm
lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rm

当然别忘记修改 root 密码啦:

$ sudo chroot /ldata/media/temp/lxc/opensuse/rootfs /bin/passwd

不过默认会有两个 getty 进程跑在 console 上边。要稍微修改一下。顺手把多余的 tty 上的也关掉好了:

$ sudo rm /ldata/media/temp/lxc/opensuse/rootfs/etc/systemd/system/getty.target.wants/getty@tty*
$ sudo mv /ldata/media/temp/lxc/opensuse/rootfs/etc/systemd/system/console-{shell,getty}.service

然后就可以启动啦:

$ sudo lxc-start -n opensuse --lxcpath=/ldata/media/temp/lxc

会默认启动 sshd,所以直接 ssh 连过去就可以用啦=w=

PS: 这个模板默认安装的是 openSUSE 12.3。记得自己改改或者装好后升级一下。

Category: Linux | Tags: lxc aufs linux Arch Linux openSuse
2
19
2014
18

利用 Aufs 和 LXC 快速建立一个用于测试的系统副本

起因是,我偶尔看到 MediaWiki 导出时可以把图片也包含在 XML 文件中,但是不确定能不能顺利地导入回去。本来是准备拿虚拟机测试的,但是得在虚拟机里安装整套环境,麻烦呀。于是,结合前段时间折腾 Aufs 和 LXC 的经验,把当前正在运行的系统利用 Aufs 搞了一份只读挂载。当然还要弄个空目录来放可写分支:

mkdir -p root data
sudo mount -t aufs -o br:$PWD/data=rw:/=ro aufs $PWD/root

其实这个样子就已经可以 chroot 进去跑 httpd 了。不过,得先改一下监听的端口,因为 chroot 环境与主系统只有文件系统是隔离的,网络空间还是共享的。chroot 中 PID 空间也是共享的,所以在里边杀进程时不小心把 PID 写错的话,是可能会把外边的进程给杀掉的……(而 LXC 中,主系统是可以杀容器中的进程,但是反过来不行,因为主系统中的进程在容器中根本没分配 PID。)

于是就来玩玩 LXC 啦。要注意把 fstab 删掉,不然 systemd 会不高兴。日志文件不能共享,否则 journald 会不高兴。因为把 mknod 权限给禁掉了,所以在容器里 loop 设备是没法创建的。如果需要,在主系统里 losetup 之后像注释里那样写一条挂载信息就好。

sudo rm root/etc/fstab
sudo rm -r root/var/log/journal
sudo mkdir root/var/log/journal
sudo chgrp systemd-journal root/var/log/journal
sudo brctl addbr br0
sudo ifconfig br0 192.168.10.1

cat > lxc.conf <<EOF
lxc.utsname = arch2
lxc.autodev = 1
lxc.tty = 1
lxc.pts = 1024
lxc.rootfs = ${PWD}/root
lxc.mount.entry = sysfs sys sysfs ro,defaults 0 0
lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0
lxc.mount.entry = /proc/sys ${PWD}/root/proc/sys none ro,bind 0 0
lxc.cap.drop = mknod sys_module mac_admin mac_override
# loop mount
# lxc.mount.entry = /dev/loop1 /home/lilydjwg/tmpfs/root/var/lib/pacman ext4 rw 0 0
#networking
lxc.network.type = veth
lxc.network.link = br0
lxc.network.flags = up
lxc.network.ipv4 = 192.168.10.3
lxc.network.name = eth0
#cgroups
lxc.cgroup.devices.deny = a
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
lxc.cgroup.devices.allow = c 1:7 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 4:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:2 rwm
lxc.cgroup.devices.allow = c 136:* rwm
EOF
sudo lxc-start -n arch-dup -f lxc.conf

当然网络和 DNS 还要进去再设置一下:

route del -net 192.0.0.0/8
route add -net 192.168.0.0/16 eth0
route add -net default gw 192.168.10.1
echo 'nameserver 192.168.10.1' > /etc/resolve.conf

LXC 挺有点复杂的。systemd 的开发者也是这么认为的,所以他们搞了个操作便捷性类似于 chroot 但是功能类似于 LXC 的东东——systemd-nspawn!比如上边那个新系统可以这么启动:

sudo systemd-nspawn -b --private-network -D root

不过很遗憾的是,要么加--private-network让新启动的容器没有网络,要么不加,和 chroot 一样与主系统共享网络。毕竟是他们用来测试 systemd 的东东嘛。调试系统的第一个进程可不容易,但是当它在另一个系统中只是一个普通进程、可以连 gdb 和 strace 时情况就大不一样啦 =w=

PS: 在 systemd-nspawn 的 manpage 中(上边那个 freedesktop.org 的链接),Arch 和 Fedora 以及 Debian 并列作为示例了呢 =w=


2015年3月14日更新:使用 Linux 3.18 及以上版本的内核,也可以使用 overlayfs 取代 aufs 来挂载,挂载命令示例如下:

modprobe overlay
mount -t overlay -o lowerdir=/,upperdir=$PWD/.lxc-data,workdir=$PWD/.lxc-root overlayfs $PWD/.lxc-root

lowerdir是只读的目录(其中的数据不会被修改),upperdir是用于记录修改的可写目录,workdir是工作目录,其必要性我也不理解,需要和upperdir同一文件系统。我习惯上指定为挂载目标目录。

overlayfs 某些操作的效率似乎比 aufs 高不少。这里是我自己用来创建这个系统副本的 Shell 脚本。

Category: Linux | Tags: linux systemd lxc aufs
4
15
2013
8

lxc 初体验

Linux Containers 是一种系统级的虚拟化方案。其实也就是个增强型的 chroot,和 BSD 的 jail 差不多吧。

准备工作

安装 lxc 以及 bridge-utils 软件包。后者是用来建立网络的。关于对内核的要求什么的请查阅此文。虽然其内容有些过时了,但是参考价值还是很高的。当然,一般新一点的通用内核都支持的啦。

然后想好在建立的 Container 里放什么系统。比如我用的 Funtoo,安装教程在这里。但其实大部分内容没什么用的。按照这里的指示下载一个合适的 stage3 包,并解压到一个目录中。portage 的安装等后续工作就不说了。

创建一个网桥并分配 IP 地址:

$ sudo brctl addbr br0
$ sudo ifconfig br0 192.168.10.1

由于我使用的是无线网络,因此不能使用一般教程中的方法将 eth0 加到网桥中去。建立个 NAT 好了:

echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -j MASQUERADE

配置

按下边这样新建一个文本文件:

# Container 的名字
lxc.utsname = funtoo
# 网络类型。我使用的是 veth
lxc.network.type = veth
lxc.network.flags = up
# 刚刚建立的网桥名
lxc.network.link = br0
#lxc.network.hwaddr 不要乱写,会出错的!让 lxc 自己决定吧。
# IP 地址。和网桥同一网段即可
lxc.network.ipv4 = 192.168.10.2
lxc.network.name = eth0
# fstab 文件
lxc.mount = /home/lilydjwg/workspace/funtoo/funtoo_root/etc/fstab
# 新系统的根文件系统
lxc.rootfs = /home/lilydjwg/workspace/funtoo/funtoo_root
# tty 数量。如果不给出的话 lxc-console 是连不上去的
lxc.tty = 7

然后,修改 funtoo 里的配置文件啦。首先是/etc/fstab

none /home/lilydjwg/workspace/funtoo/funtoo_root/dev/pts devpts defaults 0 0
none /home/lilydjwg/workspace/funtoo/funtoo_root/proc    proc   defaults 0 0
none /home/lilydjwg/workspace/funtoo/funtoo_root/sys     sysfs  defaults 0 0
none /home/lilydjwg/workspace/funtoo/funtoo_root/dev/shm tmpfs  defaults 0 0
/home/lilydjwg/tmpfs /mnt bind 0 0

最后一行就是和主系统共享的目录啦。不过我这里没有生效 :-(

然后删掉/etc/mtab

修改/etc/inittab启动配置文件。lxc-start命令启动的那个终端会是 container 的/dev/console文件:

# TERMINALS
c1:12345:respawn:/sbin/agetty --noclear 38400 console linux
c2:2345:respawn:/sbin/agetty 38400 tty1 linux

ttyN 上的 agetty 可以留着,因为我已经分配了足够的 tty 给它们。使用lxc-console -t 3 -n funtoo就可以连上 tty3。

修改/etc/resolv.conf,配置一个 DNS 服务器。

删除/etc/runlevels/default下关于 udev 的项;udev 是不会工作的。

给新系统中的 root 设置个密码:

$ sudo chroot /path/to/funtoo/root/ /bin/bash
funtoo ~ # passwd

创建新的 container。注意创建之后那个配置文件就没有用了。lxc 会复制一份到/var/lib/lxc/funtoo/config,需要的时候可以改它。

$ sudo lxc-create -f /path/to/config/file -n funtoo

启动啦

如果我没记错的话,现在就可以启动 funtoo 啦:

$ sudo lxc-start -n funtoo

不想它占用一个终端的话就加-d选项让它在后台跑吧。

不过这时候网络还没配置好。虽然 eth0 已经有 IP 地址了,也可以和主系统相互通讯,但是路由表仍需要手动加上,不然访问不了外网的。

funtoo ~ # route add -net 0.0.0.0 gw 192.168.10.1

然后就尽情地玩儿吧 :-)

参考资料

其它链接

Category: Linux | Tags: linux lxc

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com