在NLB和ALB后获取客户端真实IP地址

注:本文只对ELB+EC2有效,EKS上的NLB请参考这篇博客。

一、背景

1、ELB分类

AWS ELB提供了弹性扩展能力,用于多EC2节点流量负载、高可用、弹性扩展的调度等用途。目前主要使用以NLB和ELB为主。上一代传统负载均衡器又被称为CLB,推荐不要再继续使用CLB,而是更换到NLB和ALB。

共性:

  • 支持SSL卸载:实现方式是在ACM上传证书或者在ACM内签署证书,然后选择监听器,设置HTTPS或TLS方式的监听器;
  • 支持健康检查:二者都支持健康检查;
  • 都支持将流量转发到EC2 Instance,或者直接输入backend的IP地址进行转发。

区别:

  • ALB以7层HTTP为主,可设置基于HTTP路径的路由;ALB不支持客户端的真实IP地址透传;
  • NLB以发布4层的TCP、UDP端口为主;在Instance模式下支持真实IP地址透传,IP模式下不支持透传。

2、不同配置方式下获得真实IP地址的办法

ALB和NLB创建过程中,都会提示选择目标组(Target Group)的类型。二者都可以选择:

  • Instance:此模式下,可以将流量导入到本Region内的EC2,仅限界面上下拉框可以选择出来的实例;
  • IP:此模式下,可以将流量导入到本VPC内的IP,例如EC2、RDS实例,也可以导入到本VPC以外的IP,例如通过Direct Connect专线或者VPN连接到其他云或者连接到IDC,只要能在VPC内ping通,就可以把IP填写过来作为发布对象。

在以上两种配置模式下,负载有分别有不同的获取IP地址的模式。汇总如下表。

类型Target类型是否直接透传获取真实IP的方案
NLBInstance无须额外配置
NLBIP启用 Proxy V2 Protocol
ALBInstance启用 X-Forwarded-For Header
ALBIP启用 X-Forwarded-For Header
表1:NLB和ALB不同配置模式下的IP地址透传

下面我们将进行针对性测试。

二、创建测试用EC2环境

1、使用User Data初始化环境

首先在一个VPC、子网都工作正常的环境内,创建一台EC2。创建时候,请选择操作系统为Amazon Linux 2,确认规格内存大于1GB,磁盘大于8GB。创建时候,使用如下的User Data脚本。请注意需要在安全规则组上放行TCP协议80端口。如果您的AWS账号尚未完成备案,那么无法访问80端口,请酌情修改为50001等高位端口。

#! /bin/bash
amazon-linux-extras install php7.4 -y
yum install httpd mysql -y
echo "alias vi=vim" >> /etc/bashrc
echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php
echo "Client IP Address is: <?php printf(\$_SERVER[\"REMOTE_ADDR\"]); ?>" > /var/www/html/index.php
usermod -a -G apache ec2-user
chown -R ec2-user:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
systemctl restart httpd

在Console界面上创建EC2并输入User Data请注意,User Data有三个选项,选择第一个“As text”即可。

2、查看客户端IP地址验证EC2环境部署正常

(1)网页方式访问

通过Console界面查询本EC2的公网IP地址,然后通过网页访问这个地址。例如 http://54.222.146.51/ 此时屏幕上浏览器将打出一行字Client IP Address is: 115.171.12.170 即表示EC2部署正常。此时返回的公网IP地址就是客户端IP。

(2)命令行方式访问

也可以使用在非本EC2环境上,例如本地Mac,或者AWS其他Region的系统上,使用curl命令访问。Linux和MacOS一般自带curl。

例如在MacOS的终端上访问如下地址:

curl 54.223.103.182

返回信息如下则表示正常。

$ curl 54.222.146.51
Client IP Address is: 115.171.12.170

其中显示的公网IP地址115.171.12.170就是发起测试的客户端所在的真实IP地址。

3、查看EC2上的Apache日志,确认记录客户端IP地址正常

接下来验证Apache的日志正确的记录了客户端IP地址。首先使用SSH登录到刚才创建的EC2环境上,然后执行如下命令:

tail -f /var/log/httpd/access_log

此命令将持续的显示访问日志。返回结果如下:

[root@ip-172-31-18-221 ~]# tail -f /var/log/httpd/access_log
115.171.12.170 - - [09/Jul/2020:13:58:16 +0000] "GET / HTTP/1.1" 200 41 "-" "curl/7.64.1"
115.171.12.170 - - [09/Jul/2020:14:00:59 +0000] "GET / HTTP/1.1" 200 41 "-" "curl/7.64.1"

