背景
knative 0.14.0 实际修改可能与贴出来的代码不符,贴出来的代码只是为了方便快速实现功能
最近在搭建公司级的serverless平台,需要用到域名来访问内部服务,采取的是通过PATH来区分不同的服务,域名采用同一个。这与原生knative的设计存在差异,原生的做法是每个服务一个自己的域名,通过域名把流量打到不同的服务上,我们已经在上一篇中解决了自定义域名无法访问knative集群的问题,这一篇来解决如何通过不同的Path访问到不同的服务
方案
两个问题需要我们来解决:
- 不同服务的Path可能相同,如何区分
- 原生通过ksvc的方式不支持设置Path(通过自己创建各种类型的资源可以实现,但是控制比较复杂,而且上层需要修改适配)
解决方案:
- 每个服务一个USN,使用USN作为唯一标识
- 修改knative,支持通过Path访问
- 转发后需要rewrite url,把USN去掉,因为业务代码中的路由里不可能包含USN
其中第一点不需要代码改动,我们主要来实现第二、三点。
vs本身是支持根据Path转发的功能的,但是并没有在ksvc中暴露出来,所以我们需要在king创建vs的时候动态注入进去,同时在destination中添加url rewrite的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | func makeVirtualServiceSpec(ing *v1alpha1.Ingress, gateways map[v1alpha1.IngressVisibility]sets.String, hosts sets.String) *istiov1alpha3.VirtualService { spec := istiov1alpha3.VirtualService{ Hosts: hosts.List(), } // 自定义功能 usn := ing.Annotations["serverless.kakuchuxing.com/usn"] if usn != "" { usn = "/" strings.Trim(usn, "/") "/" } gw := sets.String{} for _, rule := range ing.Spec.Rules { for _, p := range rule.HTTP.Paths { hosts := hosts.Intersection(sets.NewString(rule.Hosts...)) if hosts.Len() != 0 { http := makeVirtualServiceRoute(hosts, usn, &p, gateways, rule.Visibility) // Add all the Gateways that exist inside the http.match section of // the VirtualService. // This ensures that we are only using the Gateways that actually appear // in VirtualService routes. for _, m := range http.Match { gw = gw.Union(sets.NewString(m.Gateways...)) } // rewrite path,重定向,消除USN if usn != "" { if http.Rewrite == nil { http.Rewrite = &istiov1alpha3.HTTPRewrite{} } http.Rewrite.Uri = "/" } spec.Http = append(spec.Http, http) } } } spec.Gateways = gw.List() return &spec } func makeVirtualServiceRoute(hosts sets.String, usn string, http *v1alpha1.HTTPIngressPath, gateways map[v1alpha1.IngressVisibility]sets.String, visibility v1alpha1.IngressVisibility) *istiov1alpha3.HTTPRoute { ... for _, host := range hosts.List() { g := gateways[visibility] if strings.HasSuffix(host, clusterDomainName) && len(gateways[v1alpha1.IngressVisibilityClusterLocal]) > 0 { // For local hostname, always use private gateway g = gateways[v1alpha1.IngressVisibilityClusterLocal] } matches = append(matches, makeMatch(host, usn, http.Path, g)...) } ... } func makeMatch(host string, usn string, pathRegExp string, gateways sets.String) []*istiov1alpha3.HTTPMatchRequest { ... // add custom usn filter,添加USN的过滤条件 if usn != "" { if i == 0 { matches[i].Uri = &istiov1alpha3.StringMatch{ MatchType: &istiov1alpha3.StringMatch_Prefix{Prefix: usn}, } } else { matches[i].Uri = &istiov1alpha3.StringMatch{ MatchType: &istiov1alpha3.StringMatch_Exact{Exact: strings.TrimRight(usn, "/")}, } } } ... } |
---|
修改比较简单,完全就是按照之前说的两点进行的。其中有一个比较tricky的地方就是实现url rewrite的方式,因为社区中的vs(istio里的crd)其实是存在问题的,我们为了规避这个问题,特意做了一些特殊设置。参考这里,大致意思就是目前vs不支持url rewrite为空,rewrite为空之后,实际访问的时候需要在url的最后加上/,否则会返回400,但是我们很多前端网站主页就是一个域名,后面不跟任何内容,那这时候就有问题了,总不能再告诉用户在最后输入一个/。规避方案其实也比较简单,就是上面代码中最后makeMatch处的if else语句,且一定要保证顺序,即最长的要在前面,因为遇到第一个匹配的规则后,后续规则会被忽略。
1 2 3 4 5 6 7 8 | http: - match: - uri: prefix: "/echo/" - uri: prefix: "/echo" rewrite: uri: "/" |
---|
如果顺序颠倒,那么当访问/echo/abc时,会重定向到//abc,返回404错误。
总结
至此,已经支持通过统一域名访问,且通过Path把请求转发到不通的服务