阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Docker附加参数导致的网络服务异常实录

88次阅读
没有评论

共计 6165 个字符,预计需要花费 16 分钟才能阅读完成。

1. 事件
背景

事发在线上环境,OpenStack 由容器化部署方案 Kolla 提供,各主要软件版本如下:

Docker 附加参数导致的网络服务异常实录

时间线

2017/3/17:测试完成修复方案,上线更新;

2017/3/15:找到 root cause,确定触发场景,确定修复方案;

2017/3/14:找到 neutron 产生无限多 tap 的场景 2,能够在模拟条件下复现问题,与环境日志记录匹配;

2017/3/13:找到 neutron 产生无限多 tap 的场景 1,能够在模拟条件下复现问题,但对比环境日志并不完全匹配,而且模拟条件的出现概率非常低,排除;

……

2017/3/11

09:00:00:在控制节点恢复网络服务(neutron-metadata-agent、neutron-dhcp-agent);

08:00:00:自动化脚本清理完成所有无效 tap 设备,控制节点成功启动 neutron-*-agent,虚拟网络服务能力恢复;

……

2017/3/10

22:00:00:运行脚本自动化清理控制节点的无效 tap 设备

21:30:00:原控制节点所属(6 台)虚拟机 恢复访问 在其他节点(nc05、nc03)恢复原控制节点的虚拟机

21:00:00:环境更新包 功能还原 在其他节点(nc05)成功启动 neutron-metadata-agent

20:30:00:整体 环境新建 虚机 分配网络还原 在其他节点(nc05)成功启动 neutron-dhcp-agent

18:30:00:放弃在控制节点还原网络服务,尝试在其他节点还原网络服务;

18:00:00:尝试删除 tap 设备,但进度较慢;尝试在代码去掉 neutron-openvsiwtch-agent 中关于 tap 设备的预读过程,因涉及点太多放弃在线修改;

17:00:00:发现控制节点存在 10000+tap 设备,link 状态为 DOWN(导致 neutorn-openvswitch-agent 启动失败);

16:40:00:整体 环境新建 虚拟机 分配网络 失效,处理人忙乱之中再次重启 neutron-dhcp-agent,发现循环错误,neutron-dhcp-agent 重启失败;

16:30:00:控制节点所属(6 台)虚拟机网络失效 怀疑是控制节点流表未刷新原因,开发同事尝试重启控制节点的 neutron-openvsiwtch-agent 刷新,但重启失败;

16:25:00:开发 开始定位 处理 开发同事检查虚拟机创建成功,但新建虚拟机无法获取 neutron-metadata 服务接口,导致业务集群配置失败;

16:10:00:线上 环境更新包 功能 失效 项目维护同事反映培训环境更新业务应用集群失效;

2. 分析
2.1. 不正常的循环

Docker 附加参数导致的网络服务异常实录

经过内部环境反复测试与现场日志对比,发现实际产生 10000 多个 tap 设备的循环位于 neutron-dhcp-agent 服务的定时同步功能函数中

下面这段整体逻辑由于一个设置 namespace 的异常而不断重试循环:

Docker 附加参数导致的网络服务异常实录

1) Neutron-dhcp-agent 循环监听是否存在更新需求(need_resunc_reasons);

2) 每次循环延迟间隔 conf.resync_interval 秒;

3) setup_dhcp_port()方法中申请一个新的 Port(新的 tapid 产生);

4) add_veth()方法创建 veth 设备(新的 tap 设备产生);

5) ensure_namespace()方法中确认 namespace,如不存在则创建;

6) set_netns()方法设置 tap 设备的 network namespace,设置失败;

7) 跳转到第一步循环;

可以看到,这段逻辑本身有一定缺陷

1) 设置 namespace 失败后,没有正确的 try…catch 流程删除之前创建的设备,导致失败的 tap 设备积累越来越多

2) ensure_namespace()方法只检查 namespace 是否存在,没有深入检查 namespace 权限等可能导致后续设置失败的属性

接下来的问题是(可能也是 neutron 在最后一步不设防的原因):namespace 是 neutron-dhcp-agent 进程自身创建的,tap 设备也是 neutron-dhcp-agent 进程自身创建的,为什么设置时会失败呢?

2.2. 为什么没有权限

容器服务的隔离与共享

Docker 附加参数导致的网络服务异常实录

Docker 附加参数导致的网络服务异常实录

其中各容器共享主机的 Network namespace,但每个容器具备非共享的 Mount namespace;在各自独立的 Mount namespace 中,共享主机 /run/netns 目录,用于共享虚拟网络的 network namespace 操作入口。

Mount

而在 Docker 实现中,(共享)使用外部存储空间、数据卷功能都最终会依赖 mount 系统调用,代码片段:

Docker 对附加传入的 private、shared 等不同属性的处理,实际对应执行 mount 系统调用时传入不同 flags,不同的 flags 对应到不同的 Mount Propagation Type:

MS_SHARED

This mount point shares mount and unmount events with other mount points that are members of its“peer group”. When a mount point is added or removed under this mount point, this change will propagate to the peer group, so that the mount or unmount will also take place under each of the peer mount points. Propagation also occurs in the reverse direction, so that mount and unmount events on a peer mount will also propagate to this mount point.

MS_PRIVATE

This is the converse of a shared mount point. The mount point does not propagate events to any peers, and does not receive propagation events from any peers.

MS_SLAVE

This propagation type sits midway between shared and private. A slave mount has a master—a shared peer group whose members propagate mount and unmount events to the slave mount. However, the slave mount does not propagate events to the master peer group.

MS_UNBINDABLE

This mount point is unbindable. Like a private mount point, this mount point does not propagate events to or from peers. In addition, this mount point can’t be the source for a bind mount operation.

