spec|KubeDL HostNetwork:加速分布式训练通信效率( 二 )


使能 Host 高性能网络 标准容器网络拓扑
在标准的容器网络通信模型中 , Master/Worker/PS 等不同 Workload 角色之间通过 Headless Service 实现服务发现 , Pod 之间通过恒定的域名相互通信 , 由 CoreDNS 实现域名到 Pod IP 的解析 , 由于 Pod 是可以漂移的但 Service 及其附属的域名是恒定的 , 即使部分 Pod 运行时异常了也能很好地实现 FailOver , 在异常 Pod 重新拉起之后与其他 Pod 重连接 。
apiVersion: training.kubedl.io/v1alpha1kind: \"TFJob\"metadata: name: \"mnist\" namespace: kubedlspec: cleanPodPolicy: None tfReplicaSpecs: PS: replicas: 2 restartPolicy: Never template: spec: containers: - name: tensorflow image: kubedl/tf-mnist-with-summaries:1.0 command: - \"python\" - \"/var/tf_mnist/mnist_with_summaries.py\" - \"--log_dir=/train/logs\" - \"--learning_rate=0.01\" - \"--batch_size=150\" volumeMounts: - mountPath: \"/train\" name: \"training\" resources: limits: cpu: 2048m memory: 2Gi requests: cpu: 1024m memory: 1Gi volumes: - name: \"training\" hostPath: path: /tmp/data type: DirectoryOrCreate Worker: replicas: 3 restartPolicy: ExitCode template: spec: containers: - name: tensorflow image: kubedl/tf-mnist-with-summaries:1.0 command: - \"python\" - \"/var/tf_mnist/mnist_with_summaries.py\" - \"--log_dir=/train/logs\" - \"--learning_rate=0.01\" - \"--batch_size=150\" volumeMounts: - mountPath: \"/train\" name: \"training\" resources: limits: cpu: 2048m memory: 2Gi requests: cpu: 1024m memory: 1Gi volumes: - name: \"training\" hostPath: path: /tmp/data type: DirectoryOrCreate 以一个经典 PS-Worker 架构的 Tensorflow 分布式训练作业为例 , Worker 负责计算参数的梯度 , 由 PS 负责聚合、更新并广播参数 , 因此每个 PS 都可能和所有 Worker 建立连接并通信 , 反之亦是 。
【spec|KubeDL HostNetwork:加速分布式训练通信效率】在 Tensorflow 框架的实现中 , 这样一个作业间拓扑结构由一个 TF Cluster Spec 结构来描述 , 每个 Role(PS or Worker)实例都包含一个 Index 标识自身索引号 , 可以通过Role+Index 获取自身或其他Role实例的服务地址 , 即可建立连接开始通信 。 在标准容器网络模式中 , 用户提交以下 TFJob , KubeDL 会生成 TF Cluster Spec 并以环境变量的形式传入并被框架接收 , 同时为每个 Role 实例都准备好 Headless Service , 它的 Endpoint 域名地址即对应 TF Cluster Spec 中的服务地址 , 每个 Pod 都拥有一份独立的 Linux Network Namespace , Pod 的端口地址空间也相互隔离 , 因此调度到相同的 Node 上也可以使用相同的容器端口 。
至此不同 Role 的实例间就能通过 Tensorflow 原生的方式开始分布式训练及通信 。

标准容器网络的好处显而易见 , 简单直观的网络设置 , FailOver 友好的网络容错 , 都使得这一方案能够满足大多数场景下的需求 。 但对高性能网络有诉求的场景下又该如何运转呢?KubeDL 给出了主机网络的解决方案 。
Host 容器网络拓扑
沿用以上的例子 , 启用主机网络的方式很简单 , 只要给 TFJob 追加一个 annotation 即可 , 其余的作业配置都无需特殊改造 , 如下所示:
apiVersion: training.kubedl.io/v1alpha1kind: \"TFJob\"metadata: name: \"mnist\" namespace: kubedl annotations: kubedl.io/network-mode: hostspec: cleanPodPolicy: None tfReplicaSpecs: PS: ... Worker: ... 当 KubeDL 发现该作业声明了使用主机网络后 , 会通过以下步骤完成网络的连接设置:
创建 Pod 时不再使用固定端口 , 而是在一定端口范围内随机出一个主机端口 , 并设置对应暴露的容器端口号 , 通过上下文的方式传递到后续的控制流中; 对 Pod 启用 HostNetwork 并设置 DNS 解析策略为 Host 优先; 不再创建 Headless Service , 取而代之的是一个正常的流量转发 Service , 暴露端口为原先的恒定值 , 目标端口为 Pod 的真实值; 生成的 TF Cluster Spec 中 , 自身对应的 Role+Index 可见 Local 地址端口为真实的主机端口 , 其他 Role 实例的地址端口都是恒定的 , 无论对方的 Pod 如何漂移都能通过 Service 正确转发; 当发生 FailOver 时 , KubeDL 会为重建后的 Pod 重新选择端口 , 新启动的 Pod 会通过 TF_CONFIG 得到新的 Local 地址端口 , 同时 KubeDL 保证对应 Service 的目标端口得到正确更新 , 其他与之相连的 Role 也能在 Service 目标端口更新后继续通信; 这样一个根据训练作业拓扑结构搭建的主机网络就准备换好了 , 与之前的不同之处在于 , 所有的 Pod 都与主机共用了一个 Network Namespace , 因此也共享了主机的端口号 , 而 Pod 之间的通信也从原先通过解析域名为 Pod IP 并建立连接 , 变成了通过 Service 实现流量的转发 , 另一方面 TF Cluster Spec 发生了变化但没有改变原生 Tensorflow 的模式 , 当前 Pod 直接获得 Local Port 监听 , 而其他的 Pod 地址看起来都是恒定的 Service 对应的域名和暴露的端口永远恒定 , 只有目标端口可能随着 FailOver 不断改变 , 这一切都通过 KubeDL 处理变得无感 。