分类 网络 下的文章

配置nginx为php-fpm合并多个X-Forwarded-For

最近用阿里云的容器服务遇到一个问题,docker里的PHP程序用X-Forwarded-For取用户IP的时候,只能取到SLB的地址。经tcpdump抓包确认,是请求经过阿里云的acsrouting的时候,在HTTP请求头中加入了多个X-Forwarded-For。查了一下,阿里云用HAProxy做负载均衡服务,又搜了一下HAProxy的资料,发现许多人在用HAProxy时遇到了同类问题,有人给HAProxy提了issue,但对方答复是根据RFC2616,多个同名的header和单个逗号分隔列表构成的header是等价的:

Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.

所以HAProxy不处理已有的X-Forwarded-For,只是简单地在header末尾加入一个新的X-Forwarded-For。可惜php-fpm对header的处理方式是同名的header只保留最后一个,所以无法取得正确的用户IP。

我找到一个解决方案是让nginx来合并多个X-Forwarded-For记录。在docker内的nginx配置中加入以下选项(一般在location ~ \.php$部分或者在fastcgi_params配置文件里):

fastcgi_param  HTTP_X_FORWARDED_FOR $http_x_forwarded_for if_not_empty;

配置好之后docker内的nginx服务器就会预先合并多个X-Forwarded-For请求header记录为逗号分隔格式,然后再传给php-fpm。

SSH端口映射

SSH提供了两种端口映射方式,一种是本地端口映射,另一种是远程端口映射。

使用本地端口映射,SSH客户端会在本地监听一个端口,所有到该端口的连接全部转发到SSH服务器上指定的主机和端口号上。例如,我需要在我自己的电脑上访问VPS服务器上的数据库,但是服务器上的MySQL绑定在了127.0.0.1的IP地址上,可以用下面的命令把我电脑上的33306端口转发到服务器上的3306端口。

ssh xts.so -l root -L 33306:localhost:3306

运行这个命令后,我就可以直接用MySQL客户端连接自己电脑的33306端口访问服务器数据库了。

SSH的-L参数完整格式是-L [bind_address]:port:host:hostport,其中bind_address是本机(客户端)的绑定IP,可以省略,缺省值是127.0.0.1,如果需要允许其它电脑访问,可以监听0.0.0.0或者简单写个*port是本机开始监听的端口,这个端口的连接会转发到服务器上;host是相对于服务器而言的目标主机地址,域名、hostname、IP地址均可;hostport是目标主机的端口号。

3497093050.svg

如果需要在本地内网访问服务器机房内网的数据库,只需要指定binding_addresshost就可以了。

远程端口映射,SSH会在服务器上监听一个端口,所有到该端口的连接全部转发到SSH客户端指定的主机和端口号上。例如,我需要让本机的80端口可以在公网访问,却又不能独立使用公网IP,可以找一台VPS服务器作远程端口映射。

ssh xts.so -l root -R *:8080:localhost:80

运行这个命令后,用浏览器打开http://xts.so:8080/就相当于访问我本地电脑的80端口。

SSH的-R参数完整格式是-R [bind_address:]port:host:hostport,其中bind_address是远程(服务器)的绑定IP,可以省略,缺省值也是127.0.0.1;port是远程服务器上开始监听的端口;host是相对于本地电脑而言的目标主机地址;hostport是目标主机的端口号。

SSH远程映射也叫反向端口映射,因为常用于把内网主机的22端口暴露到公网。

ssh xts.so -l root -R *:2222:localhost:22 -N -f

这个命令可以把服务器的2222端口映射到本机的22端口,从而允许从Internet登录内网服务器。-N告诉ssh不要运行任何命令,此连接只用于端口映射。-f选项可以让ssh转入后台运行,这样ssh命令运行之后就能回到命令行,可以继续使用其它命令。

SPDY代理省流技术的架构

基本架构

Opera Turbo技术让浏览器可以压缩浏览过程中非加密的图片内容,从而减少网络流量和提升加载速度(需要Turbo服务器的网络多线高度优化)。不过由于这一技术局限在浏览器中,并不方便使用。其实可以考虑基于OpenWRT实现一个类似的东西。