找到原因

由于启动容器时对 /run 目录没有使用 MS_SHARED 传播类型,容器重启后,之前创建的 namespace 文件会因 /run/netns 产生的 `peer group` 内其他 peer 的引用计数不能正常删除(Device or resource busy)

umount(“/run/netns/ns1”, MNT_DETACH) = -1 EINVAL (Invalid argument)

unlink(“/run/netns/ns1”)            = -1 EBUSY (Device or resource busy)

删除失败带来的后续结果是,由于文件系统层的 namespace 文件没有完全删除,而实际的 networknamespace 已经释放,所以这个“半删除”的 namespace 从用户态程序的角度就呈现出这样的状态:能够查看到 namespace,对应 2.1 第 5)步中 ensure_namespace()操作正常,但 set 操作时会失败,对应 2.1 第 6)步 set_ns()操作失败

setns(4, 1073741824)                    = -1 EINVAL (Invalid argument)

write(2,“seting the network namespace /”ns”…, 60seting the network namespace“ns1”failed: Invalid argument

解决方法

修改 neutron-*-agent 容器的启动参数:

-v /run/netns:/run/netns:shared  -v /run:/run:rw

+ -v /run/netns:/run/netns:shared  -v /run:/run:rw:shared

2.3. 确定触发场景

模拟再现故障

1) 创建网络和子网

neutron net-create –shared –provider:network_type vlan –provider:physical_network physnet1 –provider:segmentation_id 108 vlan108

neutron subnet-create –name subnet108 vlan108 192.168.0.0/24

2) 重启 neutron_dhcp_agent 容器

docker restart neutron_dhcp_agent

这时 neutron_dhcp_agent 所创建的 namespace 已被 docker daemon 和其他容器引用,权限已转移不允许 neutron_dhcp_agent 删除

3) 删除该网络的子网

neutron subnet-delete subnet108

neutron 删除子网成功,但后台实际删除 namespace 失败,而且 namespace 命名空间已释放,但文件系统接口任然存在,处于“半删除”状态,直接删除网络不会触发后续异常。

4) 为该网络重新创建子网

neutron subnet-create –name subnet108 vlan108 172.16.0.0/24

neutron 创建子网成功,这时后台应当重新创建 namespace,但由于上一步删除 namespace 动作失败导致 namespace 非正常残留,所以这里跳过创建 namespace 动作,接下来为 tap 设备设置 namespace 的动作失败,开始进入 2.1 描述的循环状态。

用 Docker 模拟局部故障

1) 启动两个容器共享可操作 network namespace 文件系统

docker run -d –name testa -it –v /run:/run:rw -v /run/netns:/run/netns:shared –privileged –net=host nova-compute:latest bash

docker run -d –name testb -it –v /run:/run:rw -v /run/netns:/run/netns:shared –privileged –net=host nova-compute:latest bash

2) 在容器 A 中创建 namespace ns1

docker exec -u root testa ip netns add ns1

3) 重启容器 A

docker restart testa

4) 使用容器 A 删除之前创建的 namespace

docker exec -u root testa ip netns del ns1

Cannot remove namespace file“/var/run/netns/ns1”: Device or resource busy

5) 使用容器 A 设置 namespace

docker exec -u root testa ip netns exec ns1 ip a

seting the network namespace“ns1”failed: Invalid argument

3. 小结
3.1. 经验总结

Mount 陷阱

每个 Mount namespace 有自己独立的文件系统视图,但是这种隔离性同时也带来一些问题:比如,当系统加载一块新的磁盘时,在最初的实现中每个 namespace 必须单独挂载磁盘。为此内核在 2.6.15 引入了shared subtrees feature:“The key benefit of shared subtrees is to allow automatic, controlled propagation of mount and unmount events between namespaces. This means, for example, that mounting an optical disk in one mount namespace can trigger a mount of that disk in all other namespaces.”每个挂载点都会标记 Propagation type,用于决定在当前挂载点下创建 / 删除(子)挂载点时,是否传播到别的挂载点。功能同样带来潜在的复杂,如 2.2 描述的权限传播转移出乎使用者的预料。

目前容器技术的存储管理和使用最终都依赖 Mount 系统调用,后续的使用场景需注意。

应该温柔的重试

如果 neutron 的重试机制“聪明”一点,就不会累计产生越来越多的 tap 设备,也就不会造成实际用户可见的网络异常。更好的方式是采用随机化、指数型递增的重试周期,有时候系统出现的一个小故障可能会导致重试请求同时出现,这些请求可能会逐渐放大故障。在不同场景应该考虑限制某个请求的重试次数或者进程整体在单位时间内的重试配额。

谨慎对待重启

我们常使用重启服务“快刀斩乱麻”,解决一般性问题,但从这件事的处理经过来看,两次重启服务使问题影响范围不断扩大,而且在其他很多场景下,重启服务时程序会重新读取外部资源,也常常会暴露出很多已经潜在、但尚未产生影响的问题。所以,在生产环境应该避免草率重启,而且还应在服务可中断时间 有计划 的演练重启,以便提前发现、解决未来被动重启时才会暴露的问题。

容器化的利与弊

这次事故由容器启动参数的不正确使用引起,但同样因为容器利于部署应用的特性,现场较快的在其他节点部署恢复了网络服务,减少了业务中断时间。

3.2. 事件还原

Docker 附加参数导致的网络服务异常实录

附录参考:

Docker 基础技术——Linux Namespace http://coolshell.cn/articles/17010.html

Mount namespace and mount propagation http://hustcat.github.io/mount-namespace-and-mount-propagation/

Shared Subtrees https://lwn.net/Articles/159077/

Mount namespaces and shared trees https://lwn.net/Articles/689856/

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-24发表,共计6165字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中