惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
O
OpenAI News
酷 壳 – CoolShell
酷 壳 – CoolShell
The GitHub Blog
The GitHub Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 聂微东
Engineering at Meta
Engineering at Meta
W
WeLiveSecurity
Hacker News: Ask HN
Hacker News: Ask HN
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News
D
Docker
F
Full Disclosure
AI
AI
罗磊的独立博客
博客园 - 【当耐特】
U
Unit 42
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Palo Alto Networks Blog
博客园_首页
H
Help Net Security
量子位
月光博客
月光博客
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
F
Fortinet All Blogs
D
DataBreaches.Net
B
Blog RSS Feed
Webroot Blog
Webroot Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
爱范儿
爱范儿
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
C
CERT Recently Published Vulnerability Notes
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Securelist

陈少文的网站

巨变与机遇的未来十年 Kubernetes 平台管理软件压力测试方案 使用镜像部署 Hexo 静态页面 终于等到你 - GitHub 镜像仓库服务(ghcr.io) 一起来学 Go --(6)Interface 一起来学 Go --(5)Goroutine 和 Channel 什么是函数式编程 如何在 Kubernetes 集群集成 Kata 柯里化与偏函数 使用 PyGithub 自动创建 Label 软件产品是团队能力的输出 Helm 2 、Helm 3 比较 IoT 变现 Kubernetes 中的 DNS 服务 国内的 Helm 镜像源 Harbor 使用自签证书支持 Https 访问 DevOps 工具链之 Prow 如何使用 kfctl 安装 Kubeflow VS Code 无法下载 Go 插件的工具包 工程师更应具有服务精神 你不知道的 Docker 使用技巧 使用 Docker 运行 Tensorflow 论中国 什么是左移 如何清空 Git 仓库全部历史记录 一禅小和尚 有风吹过厨房 时间的玫瑰 如何在 CentOS 安装 GPU 驱动 开发 Tips(19) 使用 Velero 备份 Kubernetes 集群 Kubernetes Cheat Sheet 开发 Tips(18) 如何构建一个 Java 工程 开发 Tips(17) KubeSpray 安装 Kubernetes 报错 ip in ansible_all_ipv4_addresses 基于 Kubernetes 和 Jenkins 搭建自动化测试系统 在 Kubernetes 上动态创建 Jenkins Slave 使用 Jenkins 进行服务拨测 开发 Tips(16) Kubernetes 签发 Ingress 证书及日常故障运维 Kubernetes 中 Deployment 的基本操作 Kubernetes 中的证书 如何使用 KubeBuilder 开发一个 Operator Kubernetes 1.6.0 安装问题汇总 镜像管理工具 -- Harbor 开发 Tips(15) Docker 如何拉取镜像 开发 Tips(14) 使用 Helm 安装 harbor 开发 Tips(13) 使用 S2I 构建云原生应用 在 Kubernetes 中使用 emptyDir、hostPath、localVolume 开发 Tips(12) 开发 Tips(11) 代码质量分析工具 SonarQube 使用 Kubeadm 安装 Kubernetes 集群 一起来学 Go --(4)常用函数 Kubernetes 中的 Ceph Kubernetes 之 Volumes Kubernetes 之 Labels、Selectors 开发 Tips(10) 开源正在重构商业模式 Kubernetes 之网络 Kubernetes 之 API 使用 Helm 和 Operator 快速部署 Prometheus Kubernetes 复杂有状态应用管理框架 -- Operator Kubernetes 的包管理器 -- Helm 一起来学 Go --(3)Go Modules 如何一步一步地优化博客方案 kubectl 实用指南 Kubernetes 中的基本概念 搭建远程 Kubernetes 开发环境 大公司和小公司的 ToB 思路 开发 Tips(9) Go 入门指南 一起来学 Go --(2)数据与逻辑结构 如何预防 Web 富文本中的 XSS 攻击 django-xss-cleaner 云工作时代 一起来学 Go --(1)背景与特点 SaaS 开发团队的不同阶段 你不知道的 Git 使用技巧 输出既服务 微服务设计 继续奔跑 开发 Tips(8) 从账户安全到二次验证 Django 性能之数据库查询优化 Django 性能之分库分表 敏捷开发之研发流程 打造一致性的团队 开发 Tips(7) Pytest 进阶学习之 Mock PaaS 部署之 buildpack Go 开发配置 领域输出才是 PaaS 的核心竞争力 Pytest 入门学习 开发 Tips(6) 如何使用 Jenkins、Docker、GitLab 搭建 Django 自动化部署流程
在 Kubernetes 中如何获取客户端真实 IP
微信公众号 · 2020-11-21 · via 陈少文的网站

