树莓派(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 的方式进行日常的变更管理,这种方式对于线下的实施同事来说非常的不友好,其灵活性以及方便性远低于目前的方案,遂未在团队内推广使用。上面介绍的系统是目前在线的稳定系统,使用了一段时间整体反馈还不错,后续会优化版本控制以及更加自动的回滚等操作。

通过 noVNC 实现数千台自助机的实时可视化

背景很简单,目前我们运营维护着北京市属三甲医院数千台的自助机,这里面包含挂号取号机、检查报告机以及其他若干衍生出来的自助机种类,数量已经超过一千多台,不同的院区通过密密麻麻的专线跟我们的机房源源不断的进行着各种协议的数据传输与交互。

由于各方面技术以及非技术层面的限制,只能使用微软的系统,为了管理这部分的机器,一方面我们通过 saltstack 来进行日常的产品升级以及变更,对于桌面的可视,比如我们需要知道当前这台自助机前患者的操作,就需要 VNC 来帮助了。

一年前自助机刚上线时候,没有很详细的考虑过自助机的大规模运维问题,当时每台自助机都在 Clonezilla 打包阶段打包进了 realVNC,通过一台 Win 跳板机的 realVNC 客户端去连接,realVNC 有书签的功能,所以当时需要查看每台自助机界面的运行情况相对比较方便,随着数量的增加,这种先登录一台 win 的跳板机,在使用 realVNC 书签查看的传统 CS 架构就捉襟见肘了。迁移到 web 上势在必行,本着可小动不大动的原则,调研了 realVNC 的各种 SDK,遗憾的是都不支持,加上 realVNC
有版权问题,放弃。相比之下,TightVNC 则不存在这类问题。

后来我们组的同学调研了几个基于 web 的 VNC 客户端,peer-vncnoVNCguacamole ,peer-vnc 活跃量太低,底层用的是 noVNC,guacamole 还处在孵化期(功能相当强大)并且比较重,放弃。最终选择在 noVNC 的基础上做一些工作。

由于 TightVNC 默认不支持 WebSockets, noVNC 提供了 websockify 这个工具来做 TCP socket 的代理,接受 WebSockets 的握手,转化解析成 TCP socket 流量,然后在 CS 两端传递。由于我们涉及到较多的机器信息需要维护管理,我们将所有的机器信息,比如自助机的编码,机器名,IP/MAC 相关的信息预先存储到 MySQL 里面,接着按照 token: host:port 既定格式生成每家院区的连接信息配置文件,这一步可以在 jenkins/rundeck 上建个 job 方便每次增删改查。接着就可以启动了转发了。

在浏览器中打开本地的 6080 端口即可。下图是集成到我们内部管理平台上的截图,可以快速定位到自助机的健康状况并通过 VNC 进入确诊当前的状态:

noVNC 默认情况下会以交互式的方式连接,在这个过程中会做身份权限校验(账号连接、读写控制),是否是 true color 等,这个对于生产不是很适用,我们后来将授权这块做在 Django 上,结合 LDAP 做登录认证。考虑到专线带宽的限制,默认关闭了 true color 开启了压缩。VNC 对带宽的消耗还是比较厉害的,平均下来,每开一个新链接,会消耗 1Mbps 左右的带宽,所以如果需要做实时的展示大屏,需要考虑这块的瓶颈。

下图是 noVNC 实时图像,由此我们可以看到当前这台自助机的实时状态并对问题的自助机采取一定的操作:

最后将其挂在 haproxy/Nginx 后面,Nginx 1.3 之后支持 websocket,一个快速搭建并可投入使用的自助机可视化平台就算完成了。该平台我们内部命名为 bangkok。

以上思想可以移植到 *nix 平台上,目前不少主流的云 OpenStack/OpenNebula/Digital Ocean 也集成进了 noVNC。

大陆地区的 DNS 劫持

撇开方院士的那个不谈,这种方式在大陆的流行情况我仅仅是听说,收到过我们客服的反馈而已,至于这个具体有多么的「生动」,我还真没遇到过(正常情况下,如果你的所有的流量都走 proxy,是不会遇到那种浏览器右下角弹广告的情况的,所以建议有能力的用户不管在哪里访问网络,都请主动走 proxy)。
直到我们开始启用了某个第三方的服务监控,才发现这个比例还不小,数据还是蛮有意思的,我找了两个列子,基本上一个 nslook 就能看出问题所在了。


这事要根本解决没强大的背景搞不定啊。

另外,这事对业务有没有影响,肯定是有的,但是有多大,还是要业务,比如面向最终用户的,一个访问了就是投诉过来了,但是如果是类似我们的这类看总体指标的业务,其实目前来看只要不是一个地区的大规模的劫持,还是能容忍的。

HTTP 资源的同步

breed 为 yum, deb, rsync 的都很好做,直接通过 cobbler 内置的 repo 同步很快就可以完成,即使没有 cobbler,一个脚本也能搞定,比如我们线上 Ubuntu 12.04 的就是通过 debmirror 的方式同步完成的。
但是对于 HTTP 的方式,我找了一圈没有找到很好的方式,最初我是使用的 wget,发现效率太低,后来有推友推荐使用 lftp,使用之后发现果真是半个神器,虽然达不到 rsync 那样的智能的增量,但是对于更新的不是那么频繁的资源的同步也基本能够满足要求了。
最初是写了一个 expect 脚本上去跑,后来发现还有 non-interactive 的方式,-c/-f 都可以,放到 cron 里面确实很方便:
$ cat  lftp.txt
open http://maven.twttr.com
mirror -c -n  –parallel=10 ./ ~/resources/maven
quit

$ lftp -f lftp.txt

除了上面这个问题还有个几年内无法突破的问题,方院士,自己的 PC 遇到,服务器自然也会遇到,同步到本地一是出于上面的考虑,另外一点还是为了节省带宽以及其他的机器资源消耗。目前专门有一台 VPS 供我们线上使用,在线上搞了一个 proxy,实现的方式太多了(shadowsocks + polipo),具体的就不说了,提供 http, https 的方式,基本能满足目前的需求了。