这里可以看到的IP地址115.171.12.170就是客户端地址。至此验证Apache记录web日志正常。

请记住以上方法,后文将反复使用。

三、NLB获取真实IP地址

1、NLB配置目标组为Instance模式

前文已经讲述,在此模式下,EC2可以直接获取真实IP地址。本实验中,创建一个新的NLB,主要配置简短描述如下:

  • 选择NLB
  • 类型是”Internet Facing”,即面向互联网提供外网访问能力
  • 选择监听器是TCP
  • 选择绑定2个或者3个AZ
  • 跳过向导第二步的安全设置(因为没选TLS所以不需要设置证书)
  • 在向导的第三步在配置目标组界面,选择新建目标组,名称叫做 NLB-instance
  • 目标组类型选择实例(Instance)
  • 协议选择TCP
  • 端口选择80
  • 健康检查协议选择TCP
  • 健康检查的高级参数设置不用修改
  • 如果为了缩短等待时间,也可以在健康检查的高级设置中,将间隔从30秒改为10秒,可以加快测试过程
  • 在向导的第四步注册实例,选中刚才新建的EC2,点击蓝色按钮注册,然后再点击下一步
  • 完成创建

创建完成后,返回负载均衡器的目标组界面,请确保目标组下的第二个标签页“目标”中,显示的EC2状态是”Healthy”,方可继续实验。创建NLB到显示健康状态一般需要3-5分钟时间。如果显示initial是正在初始化,如果显示unhealthy是配置不正确。

至此配置完成。开始发起测试。前文已经描述过,此模式下,是可以直接透传真实IP的,因此可以从浏览器访问本NLB的Public Endpoint,也可以使用curl命令行访问。本文使用命令和返回结果如下:

$ curl getRealIP-feb3d73830ebbe64.elb.cn-north-1.amazonaws.com.cn
Client IP Address is: 115.171.12.170

由此可以看到,EC2上的代码正确的接受到了客户端真实IP地址。

现在登录到EC2上查看Apache的访问日志,可以看到如下结果:

115.171.12.170 - - [09/Jul/2020:14:19:27 +0000] "GET / HTTP/1.1" 200 41 "-" "curl/7.64.1"
115.171.12.170 - - [09/Jul/2020:14:19:46 +0000] "GET / HTTP/1.1" 200 36 "-" "curl/7.64.1"
115.171.12.170 - - [09/Jul/2020:14:19:59 +0000] "GET / HTTP/1.1" 200 36 "-" "curl/7.64.1"
115.171.12.170 - - [09/Jul/2020:14:20:06 +0000] "GET / HTTP/1.1" 200 36 "-" "curl/7.64.1"
115.171.12.170 - - [09/Jul/2020:14:20:13 +0000] "GET / HTTP/1.1" 200 36 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
115.171.12.170 - - [09/Jul/2020:14:20:13 +0000] "GET /favicon.ico HTTP/1.1" 404 196 "http://getrealip-feb3d73830ebbe64.elb.cn-north-1.amazonaws.com.cn/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"

以上日志中记录下来的,既有curl命令行访问的日志,也有浏览器访问的日志。同时,还有一行日志是提示本网站的web目录下,没有放favicon.ico文件,这个文件是收藏夹图标,也就是地址栏上的网址小图标。这个图标不存在,所以日志里边显示404找不到文件,这个不是错误。不影响继续实验。

至此验证NLB在Instance模式下成功透传真实IP完成。

2、NLB配置目标组为IP模式

(1)发起测试

下面完全从零开始,再重新创建一遍负载均衡器。因此保留刚才的EC2配置不变,先删除负载均衡器,在删除目标组。删除NLB时候一般要等待1分钟,再去删除目标组。否则会报告目标组正在被负载均衡器使用而无法删除。

重复上一个步骤的创建NLB过程,有如下变化:

  • 在向导第三步,选择新建一个目标组,选择类型是IP
  • 在向导第四步,注册目标界面,界面与刚才完全不同,在左侧的下拉框中,选择要发布的IP地址是本VPC的还是其他位置的。如果是发布DX专线、VPN等另一侧的其他云和IDC上的IP地址,那么请选择下拉框的第二项”Other Private IP”。如果是发布本VPC内的EC2,就选择默认的本VPC
  • 在IP地址输入框中填写上刚才部署Apache的WEB Server的内网IP地址,例如本文是172.31.18.221,端口保持为80不变,点击右侧的向下箭头”Add to list”,接下来可以在下方的蓝色虚线框内看到要加入的IP地址
  • 继续完成创建。

