一张复杂网络的生长过程

虽然不是网络出身,但是对于我们全网架构的「生长」过程还是比较了解的,分几个重要的时间点讲讲里面有意思的事情。

15 年下旬

公司业务还没起飞,全网架构简单,应付一天 1k+ 的订单了(有效挂号单)绰绰有余,机房跟第三方的卡管机构通过一根 10M 的 SDH 专线打通,所有到院方的请求先经过第三方的卡管机构,由其转发再进行业务层面的处理,实际的带宽峰值不到 500kbps。

16 年初

正式开始批量接入三甲医院,同时跟医院数据打通的方式也由原先的经过第三方卡管机构的非直连方式变成了直连方式,所有接入的院区通过两根双活的专线跟我们机房打通,两根专线的意义很重大,一旦一根专线被挖掘机挖断,可以在 1s 之内切换到另外一根,上层业务通过重试机制可以做到几乎无感知。新闻上报道的某某地区由于光纤被挖掘机挖断造成网络中断的问题在后来专线数量增加的情况下时有发生,但是到目前为止对于这种双主的架构并无影响,考虑到成本等因素,暂时不会做 N + 2。

为了控制 IP 地址的分配以及部分安全性的考虑,跟院方接入全部采用双向 nat 的方式,双向 nat 的问题之前的博客有过介绍,只不过之前是通过 iptables 实现,本次通过专业的路由设备实现。经过双向的 IP 地址转后,我们对院方的地址全部隐藏,另外由于院内自助机的 IP 全部由我们自行分配,通过双向 nat 之后,我们有更大的自主权。下面这张图演示了 IP 地址转换的过程:

与此同时,为了快速实现新接入医院的稳定上线,保守采用了双防火墙双路由器的策略,通过 BGP/BFD 协议实现单条线路失败的快速收敛,总成本控制在 20K 左右:

医院的数据通过一根 2M 的专线,一根 10M 的专线汇聚到我们的核心机房,核心机房通过主流的三层结构互联。

对于办公网来说,为了实现访问 google 等国际联路,我们通过在办公网的路由器上设立 GRE 隧道的方式连接到国内的专线服务商,再由该服务商进行国际流量的转发,由于跨了国内的公网环境,会出现偶尔的延时增高以及中断的问题。

16 年中

随着接入院区的爆发式增长,成本成了很大的一个问题,原先所有院区接入同一个核心机房的方式不仅成本无法得到控制,单点也成为了一个迫在眉睫的问题。原先三层的简单架构也无法满足日后的水平横向扩展。为了解决上面的问题,花了大量时间跟北京具有运营商资质的公司进行了大量的商务沟通,实现了专线、带宽资源的大幅度下降。在保证稳定性的前提下,2M 的专线做到了平均 450RMB/m,10M 的 1500RMB/m,50M 的 4000RMB/m,100M 的 6000RMB,同时引入了阶梯价格的策略,量大会更加具有竞争力,当然三大运营商的价格会比鹏博士、天地祥云的高些,毕竟稳定性也高的多(这个后来验证确实如此)。最终跟北京三大运营商以及几家准运营商达成了友好的合作,实现了双方的共赢。上面的问题谈妥了,接下来自然引入了下面几个核心思想:
1. 链路类型,优先选用 SDH/MSTP 协议的,G.703 次之。
2. 运营商,成本跟稳定性下的权衡,优先选用电信/电信通,联通/天地祥云次之。
3. 专线接口类型,优先选用双绞线电口,光纤单模次之,方便排查问题,同时默认接入千兆全双工,百兆全双工作为备选。
4. 建立中立的中转机房,所有院区的专线先通过中转机房再进入核心机房,实现了跨区域专线的成本以及稳定性的大幅提升。
5. 在上条的基础上,全网引入骨干网 msr(metropolitan small router)、核心网 csr(core small router)、汇聚网 csw (core switch),专线 dll(dedicated leased line),边界路由器 edr(edge router),边界交换机 esw(edge switch)。优先选择中低端廉价设备,比如 msr 使用 Cisco2921/K9,csw 使用 WS-C3750X-24T-F,esw 使用 WS-2960X-24TS-L 等型号,通过 HSRP/BFD 等协议实现分而治之。