Kubernetes 依靠 kube-proxy 组件实现 Service 的通信与负载均衡。在这个过程中,由于使用了 SNAT 对源地址进行了转换,导致 Pod 中的服务拿不到真实的客户端 IP 地址信息。本篇主要解答了在 Kubernetes 集群中负载如何获取客户端真实 IP 地址这个问题。

1. 创建一个后端服务

1.1 服务选择

这里选择 containous/whoami 作为后端服务镜像。在 Dockerhub 的介绍页面,可以看到访问其 80 端口时,会返回客户端的相关信息。在代码中,我们可以在 Http 头部中拿到这些信息。

1
2
3
4
5
6
7
8
9
Hostname :  6e0030e67d6a
IP :  127.0.0.1
IP :  ::1
IP :  172.17.0.27
IP :  fe80::42:acff:fe11:1b
GET / HTTP/1.1
Host: 0.0.0.0:32769
User-Agent: curl/7.35.0
Accept: */*

1.2 集群环境

集群有三个节点,一个 master ,两个 worker 节点。

1
2
3
4
5
6
kubectl get node -o wide

NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION               CONTAINER-RUNTIME
master   Ready    master   91d   v1.17.9   192.168.13.4   <none>        CentOS Linux 7 (Core)   3.10.0-957.21.3.el7.x86_64   docker://19.3.8
node1    Ready    worker   91d   v1.17.9   192.168.13.5   <none>        CentOS Linux 7 (Core)   3.10.0-957.21.3.el7.x86_64   docker://19.3.8
node2    Ready    worker   91d   v1.17.9   192.168.13.6   <none>        CentOS Linux 7 (Core)   3.10.0-957.21.3.el7.x86_64   docker://19.3.8

1.3 创建服务

  • 创建命名空间
1
kubectl create ns realip
  • 创建负载
1
kubectl -n realip run myservice --image=containous/whoami
  • 创建服务
1
kubectl -n realip expose deploy myservice --type=NodePort --port=80
  • 查看创建的服务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
kubectl -n realip get pod,deploy,svc  -o wide

NAME                           READY   STATUS    RESTARTS   AGE    IP             NODE     NOMINATED NODE   READINESS GATES
pod/myservice-fc55d766-9ttxt   1/1     Running   0          2m1s   10.233.70.42   master   <none>           <none>

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS   IMAGES              SELECTOR
deployment.apps/myservice   1/1     1            1           2m1s   myservice    containous/whoami   run=myservice

NAME                TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE   SELECTOR
service/myservice   NodePort   10.233.13.66   <none>        80:31509/TCP   5s    run=myservice
  • 访问服务

浏览器打开 Master 节点的 EIP + :31509 时,返回如下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Hostname: myservice-fc55d766-9ttxt
IP: 127.0.0.1
IP: 10.233.70.42
RemoteAddr: 192.168.13.4:21708
GET / HTTP/1.1
Host: dev.chenshaowen.com:31509
User-Agent: Chrome/86.0.4240.198 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: lang=zh;
Dnt: 1
Upgrade-Insecure-Requests: 1

可以看到 RemoteAddr 是 Master 节点的 IP ,并不是访问客户端的真实 IP 地址。

1.3 准备 Ingress Controller 和 LB

这里为了更好模拟生产环境的场景,增加了可能的两个链路节点 Ingress 和 Load Balancer(以下简称 LB) 。

  • 安装 Ingress Controller

参考文档: 使用 Helm 安装 Ingress

  • 准备 LB

这里使用的是云厂商的 LB,如果是物理机,也可以使用针对物理机的 LB。当然,也可以使用 keepalived + haproxy 替代 LB。

2. 直接通过 NortPort 访问获取真实 IP

在上面的访问中,获取不到客户端真实 IP 的原因是 SNAT 使得访问 SVC 的源 IP 发生了变化。将服务的 externalTrafficPolicy 改为 Local 模式可以解决这个问题。

执行命令:

1
kubectl -n realip patch svc myservice  -p '{"spec":{"externalTrafficPolicy":"Local"}}'

访问服务,可以得到如下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Hostname: myservice-fc55d766-9ttxt
IP: 127.0.0.1
IP: 10.233.70.42
RemoteAddr: 139.198.254.11:51326
GET / HTTP/1.1
Host: dev.chenshaowen.com:31509
User-Agent: hrome/86.0.4240.198 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: max-age=0
Connection: keep-alive
Cookie: lang=zh;
Dnt: 1
Upgrade-Insecure-Requests: 1

Cluster 隐藏了客户端源 IP,可能导致第二跳到另一个节点,但具有良好的整体负载分布。 Local 保留客户端源 IP 并避免 LoadBalancer 和 NodePort 类型服务的第二跳,但存在潜在的不均衡流量传播风险。(Kubernetes 官方解释)

下面是对比简图:

当请求落到没有服务 Pod 的节点时,将无法访问。用 curl 访问时,会一直停顿在 TCP_NODELAY , 然后提示超时:

1
2
3
4
5
6
*   Trying 139.198.112.248...
* TCP_NODELAY set
* Connection failed
* connect to 139.198.112.248 port 31509 failed: Operation timed out
* Failed to connect to 139.198.112.248 port 31509: Operation timed out
* Closing connection 0

3. 通过 LB -> Service 访问获取真实 IP

在生产环境,通常会有多个节点同时接收客户端的流量,如果仅使用 Local 模式将会导致服务可访问性变低。引入 LB 的目的是为了利用其探活的特点,仅将流量转发到存在服务 Pod 的节点上。

如下图可以看到,在服务的 31509 端口仅 master 节点处于活跃状态,流量也仅会导向 master 节点,符合预期。

接着继续增加副本数量到 3

1
2
3
4
5
6
7
kubectl -n realip scale deploy myservice --replicas=3
kubectl -n realip get pod  -o wide

NAME                       READY   STATUS    RESTARTS   AGE     IP              NODE     NOMINATED NODE   READINESS GATES
myservice-fc55d766-9ttxt   1/1     Running   0          144m    10.233.70.42    master   <none>           <none>
myservice-fc55d766-f8wbs   1/1     Running   0          5m13s   10.233.90.143   node1    <none>           <none>
myservice-fc55d766-nzwzn   1/1     Running   0          5m13s   10.233.70.45    master   <none>           <none>

遗憾的是,Pod 并没有均匀分布在三个节点,其中有两个处于 master 上。因此 LB 的后端节点也没有完全点亮。如下图:

这就需要给 deploy 加上反亲和性的描述。执行命令:

1
kubectl -n realip edit deploy myservice

这里有两种选择。第一种是配置软策略,但不能保证全部 LB 后端点亮,均匀分配到流量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
spec:
  template:
    metadata:
      labels:
        app: myservice
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - myservice
                topologyKey: kubernetes.io/hostname

另一种是配置硬策略,强制 Pod 分配在不同的节点上,但会限制副本数量,也就是 Pod 总数不能超过 Node 总数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
spec:
  template:
    metadata:
      labels:
        app: myservice
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - myservice
              topologyKey: kubernetes.io/hostname

采用硬策略的配置,最终点亮全部后端,如下图:

4. 通过 LB -> Ingress -> Service 访问获取真实 IP

如果每一个服务都占用一个 LB,成本很高,同时配置不够灵活,每次新增服务时,都需要去 LB 增加新的端口映射。

还有一种方案是 LB 将 80、443 的流量导给 Ingress Controller,然后将流量转发到 Service,接着达到 Pod 中的服务。

此时,需要 LB 能做 TCP 层的透传,或者 HTTP 层的带真实 IP 转发,将 Ingress Controller 的 externalTrafficPolicy 设置为 Local 模式,而 Service 可以不必设置为 Local 模式。

如果想要提高可访问性,同样可以参考上面配置反亲和性,保证在每个后端节点上都有 Ingress Controller 。

将 Ingress Controller 改成 DaemonSet 也是一种部署方式。

访问服务,可以得到如下内容:

Hostname: myservice-7dcf6b965f-vv6md
IP: 127.0.0.1
IP: 10.233.96.152
RemoteAddr: 10.233.70.68:34334
GET / HTTP/1.1
Host: realip.dev.chenshaowen.com:30000
User-Agent: Chrome/87.0.4280.67 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: max-age=0
Cookie: _ga=GA1.2.896113372.1605489938; _gid=GA1.2.863456118.1605830768
Cookie: lang=zh;
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 139.198.113.75
X-Forwarded-Host: realip.dev.chenshaowen.com:30000
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Original-Uri: /
X-Real-Ip: 139.198.113.75
X-Request-Id: 999fa36437a1180eda3160a1b9f495a4
X-Scheme: http

X-Forwarded-For 中,已经能够拿到客户端的真实 IP 信息。

在有个文档中,我看到强调还需在 Ingress configuration 中增加如下内容

1
2
3
4
5
data:
  compute-full-forwarded-for: "true"
  forwarded-for-header: X-Forwarded-For
  use-forwarded-headers: "true"
  # use-proxy-protocol: "true"

但这里并没有配置,也能拿到 Forward 字段信息,可能和 Ingress Controller 版本有关。如果在返回中没有 X- 头部时,可以尝试。

这里贴一下 Ingress 的相关配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kubectl  -n realip get ingress realip -o yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubesphere.io/creator: admin
  generation: 1
  name: realip
  namespace: realip
  resourceVersion: "38923603"
  selfLink: /apis/extensions/v1beta1/namespaces/realip/ingresses/realip
spec:
  rules:
  - host: realip.dev.chenshaowen.com
    http:
      paths:
      - backend:
          serviceName: myservice
          servicePort: 80
        path: /
status:
  loadBalancer: {}
1
2
3
kubectl get  svc -n realip

router-realip    NodePort    10.233.39.119   <none>        80:30000/TCP                 3h18m

流量的转发路径:

LB(80/443) -> Ingress Controller(30000) -> myservice(80) -> myservice-fc55d766-xxxx(80)

5. 总结

本文介绍了三种获取真实 IP 的部署方式:

  • 直接通过 NortPort 访问获取真实 IP

受制于 Local 模式,可能会导致服务不可访问。需要保证对外提供入口的节点上,必须具有服务的负载。

  • 通过 LB -> Service 访问获取真实 IP

利用 LB 的探活能力,能够提高服务的可访问性。适用于服务较少,或者愿意每个服务一个 LB 的场景。

  • 通过 LB -> Ingress -> Service 访问获取真实 IP

通过 LB 将 80、443 端口的流量转到 Ingress Controller ,再进行服务分发。但 Ingress Controller 使用 Local 模式,就要求 LB 的每个后端节点都有 Ingress Controller 副本。适用于对外暴露服务数量较多的场景。

当然也可以组合使用,对于并不需要获取客户端真实 IP 的服务,可以继续使用 Cluster 模式。

6. 参考文档