从零开始:使用LXC构建开发容器,配置NAT、DNS与SSH(以openSUSE Tumbleweed为例)
最近将用于构建开发环境的工具从Docker迁移到了LXC。由于LXC的配置相对复杂、自由度高,在此仅列出笔者的实践以供参考。本文将在openSUSE Tumbleweed中安装并配置LXC容器,实现NAT、DNS、SSH等功能。
如果没有特别说明,本文中所有命令均以root
身份运行
安装LXC
使用zypper
安装LXC
及相关组件:
1 | zypper -v cc |
如果lxc-checkconfig输出了lxc的版本及支持情况,则证明lxc已经安装好了。
配置UID、GID映射
参考Tutorial,创建非特权容器需要做UID、GID映射,从而使得容器内无法获得主机的root权限:
1
2
3
4
5
6cp /etc/lxc/default.conf /etc/lxc/default.conf.original # backup the original file
cp /etc/lxc/default.conf /etc/lxc/unprivileged.conf
echo "root:100000:65536" >> /etc/subgid
echo "root:100000:65536" >> /etc/subuid
echo "lxc.idmap = u 0 100000 65536" >> /etc/lxc/unprivileged.conf
echo "lxc.idmap = g 0 100000 65536" >> /etc/lxc/unprivileged.conf
配置NAT
编辑lxc-net配置文件
参考ArchWiki,
首先新建/etc/default/lxc-net
文件并写入以下内容(可以自由选择在保留IP地址区段内的ip_prefix;笔者选择了172.27.0):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30# Leave USE_LXC_BRIDGE as "true" if you want to use lxcbr0 for your
# containers. Set to "false" if you'll use virbr0 or another existing
# bridge, or mavlan to your host's NIC.
USE_LXC_BRIDGE="true"
# If you change the LXC_BRIDGE to something other than lxcbr0, then
# you will also need to update your /etc/lxc/default.conf as well as the
# configuration (/var/lib/lxc/<container>/config) for any containers
# already created using the default config to reflect the new bridge
# name.
# If you have the dnsmasq daemon installed, you'll also have to update
# /etc/dnsmasq.d/lxc and restart the system wide dnsmasq daemon.
LXC_BRIDGE="lxcbr0"
LXC_ADDR="<ip_prefix>.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="<ip_prefix>.0/24"
#LXC_DHCP_RANGE="<ip_prefix>.2,<ip_prefix>.254"
#LXC_DHCP_MAX="253"
# Uncomment the next line if you'd like to use a conf-file for the lxcbr0
# dnsmasq. For instance, you can use 'dhcp-host=mail1,10.0.3.100' to have
# container 'mail1' always get ip address 10.0.3.100.
#LXC_DHCP_CONFILE=/etc/lxc/dnsmasq.conf
# Uncomment the next line if you want lxcbr0's dnsmasq to resolve the .lxc
# domain. You can then add "server=/lxc/10.0.3.1' (or your actual $LXC_ADDR)
# to your system dnsmasq configuration file (normally /etc/dnsmasq.conf,
# or /etc/NetworkManager/dnsmasq.d/lxc.conf on systems that use NetworkManager).
# Once these changes are made, restart the lxc-net and network-manager services.
# 'container1.lxc' will then resolve on your host.
#LXC_DOMAIN="lxc"
配置firewalld
选择使用IP地址伪装(masquerade)实现NAT。考虑与firewalld
兼容,这里没有直接用iptables
,
而是使用direct模式描述规则。
其中ip_prefix
需与/etc/default/lxc-net
中的保持一致:
1
2
3systemctl status firewalld # enabled; active (running)
firewall-cmd --permanent --zone=trusted --add-interface=lxcbr0
firewall-cmd --direct --permanent --add-rule ipv4 nat POSTROUTING 0 -s <ip_prefix>.0/24 ! -o lxcbr0 -j MASQUERADE
配置ip_forward
接下来在宿主机中启用端口转发: 1
2
3
4echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/99-ip_forward.conf
echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.d/99-ip_forward.conf
echo "net.ipv6.conf.all.disable_ipv6 = 0" >> /etc/sysctl.d/99-ip_forward.conf
sysctl --system # apply sysctl conf
启用lxc-net.service
最后启动systemd服务,并令其开机自启: 1
2
3systemctl status lxc-net
systemctl start lxc-net
systemctl enable lxc-net # lxc-net auto start
配置DNS(宿主机部分)
利用dnsmasq
,令lxcbr0
为容器提供DNS解析服务:
1
2
3
4
5
6systemctl status dnsmasq
echo "port=5353" >> /etc/dnsmasq.d/lxc.conf
echo "bind-interfaces" >> /etc/dnsmasq.d/lxc.conf
echo "interface=lxcbr0" >> /etc/dnsmasq.d/lxc.conf
systemctl start dnsmasq
systemctl enable dnsmasq
编辑容器配置文件;创建并进入容器
参考Manpage,
可先将配置文件拷贝一份出来,作为创建容器时使用配置文件:
cp /etc/lxc/unprivileged.conf /etc/lxc/<conf_name>.conf
为容器分配的ipv4地址必须是空闲的,其中ip_prefix
需与lxcbr0
中的保持一致;
若需要ipv6地址也可以进行分配,对应的配置项分别为lxc.net.0.ipv6.address
与lxc.net.0.ipv6.gateway
;
此处将apparmor.profile
设置为unconfined
是出于实践需要,且作为一个开发环境无须启用之;
将与网络代理有关的环境变量设为全部不走代理,是因为LXC默认从宿主机继承所有环境变量,但容器通过网桥上网无需经过代理。
向<conf_name>.conf
中追加写入以下内容:
1
2
3
4
5
6
7
8
9
10
11lxc.net.0.ipv4.address = <ip_prefix>.<ip_suffix>/24
lxc.net.0.ipv4.gateway = <ip_prefix>.1
lxc.apparmor.profile = unconfined
lxc.environment = NO_PROXY=localhost,127.0.0.1,::1,0.0.0.0,*
lxc.environment = no_proxy=localhost,127.0.0.1,::1,0.0.0.0,*
lxc.environment = SOCKS_PROXY=
lxc.environment = ftp_proxy=
lxc.environment = gopher_proxy=
lxc.environment = http_proxy=
lxc.environment = https_proxy=
lxc.environment = socks_proxy=
创建并进入容器,其中--template download
意为从LXC
Images这个列表中获取镜像,依次输入发行版、版本号、CPU架构即可:
1
2
3lxc-create --name <container_name> --config /etc/lxc/<conf_name>.conf --template download
lxc-start --name <container_name>
lxc-attach --name <container_name>
容器创建完成后,其配置文件在宿主机中仍可以进行读写,修改的部分在容器重启后生效,路径为:/var/lib/lxc/<container_name>/config
所有的LXC命令都是以“lxc-”开头的,而形如“lxc 空格 子命令”的格式为LXD独有,并不在本文讨论范围内。 命令用法可以参考Manpages。
配置DNS(容器内部分)
首先要注意/etc/resolv.conf
是否是一个符号链接,根据具体情况不同,有不同的配置方法。
以openSUSE为例,可先关闭netconfig
,
而后手动写入/etc/resolv.conf
文件。 1
2
3# in container
sed -i 's/^NETCONFIG_DNS_POLICY="auto"/NETCONFIG_DNS_POLICY=""/' /etc/sysconfig/network/config
echo "nameserver <ip_prefix>.1" > /etc/resolv.conf
配置SSH
安装openssh-server
以openSUSE Leap为例,下载并安装有关组件,而后修改配置文件:
1
2
3# in container
zypper up # maybe configure zypper mirrors first
zypper in nano openssh-server # install vim if preferred
编辑sshd_config配置文件
在/etc/ssh/sshd_config
中,将对应配置项修改为以下内容:
1
2
3
4
5Port <preferred_port>
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication yes
AllowTcpForwarding yes
设置容器登录密码
要使用密码登录容器,可用passwd
命令设置一个密码:
# in container
passwd
利用hook令sshd自启
容器并不通过如multi-user.target
这样的systemd服务目标来启动,
所以sshd
自启动需要用到LXC提供的钩子lxc.hook.start
。
仿照sshd.service
,在容器内部创建一个脚本启动sshd
:
1
2
3
4
5
source /etc/sysconfig/ssh
/usr/sbin/sshd-gen-keys-start
/usr/sbin/sshd -t $SSHD_OPTS
nohup /usr/sbin/sshd -D $SSHD_OPTS > /var/log/sshd.log 2>&1 &
在宿主机的容器配置文件(/var/lib/lxc/<container_name>/config
)中加入以下内容:
lxc.hook.start = <script_path>
script_path
为容器内脚本的路径。
在宿主机关机时自动停止所有LXC容器
有LXC容器运行时直接关机将导致关机速度缓慢,且可能导致容器处于不正确的状态。
为解决此问题,可以注册一个systemd服务,运行脚本来停止所有运行中容器。
先写关闭所有运行中LXC容器的脚本: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Get a list of all running LXC containers
running_containers=$(lxc-ls --running)
if [[ -z "$running_containers" ]]; then
echo "No running LXC containers found."
exit 0
fi
# Stop each running container
for container in $running_containers; do
echo "Stopping container: $container"
lxc-stop -n "$container"
done
echo "All running LXC containers have been stopped."
再在/etc/systemd/system/
下创建.service
文件:
1
2
3
4
5
6
7
8
9
10
11[Unit]
Description=Stop all LXC containers
DefaultDependencies=no
Before=shutdown.target reboot.target halt.target poweroff.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/stop_all_lxc.sh
[Install]
WantedBy=shutdown.target
结语
至此,这样的LXC容器配置已经能够满足作为独立开发环境的需求,可以用IDE的remote功能访问容器内环境了;
本文即是在容器中配置Node.js环境并完成编写的。
就笔者的个人使用体验而言,LXC比Docker配置更灵活,性能也更优,是值得考虑的选择。