着重说下最后的两点,对于第 4 点,其架构图如下:

可以看到,这一阶段引入了 msr/csw 等角色,将运营商的专线接入诸如鹏博士、天地祥云等中立机房的进行中转,最终汇聚到核心的机房。每一台中转机房的 msr 承载 20 条左右的专线,通过上方的 csw 进行线性扩展,目前每条专线的带宽在 2~10M 左右,所以不会给机器造成很大的压力。

对于第 5 点,在核心骨干汇聚分成之后,一方面是实现了多网分离,即生产网、测试网、OOB 网,办公网以及到院方的专线网的分离,极大的方便了 tcp/ip 层面的访问控制,在这个阶段,所有对人的访问控制(人访问线上线下)全部通过在网络设备上加白名单的方式实现,算是实现了基础的访问控制,虽然比较繁琐。另外一方面全网由之前的树根结构扩展到了树根枝干末梢叶子的网状结构,扩展性以及冗余性都有了很大的提高,下面这张图是相对比较成型的网络拓扑:

这个阶段,除了院内、机房的大幅度改造之外,办公网尤其是国际网络也做了优化,从原先的 GRE 隧道方式一步到位成了专线直连方式,办公网通过专线直接到服务商节点,节点直接到第一线的香港节点,同时,为了避免所有流量都走国际网络,我们在内网通过 DNS view 的方式,将主要被墙的域名解析丢给 Google DNS,其余则解析丢给国内 DNS,DNS 层面解析结束之后,TCP/HTTP 的流量则通过在路由层面通过不同的 ACL 规则实现国内国际流量的划分。下图是日常访问 Google 的 ICMP 延时:

16 年底

为了进一步控制成本,响应国产化的号召,原先一家院区 4 台设备(2 台防火墙、2 台路由器)的架构,在保证稳定性不变的情况下,优化成了由 2 台带路由功能的防火墙(USG 6306)组成的专线网络:

在同样秒级切换的情况下,成本降低到了原先的一半不到。同时,考虑到后续接入非三甲院区的特殊性,目前正在调研舍弃大型物理硬件,通过 4G 网卡配套 VPN 的方式来进行网络层面的直连。

在优化成本以及性能的基础上,还进行了下面几点的优化:
1. 通过 PowerDNS 的 API 实现了上千条内网的 DNS 正反向解析,这对于使用 mtr 细颗粒度的排查定位问题提供了很大的帮助
2. QoS 的优化,优先走生产上的业务流量,其次生产上的非核心(日志、监控等),在 msr 上通过 CoPP 实现
3. 全网设备的配置备份问题,所有设备的配置每日自动备份到内网的 gitlab 上
4. 为了实现生产网访问被墙的资源,在办公网搞了两套 tinyproxy,生产的机器通过 http_proxy,访问前端的 Haproxy 实现了网络资源的自由访问
5. 办公网无线的 LDAP 认证,这块在全网统一账户实践有所涉及

从 15 年底到 16 年底的一年多时间,通过不断的演变,一个相对比较完善有很强冗余性的大网基本建设完成,该架构支撑目前每天的 100k 订单(有没有发现一年期间增长了多少倍),40k 的有效挂号量没有任何瓶颈。

在建设初期遇到不少跟院方通信的网络中断问题,起初做贼心虚以为是自身的问题,后来通过监控不断的完善,实现了分钟级别的问题发现与诊断。

