容器技术
ubuntu使用APT安装docker并指定版本
Helm部署与使用
Helm常用命令
从Helm仓库创建应用流程示例
Helm部署与使用
K8S中部署mysql-ha高可用集群
helm启动mysql-ha
helm几个常用仓库
Kubernetes使用helm部署Mysql-Ha
k8s入门:Helm 构建 MySQL
docker批量修改tag(批量push)
k8s之yaml文件详解
将 MySQL 通过 bitpoke/mysql-operator 部署到 k8s 内部
k8s pvc扩容:pvc创建后扩容
K8S性能分析
部署Metrics Server
Kubernetes集群搭建
kubespray 部署常见问题和优化汇总
kubernetes-sigs/kubespray at release-2.15
K8S-pod配置文件详解
KubeSphere知识库
在 Kubernetes 上最小化安装 KubeSphere
卸载 KubeSphere 和 Kubernetes
KubeSphere 应用商店
修改pod中容器的时区
k8s之Pod安全策略
Harbor 登陆失败,用户名或者密码不正确。405 Not Allowed
Docker-leanote_n1
kubesphere/kubekey
Kubernetes Static Pod (静态Pod)
kubernets kube-proxy的代理 iptables和ipvs - 30岁再次出发 - 博客园
k8s生产实践之获取客户端真实IP - SSgeek - 博客园
kube-proxy ip-tables故障解决
k8s入门:Helm 构建 MySQL
docker批量修改tag(批量push)
prometheus operator 监控redis-exporter
Helm3 安装 ElasticSearch & Kibana 7.x 版本
kubernete强力删除namespace_redis删除namespace命令
EFK (Elasticsearch + Fluentd + Kibana) 日志分析系统
k8s日志收集实战(无坑)
fluentd收集k8s集群pod日志
Elasticsearch+Fluentd+Kibana 日志收集系统的搭建
TKE/EKS之configmap,secret只读挂载
K8s基于Reloader的ConfigMap/Secret热更新
使用 Reloader 实现热部署_k8s reloader
k8s使用Reloader实现更新configmap后自动重启pod
在 Kubernetes 上对 gRPC 服务器进行健康检查 | Kubernetes
Kubernetes ( k8s ) gRPC服务 健康检查 ( livenessProbe ) 与 就绪检查 ( readinessProbe )
排查kubernetes中高磁盘占用pod
helm 安装 MongoDB 集群
helm 安装 Redis 1 主 2 从 3哨兵
【k8s】使用 Reloader 实现热部署
k8s证书过期,更新后kubelet启动失败
kubeadm证书/etcd证书过期处理
三种监控 Kubernetes 集群证书过期方案
K8s 集群(kubeadm) CA 证书过期解决方案
k8s调度、污点、容忍、不可调度、排水、数据卷挂载
5分钟搞懂K8S的污点和容忍度(理论+实战)
Kubernetes进阶-8基于Istio实现微服务治理
macvlan案例配置
快速解决Dockerhub镜像站无法访问问题
info_scan开源漏洞扫描主系统部署
本文档使用 MrDoc 发布
-
+
首页
k8s生产实践之获取客户端真实IP - SSgeek - 博客园
- [1、概述](#1概述) - [2、环境介绍](#2环境介绍) - [3、相关说明](#3相关说明) - [4、环境准备](#4环境准备) - [5、负载配置](#5负载配置) - [6、Ingress Controller 配置](#6ingress-controller-配置) - [7、服务端验证](#7服务端验证) - [8、小结](#8小结)  ## 1、概述 通常`web`应用获取用户客户端的真实`ip`一个很常见的需求,例如将用户真实`ip`取到之后对用户做白名单访问限制、将用户`ip`记录到数据库日志中对用户的操作做审计等等 在`vm`时代是一个比较容易解决的问题,但当一切云原生化(容器化)之后变得稍微复杂了些 `k8s`中运行的应用通过`Service`抽象来互相查找、通信和与外部世界沟通,在`k8s`中是`kube-proxy`组件实现了`Service`的通信与负载均衡,流量在传递的过程中经过了源地址转换`SNAT`,因此在默认的情况下,常常是拿不到用户真实的`ip`的 这个问题在`k8s`官方文档([https://kubernetes.io/zh/docs/tutorials/services/source-ip/](https://kubernetes.io/zh/docs/tutorials/services/source-ip/))中基于`Cluster IP`、`NodePort`、`LoadBalancer`三种不同的`Service`类型进行了一定的说明,这里不再剖析 ## 2、环境介绍 本篇仅介绍**私有云**+**外部硬件负载**+**`k8s`集群**的真实场景下如何进行配置 相关环境及设备说明如下 | 组件名 | 型号或版本 | | --- | --- | | 硬件负载设备 | SANGFOR(深信服) AD 6.5R1 | | k8s Ingress 控制器 | NGINX Ingress Controller 0.25.0 | | k8s 集群 | Kubernetes 1.17.0 | ## 3、相关说明 真实生产场景下,一般提供给用户的都是七层`https`服务 首先域名解析在外部负载设备绑定的公网`ip`上,负载周边可能还会有一些安全设备例如`WAF`等,这里不多介绍 流量经过负载后进入到`k8s`集群中,其中`Ingress Controller`以`DaemonSet`方式部署并使用`hostNetwork`模式接收并处理到达宿主机的`80`、`443`端口流量 关于`https`证书的配置,一般有以下两种可选方式: - 配置在负载设备(负载类型如果只考虑七层负载),由负载负责将数据包封包解包,并转发到后端,如果用户通过`https`形式访问,流量经过的流程是:用户端——>负载`80`端口——>负载`443`端口——>服务端(`k8s node`)的`80`端口 - 配置在后端,例如`Ingress`资源上,如果用户通过`https`形式访问,流量经过的流程是:用户端——>负载`80`端口——>服务端(`k8s node`)的`80`端口——>服务端(`k8s node`)的`443`端口 但是为了获取用户的真实`ip`,只能选择方式一,因为如果证书配置在后端服务,流量经过负载时是加密的,负载一般在没有证书的情况下,是无法对数据包进行解包操作透传用户`ip`的 以上在公有云环境下,例如腾讯云`CLB`、阿里云新的应用型负载`ALB`或传统型负载`CLB`均有涉及,可能**不尽详细** ## 4、环境准备 首先需要准备一个后端获取用户请求,显示打印或输出的应用,可以自己手撸一个简单应用,当然为了操作简单也可以选择`nginx`容器在应用日志中查看,更好的方式是选择`whoami`、`echoserver`这类镜像 其中`whoami`可以在控制台访问服务时打印用户请求等相关信息,`echoserver`可以在浏览器呈现用户请求等相关信息 这里为了模拟和真实应用一样的场景,选择更为直观的`echoserver`,其源镜像地址为`gcr.io/google-containers/echoserver` 如果网络不佳,可以从我的地址获取`ssgeek/echoserver` 首先基于`k8s`部署该应用,创建`deploy`、`svc`、`ing`,定义如下 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: echoserver labels: app: echoserver spec: selector: matchLabels: app: echoserver template: metadata: labels: app: echoserver spec: containers: - name: echoserver image: ssgeek/echoserver:latest ports: - containerPort: 8080 env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP --- apiVersion: v1 kind: Service metadata: name: echoserver labels: app: echoserver spec: ports: - port: 80 targetPort: 8080 name: http selector: app: echoserver --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: echoserver annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: echo.ssgeek.com http: paths: - backend: serviceName: echoserver servicePort: 80 path: / ``` ## 5、负载配置 这里简单分析及列出关键配置 部署好后端服务后,开始配置外部(深信服)负载,除了导入`https`证书外,还需要在转发的请求头中插入`X-Forwarded-For`头部,确保用户`ip`在经过负载时作为请求头的一部分传递到后端服务器  由于负载设备到后端的`80`端口,因此后端只接收`http`请求,也就是请求经过负载处理`https`及证书相关动作   未添加请求头部改写时,对请求抓包的现象对比如下(分别为无`https`配置时和有`https`配置但未改写请求头部时)  ## 6、Ingress Controller 配置 修改`Nginx Ingress Controller`配置,添加如下内容 参考:[https://kubernetes.github.io/ingress-nginx/user-guide/](https://kubernetes.github.io/ingress-nginx/user-guide/) ```yaml data: use-forwarded-headers: "true" compute-full-forwarded-for: "true" forwarded-for-header: "X-Forwarded-For" ``` - use-forwarded-headers 如果为`true`,会将传入的`X-Forwarded-*`头传递给`upstreams` 如果为`false`,会忽略传入的`X-Forwarded-*`头,用看到的请求信息填充它们。如果直接暴露在互联网上,或者它在基于`L3/packet-based load balancer`后面,并且不改变数据包中的源`IP`时使用此选项 - forwarded-for-header 设置标头字段以标识客户端的原始`IP`地址。 默认: X-Forwarded-For - compute-full-forwarded-for 将远程地址附加到 `X-Forwarded-For`标头,而不是替换它。 启用此选项后,`upstreams`应用程序将根据其自己的受信任代理列表提取客户端`IP` ## 7、服务端验证 服务端请求暴露及应用获取`ip`效果如下  正常情况可拿到以下几类`ip` `k8s pod`自身的`ip` `k8s pod`所在`node`的`ip` 位于请求头`X-Forwarded-For`字段中 位于请求头`X-Forwarded-For`字段、`x-original-forwarded-for`字段、`x-real-ip`字段中 关于`x-forwarded-for`、`x-original-forwarded-for`、`x-real-ip`的说明: `X-Forwarded-For`用于记录从客户端地址到最后一个代理服务器的所有地址 `X-Real-IP`用于记录请求的客户端地址 `X-Original-Forwarded-For`字面意思是原始转发 IP,这是`Ingress`的功能,`Ingress`将用户的真实`IP`记录到了这个字段 对应用来说,以`java`应用为例,获取用户`ip`的代码如下 ```java /** * 获取操作用户ip * @return */ private String getIp() { HttpServletRequest request; try { request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } catch (NullPointerException e) { return "127.0.0.1"; } //取客户端ip String ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1")) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } return ipAddress; } ``` ## 8、小结 本文记录了私有云和有外部负载的真实场景下,`k8s`集群中的应用获取用户`ip`的相关实现逻辑及关键处理,希望能帮助到大家 See you ~
adouk
2023年3月24日 09:49
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码