Service & Ingress
熟悉 k8s 的同学都知道,k8s 为了能够访问部署在其内部的服务,抽象出一个称为 Service
的对象,这个 Service
对象就好比一组 Pod
(也可理解成一组服务) 的 LoadBalance,这样就避免了每次重启容器 IP 地址变动的问题。
大多数情况,部署在 k8s 中的都是 Web 服务器,为每一个 Web 服务器都去分配一个 LoadBalance 显得过于浪费。为了解决这个问题,k8s 又抽象出一个称为 Ingress
的对象,这个 Ingress
对象可以简单看作一个 Web 反向代理,如下图,可以根据请求域名,请求 path
将请求转发至具体的 Pod
。
那么,根据文章标题「外部访问k8s中的Web服务方案」,似乎使用 Ingress
就能解决,但往往是 "理想很丰满 现实很骨感",Ingress
并不能完全兼容我们目前的情况。
1.0 手动模式
在没使用 k8s 之前,我们是通过 Nginx 作为 Web 的反向代理服务器,Nginx 的配置中有很多 rewrite
规则,lua
脚本;在使用 k8s 之后,这些 Nginx 配置不能完全地迁移到 Ingress
中,所以就用了如下图的方案:
图中 ➀ 的 Nginx 部署在 k8s 集群中,还继续使用之前的配置,不同的是将请求转发至相应的 Service
上。但这有个很大的问题就是每新增一个应用,我都需要手动地查询出 Service
对象的 IP,并增加 Nginx 的配置。随着服务的越拆越微,手动维护的成本就越来越高,自动化的方案就越来越迫切。
2.0 自动模式
如下图所示,该方案只是新增了图中 ➁ 的 Nginx Ingress
。利用 Nginx
的泛域名转发,将没有匹配到的域名全部转发给 Ingress
, 再由 Ingress
配置的规则转发至具体的后端服务中。
因为我们大多数情况下的转发配置还是很简单的,使用 Ingress
完全能满足。这样就在创建应用的自动化流程中新增一个 Ingress
资源,而不用去手动维护 Nginx 的配置了。
比如我在 ➀ 中的 Nginx 配置如下:
...
// 具体的 xx.mydomain.com 域名转发至具体的 Service
server {
listen 80;
server_name xx.mydomain.com;
location / {
proxy_pass http://xx;
}
}
// 没有匹配到的 mydomain.com 域名都转发至 ingress
server {
listen 80;
server_name ~^([\w-]+)\.mydomain\.com$;
location / {
proxy_pass http://ingress-controller;
}
}
...
Invalid CORS request
但是在迁移该方案后,在使用 HTTPS
的情况下,访问出现了 403 Invalid CORS request
的错误,出现了跨域的问题,看了后端的 Spring Boot 有个 CORS 的配置:
...
@Configuration
@EnableSpringDataWebSupport
public class WebConfig implements WebMvcConfigurer {
...
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080");
}
}
这里的 allowedOrigins
只是 http://localhost:8080
,但为什么使用 HTTP
去访问没问题呢,继续 Debug 跟踪发现 DefaultCorsProcessor
类中的 processRequest
方法有关, processRequest
会对请求做 CORS 的检测:
其中有个 isSameOrigin
方法会取 http header 中的 X-Forwarded-Proto
X-Forwarded-Host
X-Forwarded-Port
去与 header 中的 Origins
地址去对比,一致则能通过;
之前在 ➀ 中的 Nginx 就将这些相关的信息放在 header 中透传给后端了:
...
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
...
看来是 ➁ 中的 Nginx Ingress 没有把这个信息继续透传过去,继续看 Nginx Ingress 源码,发现它是根据一个 golang 的 nginx 模版 nginx.tmpl
生成 Nginx 的配置:
可见模版中对 X-Forwarded-Proto
X-Forwarded-Host
X-Forwarded-Port
进行了重新配置,但这三个变量 $best_http_host
$pass_port
$pass_access_scheme
好像不是 Nginx 的内置变量,继续看是在哪里定义的,发现在 lua_ingress.lua
中有如下定义:
可以知道在使用 Nginx Ingress 的时候配置 use-forwarded-headers 为 true,就能使用 ➀ 中 Nginx 透传过来的配置了。
访问客户端真实的 IP
在之前的方案中将 ➀ 中 Nginx $remote_addr
获取到的客户端真实 IP 放在 http header X-Real-IP
传给后端:
proxy_set_header X-Real-IP $remote_addr;
但 ➁ 中的 Nginx Ingress 好像又对 X-Real-IP
做了处理,他直接使用了 $remote_addr
, 这样岂不是获取到的都是 ➀ 中 Nginx 的 IP 了。
但是情况并不是这样的,它确能够获取到真实的 IP,这里就比较奇怪了,找了很久的代码都没发现有对 $remote_addr
变量有处理,后来发现它是直接使用了 Nginx 的 ngx_http_realip 模块进行处理,简单来说就是解析 http header 中的 X-Forwareded-For
中的地址,从而获取真实的 IP,在 ➁ 中的模版中有一段关于 ngx_http_realip 模块的配置:
有关于 ngx_http_realip 模块可以参考我的另一篇文章 ngx_http_realip模块获取客户端真实IP
小结
如果用 2.0 的自动化方案,➀ 中的 Nginx 需要 http header 中传递相关信息,配置如下:
server {
listen 80;
server_name ~^([\w-]+)\.mydomain\.com$;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name ~^([\w-]+)\.mydomain\.com$;
include wildcard_ssl_conf;
location / {
proxy_http_version 1.1;
proxy_pass http://nginx-ingress;
proxy_set_header Host $host;
# spring cors check
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
# websocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# real ip
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
➁ 中的 Nginx Ingress 需要通过 ConfigMap
来配置 use-forwarded-headers:
apiVersion: v1
data:
use-forwarded-headers: "true"
kind: ConfigMap
metadata:
name: nginx-ingress-controller
namespace: nginx-ingress
有疑问加站长微信联系(非本文作者)