下面列一些有趣的问题:
最常见的,某院区由于专线被挖断,单条专线中断,平均一个月一次。院方机房全部或者部分掉电,导致所有专线中断;院方机房制冷设备故障,导致专线设备罢工死机。院方线下自助机网络设备故障,导致该区域自助机全部脱网,类似的事情不一而足。再比如我们的 RPC 服务访问某院区 HIS,凡是下一跳为 10.222.0.45 均能够访问终点 IP,凡是下一跳为 10.222.0.41 则无法访问同样的的终点 IP,经过抓包发现由于院方的两台核心路由故障导致。最喜剧的性的事,某院区偷懒将我们跟 HIS 直连的 IP 划分到他们的核心网络,由于 arp-proxy 的问题导致全院 HIS 宕机数个小时。当然医院 HIS 宕机其实是件很频繁的事,之前仅仅因为处在一个封闭落后并且无知的环境,很多时候重启应用重启机器问题就算是绕过去了,现在一下子暴露在了一支「正规军」面前,一下子露馅了。

树莓派(raspberrypi)、saltstack 在线下自助机运维上的应用

目前每家院区都分布了从数台到近百台规模不等的自助机,覆盖了北京市属 22 家医院的三十多个院区,一千多台的日常变更、升级管理、甚至常人看来很简单的开关机成了摆在眼前的一大问题。下面这篇博客会抛出 4 个问题并且分享下我们线上的实战经验。

1. 开关机,一个看似很幼稚的问题
你去市面上问一个的有点经验的运维,「服务器怎么开关机」,他可能第一反应是,「服务器要关机吗?」如果你接着问,「如果现在就有这么一个需要,需要先关机过段时间再开机,怎么办?」,他很可能会告诉你,「远程卡啊,这不是标配吗,再不行,电话个驻场的帮忙开下呗」。 以上的情况对于机房的服务器是适用的,但是对于自助机,放在院内各个大厅角落,物理环境恶劣,随时断网断电,尘埃飞扬,更别谈恒温空调 UPS 之类的,要实现开关机,真不是一件容易的事情。我们在院内用的自助机,说白了就是一台组装的 PC,仅仅是通过串口并口连接了一些读卡器扫码器等外置设备。既然是消费级别的 PC,就不会有远程卡这么「高端」设备,当然,对于 PC 级别来说,市面上是有跟企业级别 iDRAC/iLO/iMana 类似技术的设备的:Intel 的 AMT,最终的实现效果跟远程卡几乎一样,另外一种能实现开机的方式就是通过 Wake-on-LAN 这种古老的协议。

有开就有关,AMT 能开能关,Wake-on-LAN 就没办法了。但是基本思想很简单,需要在每台机器上安装一个 agent,通过此 agent 来下发关机以及后续的变更操作。

有了上面的思路,我们来对比下 AMT 以及 Wake-on-LAN 这两种方案的优劣,如下图:

对于目前的业务来说,我们最看重的是经济成本以及可扩展性,很明显,Wake-on-LAN 完爆 AMT。

有了大方向,下面是具体的实现,首先是至少有一台常年开机的机器用来唤醒其他的机器,选自助机肯定不行,这些机器本身就会出现白天开机晚上关机的状态,后来想到了树莓派,设备小,可以完全放进一台自助机里面,只要该自助机的电源线不拔,就能保证树莓派的常年开机状态;管理简单,默认使用的 Raspbian 系统,源的同步,账户的管理,监控、变更等等跟我们线上完全打通;经济成本低,一台树莓派配上 SD 卡电源线插座传感器(这个后面会单独提)不到 400。有点需要注意的是,很多情况下院内会有多个 vlan,正常情况下 Wake-on-LAN 不支持跨网段的唤醒,一般来说,放在不同位置的自助机网段会不大一样,加上我们需要监控不同位置的温湿度烟雾的状态,所以最终决定出单家院区 4 台树莓派的标准配置。下图是一套树莓派的配置图:

搞明白了上面的核心问题,接下来要解决的是流程类的问题,每上一个新院区,我们会直接格式化 SD 卡灌装定制的 raspbian 系统,salt 初始化,zabbix/freeipa 注册,实施人员到现场之后插电接网即可。

