5. docker container——容器网络

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容器的网络部分初始化完毕。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • docker之容器通信 这节属于了解学习,算是烂尾,最后我也没找到合适的方式去固定容器ip,然后作为正式环境去跑,...
    道无虚阅读 5,475评论 1 7
  • 概述 自从docker容器出现以来,容器的网络通信就一直是大家关注的焦点,也是生产环境的迫切需求。而容器的网络通信...
    糙老爷们儿吃什么樱桃阅读 3,618评论 1 5
  • 转载自 http://blog.opskumu.com/docker.html 一、Docker 简介 Docke...
    极客圈阅读 10,487评论 0 120
  • 问题 我的开发环境,主机是windows7,虚拟机是ubuntu16,VirtualBox5.2。今天发现不能再虚...
    凡城长路阅读 3,042评论 2 0
  • 自己的视力一直在下降.虽然没有想象到需要戴眼镜的程度.但自己也深切地感受到,但是仅仅给自己带来的种种不便.基于这个...
    董鱼阅读 391评论 2 0