通过几个步骤以实现创建的容器支持网络:
- 创建一对虚拟网络设备 veth1/veth2
- 设置 veth1 的 MAC 地址
- 将 veth1 加到网桥 kaixindeken0 上
- 激活 veth1
- 创建子进程
- 将 veth2 移动到 子进程网络 namespace 中,并重命名为 eth0
- 等待子进程结束
- 删除网络设备 veth1 和 veth2
所以我们必须进一步优化我们的 start() 逻辑:
首先我们应该为 docker::container_config 增加网络相关的配置:
包含头文件:
#include <net/if.h> // if_nametoindex
#include <arpa/inet.h> // inet_pton
#include "network.h"
增加 docker::container_config 配置:
// docker 容器启动配置
typedef struct container_config {
std::string host_name; // 主机名
std::string root_dir; // 容器根目录
std::string ip; // 容器 IP
std::string bridge_name; // 网桥名
std::string bridge_ip; // 网桥 IP
} container_config;
然后在 main.cpp 中设置好容器 IP,要加入的网桥名 docker0,以及网桥的 IP:
int main(int argc, char** argv) {
std::cout << "...start container" << std::endl;
docker::container_config config;
config.host_name = "kaixindeken";
config.root_dir = "./kaixindeken";
// 配置网络参数
config.ip = "192.168.0.100"; // 容器 IP
config.bridge_name = "docker0"; // 宿主机网桥
config.bridge_ip = "192.168.0.1"; // 宿主机网桥 IP
docker::container container(config);
container.start();
std::cout << "stop container..." << std::endl;
return 0;
}
我们再来根据上面的网络设备加载的逻辑重构 start() 方法:
private:
// 保存容器网络设备, 用于删除
char *veth1;
char *veth2;
public:
void start() {
char veth1buf[IFNAMSIZ] = "kaixindeken0X";
char veth2buf[IFNAMSIZ] = "kaixindeken0X";
// 创建一对网络设备, 一个用于加载到宿主机, 另一个用于转移到子进程容器中
veth1 = lxc_mkifname(veth1buf); // lxc_mkifname 这个 API 在网络设备名字后面至少需要添加一个 "X" 来支持随机创建虚拟网络设备
veth2 = lxc_mkifname(veth2buf); // 用于保证网络设备的正确创建, 详见 network.c 中对 lxc_mkifname 的实现
lxc_veth_create(veth1, veth2);
// 设置 veth1 的 MAC 地址
setup_private_host_hw_addr(veth1);
// 将 veth1 添加到网桥中
lxc_bridge_attach(config.bridge_name.c_str(), veth1);
// 激活 veth1
lxc_netdev_up(veth1);
// 容器创建前的一些配置工作
auto setup = [](void *args) -> int {
auto _this = reinterpret_cast<container *>(args);
_this->set_hostname();
_this->set_rootdir();
_this->set_procsys();
// 配置容器内网络
// ...
_this->start_bash();
return proc_wait;
};
// 使用 clone 创建容器
process_pid child_pid = clone(setup, child_stack,
CLONE_NEWUTS| // UTS namespace
CLONE_NEWNS| // Mount namespace
CLONE_NEWPID| // PID namespace
CLONE_NEWNET| // Net namespace
SIGCHLD, // 子进程退出时会发出信号给父进程
this);
// 将 veth2 转移到容器内部, 并命名为 eth0
lxc_netdev_move_by_name(veth2, child_pid, "eth0");
waitpid(child_pid, nullptr, 0); // 等待子进程的退出
}
~container() {
// 退出时,记得删除创建的虚拟网络设备
lxc_netdev_delete_by_name(veth1);
lxc_netdev_delete_by_name(veth2);
}
在 clone 里增加了 CLONE_NEWNET 这个参数。
从上面的步骤我们可以看到,创建好网络设备之后、子进程创建期间,需要在容器内部配合外部的网络设备进行相关配置:
- 激活容器内部的 lo 设备
- 配置 eth0 的 IP 地址
- 激活 eth0
- 设置网关
- 设置 eth0 的 MAC 地址
private:
void set_network() {
int ifindex = if_nametoindex("eth0");
struct in_addr ipv4;
struct in_addr bcast;
struct in_addr gateway;
// IP 地址转换函数,将 IP 地址在点分十进制和二进制之间进行转换
inet_pton(AF_INET, this->config.ip.c_str(), &ipv4);
inet_pton(AF_INET, "255.255.255.0", &bcast);
inet_pton(AF_INET, this->config.bridge_ip.c_str(), &gateway);
// 配置 eth0 IP 地址
lxc_ipv4_addr_add(ifindex, &ipv4, &bcast, 16);
// 激活 lo
lxc_netdev_up("lo");
// 激活 eth0
lxc_netdev_up("eth0");
// 设置网关
lxc_ipv4_gateway_add(ifindex, &gateway);
// 设置 eth0 的 MAC 地址
char mac[18];
new_hwaddr(mac);
setup_hw_addr(mac, "eth0");
}
然后再在容器的 setup 中调用这个方法:
……
_this->set_procsys();
_this->set_network(); // 容器内部配合网络配置
_this->start_bash();
return proc_wait;
因为这时我们已经开始需要使用刚才编译的 network.o 和 nl.o 这两个 gcc 编译的链接文件,因此我们不妨编写一个非常简单的 Makefile:
C = gcc
CXX = g++
C_LIB = network.c nl.c
C_LINK = network.o nl.o
MAIN = main.cpp
LD = -std=c++11
OUT = docker-run
all:
make container
container:
$(C) -c $(C_LIB)
$(CXX) $(LD) -o $(OUT) $(MAIN) $(C_LINK)
clean:
rm *.o $(OUT)
Makefile 里的命令应该是 Tab 开头而不是空格。
再编译执行,并进入容器,我们就能够通过 ifconfig 查看网络,同时,我们已经能够 ping 通宿主机网络了。
外网访问
想要让容器能够对外网进行访问,我们可以通过 iptables 进行源地址转换,达到网络访问的目的。
源地址转换即内网地址向外访问时,发起访问的内网ip地址转换为指定的ip地址(可指定具体的服务以及相应的端口或端口范围),这可以使内网中使用保留ip地址的主机访问外部网络,即内网的多部主机可以通过一个有效的公网i IP 地址访问外部网络。
首先,我们需要在容器内部配置好 DNS:
vi /etc/resolv.conf # 容器中没有 vim 命令
并写入:
nameserver 114.114.114.114
这还不够,因为宿主机的防火墙会限制这一举措,因此,我们还需要对防火墙进行相关配置:
sudo iptables -t nat -A POSTROUTING -s 192.168.0.1/16 ! -o docker0 -j MASQUERADE
删除这条规则只需将 -A 改为 -D
当这条命令执行后,网桥便能够将来自容器的网络访问转换到外网去,至此便完成了容器网络的访问支持:
现在我们能够访问外网后,不妨给容器安装一些软件,刚才我们在写入 DNS 时候会发现,我们并没有 vim 工具,不妨安装一下这个:
apt-get update
apt-get install vim
评论区