由此就创建好了一个基于IP地址的目标组,并且发布到NLB。和刚才的步骤一样,一定要等待到目标组中的目标健康状态显示为”healthy”才可以继续测试。如果显示为unhealthy,应该先对EC2和NLB的配置排错。

现在通过浏览器或者curl访问NLB对应的Public Endpoint,访问命令和输出结果如下。

$ curl getRealIP-36717dcdfeafd879.elb.cn-north-1.amazonaws.com.cn
Client IP Address is: 172.31.23.189

可以看到在以上例子中,浏览器和curl返回的地址都变成了内网IP了,这是NLB落地到本Subnet上的内网地址。接下来在按照前文的方法,查看Apache的日志。可以发现如下:

172.31.23.189 - - [09/Jul/2020:14:36:19 +0000] "GET / HTTP/1.1" 200 35 "-" "curl/7.64.1"
172.31.23.189 - - [09/Jul/2020:14:36:22 +0000] "GET / HTTP/1.1" 200 35 "-" "curl/7.64.1"
172.31.23.189 - - [09/Jul/2020:14:36:28 +0000] "GET / HTTP/1.1" 200 35 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
172.31.23.189 - - [09/Jul/2020:14:36:28 +0000] "GET /favicon.ico HTTP/1.1" 404 196 "http://getrealip-36717dcdfeafd879.elb.cn-north-1.amazonaws.com.cn/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"

Apache中的访问日志也变成了内网地址。这就是所谓的无法获取客户端真实IP地址。

(2)为NLB启用Proxy V2 Protocol协议

AWS NLB提供了Proxy V2协议的支持。Proxy Protocol的工作原理是在TCP包头上增加一个很小的字段,描述真实客户端的IP地址等信息。Proxy协议对于普通Web服务器而言是透明的,因为其不工作在HTTP层,因此在默认状态下,EC2上的Apache(同理Ngnix等)默认是无法感知到的,需要进行额外的配置。配置分成两个步骤:一是在NLB上启用Proxy V2协议;二是在Web Server上启用对Proxy协议的解析。二者必须相搭配。如果只是对NLB启用Proxy V2协议,而Web Server不做任何修改,浏览器会返回400 Bad Request,网站无法打开。

启用Proxy协议的操作过程是:

  • 定位到当前使用的NLB,找到这个NLB使用的目标组
  • 进入到目标组界面,进入第一个标签页”描述”(Description),将页面向下滚动
  • 在下方找到”属性”(Description),点击编辑按钮,选中”Proxy protocol v2″对应的Enable的支持
  • 保存目标组生效

以上过程完成后等待大约1分钟,再去访问NLB,可以看到已经变成400 Bad Request了。不止curl,通过浏览器访问也是这个错误。

$ curl getRealIP-36717dcdfeafd879.elb.cn-north-1.amazonaws.com.cn
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>

现在查看Apache的访问日志,也显示为400 Bad Request。

172.31.23.189 - - [09/Jul/2020:14:47:34 +0000] "" 400 226 "-" "-"
172.31.23.189 - - [09/Jul/2020:14:47:34 +0000] "" 400 226 "-" "-"
172.31.23.189 - - [09/Jul/2020:14:47:36 +0000] "" 400 226 "-" "-"
172.31.23.189 - - [09/Jul/2020:14:47:37 +0000] "" 400 226 "-" "-"
172.31.23.189 - - [09/Jul/2020:14:47:37 +0000] "" 400 226 "-" "-"
172.31.23.189 - - [09/Jul/2020:14:47:37 +0000] "" 400 226 "-" "-"

这说明还需要继续配置EC2上的Apache以打开对Proxy协议支持。

(3)在EC2的Apache上打开Proxy协议支持

Apache2可以通过mod_remoteip模块,一键打开对Proxy协议的支持。如果是通过本文的User Data方法,使用Amazon Linux 2安装的Apache,则已经内置了mod_remoteip模块。如果是使用CentOS系统自带的yum安装,也应该是内置支持的。如果您是手工安装的Apache,则需要查找对应模块,是否已经编译正确并放置so文件到正确的路径下。

编辑如下配置文件。

vi /etc/httpd/conf/httpd.conf

在大约第119行,找到 DocumentRoot “/var/www/html” 这一行。在这一行的下边,加入如下一行配置:

RemoteIPProxyProtocol On