网络架构如下图所示:

1587764912.png

路由器上可以有一个程序嗅探并劫持明文HTTP流量,利用端口号和请求内容侦测把HTTP请求识别出来,通过SPDY通道发给Proxy Server处理。Proxy Server负责HTTP请求原始资源,利用其优质的网络连接和强大的服务器计算能力把请求回来的图片压缩缓存下来,并对客户端提供响应。

一些细节

当用户发送TCP SYN到80端口的时候就可以开始劫持流量,可能需要在内核模块上挂钩子完成任务,可以看看libcap和redsocks库是否可用。

和代理服务器间使用SPDY协议通信,保持长连接主要是为了得到它的多路复用的好处,少维护一点TCP连接,毕竟一般运行OpenWRT的设备内存都不大,服务器也可以从减少连接数中受益。

Proxy Server的前端使用SPDY,后端可以直接使用开源的HTTP代理程序加入压缩模块。如果不考虑复杂的认证(认证可以利用SPDY环节的TLS客户端证书功能完成),可以优先考虑基于polipo等轻量级的HTTP代理实现。

图片压缩环节可以考虑支持GPU加速的库,使用CUDA或者OpenCL实现,当然前提是压缩服务器上有显卡。可以将SPDY代理模块和HTTP压缩模块划分到不同的服务器硬件上组建集群。


以上只是基本架构的考虑,尚没有具体实现,之前计划基于Socks服务器实现一个原型,时间关系并没有完成。

用iptables的DNAT技术架设PPTP中继服务器

微软的PPTP协议是非常流行的VPN技术,它的指令控制部分使用1723端口上的TCP连接传输,而实际的VPN数据则使用GRE协议封装。如果你在一个防火墙的后面使用PPTP协议的VPN技术,那么你的防火墙必须支持PPTP穿隧才能正常使用。这是因为一般的防火墙都使用UDP或者TCP端口号的方式来生成和记录NAT对应关系,而GRE协议没有端口号。不过好在微软在GRE协议之上加了个东西,那就是PPP数据的Channel ID,利用这个东西,防火墙就可以根据它来记录NAT对应关系,把GRE报文转交给正确的内网主机。

现代的路由器一般都支持PPTP穿隧技术,所以基本无需自己设置就可以在防火墙后访问公网上的PPTP服务器建立VPN连接。另一方面,现在的商用路由器几乎都能识别1723端口,如果你在公司的路由上对1723端口做了DNAT映射,那么来自外网的PPTP请求也能正确地和防火墙后的VPN服务器建立连接。

如果使用Linux的iptables做软件防火墙,而没有使用路由器,我们依然可以建立起一个DNAT通到内网的PPTP服务器。这首先需要Linux内核加载ip_nat_pptpip_conntrack_pptp两个模块。可以用lsmod命令检查是否已经载入。

# lsmod | grep pptp

如果没有载入,可以使用modprobe加载内核模块

# modprobe ip_nat_pptp
# modprobe ip_conntrack_pptp

当然,为了做NAT,把IPv4 Forward打开也是非常必要的

# sysctl -w net.ipv4.ip_forward=1

接下来可以设置允许PPTP的TCP协议和GRE协议连入

# iptables -A INPUT -d $FIREWALL_IP -p tcp --dport 1723 -j ACCEPT
# iptables -A INPUT -d $FIREWALL_IP -p gre -j ACCEPT
# iptables -A FORWARD -d $PPTPD_IP -p tcp --dport 1723 -j ACCEPT
# iptables -A FORWARD -d $PPTPD_IP -p gre -j ACCEPT

最后配置DNAT规则完成转发,当然为了识别DNAT后的地址对应关系,转换地址后还需要把它加入MASQUERADE里。

# iptables -t nat -A PREROUTING -d $FIREWALL_IP -p tcp --dport 1723 -j DNAT --to-destination $PPTPD_IP
# iptables -t -A POSTROUTING -d $PPTPD_IP -p tcp --dport 1723 -j MASQUERADE

