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

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 的配置。随着服务的越拆越微,手动维护的成本就越来越高,自动化的方案就越来越迫切。

1.5 解析请求的方式

此方案就是重请求的 url 中解除拼接出 k8s 的 service name, 并通过 proxy_pass 直接转发。不过可以看出此方案具有一定的局限性,接口的地址必须提前约定好。

# 这里配置 kube-dns 地址
resolver x.x.x.x;

location / {
    set $service '';
    # 通过 lua 请求请求域名中解析并拼接出 k8s service
    rewrite_by_lua '
        local host = ngx.var.host;
        local m = ngx.re.match(host, "(.+).aihaisi.com")
        if m then
            ngx.var.service = "my-svc-" .. m[1]
        end
    ';
    // 直接转发至k8s 的service name, 这里需要设置下 nginx 使用 k8s 的 kube-dns 进行解析域名
    proxy_pass http://$service;
}

也可以不用 lua,用nginx 的 rewrite 正则截取

# 这里配置 kube-dns 地址
resolver x.x.x.x;

location / {
    rwrite ^/([a-zA-Z0-9]*)/(.+)$ /$2 break;
    proxy_pass http://$1/$2$is_args$args;
}

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 的内置变量 ngx.var.scheme ngx.var.server_port ngx.var.http_host; ➀ 中的 Nginx 承接外部的流量并提供了 HTTP 和 HTTPS,但在将请求代理给 ➁ 中的 Nginx Ingress 使用了 HTTP 协议,所以当你使用 HTTPS 去访问时,Origin 中的 schema 是 HTTPS,而从 ngx.var.scheme 获取到的值是 HTTP,这就是为什么使用 HTTPS 访问会出现跨域的错误了。

所以在配置 Nginx Ingress 时,设置 use-forwarded-headers 为 true(默认是 false),就能使用 ➀ 中 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个location配置块使用了多个 Nginx 模块...
    SkTj阅读 7,542评论 0 12
  • 本文将介绍在 k8s 中向外界提供服务的几种方法port-forward、NodePort,以及 更加常用的提供服...
    HoPGoldy阅读 18,577评论 0 14
  • 一、Ingress概念 Kubernetes关于服务的暴露主要是通过NodePort方式,通过绑定宿主机的某个端口...
    沉沦2014阅读 10,849评论 0 6
  • Ingress 管理群集中服务的外部访问的API对象,通常是HTTP。Ingress可以提供负载平衡,SSL 终止...
    bern85阅读 2,490评论 0 5
  • 嘛,以后就把这里当做根据地吧。唉,对以后的事真的是一点期待都没有,浑浑噩噩过了3个月,什么都没干,还想着去找实习,...
    sakura_1阅读 155评论 0 0