docker容器网络模式
docker支持多种网络模式,包括bridge、none、host、container、overlay、macvlan等等。
bridge
默认的模式。创建一对veth pair,pair的两端可以保证哪一端的收据都会forward到另一端。创建容器时,为容器分配自己的network namespace,将veth pair一端放在该namespace下,一端放在宿主机的namespace下,并与linux bridge桥接。
docker在容器内与外部的通信上做了NAT。
- 当外部的请求到达容器时,实际访问的是host_ip:port,host接到请求后做DNAT,替换为容器IP与端口,host上的网络设备知道容器与veth pair的关系,将流量转发到veth-pair上,进入docker容器内。
- 容器内部请求到达外部,访问外界提供服务的ext_ip:port,容器发起的包中,IP为容器IP,端口为系统分配的一个端口。经过veth-pair到达网桥时,做数据转发,转发到物理网卡。物理网卡做SNAT,替换为host IP,端口不变。
host
host模式下,容器的网络与宿主机共享一个namespace。影响隔离性的方式,虽然没有NAT更加快。
container
与另一个容器共享一个namespace,容器间的通信可以通过localhost实现。kubernetes的pod就采用这种模式
none
创建一个namespace,里面只有一个loopback接口,需要用户自己定义网络。
libcontainer
docker源码中的sandbox, endpoint等概念可能比较confusing。docker抽象了libcontainer来管理容器,这些相关的概念都可以在网上找到资料。
- https://www.cnblogs.com/YaoDD/p/6386166.html这篇文章介绍了libcontainer的模型
源代码里的容器网络
在创建容器时,daemon会为容器准备好网络环境,参考之前的分析,初始化容器网络主要在start启动容器的initializeNetworking()
中完成。
- 判断network mode,容器是否使用了container network stack,即是否和其他容器共享一个network namespace,如果是,则:
-
getNetworkedContainer()
获取共享的容器的nc -
initializeNetworkingPaths()
将hostnamepath, hostpath, resolconfpath和nc设为一样 - hostname和domainname设为一样
-
- 判断是否是host模式,如果是:
- hostname设为
os.HostName()
- hostname设为
- 剩下的模式有:bridge、none,统一进入
allocateNetwork()
- 最后创建hosts, hostname等文件
func (daemon *Daemon) initializeNetworking(container *container.Container) error {
var err error
if container.HostConfig.NetworkMode.IsContainer() {
// we need to get the hosts files from the container to join
nc, err := daemon.getNetworkedContainer(container.ID, container.HostConfig.NetworkMode.ConnectedContainer())
if err != nil {
return err
}
err = daemon.initializeNetworkingPaths(container, nc)
if err != nil {
return err
}
container.Config.Hostname = nc.Config.Hostname
container.Config.Domainname = nc.Config.Domainname
return nil
}
if container.HostConfig.NetworkMode.IsHost() {
if container.Config.Hostname == "" {
container.Config.Hostname, err = os.Hostname()
if err != nil {
return err
}
}
}
if err := daemon.allocateNetwork(container); err != nil {
return err
}
return container.BuildHostnameFile()
}
container和host模式都非常简单,只需要共享已经存在的网络空间即可,而bridge模式需要新建一个网络空间:
- 获取netController,这是在daemon初始化的时候创建的模块。
- 由于daemon之前的错误退出,该容器可能存在上次操作的sandbox,首先要
SandboxDestroy()
清理历史遗留问题 - 如果用户选择了none模式或container模式,直接返回。这里的逻辑看起来是重构没有做完全,有些冗余。
- 现在只剩下bridge模式一种了,调用
cleanOperationalData()
,将endpoint ID, gateway, IP, MAC等都设为空 -
connectToNetwork()
连接到用户指定或者默认的网络中- 在验证参数合法后,
findAndAttachNetwork(container, idOrName, endpointConfig)
-
getNetworkID()
获取要加入的网络的ID或name -
FindNetwork()
根据ID/name获取网络n - 分配IP,配置到epConfig中
- 与swarm相关的内容(先不看)
-
- 在attach network中已经配置好了IP,所以可以填充endpointConfig.IPAMConfig和endpointConfig.NetworkID,配置容器的这张网卡的IP和在哪个网络里
-
updateNetworkConfig()
做了一些trivial的网络配置 -
getNetworkSandbox()
在加入网络时,可能容器的sandbox已经存在,比如create之后network connect了,再start,这时候sandbox已经建立了,所以需要先尝试获取sandbox -
buildCreateEndpointOptions()
配置即将创建的endpoint,包括IP,name,protocol,port,MAC、DNS等等,还要从sandbox中获取端口映射表或者创建映射表。 -
CreateEndpoint()
调用libnetwork,创建虚拟网卡。之后updateEndpointNetworkSettings()
- 如果sandbox不存在,需要创建一个,则
buildSandboxOptions()
之后NewSandbox()
并updateSandboxNetworkSettings()
- 底层调用了namespace_linux.go中的
NewSandbox()
函数,创建一个network namespace。所以所谓sandbox在linux下的实现就是network namespace
- 底层调用了namespace_linux.go中的
- sandbox和endpoint都就绪,接下来调用
Join()
把ep加入到sandbox中。 -
ActivateContainerServiceBinding()
服务发现功能,将容器名加入到DNS中 -
LogNetworkEventWithAttributes()
加入日志监控
- 在验证参数合法后,
func (daemon *Daemon) allocateNetwork(container *container.Container) error {
start := time.Now()
controller := daemon.netController
if daemon.netController == nil {
return nil
}
// Cleanup any stale sandbox left over due to ungraceful daemon shutdown
if err := controller.SandboxDestroy(container.ID); err != nil {
logrus.Errorf("failed to cleanup up stale network sandbox for container %s", container.ID)
}
if container.Config.NetworkDisabled || container.HostConfig.NetworkMode.IsContainer() {
return nil
}
updateSettings := false
if len(container.NetworkSettings.Networks) == 0 {
daemon.updateContainerNetworkSettings(container, nil)
updateSettings = true
}
// always connect default network first since only default
// network mode support link and we need do some setting
// on sandbox initialize for link, but the sandbox only be initialized
// on first network connecting.
defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
if nConf, ok := container.NetworkSettings.Networks[defaultNetName]; ok {
cleanOperationalData(nConf)
if err := daemon.connectToNetwork(container, defaultNetName, nConf.EndpointSettings, updateSettings); err != nil {
return err
}
}
// the intermediate map is necessary because "connectToNetwork" modifies "container.NetworkSettings.Networks"
networks := make(map[string]*network.EndpointSettings)
for n, epConf := range container.NetworkSettings.Networks {
if n == defaultNetName {
continue
}
networks[n] = epConf
}
for netName, epConf := range networks {
cleanOperationalData(epConf)
if err := daemon.connectToNetwork(container, netName, epConf.EndpointSettings, updateSettings); err != nil {
return err
}
}
// If the container is not to be connected to any network,
// create its network sandbox now if not present
if len(networks) == 0 {
if nil == daemon.getNetworkSandbox(container) {
options, err := daemon.buildSandboxOptions(container)
if err != nil {
return err
}
sb, err := daemon.netController.NewSandbox(container.ID, options...)
if err != nil {
return err
}
updateSandboxNetworkSettings(container, sb)
defer func() {
if err != nil {
sb.Delete()
}
}()
}
}
if _, err := container.WriteHostConfig(); err != nil {
return err
}
networkActions.WithValues("allocate").UpdateSince(start)
return nil
}
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
start := time.Now()
if container.HostConfig.NetworkMode.IsContainer() {
return runconfig.ErrConflictSharedNetwork
}
if containertypes.NetworkMode(idOrName).IsBridge() &&
daemon.configStore.DisableBridge {
container.Config.NetworkDisabled = true
return nil
}
if endpointConfig == nil {
endpointConfig = &networktypes.EndpointSettings{}
}
n, config, err := daemon.findAndAttachNetwork(container, idOrName, endpointConfig)
if err != nil {
return err
}
if n == nil {
return nil
}
var operIPAM bool
if config != nil {
if epConfig, ok := config.EndpointsConfig[n.Name()]; ok {
if endpointConfig.IPAMConfig == nil ||
(endpointConfig.IPAMConfig.IPv4Address == "" &&
endpointConfig.IPAMConfig.IPv6Address == "" &&
len(endpointConfig.IPAMConfig.LinkLocalIPs) == 0) {
operIPAM = true
}
// copy IPAMConfig and NetworkID from epConfig via AttachNetwork
endpointConfig.IPAMConfig = epConfig.IPAMConfig
endpointConfig.NetworkID = epConfig.NetworkID
}
}
err = daemon.updateNetworkConfig(container, n, endpointConfig, updateSettings)
if err != nil {
return err
}
controller := daemon.netController
sb := daemon.getNetworkSandbox(container)
createOptions, err := buildCreateEndpointOptions(container, n, endpointConfig, sb, daemon.configStore.DNS)
if err != nil {
return err
}
endpointName := strings.TrimPrefix(container.Name, "/")
ep, err := n.CreateEndpoint(endpointName, createOptions...)
if err != nil {
return err
}
defer func() {
if err != nil {
if e := ep.Delete(false); e != nil {
logrus.Warnf("Could not rollback container connection to network %s", idOrName)
}
}
}()
container.NetworkSettings.Networks[n.Name()] = &network.EndpointSettings{
EndpointSettings: endpointConfig,
IPAMOperational: operIPAM,
}
if _, ok := container.NetworkSettings.Networks[n.ID()]; ok {
delete(container.NetworkSettings.Networks, n.ID())
}
if err := daemon.updateEndpointNetworkSettings(container, n, ep); err != nil {
return err
}
if sb == nil {
options, err := daemon.buildSandboxOptions(container)
if err != nil {
return err
}
sb, err = controller.NewSandbox(container.ID, options...)
if err != nil {
return err
}
updateSandboxNetworkSettings(container, sb)
}
joinOptions, err := buildJoinOptions(container.NetworkSettings, n)
if err != nil {
return err
}
if err := ep.Join(sb, joinOptions...); err != nil {
return err
}
if !container.Managed {
// add container name/alias to DNS
if err := daemon.ActivateContainerServiceBinding(container.Name); err != nil {
return fmt.Errorf("Activate container service binding for %s failed: %v", container.Name, err)
}
}
if err := updateJoinInfo(container.NetworkSettings, n, ep); err != nil {
return fmt.Errorf("Updating join info failed: %v", err)
}
container.NetworkSettings.Ports = getPortMapInfo(sb)
daemon.LogNetworkEventWithAttributes(n, "connect", map[string]string{"container": container.ID})
networkActions.WithValues("connect").UpdateSince(start)
return nil
}
至此,docker容器的网络部分初始化完毕。
网友评论