第11章 流量路由Ingress
Ingress是Kubernetes集群对外暴露服务的核心方式之一,另外一个方式是云产品负载均衡。
本章会对Ingress进行详细的说明,内容包括基本原理,场景化需求,获取客户端真实IP地址以及白名单机制。
11.1 基本原理
11.1.1 解决的问题
Kubernetes 集群有四种类型的服务,分别是ClusterIP、NodePort、LoadBalancer以及ExternalName。
ClusterIP 类型的服务只能在集群内访问,而NodePort 和LoadBalancer两种类型的服务都可以从集群外部访问。这三种服务有一个共同特点,就是理论上只能通过四层协议来访问。LoadBalancer类型的服务虽然也可以配置对应负载均衡的HTTP/HTTPS属性,但也只提供了简单的代理,无法应对复杂的七层的策略路由场景。
Ingress的存在,正是为了解决以上问题。Ingress的职责是将不同URL的请求转发给不同的服务,以实现复杂路由策略。典型的Ingress 组件,是基于Nginx七层代理实现的Nginx Ingress Controller。如图11-1所示,这是一个典型的七层路由的例子。
图11-1 七层路由典型示例
访问www.bk.com/bookinfo相关的信息需要路由到Bookinfo这个服务进行处理,而访问www.bk.com/subscription相关的信息需要路由到Subscription这个服务进行处理。
11.1.2 基础用法
我们需要定义Ingress模板去实现上一节中的功能。前面的内容中分别定义了Ingress的API版本、对象类型以及元数据,而具体的路由规则在Spec中指定。
11.1.3 配置安全路由
上一节中的定义,实现了一个HTTP协议的站点路由。然而HTTP协议是明文在网络上传输的,容易被窃取信息。大多数情况下,我们需要部署一个安全的站点,采用HTTPS协议访问。本节将讲述如何部署安全路由。
如果是测试环境,通常只需要自签名证书即可,具体操作有以下几个步骤。
- 使用openssl工具生成私钥和证书文件。
- 创建Secret,并将私钥和证书存放到其中。
- 修改Ingress模板文件,添加
spec.tls配置。
创建或者更新Ingress-example后,证书就会被Ingress控制器配置到对应的七层代理中。
如果是在正式环境中,通常需要去CA申请证书,并拿到私钥文件(tls.key)和证书链文件(tls.pem),这时可以跳过上面的第1步,只执行第2步和第3步即可。
关于安全路由的配置,需要注意以下几点:
tls.key和tls.crt字段名称不能修改。- Secret必须和Ingress在同一个命名空间,否则证书配置会失败。
tls.key、tls.crt存放的其实是key和crt的Base64编码文件,在Linux环境中可以用Base64命令解码对比证书信息。
11.1.4 全局配置和局部配置
有些场景下,我们需要对Ingress做更复杂的配置,例如让访问日志打印出响应时长以统计Web性能,或将URL重定向到根路径。
对于常规的Nginx来说,我们只需要修改nginx.conf文件即可。然而对Kubernetes来说,因为Nginx Ingress Controller封装了Nginx,所以我们不能直接修改Nginx的配置。
正确的做法是,通过在Ingress的编排文件中添加Annotation的方式配置局部参数,或者通过修改nginx-configuration这个ConfigMap的方式来配置全局参数。
11.1.5 实现原理
在Ingress的使用过程中,常常会遇到一些问题,比如客户端访问偶尔报“502”或“504”错误、域名证书配置不生效、获取不到客户端真实IP地址等。
了解Ingress路由的实现原理,对于解决Ingress各种问题十分有帮助。本节将讲解Ingress是如何实现复杂的七层路由策略的。
以阿里云的Ingress组件为例,Ingress可以分为三个部分,分别是入口SLB(由nginx-ingress-lb这个Service创建)、控制器以及Nginx代理,如图11-2所示。
图11-2 基于Nginx实现的Ingress
Ingress 的实现包含了两部分内容,一个是Controller,即Ingress控制器,另一个是Nginx代理。当创建一个Ingress对象的时候,控制器会作为Ingress对象和Nginx之间的翻译官,把Ingress中定义的路由配置转换成Nginx的配置。
如果Ingress定义有错误,翻译工作会失败,导致最新的规则没有办法下发到Nginx里。控制器日志和Nginx的Access/Error日志,都会汇总到Nginx Ingress Controller这个Pod的标准输出,可以从Pod的标准输出查看控制器的日志或者Nginx的访问日志。
一般情况下,如果需要查看生效的Nginx配置,我们可以登录到Nginx Ingress Controller容器里,查看配置文件/etc/nginx/nginx.conf。
值得一提的是,为了减少对Nginx配置高频率加载这样的操作,Nginx Ingress Controller引入了Lua模块。Lua模块可以协助Nginx低“成本”地更新Upstream,以避免此类操作对业务的影响。
在Ingress控制器里获取Lua配置的方法如下。
- 进入Pod。
- 查看配置。
11.2 场景化需求
11.2.1 多入口访问Ingress
Nginx Ingress Controller和其入口SLB是解耦的,若要增删改查Ingress,控制器就会去配置Nginx,但不会影响入口SLB。所以我们可以通过创建多个入口SLB的方式(即LoadBalancer类型的Service),把Ingress暴露到集群之外。
特别是,为了节约SLB的费用,可以将Ingress入口SLB改成内网类型,然后手动在SLB上绑定一个弹性公网IP地址,这样内网和外网都可以访问Ingress,同时只需要一个SLB。
11.2.2 部署多套Ingress Controller
有时候,我们需要部署多套Ingress Controller,一套给VPC内网使用,另一套给公网使用,如图11-3所示。
图11-3 部署多套Ingress控制器
若集群有多套Ingress控制器,在创建Ingress的时候,怎么区分由哪一个控制器来配置Ingress呢?那就要通过IngressClass来区分。
部署Ingress Controller的时候,会传入一个--ingress-class来标记控制器,然后在创建Ingress的时候,通过在Annotation中加入kubernetes.io/ingress.class参数,Ingress控制器将kubernetes.io/ingress.class参数的值和设定的--ingress-class的值相比较,如匹配得上,则由自己来负责配置Ingress,如匹配不上,则忽略这个Ingress,同时,日志中会打印出类似“ignoring add for ingress test-second-nginx based on annotation kubernetes.io/ingress.class with value”这样的记录。
当然,集群会有默认的控制器,如果Ingress并未添加kubernetes.io/ingress.class这个注解,则由默认的控制器来解析Ingress。此外,新的控制器也会有自己的一套“nginx-configuration”,用于对Ingress-nginx的全局配置。
11.3 获取客户端真实IP地址
11.3.1 理解客户端真实IP地址的传递过程
这里以阿里的Kubernetes为例,最简单的访问链路是:Client → SLB → Ingress-nginx → Pod。我们分两种情况说明下。
第一种情况,SLB配置为四层(默认),如图11-4所示。
图11-4 四层网络获取客户端IP地址
- 第1步,通过客户端访问SLB,源IP地址为1.1.1.1,假设源端口为随机端口42000,目的IP地址为SLB的IP地址2.2.2.2,目的端口为80。
- 第2步,Ingress-nginx收到来自SLB的报文,源IP地址为1.1.1.1,源端口为42000,目的IP地址为3.3.3.3,目的端口为80。即Ingress-nginx“看到”的是Client在访问自己,而非SLB,因此Ingress-nginx能获取到客户端的真实IP地址,无须做任何配置改动。接着Ingress-nginx会将客户端真实IP地址放在
X-Real-IP和X-Forwarded-For这两个Header里传给Pod。虽然Ingress-nginx的remote_addr也会存放客户端真实IP地址,但remote_addr并不会作为Header传递下去,Pod中不能以此Header作为获取客户端IP地址的方式。
需要说明一点,为了简化原理,第2步省去了SLB转发给节点,再由节点转发给Ingress-nginx的中间环节,事实上,某些情况下SLB也确实能直接转发给Pod,不经过节点。
第二种情况,SLB配置为七层,如图11-5所示。
图11-5 七层网络获取客户端IP地址
此种情况下,Client发请求到SLB,SLB会将Client的IP地址放到X-Forwarded-For(阿里云的SLB七层监听默认开启了X-Forwarded-For)这个Header里,然后传递给Ingress-nginx。而Ingress-nginx收到请求报文的源IP地址是SLB内部的IP地址,因此默认情况下,Ingress-nginx会将SLB的内部IP地址视为客户端真实IP地址,需要配置Ingress-nginx来开启X-Forwarded-For特性。
下面来说明开启X-Forwarded-For后,X-Forwarded-For的传递过程。
- 第1步,一级代理Tenginx收到Client的请求,Tenginx会将Client的IP地址“append”到
X-Forwarded-For中(此时看到的就是:“X-Forwarded-For:1.1.1.1”)并且将此Header一起发送给二级代理Ingress-nginx。 - 第2步,二级代理Ingress-nginx收到请求,会继续将前一级代理的IP地址(这里是2.2.2.2)“append”到
X-Forwarded-For中(此时看到的是“X-Forwarded-For:1.1.1.1, 2.2.2.2”)并将此Header通过请求一并发送给Pod。
这里有两点要注意:
- 每经过一层代理,Nginx都会把前一级的IP地址“append”到
X-Forwarded-For中,而不是直接覆盖。- “append”意为追加,即把前一级的IP地址追加到后面,所以无论经过多少级代理,Client的真实IP地址都是放在
X-Forwarded-For中的第一个IP地址。
11.3.2 ExternalTrafficPolicy的影响
ExternalTrafficPolicy字段通常出现在LoadBalancer类型的服务中,它有两个值:Local和Cluster。下面用两张图解释一下Local和Cluster的区别,如图11-6、图11-7所示。
图11-6 Local模式网络转发
在Local模式下,SLB会将流量发往Pod所在的节点,即节点1和节点3,然后转发到本节点的Pod上。
图11-7 Cluster模式网络转发
在Cluster模式下,SLB会将流量随机转发到任意一个Worker节点,然后Worker节点再随机转发到其中一个Pod上,既可能是转发到本节点的Pod上,也可能是转发到其他节点的Pod上。跨节点转发是由SNAT实现的,而SNAT会修改掉报文的Source IP地址,Pod收到的报文的Source IP地址就是节点的IP地址,这样就把前一级的真实IP地址替换掉了。前面讲过,SLB在四层的情况下,Client的真实IP地址是透传(不经过代理修改,直接转发)的,因此,Ingress-nginx无法获取客户端的真实IP地址。
11.3.3 如何获取客户端真实IP地址
根据上节所讲,需要将ExternalTrafficPolicy的值设置为Local,才能保证Ingress-nginx可以获取客户端的真实IP地址。这里分两种情况。
- SLB为四层转发时,Ingress-nginx可以正确获取到客户端真实IP地址,不需要做任何改动,Pod需要从Header里获取客户端真实IP地址。
- SLB为七层转发时,Tenginx会将客户端真实IP地址放到
X-Forwarded-For里,同样,Ingress-nginx也会添加Tenginx的IP地址到X-Forwarded-For并传递给Pod。当Tenginx前面有多层代理时也是一样的。同时,我们需要在SLB和Ingress-nginx上开启X-Forwarded-For。SLB默认开启,Ingress-nginx开启X-Forwarded-For需要修改nginx-configuration,在data中添加:
data:
enable-real-ip: "true"
forwarded-for-header: "X-Forwarded-For"
proxy-real-ip-cidr: 0.0.0.0/0 # 或者限定SLB内部的CID`proxy-real-ip-cidr: 0.0.0.0/0 # 或者限定SLB内部的CIDR`
## 11.4 白名单功能
[[Nginx Ingress Controller]]官方网站给出了Ingress白名单配置的一般方法:
- 通过[[Annotation]]配置单个Ingress,作用范围是本Ingress,对应的Annotation:`nginx.ingress.kubernetes.io/whitelist-source-range`,值填写CIDR,用逗号分隔符分隔,如 `“192.168.0.0/24, 10.0.0.10”`.
- 配置到`nginx-configuration`里,作用范围为全局,参数:`whitelist-source-range`.要说明的一点是,全局参数会被Annotation的设置覆盖.
Ingress白名单的原理是判断客户端真实IP地址是否在白名单列表里,所以Ingress-nginx能否正确获取客户端的真实IP地址,是白名单能否生效的关键.因此,只要解决了Ingress-nginx如何获取客户端真实IP地址的问题,那么Ingress白名单问题就迎刃而解.
前面讲了Ingress-nginx无法获取客户端真实IP地址的一些典型场景,比如:`nginx-ingress-lb`的`ExternalTrafficPolicy`是`Cluster`,需要改为`Local`;`nginx-ingress-lb`的SLB是七层的,需要手动设置`X-Forwarded-For`;`nginx-ingress-lb`前面还存在代理等.前面已经讲过如何获取客户端真实IP地址,这里不再赘述.
Ingress-nginx的新版本实现比较简单,Ingress控制器直接在`nginx.conf`的location下面添加:
```nginx
if ($the_real_ip !~ "^(110\.110\.0\.0/24)") {
return 403;
}对于老版本的Ingress-nginx,是通过Nginx的Geo模块实现的,通常配置成这样:
geo $the_real_ip {
default 1;
110.110.0.0/24 0;
}
server {
...
if ($block_ip) {
return 403;
}
}$the_real_ip如果落在110.110.0.0/24中,则放行(0),否则进入“default”,即拒绝(1)。可以通过查看Ingress-nginx的配置文件来确认白名单是否配置成功,配置文件路径为/etc/nginx/nginx.conf。
11.5 总结
本章讲述了Ingress的基本用法和原理,以及一些使用场景。事实上,Ingress在使用的过程中会有更多更复杂的场景,无法一一列出。对于Ingress在实际使用过程中遇到的一些问题,本章可以作为参考。