需要注意的是,这个配置应加载于默认网站或者VirtualHost的配置段内,不能在其他位置任意填写,否则Apache会无法启动。修改完成后,保存退出,重新启动服务。

systemctl restart httpd

重启启动Apache完成后,在通过浏览器和curl访问NLB,可以看到curl输出结果如下:

$ curl getRealIP-36717dcdfeafd879.elb.cn-north-1.amazonaws.com.cn
Client IP Address is: 115.171.12.170

这表示程序代码已经正确的获得了客户端真实IP地址。

在查看Apache日志,可看到输出如下:

172.31.23.189 - - [09/Jul/2020:14:54:28 +0000] "" 400 226 "-" "-"
172.31.23.189 - - [09/Jul/2020:14:54:34 +0000] "" 400 226 "-" "-"
115.171.12.170 - - [09/Jul/2020:14:54:57 +0000] "GET / HTTP/1.1" 200 36 "-" "curl/7.64.1"
115.171.12.170 - - [09/Jul/2020:14:55:05 +0000] "GET / HTTP/1.1" 200 36 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"

这个日志记录的是刚完成配置的切换的时间段,上边的172内网IP和访问代码400表示此前配置不正确,无法获得真实IP。下边的记录就是正常获取到了真实IP地址的记录。这表示启用Proxy V2协议成功。

另外需要注意的是,当配置完成后,客户端已经能获取真实IP地址了,但是每过一段时间,还可以在Apache的访问日志看到如下一条内网IP的记录:

172.31.23.189 - - [09/Jul/2020:14:56:56 +0000] "-" 408 - "-" "-"

这一条是NLB超时后自动关闭连接留下的记录,HTTP代码为408。这是正常的。

至此NLB的实验完整。

四、ALB获取真实IP地址

接下来进行ALB的实验,请删除如下资源:

  • 删除刚才配置的NLB负载均衡器
  • 删除刚才配置的目标组
  • 修改Apache配置文件”/etc/httpd/conf/httpd.conf”,删除掉刚才加入的配置”RemoteIPProxyProtocol On”,并重启Apache服务

1、ALB配置目标组为Instance模式

(1)配置ALB

请确认已经删除刚才的目标组,并且恢复了Apache配置文件,并重启Apache服务。

  • 选择ALB
  • 类型是”Internet Facing”,即面向互联网提供外网访问能力
  • 选择监听器是HTTP,端口为80
  • 选择绑定2个或者3个AZ
  • 跳过向导第二步的安全设置(因为没选TLS所以不需要设置证书)
  • 在向导的第三步配置安全组界面,选择一个预先配置好的安全组,开放80端口访问
  • 在向导第四步,配置目标组界面,选择新建目标组,名称叫做 ALB-instance
  • 目标组类型选择实例(Instance)
  • 协议选择TCP
  • 端口选择80
  • 健康检查协议选择TCP
  • 健康检查的高级参数设置不用修改
  • 如果为了缩短等待时间,也可以在健康检查的高级设置中,将间隔从30秒改为10秒,可以加快测试过程
  • 在向导的第五步注册实例,选中刚才的EC2,点击蓝色按钮注册,然后再点击下一步
  • 完成创建

创建ALB需要等待3-5分钟,观察对应的目标组下的主机为healthy状态后,可以继续实验。

通过curl访问,可以获得如下返回结果。

$ curl getRealIP-565047248.cn-north-1.elb.amazonaws.com.cn
Client IP Address is: 172.31.16.199

由此可以看到ALB的instance模式下,应用获得是内网IP,不是客户端真实IP地址。接下来查看Apache日志。