2. 如何优雅的开关机
上面的话题仅仅覆盖了基本的原理,在我们上线的早期,每个院区仅仅上线了个位数的机器,同时运行的院区在三四个左右。这里需要提到一个背景,每个院区的部分自助机,同一个院区的不同自助机,开关机时间都不同,比如友谊位于门诊大厅的自助机是每天早上 7:00 准时开机对外使用,但是二三楼的自助机则是每天早上 07:10 启用,还有一台位于急诊大厅的机器则是全年 24 小时开机,而对于同仁医院来说,所有的机器都是早上 06:45 开机。

早期,我们通过 saltstack/jenkins/git 在树莓派上维护对应的院区的 crontab 列表来实现自助机的开关机。

另外一个频繁出现的问题是,院方会经常性的提出修改自助机开关机服务时间的需求,每次运营转达给我们再经过 git/jenkins 提交的效率异常的低效。为了彻底的将这部分重复低效的工作从运维手里解放出来,我们为院方开发了一套开放平台(open),除了日常的开发者文档更新、开发接口 API 调用,接口测试用例等之外,一块重要的功能就是为院方开放了自助机服务器时间的自主修改查看权限,这样将日常繁杂的自助机时间管理下移到了院方手里。

目前该平台支持全局、个别自助机的开关机时间:

bangkok 上面的 KioskScheduler 进程定期会向 open 平台同步各个院区所有自助机的开关机时间,将其写入到 MySQL,底层通过 APScheduler 这个任务框架来调度,使用 BackgroundScheduler 调度器,默认的 MemoryJobStore 作业存储。每家院区的规则都分为全局跟单独两种,全局规则针对该院区的所有自助机有效,优先级低;单台自助机规则仅针对单台自助机规则生效,优先级高。KioskScheduler 根据以上优先级的不同,优先执行高优先级的规则。这样「积水潭 2017 年春节期间,除了 10、11 号自助机在每天的 08:00 ~ 16:00 开机之外,其余所有的自助机均关机」的规则就能顺利实现了。

同时,为了确保 KioskScheduler 运行正常,应用层面通过 monit 实现进程的监控,业务层面的规则执行与否以及是否达到预计,我们通过 python-nmap 实现了一个批量扫描的脚本,每次开关机时间点触发后的 5min/10min/15min 三个阶段,对命中规则的自助机进行批量的存活性扫描,对于未达到期望的自助机会触发报警到我们的自助机运维同事那边进行人工处理。

对于开机的方式,部署在院内的 4 台树莓派通过 syndic 机制收到请求后同时向目标机器发起唤醒的请求,根据之前的经验,单台树莓派的唤醒偶尔会出现唤醒失败的情况,另外一点 4 台里面只有保证至少有 1 台存活,那么开机就能成功触发。

3. 如何低成本实现 OOB 的核心功能?
先来看下行业标杆的 Dell 远程卡(OOB)提供的一些核心功能,如下图:

根据我们日常使用频率的统计,80% 以上的操作花在了如下的操作上:

1. 服务器的开关机,尤其是机器 hang 住了/kernel panic 之后的开关机
2. 由于各种原因无法 ssh 之后需要通过虚拟终端进入查看当前机器屏幕的状态
3. 机器主要零部件的硬件状态,健康状况

对于一台自助机来说,其核心是简化后的服务器,没有 raid 等企业功能,除此之外,提供的服务跟服务器并无二异,仅仅是加上了 GUI 而已。对于第一、二两点,上面的话题已经阐述过,比较遗憾的是目前并不能对机器的死机、蓝屏实现脱离系统的开关机重启以及实时屏幕的查看,但是对于日常的开关机,VNC 远程操控,绰绰有余;对于第三点,目前诸如磁盘等底层硬件的监控是放在应用层来监控,对于我们来说,个别自助机硬件层面的宕机并不会影响整个院区的使用,我们及时报修返厂更换即可解决此类问题,众所周知的是,对于绝大多数的医院,其院内的物理环境,比如尘埃浓度(PM10)、温湿度等跟正规的机房比相距甚远,早期,我们发现某院区的部分的自助机经常性的宕机蓝屏,经过若干天系统级别应用级别的排查,都无所发现,后来现场的志愿者提醒我们是不是温度太高导致的,现场勘查后发现,西区的部分自助机直接放在了仅有一顶透明帐篷遮蔽的半室外空间内,6 月市内温度 30 度的情况下,这部分自助机箱体的温度已经接近 40 度了,蓝屏在所难免了。

