1. 导言

绝大多数业务场景都是需要知道客户端IP的
在k8s中运行的业务项目,如何获取到客户端真实IP?
本文总结了通行的2种方式
要答案的直接看方式一、方式二和总结
SEO 关键字
nginx ingress客户端真实ip
kubernets获取客户端真实ip
rke获取客户端真实ip
rancher获取客户端真实ip
本文由 www.iamle.com 流水理鱼 原创,wx公众号同名

1.1 流量链路介绍

7层转发链路 Client(客户端) > Nginx > K8s Ingress(Nginx ingress)
4层转发链路 Client(客户端) > 公有云LB > K8s Ingress(Nginx ingress)
ps: 实际业务会串联更多层级的转发。WAF、CDN、Api Gateway一般是http 7层转发,LB一般是4层tcp转发

1.2 准备whoami探针

whomai是一个go编写的调试探针工具,回显http头信息
在k8s中部署一个containous/whoami用来作为探针,配置好ingress公网和访问,这样客户端web访问可以看到基本的http头信息,方便调试

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
  namespace: default
  labels:
    app: whoami
spec:
  replicas: 1
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
      - image: containous/whoami
        imagePullPolicy: Always
        name: whoami
        ports:
        - containerPort: 80
          name: 80tcp02
          protocol: TCP
      dnsPolicy: ClusterFirst
      restartPolicy: Always
EOF 

ps:ingress自行增加

客户端web访问,回显http头示例

Hostname: whoami-65b8cc4b-6vwns
IP: 127.0.0.1
IP: 10.42.2.12
RemoteAddr: 10.42.1.0:47850
GET / HTTP/1.1
Host: whoami.iamle.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6,la;q=0.5
Cookie: _ga=GA1.2.30707523.1570429261;
Dnt: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 8.8.8.8, 10.0.0.1
X-Forwarded-Host: whoami.iamle.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Original-Forwarded-For: 8.8.8.8
X-Original-Uri: /
X-Real-Ip: 8.8.8.8
X-Request-Id: 3852c9780589ffba4c1f9f2785691d5f
X-Scheme: https

2. 两种方式 7层http头X-Forwarded-For透传 和 4层Proxy Protocol透传

获得客户端真实IP有针对7层和针对4层两种方式

2.1 7层http头X-Forwarded-For透传介绍

http工作在网络第7层,http中有个X-Forwarded-For字段

大部分CDN、WAF、LB用X-Forwarded-For字段来存客户端IP,也有用X-Real-Ip字段,cloudflare、百度云加速还扩展了CF-Connecting-IP字段
标准数据为

X-Forwareded-For:Client,proxy1,proxy2,proxy3……

第一个ip是客户端ip,后面的proxy为路过一层就加一层的ip
这里的proxy可以是WAF、CDN、LB、Api Gateway等

2.2 4层Proxy Protocol透传

tcp工作在网络第4层,Proxy Protocol就是在tcp中增加一个小的报头,用来存储额外的信息

代理协议即 Proxy Protocol,是haproxy的作者Willy Tarreau于2010年开发和设计的一个Internet协议,通过为tcp添加一个很小的头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),在网络情况复杂又需要获取客户IP时非常有用。
其本质是在三次握手结束后由代理在连接中插入了一个携带了原始连接四元组信息的数据包。

目前 proxy protocol有两个版本,v1仅支持human-readable报头格式(ASCIII码),v2需同时支持human-readable和二进制格式,即需要兼容v1格式
proxy protocol的接收端必须在接收到完整有效的 proxy protocol 头部后才能开始处理连接数据。因此对于服务器的同一个监听端口,不存在兼容带proxy protocol包的连接和不带proxy protocol包的连接。如果服务器接收到的第一个数据包不符合proxy protocol的格式,那么服务器会直接终止连接。

Proxy protocol是比较新的协议,但目前已经有很多软件支持,如haproxy、nginx、apache、squid、mysql等等,要使用proxy protocol需要两个角色sender和receiver,sender在与receiver之间建立连接后,会先发送一个带有客户信息的tcp header,因为更改了tcp协议头,需receiver也支持proxy protocol,否则不能识别tcp包头,导致无法成功建立连接。
nginx是从1.5.12起开始支持的

3. 方式一 X-Forwarded-For配置

适用于7层http转发

3.1 NGINX Ingress Controller X-Forwarded-For配置