172.31.16.199 - - [09/Jul/2020:15:19:52 +0000] "GET / HTTP/1.1" 200 35 "-" "ELB-HealthChecker/2.0"
172.31.4.225 - - [09/Jul/2020:15:20:01 +0000] "GET / HTTP/1.1" 200 34 "-" "ELB-HealthChecker/2.0"
172.31.16.199 - - [09/Jul/2020:15:20:02 +0000] "GET / HTTP/1.1" 200 35 "-" "ELB-HealthChecker/2.0"
172.31.16.199 - - [09/Jul/2020:15:20:09 +0000] "GET / HTTP/1.1" 200 35 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
172.31.16.199 - - [09/Jul/2020:15:20:09 +0000] "GET /favicon.ico HTTP/1.1" 404 196 "http://getrealip-565047248.cn-north-1.elb.amazonaws.com.cn/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
172.31.4.225 - - [09/Jul/2020:15:20:11 +0000] "GET / HTTP/1.1" 200 34 "-" "ELB-HealthChecker/2.0"
172.31.16.199 - - [09/Jul/2020:15:20:12 +0000] "GET / HTTP/1.1" 200 35 "-" "ELB-HealthChecker/2.0"
172.31.16.199 - - [09/Jul/2020:15:20:14 +0000] "GET / HTTP/1.1" 200 35 "-" "curl/7.64.1"
172.31.16.199 - - [09/Jul/2020:15:20:16 +0000] "GET / HTTP/1.1" 200 35 "-" "curl/7.64.1"
172.31.4.225 - - [09/Jul/2020:15:20:21 +0000] "GET / HTTP/1.1" 200 34 "-" "ELB-HealthChecker/2.0"
172.31.16.199 - - [09/Jul/2020:15:20:22 +0000] "GET / HTTP/1.1" 200 35 "-" "ELB-HealthChecker/2.0"

这个段Apache日志里边包含了curl的访问,可以看到curl/7.64.1的版本信息,也包含了MacOS上的浏览器的访问,他们都是留下的内网IP地址的记录。此外,还可以看到ALB发行的健康检查的日志记录,其Agent名称是”ELB-HealthChecker/2.0″。由此将继续展开下一步配置。

(2)修改应用代码获取X_FORWARDED_FOR

ALB是7层负载均衡器,可以理解为7层HTTP代理,因此在处理转发规则时候,可以对7层的数据包做快速修改,包括重写地址、修改包内容等。ALB将客户端真实IP地址放在了额外的HTTP Header中,名称叫做”X_FORWARDED_FOR”。这个动作是在ALB上默认就启用的,因此ALB其实本身无须任何配置。那么为了在应用层面打出这个配置,需要修改下应用代码。本实验采用的是Apache+PHP方式,在用于测试的网页上直接打出的客户端地址。只需要轻微修改,更换下HTTP Header名称,即可打印出真实IP。

首先登录到EC2,编辑如下文件:

vi /var/www/html/index.php

可以看到这个PHP简单到只有一行,内容如下:

Client IP Address is: <?php printf($_SERVER["REMOTE_ADDR"]); ?>

这里的变量REMOTE_ADDR就是众多HTTP头的一个,这是标准协议中默认的客户端地址。为了显示ALB增加的特殊的X_FORWARDED_FOR,这里将原来的 REMOTE_ADDR 替换为 HTTP_X_FORWARDED_FOR 即可,代码修改为如下:

Client IP Address is: <?php printf($_SERVER["HTTP_X_FORWARDED_FOR"]); ?>

保存退出,无须重启,再去浏览器上和curl上访问。访问结果如下:

$ curl getRealIP-565047248.cn-north-1.elb.amazonaws.com.cn
Client IP Address is: 115.171.12.170%  

可以看到curl已经正确的返回了客户端的真实IP地址。同时浏览器也工作正常。

(3)修改Apache日志打印X_FORWARDED_FOR

为了便于分析用户访问行为的日志,还需要将Apache上的WEB日志中记录的IP地址也修改为客户端真实IP。需要编辑配置文件:

vi /etc/httpd/conf/httpd.conf

在Apache配置文件中,大约第197行,找到如下一行配置:

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

这是Apache默认的日志格式,是记录信息比较完整的多个字段的混合模式。为了输入真实IP,需要将其修改为如下:

LogFormat "%{X-Forwarded-For}i %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

如上修改主要是在%h之前,增加了输出%{X-Forwarded-For}i的字段,其他配置没有变化,同时保留了%h的内网主机地址信息。现在重新启动Apache服务:

systemctl restart httpd

再次观察Apache日志,可以看到如下输出:

115.171.12.170 172.31.4.225 - - [09/Jul/2020:15:34:50 +0000] "GET / HTTP/1.1" 200 36 "-" "curl/7.64.1"
115.171.12.170 172.31.4.225 - - [09/Jul/2020:15:34:51 +0000] "GET / HTTP/1.1" 200 36 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
- 172.31.4.225 - - [09/Jul/2020:15:34:51 +0000] "GET / HTTP/1.1" 200 22 "-" "ELB-HealthChecker/2.0"
115.171.12.170 172.31.4.225 - - [09/Jul/2020:15:34:52 +0000] "GET / HTTP/1.1" 200 36 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
115.171.12.170 172.31.4.225 - - [09/Jul/2020:15:34:52 +0000] "GET / HTTP/1.1" 200 36 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
115.171.12.170 172.31.4.225 - - [09/Jul/2020:15:34:53 +0000] "GET / HTTP/1.1" 200 36 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
- 172.31.16.199 - - [09/Jul/2020:15:34:53 +0000] "GET / HTTP/1.1" 200 22 "-" "ELB-HealthChecker/2.0"
- 172.31.4.225 - - [09/Jul/2020:15:35:01 +0000] "GET / HTTP/1.1" 200 22 "-" "ELB-HealthChecker/2.0"