这样,当PPTP客户端拨号到$FIREWALL_IP上的时候,实际是后面的$PPTPD_IP服务器在提供服务。运行iptables的机器也可以视为PPTP中继服务器,向后面的服务转发数据。

阿里云9月1日事故的记录

事件经过

事情发生 9.1 11:40

9月1日上午11点多钟,我正登录服务器进行维护工作,因为前一天在服务器部署了新的seafile,还有搜索等功能需要手动开启。大约到了11:40的时候,朋友让我帮他测试一个IP的路由路径,我就在服务器上运行了mtr测试。然而mtr刚发送了几个数据包,就自己退出了。我很奇怪地再运行一次,系统却提示我无法运行mtr,没有那个程序。这还真是奇怪的事情,前几秒还在运行的程序,现在就没了。我以为是不是PATH环境变量出什么问题了,运行which mtr也找不到。于是决定重新安装一个,直接apt-get安装。结果问题又来了,apt-get提示我程序配置出错,要运行dpkg --configure修复。我再按提示运行,却看到提示说dpkg命令也不存在了。

我意识到服务器出现问题了,本着“重试、重启、重装”的三重原则,我选择了重启服务器。结果悲剧地无法启动了,连接管理终端发现机器卡在Booting from Hard Disk...就不动了。我的第一反应是保护自己的数据,马上到阿里云后台的磁盘管理中创建了一个磁盘快照。然后提交工单寻求技术支持。

提交工单 9.1 12:16

12:16我提交了工单,等着十几分钟没有回应,不过因为是个人网站,我就没有打电话催促。此时我还不知道阿里云发生了线上事故,只以为我的云服务器硬盘坏掉了,不过考虑到阿里云宣传的多重备份的硬盘管理策略,加上我又做了一个快照,我并不太担心自己的数据丢失。于是我提交完工单就和同事吃饭去了。

客服回复 9.1 12:50

回到办公室的时候,已经是下午一点多钟,阿里云工单有了进展,客服在12:49接手工单,在12:52给出事故原因:因云盾升级触发bug,导致少量文件被系统误删除。我们已经第一时间启动系统回滚。被误删除的文件正在陆续恢复,您无需进行手动恢复操作,请耐心等待。对您带来的不便我们深表歉意。

好吧,这时候我觉得阿里云的服务还不错,事故原因合情合理,客服也代表公司表达了歉意。甭管多大的软件公司,谁能不出点差错呢,这次也就和以前杀毒软件病毒库升级误杀系统文件导致开不了机一样。既然让我不要操作,我就不管了。

论坛炸锅 9.1 15:40

我本以为恢复一个误删的文件并不难,对于那些没重启的机器,既然云盾能把文件删了,自然可以统一往下分发文件;对于已经重启启动不能的云服务器,阿里完全可以用程序启一个等同系统的实例然后把用户的文件系统挂上去,再参照前面的方法恢复。

阿里云总共能用的系统镜像也没几个,统一恢复工作应该不会超过一个小时就能完成。结果事实证明我毕竟还是too young,时间过去了三四个小时,还是没有完成恢复。v2ex和阿里云官网论坛上哀鸿遍野,无数人在诉说自己被老板被客户骂到快死掉的过程。

等待了三个多小时后,阿里云工单上终于回复:您好,问题目前还在恢复中,有进一步进展,我们会随时和您反馈,对您带来的不便我们深表歉意,还请您耐心等待。

阿里云的官网则挂出了公告

尊敬的客户: 因云盾安骑士server组件的恶意文件查杀功能升级触发了bug,导致部分服务器的少量可执行文件被误隔离。系统在第一时间启动了回滚,目前被误隔离的文件已基本恢复。我们正在回访个别尚未恢复的客户,协助尽快恢复。对于受影响的客户,我们将立即启动百倍时间赔偿,并避免类似失误再次发生。我们深知这一失误对您业务带来的影响和损失,再次致以最深刻的歉意。