查看NGINX Ingress Controller的ConfigMaps配置文档,可以找到以下配置项
use-forwarded-headers
如果为true,NGINX会将传入的 X-Forwarded-* 头传递给upstreams。当NGINX位于另一个正在设置这些标头的 L7 proxy / load balancer 之后时,请使用此选项。
如果为false,NGINX会忽略传入的 X-Forwarded-* 头,用它看到的请求信息填充它们。如果NGINX直接暴露在互联网上,或者它在基于 L3/packet-based load balancer 后面,并且不改变数据包中的源IP,请使用此选项。
ps: NGINX Ingress Controller直接暴露互联网也就是Edge模式不能开启为true,否则会有伪造ip的安全问题。也就是k8s有公网ip,直接让客户端访问,本配置不要设为true!

forwarded-for-header
设置标头字段以标识客户端的原始IP地址。 默认: X-Forwarded-For
ps:如果 NGINX Ingress Controller 在CDN,WAF,LB等后面,设置从头的哪个字段获取IP,默认是X-Forwarded-For
这个配置应该和use-forwarded-headers配合使用

compute-full-forwarded-for
将远程地址附加到 X-Forwarded-For 标头,而不是替换它。 启用此选项后,upstreams应用程序将根据其自己的受信任代理列表提取客户端IP

修改configmap nginx-configuration配置

kubectl -n ingress-nginx edit cm nginx-configuration

在apiVersion: v1下,kind: ConfigMap上加入

data:
  compute-full-forwarded-for: "true"
  forwarded-for-header: "X-Forwarded-For"
  use-forwarded-headers: "true"

或者直接apply附加配置

kubectl apply -f - <<EOF
apiVersion: v1
data:
  compute-full-forwarded-for: "true"
  forwarded-for-header: X-Forwarded-For
  use-forwarded-headers: "true"
kind: ConfigMap
metadata:
  labels:
    app: ingress-nginx
  name: nginx-configuration
  namespace: ingress-nginx
EOF

ps:如果nginx-configuration不在namespace ingress-nginx中就在namespace kube-system中找

3.2 Nginx作为边缘节点(Edge)配置

作为Edge需要重写remote_addr,保证了客户端IP不会被伪造
必须:X-Forwarded-For 重写为 $remote_addr
非必须扩展:X-Real-IP 重写为 $remote_addr

upstream wwek-k8s {
    server 8.8.8.8:443;
    server 8.8.8.7:443;
    server 8.8.8.6:443;
}

map $http_upgrade $connection_upgrade {
    default Upgrade;
    ''      close;
}
server {
    if ($http_x_forwarded_proto = '') {
        set $http_x_forwarded_proto  $scheme;
   }
location / {

        proxy_set_header Host              $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port  $server_port;
        #proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-For   $remote_addr;
        proxy_set_header X-Real-IP         $remote_addr;

        proxy_pass          https://wwek-k8s;
        proxy_http_version  1.1;
        proxy_set_header    Upgrade $http_upgrade;
        proxy_set_header    Connection $connection_upgrade;
        proxy_read_timeout 900s;
        proxy_buffering off;
    }
}

3.3 X-Forwarded-For是否可以伪造

客户端是否能伪造IP,取决于边缘节点(Edge)是如何处理X-Forwarded-For字段的。
客户端直接连接的首个proxy节点都叫做边缘节点(Edge),不管是网关、CDN、LB等只要这一层是直接接入客户端访问的,那么他就是一个边缘节点。

不重写-不安全的边缘节点(Edge)
边缘节点如果是透传http头中的X-Forwarded-For字段,那么这个就是不安全的,客户端可以在http中实现包含X-Forwarded-For字段值,这个值又被透传了。

#不安全
X-Forwareded-For:Client(Edge不重写,只透传),proxy1,proxy2,proxy3……

重写-安全的边缘节点(Edge)
边缘节点(Edge)如果重写remote_addr到X-Forwarded-For,那么这就是安全的。边缘节点(Edge)获取的remote_addr就是客户端的真实IP

#安全
X-Forwareded-For:Client(Edge获取的remote_addr),proxy1,proxy2,proxy3……

4. 方式二 Proxy Protocol 配置实例

适用于4层tcp转发
公有云的负载均衡LB一般都支持Proxy Protocol

查看NGINX Ingress Controller的ConfigMaps配置文档,可以找到如何配置Proxy Protocol
use-proxy-protocol
启用或禁用roxy Protocol,以接收通过代理服务器和负载均衡器(例如HAProxy和Amazon Elastic Load Balancer(ELB))传递的客户端连接(真实IP地址)信息。

NGINX Ingress Controller 作为receiver角色 Proxy Protocol配置