在以Apache日志中,留下了两个IP地址信息,一个是X-Forwarded-For头的真实客户端IP,一个是ALB落在VPC内的内网IP,这表示记录真实IP地址成功。此外,对于ALB的健康检查,因为是纯内网中进行检查,不会有公网IP,因此在日志中,开头第一个字段是 “-” 符号表示没有获取到数值,这是正常的。至此,就完成了ALB获取真实IP地址的过程。

2、ALB配置目标组为IP模式

在开始测试之前,请先将上一个ALB实验中修改过的/var/www/html/index.php的代码还原,将/etc/httpd/conf/httpd.conf里的AccessLog格式部分配置还原,这样方可获得原始测试效果。

重复上一个步骤的创建ALB过程,有如下变化:

  • 在向导第三步,选择新建一个目标组,选择类型是IP,名称叫做alb-ip
  • 在向导第四步,注册目标界面,界面与刚才完全不同,在左侧的下拉框中,选择要发布的IP地址是本VPC的还是其他位置的。如果是发布DX专线、VPN等另一侧的其他云和IDC上的IP地址,那么请选择下拉框的第二项”Other Private IP”。如果是发布本VPC内的EC2,就选择默认的本VPC
  • 在IP地址输入框中填写上刚才部署Apache的WEB Server的内网IP地址,例如本文是172.31.22.109,端口保持为80不变,点击右侧的向下箭头”Add to list”,接下来可以在下方的蓝色虚线框内看到要加入的IP地址
  • 继续完成创建。

创建ALB完成后,观察目标组,直到目标组中的目标IP健康度是Healthy,即可开始测试。现在使用浏览器或者curl访问新配置好的ALB,即可看到输出。

$ curl getRealIP-1448662585.cn-north-1.elb.amazonaws.com.cn
Client IP Address is: 172.31.13.97%        

由此可看到,在ALB配置目标组为IP模式下,应用代码获取的依然是内网IP。

接下来查看Apache的日志,可以看到日志中记录的IP地址也是内网IP。

172.31.17.188 - - [10/Jul/2020:01:58:48 +0000] "GET / HTTP/1.1" 200 35 "-" "curl/7.64.1"
172.31.17.188 - - [10/Jul/2020:01:58:50 +0000] "GET / HTTP/1.1" 200 35 "-" "curl/7.64.1"
172.31.13.97 - - [10/Jul/2020:01:58:53 +0000] "GET / HTTP/1.1" 200 34 "-" "ELB-HealthChecker/2.0"
172.31.17.188 - - [10/Jul/2020:01:58:55 +0000] "GET / HTTP/1.1" 200 35 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
172.31.17.188 - - [10/Jul/2020:01:58:55 +0000] "GET /favicon.ico HTTP/1.1" 404 196 "http://getrealip-1448662585.cn-north-1.elb.amazonaws.com.cn/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.58"
172.31.17.188 - - [10/Jul/2020:01:58:59 +0000] "GET / HTTP/1.1" 200 35 "-" "ELB-HealthChecker/2.0"

至此,实验也就验证了如下结论:使用ALB的时候,无论选择目标组是Instance还是选择IP模式,都只能获取到ALB的内网IP。

接下来,为了获取客户端真实IP,操作步骤与ALB在Instance模式下的步骤相同,即修改PHP代码获取 HTTP_X_FORWARDED_FOR 头;且修改Apache配置文件,在日志中加入 %{X-Forwarded-For}i 即可打印真实IP。操作过程与上一步实验过程相同。这里不再赘述。

五、小结

本文以Apache+PHP为例,提供了一个快速实验环境用于快速了解NLB和ALB获取真实IP地址的过程。对于Nginx,本文没有详细描述,请在网上搜索有关文章,参考类似配置流程即可实现。

另外需要注意的是:EKS下的NLB的获取真实IP过程与本文有所不同,请参考这篇博客。

六、参考文档

https://repost.aws/zh-Hans/knowledge-center/elb-capture-client-ip-addresses