外部访问k8s中的Web服务方案

猴子精h · · 1399 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Service & Ingress

熟悉 k8s 的同学都知道,k8s 为了能够访问部署在其内部的服务,抽象出一个称为 Service 的对象,这个 Service 对象就好比一组 Pod (也可理解成一组服务) 的 LoadBalance,这样就避免了每次重启容器 IP 地址变动的问题。

大多数情况,部署在 k8s 中的都是 Web 服务器,为每一个 Web 服务器都去分配一个 LoadBalance 显得过于浪费。为了解决这个问题,k8s 又抽象出一个称为 Ingress 的对象,这个 Ingress 对象可以简单看作一个 Web 反向代理,如下图,可以根据请求域名,请求 path 将请求转发至具体的 Pod

image.png

那么,根据文章标题「外部访问k8s中的Web服务方案」,似乎使用 Ingress 就能解决,但往往是 "理想很丰满 现实很骨感",Ingress 并不能完全兼容我们目前的情况。

1.0 手动模式

在没使用 k8s 之前,我们是通过 Nginx 作为 Web 的反向代理服务器,Nginx 的配置中有很多 rewrite 规则,lua 脚本;在使用 k8s 之后,这些 Nginx 配置不能完全地迁移到 Ingress 中,所以就用了如下图的方案:

外部访问k8s中的web服务(旧).png

图中 ➀ 的 Nginx 部署在 k8s 集群中,还继续使用之前的配置,不同的是将请求转发至相应的 Service 上。但这有个很大的问题就是每新增一个应用,我都需要手动地查询出 Service 对象的 IP,并增加 Nginx 的配置。随着服务的越拆越微,手动维护的成本就越来越高,自动化的方案就越来越迫切。

2.0 自动模式

如下图所示,该方案只是新增了图中 ➁ 的 Nginx Ingress。利用 Nginx 的泛域名转发,将没有匹配到的域名全部转发给 Ingress, 再由 Ingress 配置的规则转发至具体的后端服务中。

因为我们大多数情况下的转发配置还是很简单的,使用 Ingress 完全能满足。这样就在创建应用的自动化流程中新增一个 Ingress 资源,而不用去手动维护 Nginx 的配置了。

外部访问k8s中的web服务.png

比如我在 ➀ 中的 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 的检测:

WX20200201-001207@2x.png

其中有个 isSameOrigin 方法会取 http header 中的 X-Forwarded-Proto X-Forwarded-Host X-Forwarded-Port 去与 header 中的 Origins 地址去对比,一致则能通过;

WX20200201-001710@2x.png

之前在 ➀ 中的 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 的配置:

WX20200201-003739@2x.png

可见模版中对 X-Forwarded-Proto X-Forwarded-Host X-Forwarded-Port 进行了重新配置,但这三个变量 $best_http_host $pass_port $pass_access_scheme 好像不是 Nginx 的内置变量,继续看是在哪里定义的,发现在 lua_ingress.lua 中有如下定义:

WX20200201-004208@2x.png

可以知道在使用 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 模块的配置:

WX20200201-005524@2x.png

有关于 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

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:猴子精h

查看原文:外部访问k8s中的Web服务方案

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1399 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传