kubectl -n ingress-nginx edit cm nginx-configuration

在apiVersion: v1下,kind: ConfigMap上加入

data:
  use-proxy-protocol: "true"

或者直接apply附加配置

kubectl apply -f - <<EOF
apiVersion: v1
data:
  use-proxy-protocol: "true"
kind: ConfigMap
metadata:
  labels:
    app: ingress-nginx
  name: nginx-configuration
  namespace: ingress-nginx
EOF

ps: 注意需要上一层LB支持Proxy Protocol,才能这么配置,否则会导致无法链接

5. 总结

7层http头X-Forwarded-For透传
链路proxy有透传X-Forwarded-For
访问链路上多层proxy,任意一个节点不支持Proxy Protocol

4层协议Proxy Protocol透传
上下游可控都支持Proxy Protocol协议
链路proxy中丢失了http头
https反向代理http(某些情况下由于Keep-alive导致不是每次请求都传递x-forword-for

应该用那种方式?
7层用X-Forwarded-For,4层用Proxy Protocol
如果链路的边缘节点(Edge)X-Forwarded-For字段是安全的,建议用X-Forwarded-For
如果链路proxy全路径都支持Proxy Protocol,那么建议用Proxy Protocol
如果有4层tcp业务应用,那么获取客户端IP就的用Proxy Protocol
总之搞清楚了这2种方式的原理按照场景选择

5. 参考

1.背景

目的: 老式门禁接入hass智能家居,实现门禁智能控制
物料:
* 某宝ESP8266集成板HW-62 1个,本板集成了电源模块(输入DC 7-30V)+ESP8266+继电器一个,以下简称ESP集成板(买了很久了,这次才利用上)
* 电源适配器 12V DC 1个

软件: 已经在用HomeAssistant(版本0.105.2)(以下简称hass)

控制模块有很多选择,都是控制单元驱动继电器。NodeNUC+继电器可能是更流行的方案
ESP8266芯片为控制模块,继电器为操作模块

某宝ESP8266集成板 HW-622 是老早之前买的一直没使用,本次可以利用上
他集成了宽电压输入的电源模块、ESP8266芯片、继电器,经过万能表检查,驱动继电器工作的GPIO是GPIO4
-w604

实现原理:
ESP8266刷ESPEasy系统,连上wifi,作为MQTT客户端接入hass
继电器接门禁实现具体控制

2.门禁-室内机改装

已经确定这套老式门禁无需摘话机直接按开门按键就能开门
所以只需要短接1s开门按键就可以开门了

把开门按钮的2条线引出
这2条线就接继电器,公共端COM、常闭触点NC
很简单的电路图就不画了

3.ESPEasy

ESP8266刷ESPEasy系统
本例版本为mega-20200204
刷机教程、连接方法就不累述了,网上搜
下面列一些主要配置

3.1 Config

UnitName设置为 esp-entrance-guard
这个值重要,MQTT会用到

-w864

3.2 Controllers

MQTT加这个 Home Assistant (openHAB) MQTT
不明白的配置项保持默认值不去改动,只改动明确需要的
Enabled这个必须勾选

3.2 Hardware

本ESP集成板使用了GPIO4作为继电器的驱动IO口
ESPEasy的I2C Interface默认使用GPIO-4 GPIO-5本板就冲突了
本例改为了空闲的12、13

另外wifi信号灯本ESP集成板是GPIO2

-w874

3.3 Hardware

增加一个“Switch input – Switch”设备

名字为 Door
GPIO为 GPIO4 这个是和继电器IO连接的
-w935

勾选 Send to Controller
-w681

增加完成后
-w1095

3.4 Tools高级选项

ESPEasy默认未启用Rules,需要在这里打开

Rules 启用
MQTT Retain Msg 启用
MQTT use unit name as ClientId 启用
Use NTP 启用
NTP Hostname ntp1.aliyun.com

-w952

3.5 Rules

必须在3.4节中打开Rules,才能看到本菜单
规则解释
当Door#State状态为1的时候设置启用timer1时间2s
当time1被激活的时候设置GPIO4的值为0,并在MQTT中推送GPIO为0(因为打开门禁的时候GPIO值为1)

on System#Boot do
  GPIO,4,0
endon

on Door#State=1 do
  timerSet,1,2
endon

on rules#timer=1 do
  gpio,4,0
  Publish %sysname%/gpio/4,0
endon

Publish %sysname%/gpio/4,0
这个是解决hass中复位的问题,这样上电后MQTT读取的GPIO的值为0
如果不这样做,停电后再上电,读取到MQTT中GPIO的值为1就会触发一次继电器
这样做后hass中也可以不写任何“复位开关”的自动化配置

4.HomeAssistant

switch:
  - platform: mqtt
    name: esp_entrance_guard
    state_topic: esp-entrance-guard/Door/State
    command_topic: esp-entrance-guard/gpio/4
    payload_on: "1"
    payload_off: "0"
    state_on: "1"
    state_off: "0"
    optimistic: false
    qos: 0
    retain: true

# 界面显示中文
homeassistant:
  customize:
    switch.esp_entrance_guard:
      friendly_name: "门禁"

展示

1581918354074087
图解:
* 左边是hass,右边是MQTT客户端,下面是ESP集成板
* 黄灯亮代表继电器被激活
* 在对MQTT topic esp-entrance-guard/gpio/4 发送 1 继电器就被激活了
* 因为门禁开关是一个复位类型的开关,利用ESPEasy的rule脚本自动重置状态
* 图中有个错误gpio/4状态没有联动,这个问题已经解决见3.5节,解决之前录制的

小贴士

  • 调试MQTT客户端推荐“MQTT Explorer”因为他能直接看到所有Topic的活动情况,从而大大提高调试效率
  • 如果拿到一个未知的ESP8266集成板,不清楚这个模块和继电器如何连接的,首先还是尽量找到卖家或厂商拿到说明书。万一没说明书,用万能表的测二级管档或电阻档能自己找出来

1.综述

一句话: 关注 CNCF 基金会 Cloud Native 云原生互动全景图

CNCF云原生互动全景图
打开网站,全景图是可以点击的,在图中找你关注的领域

2.找到你关注的分类领域

比如我关注“API Gateway API网关”,就点击他的图标就可以看到相关信息
APISIX

KrakenD

非常直观的一个概览
项目开发语言,代码提交柱状图,等开源代码维护情况信息

可以说CNCF的全景图就是一张开启云原始大门的大地图,地图在手开始遨游吧

3.CNCF分类大纲 (截止2020年02月08日)

CNCF云原生互动全景图

App Definition and Development 应用定义和开发

  • Database 数据库
  • Streaming & Messaging 流处理和消息系统
  • Application Definition & Image Build 应用程序定义和图像构建
  • Continuous Integration & Delivery 持续集成与交付

Orchestration & Management 编排和管理

  • Scheduling & Orchestration 计划与编排
  • Coordination & Service Discovery 协调与服务发现
  • Remote Procedure Call 远程过程调用
  • Service Proxy 服务代理
  • API Gateway API网关
  • Service Mesh 服务网格

Runtime 运行时

  • Cloud Native Storage 云原生存储
  • Container Runtime 容器运行时
  • Cloud Native Network 云原生网络

Provisioning 提供者

  • Automation & Configuration 自动化与配置
  • Container Registry 容器注册中心
  • Security & Compliance 安全与合规
  • Key Management 密钥管理

Platform 平台

  • Certified Kubernetes – Distribution k8s认证过的产品
  • Certified Kubernetes – Hosted k8s认证过的主机
  • Certified Kubernetes – Installer k8s认证过的安装工具
  • PaaS/Container Service PaaS平台/容器服务

Observability and Analysis 可观察性和分析

  • Monitoring 监控
  • Logging 日志
  • Tracing 跟踪
  • Chaos Engineering 混沌工程

Serverless 无服务器

Members 加入CNCF的会员

Special 特别的

  • Kubernetes Certified Service Provider k8s认证服务提供商
  • Kubernetes Training Partner k8s培训合作伙伴

采用多级拦截,后置拦截的方式体系化解决

1.分层拦截

1.1第一层 商业web应用防火墙(WAF)

直接用商业服务

传统的F5硬件,不过现在用的很少了
云时代就用云时代的产品,典型代表 阿里云 web应用防火墙

1.2第二层 API 网关(API Gateway)层

API 网关(API Gateway)

kong为代表的开源 API 网关 实现
openresty + lua 自实现
windows平台 安全狗、云锁 实现

1.3第三层 应用层

用Redis内置lua脚本

redis是块砖,哪里需要哪里搬
redis内置了lua引擎,2.6版本后你可以编写一段lua脚本,完成逻辑判断流程

常见的有对某维度计数器法 对某维度令牌桶法
维度的概念比如就是IP或者IP+模块等, 多个字段合并成一个维度

本方案满足绝大多数应用层的限流需求
当然也可以自己用应用层程序实现,前提是redis+lua满足不了你的需求

2.后置拦截

基本的套路其实很简单,从日志这里计算出恶意IP,恶意用户,再给其他系统用
分控的基本思想也是这样的

已经在用ELK日志系统:可以用ES中定时查询高频IP,送入WAF做拦截
已经在用流计算系统:flink和spark等流计算系统计算出高频恶意IP,用户等

然后就可以应用这些计算出的结果数据做限制,封禁等

3.一+二+三+后置协同工作

第一层Waf当然有拦截,但是对于新IP他不会马上生效, 会有几分钟的时间才会拦截
特别是恶意爬虫IP池一上,大量新IP就来了,第一层会放过来,如果只是一层,结果就是数据库慢查询告警叮叮叮

配合上二层 三层 一层一层拦截
如果没有精力搞二层,那么第一层用买的,第二层不做,搞第三层

后置拦截的结果可以作为长期封禁使用
这种多次拦截的策略和多级缓存的概念是不是很像
多层次的拦截保障源站监控告警静悄悄

面向C端的产品被爬虫,被恶意访问的概率会大很多
面向B端的网页也不是没有风险
面向B端的API也有限流的需求

Istio

Istio官网
Istio中文官网
Istio开源
无需太多介绍Service Mesh明日之星,扛把子,截止2019.11还有太多问题没解决
复杂性,性能让人望而却步,能上生产的是真要技术厉害,还得内心强大,项目允许

Linkerd

Linkerd官网
Linkerd中文官网

A service mesh for Kubernetes and beyond. Main repo for Linkerd 2.x.
Linkerd is a service mesh, designed to give platform-wide observability, reliability, and security without requiring configuration or code changes.

Linkerd is a Cloud Native Computing Foundation (CNCF) project.

Maesh

Maesh官网
Maesh开源

Containous(Traefik 团队)推出了全新设计的轻量级 service mesh(服务网格)工具:Maesh,Maesh 允许对 Kubernetes 集群内流动的流量进行管理,这与入流量和出流量同样很重要。
Maesh 构建在 Traefk 之上,是一个简单但功能齐全的服务网格,支持最新的服务网格接口规范 SMI,有助于与现有的一些解决方案进行集成。此外,Maesh 默认是需要开启使用的,这意味着在你决定将它们添加到网格之前,你的现有服务不会受到影响。

比较喜欢用go语言编写的东西,毕竟CNCF中绝大多数项目都是go语言编写的,看后续走向

Consul

Consul官网
Consul开源

Secure Service Networking
Consul is a service networking solution to connect and secure services across any runtime platform and public or private cloud

consul原本是个分布式kv,可以用来做配置中心,注册中心,现在官方默认的宣传语都改成了连接和保护服务

定位服务发现(Service discovery)和 服务网格(Service Mesh)

Envoy

Envoy官网
Envoy开源
ENVOY IS AN OPEN SOURCE EDGE AND SERVICE PROXY, DESIGNED FOR CLOUD-NATIVE APPLICATIONS
ENVOY是开放源代码边缘和服务代理,专为云应用程序而设计

envoy作为服务网格中的通用数据平面, xDS API 已经成为数据平面API接口的实施标准
就好像AWS的OSS对象存储中的S3一样,成了事实标准

SOFAMson

SOFAMson官网
SOFAMson开源

SOFAMson 是一款使用 Go 语言开发的 Service Mesh 数据平面代理,旨在为服务提供分布式、模块化、可观察和智能化的代理能力。SOFAMosn 是 SOFAStack 中的一个项目,其中 MOSN 是 Modular Observable Smart Network 的简称。SOFAMosn 可以与任何支持 xDS API 的 Service Mesh 集成,亦可以作为独立的四、七层负载均衡使用。未来 SOFAMosn 将支持更多云原生场景,并支持 Nginx 的核心转发功能。

目前蚂蚁金服有大规模落地,2019年的双十一已经做了生产实践
API接口方面也是兼容Envoy的 xDS API 标准

错误显示如下:
———————————————————————————————————————
尝试连接到您的日志时出错:服务器响应无效 – 从日志服务器接收的对 blogger.getUsersBlogs 方法的响应无效:Invalid response document returned from XmlRpc server必须先纠正此错误才能继续操作。
———————————————————————————————————————


解决方法如下:

找到chass.ixr.php,这个文件位于wp-includes文件夹下,然后用一个文本编辑工具打开它,查找:

$length = strlen($xml);

改为:

$length = strlen($xml)+3;