统一恢复完成 9.1 18:37

18:15阿里云工单回复Windows系统已经恢复,Linux系统还需要时间。18:37再次更新,说全部恢复完毕,并让我如果有问题随时反馈。然而我的服务器还是挂着的,我在18:43反馈了服务器的实际情况,表示并没有修复,不同意关闭工单,要求进一步处理。

收到阿里云通知邮件 21:09

时间又过去两个多小时,收到了阿里云的通知邮件:

如果您的ECS机器云盾进程已经退出,我们无法下发文件进行修复,请通过以下方式开启: Windows机器请启动 Alibaba Security Aegis Detect Service 服务 步骤: 1)打开计算机管理 2)选择 “服务” 3)找到 Alibaba Security Aegis Detect Service 服务并启动 4)如果找不到该服务,请尝试查找并启动 Alibaba Security Aegis Update Service 服务 Linux 机器 方案1: service aegis start 方案2: 进入/usr/local/aegis/aegis_client目录,选择高版本子目录,例如(aegis_00_79) 进入该子目录,运行 AliYunDun 进程

工单上并没有任何解释或者回复,我留言表示服务器挂了无法启动阿里云盾的进程。

最终解决方案 9.2 00:05

午夜钟声敲响不久,阿里云给出了适用我的最终解决方案:

如果您重启了主机,后台就无法正常恢复被隔离的文件了. 当前建议: 1. 登录主机控制台,对系统盘打个快照,等待100%完成, 2. 回滚系统盘至之前正常使用的快照点. 打快照的目的是如果当前系统中有重要数据,回滚后如果系统正常了,我们可以把您打的快照挂载到正常系统中,供您做数据恢复.

问题解决 9.2 01:04

终于在凌晨一点多的时候,问题得以全部解决,网站数据恢复成功,重新对外提供服务。

事件反思

客服回复不及时

遇到突发事故,阿里云的客诉量肯定大幅上涨,这个可以理解。但是阿里云似乎并没有采取什么有效手段来增加人手,及时回应客户的工单。提出问题后长达几个小时没有任何人回应,客服电话长时间无法接通,让客户自己干着急。

缺少必要的技术建议

我在个人网站一直挂掉,苦等到下午三点多钟的时候,同事反应公司的服务器也出现了系统命令丢失的问题。我的个人服务器在青岛,公司的服务器在北京,原本我以为只是阿里小范围测试时出现的问题,没想到居然北京机房也被波及。我立刻阻止他重启服务器,告诉他重启很可能会马上挂掉,因为操作系统的程序都被误删除了。然而阿里云的公告和故障申诉的工单中都没有提示用户这一点。

相信吐槽最多、损失最大的是商业用户,他们不指望你赔偿多少倍的云服务使用时间,就希望能尽快恢复服务器运行,甚至有的用户可能丢失一部分数据也可以接受。阿里云的技术人员完全有能力在事发一小时后整理出一份处理意见表,说明这次事故导致的系统程序损坏、用户数据损坏、系统无法启动等几种情况分别要怎么处理,可以最快恢复对外服务。

既然下午三点的时候云盾的误杀行为已经停止,进入恢复数据的阶段。此时投入应急的机器,配合客户紧急迁移业务程序到新的云主机上来恢复服务也不是不可行的。以我们公司的情况为例,用着阿里云的四台ECS,开几台新的云主机上线部署一次也就半小时到一小时的事情。

论坛上有人分享通过自行回滚到前一天的快照已经恢复服务,而阿里云官方在工单上要求用户不要操作,然后在长达十几个小时之后才给出回滚到前一天的快照,人工恢复数据的处理方案,真的是耗时太久了,损失也太大了。


经历这件事情知道云服务商宣传的99.999%的可靠度也就是个统计数字,自己中奖了就已经后悔莫及,经常备份数据或者设置备用机房才能让业务更安全。

最后发现今天各新闻网站全然没有报道此次事故,不知道是因为事故太小还是因为阿里太大。现在只好坐等阿里的100倍赔偿。