背景

knative 0.14.0

最近在搭建公司级的serverless平台,需要用到域名来访问内部服务,采取的是通过PATH来区分不同的服务

问题

申请完域名后,分别通过域名和IP:PORT形式访问已部署的helloworld服务

 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
curl -v -H "Host: api-test.sls.intra.xiaojukeji.com" http://api-test.sls.intra.xiaojukeji.com/
*   Trying 10.88.128.112...
* TCP_NODELAY set
* Connected to api-test.sls.intra.xiaojukeji.com (10.88.128.112) port 80 (#0)
> GET / HTTP/1.1
> Host: api-test.sls.intra.xiaojukeji.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 426 Upgrade Required
< Date: Thu, 09 Jul 2020 11:59:20 GMT
< Content-Length: 0
< Connection: keep-alive
< server: istio-envoy
<
* Connection #0 to host api-test.sls.intra.xiaojukeji.com left intact

# 10.190.16.26 为 knative-ingress-gateway的容器IP
curl -v -H "Host:api-test.sls.intra.xiaojukeji.com" http://10.190.16.26/
*   Trying 10.190.16.26...
* TCP_NODELAY set
* Connected to 10.190.16.26 (10.190.16.26) port 80 (#0)
> GET / HTTP/1.1
> Host:api-test.sls.intra.xiaojukeji.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< date: Thu, 09 Jul 2020 12:03:05 GMT
< server: istio-envoy
< content-length: 0
<
* Connection #0 to host 10.190.16.26 left intact

可以看到都无法正常返回,通过域名访问的时候返回了426,通过IP:PORT访问的时候返回了404。

排查

426 Upgrade Required

这个问题直接google一搜就出来答案了,参考 这里,其实这是envoy的能力,只要在envoy运行的容器中设置ISTIO_META_HTTP10环境变量为”1”问题就解决了,即**ISTIO_META_HTTP10: '"1"'**

404 Not Found

这个问题就涉及到VirtualService了,简称vs,在介绍vs之前我们先大致过一下knative创建集群的流程

假设我们通过kubectl操作,此时我们通过kubectl create -f helloworld.yaml的方式创建ksvc服务,如果集群各组件正常工作,且ksvc内容正确,那么稍微过一会就可以在集群中看到我们的服务了,我们需要做的仅仅是执行一条命令而已。可以看到knative封装的太好了,极大的简化了用户操作,对于对集群没有高级需求的用户非常友好,同时也有利于我们快速入门,否则,如果要执行一堆命令的话,就真的可以从入门到放弃了

但是我们毕竟是管理员,还是要对自己提高要求的,一定要搞清楚里面的原理,各组件之间的交互,否则系统对于我们来说就完全是个黑盒,不出问题还好,出问题就傻眼了。了解源码也是必须的,说到源码,只能感叹knative的源码要比k8s的源码封装的好太多了,其中一个原因也使得益于k8s提供的丰富的扩展机制:crd、operator、informer、webhook等。knative的源码真应该也值得拿出来一起分享,仔细研读。

回到正题,网路路由能力我们选择的是istio,我们大致分两种类型的资源进行介绍,和网络有关的 vs 和网络无关的

和网络无关的资源创建流程

ksvc --> configuration --> revision --> deployment

和网络有关的资源创建流程

ksvc --> route --> king--> virtualservice

我们的问题是和网络有关的,所以重点关注下面这个流程,最终对接istio的是vs,于是我们直接去看vs的配置,发现和域名相关的有两个地方,spec.hosts 和 spec.http.match.authority,于是想到的最简单的修改方式就是把我们的域名加入到spec.hosts中,去掉spec.http.match.authority,通过看代码发现这两处并没有可以修改的机制,于是想到利用MutatingWebhook来实现修改

控制这两个属性的地方都在net-istio的controller中,webhook对应的是net-istio的webhook,按照上面的分析,我们需要在webhook中添加对应的代码,主要改动两个地方,如下

 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
// 注册virtualservice类型,表示要对其进行mutate的操作,我们只需要在此注册即可,controller会自动修改对应的MutatingWebhookConfiguration,添加对应的资源和操作
var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{
	appsv1.SchemeGroupVersion.WithKind("Deployment"): &defaults.IstioDeployment{},
  v1alpha3.SchemeGroupVersion.WithKind("VirtualService"): &defaults.IstioVirtualService{},
}

// pkg/defaults/virtualservice_default.go,以去掉match.authority举例
package defaults

import (
	"context"

	"istio.io/client-go/pkg/apis/networking/v1alpha3"

	"knative.dev/pkg/apis"
)

type IstioVirtualService struct {
	v1alpha3.VirtualService `json:",inline"`
}

func (vs *IstioVirtualService) Validate(context.Context) *apis.FieldError {
	return nil
}

func (vs *IstioVirtualService) SetDefaults(ctx context.Context) {
	for _, http := range vs.Spec.Http {
		for _, match := range http.Match {
			match.Authority = nil
		}
	}
}

可以看到整体修改很少,修改完之后重新编译,制作镜像,修改线上Pod的Image,触发原地重启,然后删除掉原有的vs,新的vs自动生成,查看新的vs,wtf? 居然和之前一样,没有实现我们的效果,查kube-apiserver日志没有看到在创建vs时调用webhook,查看webhook的日志,也没有发现调用,但是在创建deployment时却会调用,然后查看webhook的配置,发现资源里也已经加上了,查了好久还是没有找到原因,不知道是哪个姿势不对了。由于时间关系暂时换另一种方式实现,即直接在创建vs的地方修改,如下

 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
// 往spec.hosts中添加自定义域名,通过annotation的方式,把需要添加到hosts中的域名放到annotation中
func makeVirtualServiceSpec(ing *v1alpha1.Ingress, gateways map[v1alpha1.IngressVisibility]sets.String, hosts sets.String) *istiov1alpha3.VirtualService {
	// add custom domains for didi serverless platform
	customHostStr := ing.Annotations["serverless.didichuxing.com/domains"]
	if len(customHostStr) > 0 {
		customHosts := strings.Split(customHostStr, ";")
		for _, host := range customHosts {
			hosts[host] = struct{}{}
		}
	}
	spec := istiov1alpha3.VirtualService{
		Hosts: hosts.List(),
	}

	...
}

// 删除spec.http.match.authority,直接注释掉下面Authority赋值部分的代码
func makeMatch(host string, pathRegExp string, gateways sets.String) *istiov1alpha3.HTTPMatchRequest {
	match := &istiov1alpha3.HTTPMatchRequest{
		Gateways: gateways.List(),
		// remove default authority for didi serverless platform
		//Authority: &istiov1alpha3.StringMatch{
		//	// Do not use Regex as Istio 1.4 or later has 100 bytes limitation.
		//	MatchType: &istiov1alpha3.StringMatch_Prefix{Prefix: hostPrefix(host)},
		//},
	}
	// Empty pathRegExp is considered match all path. We only need to
	// consider pathRegExp when it's non-empty.
	if pathRegExp != "" {
		match.Uri = &istiov1alpha3.StringMatch{
			MatchType: &istiov1alpha3.StringMatch_Regex{Regex: pathRegExp},
		}
	}
	return match
}

首先修改ksvc,添加对应的annotaiton,然后继续之前的操作进行编译,打镜像,原地升级,删除vs,新的vs自送生成,此时可以看到已经使我们期望的效果了,然后用域名访问,HelloWorld终于可以正常访问了。

总结

问题是解决了,但是为什么通过webhook的方式不生效,现象看起来是没调用webhook,还需要再去看下k8s有关webhook调用的部分的代码,很可能又是一个知识盲区。

通过此次问题排查,学习到了knative整个流程、原理,理清了各组件的交互,对后续问题排查有很大的帮助