美文网首页
5. docker container——容器网络

5. docker container——容器网络

作者: lovenashbest | 来源:发表于2018-07-31 18:46 被阅读0次

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来管理容器,这些相关的概念都可以在网上找到资料。

源代码里的容器网络

在创建容器时,daemon会为容器准备好网络环境,参考之前的分析,初始化容器网络主要在start启动容器的initializeNetworking()中完成。

  • 判断network mode,容器是否使用了container network stack,即是否和其他容器共享一个network namespace,如果是,则:
    • getNetworkedContainer()获取共享的容器的nc
    • initializeNetworkingPaths()将hostnamepath, hostpath, resolconfpath和nc设为一样
    • hostname和domainname设为一样
  • 判断是否是host模式,如果是:
    • hostname设为os.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
    • 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容器的网络部分初始化完毕。

相关文章

网友评论

      本文标题:5. docker container——容器网络

      本文链接:https://www.haomeiwen.com/subject/hlfhvftx.html