为了及时发现这类问题,我们后续在所有树莓派上全部引进了温度、湿度以及烟雾的传感器,成本非常低,一个 DHT11 的温湿度传感器加上一个 MQ-2 的烟雾传感器成本在 20RMB 以内,加上现成的 Adafruit_DHT/RPi.GPIO 的库直接调用,完美解决此类问题。通过分散在不同地点的四台树莓派,我们能够推断出当前院内的物理环境,这对改进目前自助机的物理位置有非常重要的意义。

下图是某院区蓝屏故障增大期间周边温度的变化,观察可以发现具备一致性关系:

类似的,我们在院方机房放置的连接专线的路由设备,同样发现多次类似的问题,下图是某院区机房空调设备故障温度升高导致设备丢包增大中断服务的监控:

很多看似简单的问题,一旦场景转变了,需要应对的措施也就随之改变了。

4. 如何有效实现自助机的变更操作
自助机上的变更,主要分为两类,一种是最为频繁的版本升级,包括自助机上挂号缴费程序的版本、第三方依赖的程序升级,这类变更的特点是,每次的程序升级,其升级包都相对较大,小的几兆,大的有上百兆;第二种变更更多的集中在日常的系统变更,比如修改 hosts 以对应不同的 nat 影射、增删改查注册表等系,对于这类变更,最大的特点是占用的带宽小,但是操作频繁。

针对这两种变更,分别设计了两种不完全一样的变更方式,但是底层都是通过 saltstack master/syndic/minion 的架构实现文件的传输。中心端一台 master(存在单点的问题),每家院区 4 台部署了 syndic 的树莓派,同时跑 master/syndic,向中心端注册,底层的自助机部署有 minion 向其中一台 syndic 注册,目前 minion 到 syndic 同样存在单点问题,后续考虑将 syndic 升级成 MultiSyndic。

对于第一种变更方式,我们将要升级的版本库文件打包上传至 git,通过 jenkins 将数据从打包机 git pull 下来之后,在打包机通过 salt 将升级的文件分发到 salt syndic 上,syndic 上会起 http 服务,自助机每次开机的时候会自动的向 http 服务检查是否有最新的版本,如果有的话则会升级,对于回滚,我们将版本号递增内容回滚至上一版本重启机器即可。
对于第二种变更方式,如果是修改注册表之类的变更,我们使用 cmd.run 直接将需要跑的 key 执行结束,类似下面这样:
cmd.run 'reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" /v "{5399-XXX-CBA0}" /t reg_dword /d 1  /f'

如果是传输文件的变更,跟第一种方式类似,不同的是,文件落地到 syndic 之后,我们会直接通过 master/minion 的方式 push 到每台自助机,而不是主动 pull 的方式。同时为了确认文件的完整,每次从 master 到 syndic,从 syndic 到 minion 的两个关键步骤都会做一次 md5 校验,最终实现的效果可以参考早期的 saltpad 版本,下图是 bangkok 中变更的页面展示:

除了最基本的单次变更外,增加了多次执行的执行步骤以及执行步骤的模版功能,前者对于多合一的操作步骤很适用,后者对于经常使用的诸如修改 hosts 文件等很适用。

在这之前,我们尝试直接通过 salt master/minion 的方式进行日常的变更管理,这种方式对于线下的实施同事来说非常的不友好,其灵活性以及方便性远低于目前的方案,遂未在团队内推广使用。上面介绍的系统是目前在线的稳定系统,使用了一段时间整体反馈还不错,后续会优化版本控制以及更加自动的回滚等操作。