Knative 初体验:Serving Hello World
本篇文章就通过一个 Hello World 和 Knative 来一个“约会”,让你一睹 Knative 这位白富美的真容。
安装 Knative
Knative 社区提供的安装步骤见 这里 ,整个流程大概包含如下三个部分:
- 准备 kubernetes 环境(你可以在阿里云容器服务中 快速创建一个 kubernetes 集群 )
- 安装 istio
- 安装 Knative 组件
虽然看起来只有三步,但是每一步其实都需要手动做大量的工作,执行一堆命令。另外社区文档提供的 yaml 文件默认使用了大量的 gcr.io 镜像,目前国内无法拉取 gcr.io 镜像。所以这些 yaml 文件在国内不能直接使用,至少需要手动同步 30 多个镜像才行。
不过别着急, 阿里云容器服务的应用目录 已经有 Knative 的安装包,现在只需要在阿里云容器服务上面点击几下鼠标就能轻轻松松搭建一个 Knative 集群 O ^ ~ ^ O O ^ ~ ^ O O ^ ~ ^ O
创建 Kubernetes 集群
阿里云容器服务可以通过管理控制台非常方便地创建 Kubernetes 集群。具体过程可以参考 创建 Kubernetes 集群 。
容器服务提供了专有集群和托管集群两种类型,如果不知道该怎么选择建议你直接选择托管版的 Kubernetes 集群。托管版无需你自己承担 Kubernetes Master 组件的管理和运维,你只需要提供 Node 节点即可。
安装 Istio
Knative Serving 运行需要基于 Istio,目前阿里云容器服务 Kubernetes 已提供了一键部署的方式来安装配置 Istio。具体过程可以参考 部署 Istio 。
登录 容器服务管理控制台,单击左侧导航栏中的集群,进入集群列表页面。选择所需的集群并单击操作列更多 > 部署 Istio。
根据需要进行配置,然后点击部署按钮。稍等十几秒钟之后,Istio 环境就可以部署完毕。
部署 Istio IngressGateway
在容器服务管理控制台的应用目录中找到 ack-istio-ingressgateway 组件。点击 参数
标签可以看到默认参数提供了 Istio IngressGateway 的配置项,如果需要定制化参数可以在此进行修改。选择好目标 Kubernetes 集群
然后点击 创建
按钮即可完成 Ingressgateway 的创建。
在容器服务左侧的容器组页面中选择 Kubernetes 集群和 istio-system
namespace 确认运行状态,如下所示。
部署 Knative CRD
登录容器服务管理控制台,点击左侧的应用目录,在右侧选中 ack-knative-init,如下:
点击创建按钮部署 Knative 初始化所需的内容,包括部署 CRD 等。
部署 Knative Serving
登录容器服务管理控制台,点击左侧的应用目录,在右侧选中 ack-knative-serving,如下:
点击参数, 可以通过修改参数配置进行定制化,默认参数提供了使用 Istio IngressGateway 的配置项,然后点击创建按钮。
Serving Hello World
Serverless 一个核心思想就是按需分配,那么 Knative 是如何实现按需分配的呢?另外在前面的文章中你已经了解到 Knative Serving 在没有流量的时候是可以把 Pod 缩容到零的。接下来就通过一些例子体验一下 Knative 缩容到零和按需自动扩缩容的能力。
部署 helloworld-go 示例
Knative 官方给出了好几种语言的 Helloworld 示例 ,这些不同的语言其实只是编译镜像的 Dockerfile 有所不同,做好镜像之后的使用方式没什么差异。本例以 go 的 Hello World 为例进行演示。官方给出的例子都是源码,需要编译长镜像才能使用。为了你验证方便我已经提前编译好了一份镜像 registry.cn-hangzhou.aliyuncs.com/knative-sample/simple-app:07 , 你可以直接使用。
首先编写一个 Knative Service 的 yaml 文件 helloworld-go.yaml
, 内容如下:
复制代码
apiVersion: serving.knative.dev/v1alpha1 kind:Service metadata: name: helloworld-go namespace:default spec: template: metadata: labels: app: helloworld-go annotations: autoscaling.knative.dev/target:"10" spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/simple-app:07 env: - name: SIMPLE_MSG value:"helloworld-go-07"
注意其中 autoscaling.knative.dev/target: "10"
这个 Annotation 是设置每一个 Pod 的可处理并发请求数 10 ,Knative KPA 自动伸缩的时候会根据当前总请求的并发数和 autoscaling.knative.dev/target
自动调整 Pod 的数量,从而达到自动扩缩的目的。更多的策略信息我会在后续的文章中一一介绍。
现在使用 kubectl 命令把 yaml 提交到 Kubernetes 中:
- 部署 helloworld-go
复制代码
└─# kubectlapply-f helloworld-go.yaml service.serving.knative.dev/helloworld-gocreated
- 查看 helloworld-go pod
复制代码
└─# kubectlgetpod NAME READY STATUS RESTARTS AGE helloworld-go-lq6ns-deployment-869cbcc75d-qrln72/2Running06s
到此 helloworld-go 已经运行起来了,接下来访问一下 helloworld-go 这个服务吧。
访问 helloworld-go 示例
在访问 helloworld-go 之前我要先来介绍一下在 Knative 模型中流量是怎么进来的。Knative Service 和 Kubernetes 原生的 Deployment 不一样,Knative 不会创建 Loadbalance 的 Service,也不能创建 NodePort 类型的 Service,所以不能通过 SLB 或者 NodePort 访问。只能通过 ClusterIP 访问。而 ClusterIP 是不能直接对外暴露的,所以必须经过 Gateway 才能把用户的流量接入进来。本例就是使用 Istio 的 Gateway 承接 Knative 的南北流量 (进和出)。如下图所示是 Knative 模型中流量的转发路径。用户发起的请求首先会打到 Gateway 上面,然后 Istio 通过 VirtualService 再把请求转发到具体的 Revision 上面。当然用户的流量还会经过 Knative 的 queue 容器才能真正转发到业务容器,关于这方面的细节我在后续的文章再进行详细的介绍。
所以想要访问 Knative 的服务首先要获取 Gateway 的 IP 地址,可以通过如下方式获取 Gateway 的 IP:
复制代码
└─# kubectl get svc istio-ingressgateway --namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*].ip}" 39.97.31.219
前面也介绍了 Gateway 是通过 VirtualService 来进行流量转发的,这就要求访问者要知道目标服务的名字才行 (域名),所以要先获取 helloworld-go 的域名, 注意下面这条命令中的 ${SVC_NAME}
需要替换成 helloworld-go
,这个名字必须要和 Knative Service 的名字一致,因为每一个 Service 都有一个唯一的名字。
复制代码
└─# kubectlgetroute${SVC_NAME}--outputjsonpath="{.status.domain}" helloworld-go.default.example.com
至此你已经拿到 IP 地址和 Hostname,可以通过 curl 直接发起请求:
复制代码
└─# curl -H"Host: helloworld-go.default.example.com""http://39.97.31. 219"helloworld-go-07-v3
为了方便你进行测试,我提供了一个脚本 run-test.sh
,你可以使用此脚本测试你自己的 Service , 你自己在测试的时候把 SVC_NAME 换成自己的 Service Name 就行了。
复制代码
#!/bin/bash SVC_NAME="helloworld-go" exportINGRESSGATEWAY=istio-ingressgateway exportIP_ADDRESS=$(kubectl get svc$INGRESSGATEWAY--namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*]['ip']}") echo"IP_ADDRESS:${IP_ADDRESS}" exportGATEWAY_IP=`kubectl get svc$INGRESSGATEWAY--namespace istio-system --output jsonpath="{.status.loadBalancer.ingress[*]['ip']}"` exportDOMAIN_NAME=`kubectl get route${SVC_NAME}--output jsonpath="{.status.domain}"` kubectl get ksvc${SVC_NAME}--output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain time curl -H"Host:${DOMAIN_NAME}"http://${IP_ADDRESS}-v
缩容到零
刚刚部署完 Service 的时候 Knative 默认会创建出一个 Pod 提供服务,如果你超过即使秒没有访问 helloworld-go 这个服务那么这个 Pod 就会自动删除,此时就是缩容到零了。现在看一下 Pod 情况, 你可能会发现没有 Pod
复制代码
└─# kubectlgetpod -o wide Noresourcesfound.
现在执行一下 run-test.sh
发起一个请求到 Knative Service
复制代码
└─# ./run-test.sh IP_ADDRESS:39.97.31.219 NAME DOMAIN helloworld-go helloworld-go.default.example.com * Rebuilt URL to: http://39.97.31. 219/ * Trying39.97.31.219... * TCP_NODELAYset * Connected to39.97.31.219(39.97.31.219) port80(#0) > GET / HTTP/1.1 > Host: helloworld-go.default.example.com > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < content-length: 28 < content-type: text/html; charset=utf-8 < date: Mon, 03 Jun 2019 03:47:58 GMT < server: istio-envoy < x-envoy-upstream-service-time: 2681 < * Connection #0 to host 39.97.31. 219 left intacthelloworld-go-07-v3
real 0m2.775s user 0m0.007s sys 0m0.007s {1}
注意 run-test.sh
结果中,这面这一段:
复制代码
real 0m2.775s user 0m0.007s sys 0m0.007s
real 0m2.775s
意思意思是 curl
请求执行一共消耗了 2.775s
, 也就是说 Knative 从零到 1 扩容 + 启动容器再到服务响应请求总共消耗了 2.775s
(我之前的测试导致在 Node 上面缓存了镜像,所以没有拉镜像的时间)。可以看出来这个速度还是很快的。
再看一下 pod 数量, 你会发现此时 Pod 自动扩容出来了。并且 Pod 数量为零时发起的请求并没有拒绝链接。
复制代码
└─# kubectlgetpod NAME READY STATUS RESTARTS AGE helloworld-go-p9w6c-deployment-5dfdb6bccb-gjfxj2/2Running031s
按需分配,自动扩缩
helloworld-go 自动扩容测试
接下来再测试一下 Knative 按需扩容的功能。使用社区提供的 hey 进行测试。hey 有 Windows、Linux 和 Mac 的二进制可以在 这里下载 。
使用这个命令测试之前需要在本机进行 Host 绑定,对于 helloworld-go 来说要把 helloworld-go 的域名绑定到 Istio Gateway 的 IP 上,/etc/hosts 添加如下配置
复制代码
39.97.31. 219helloworld-go.default.example.com
如下所示 这条命令的意思是:
-z 30s -c 50
测试结果如下:
复制代码
└─# hey -z30s -c50"http://helloworld-go.default.example.com/"&& kubectlgetpods Summary: Total:30.0407secs Slowest:0.1453secs Fastest:0.0266secs Average:0.0378secs Requests/sec:1323.2700 Total data:1113056bytes Size/request:28bytes Response time histogram: 0.027[1] | 0.038[23584] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.050[15839] |■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.062[255] | 0.074[30] | 0.086[28] | 0.098[14] | 0.110[0] | 0.122[0] | 0.133[0] | 0.145[1] | Latency distribution: 10%in0.0330secs 25%in0.0351secs 50%in0.0371secs 75%in0.0407secs 90%in0.0428secs 95%in0.0442secs 99%in0.0495secs Details (average, fastest, slowest): DNS+dialup:0.0001secs,0.0266secs,0.1453secs DNS-lookup:0.0000secs,0.0000secs,0.0036secs req write:0.0000secs,0.0000secs,0.0009secs resp wait:0.0376secs,0.0266secs,0.1453secs resp read:0.0001secs,0.0000secs,0.0100secs Status code distribution: [200]39752responses NAME READY STATUS RESTARTS AGE helloworld-go-lq42n-deployment-68ddd64944-nkwpn2/2Running077s
回想一下刚才 helloworld-go.yaml 文件配置,已经设置了 autoscaling.knative.dev/target: "10"
这个 Annotation。这表示每一个 Pod 能够接受并发 10 个请求,而刚才并发请求数设置的是 50 所以理论上应该会创建出来 5 个 Pod?,
上面结果中最后一部分, 是 kubectl get pods
的结果,如下所示:
复制代码
NAME READY STATUS RESTARTS AGE helloworld-go-lq42n-deployment-68ddd64944-nkwpn2/2Running077s
可以看到实际只有一个 Pod,为什么呢?这是因为虽然并发 50 ,但是每一个请求很快就结束了。看一下刚才测试的结果, 截取核心的一部分展示如下。可以看到最慢的一个请求 0.1453
秒就处理完了。而且 99% 的请求 RT 都没超过 0.0495
秒。
复制代码
... ... Total:30.0407secs Slowest:0.1453secs Fastest:0.0266secs Average:0.0378secs Requests/sec:1323.2700 ... ... Latency distribution: 10%in0.0330secs 25%in0.0351secs 50%in0.0371secs 75%in0.0407secs 90%in0.0428secs 95%in0.0442secs 99%in0.0495secs ... ...
所以一秒内是可以完整的处理完 50 个请求的,也就不需要扩容了。
再换一个例子,让每一个请求处理的时间拉长一些看看效果。
autoscale-go 自动扩缩测试
如果单个请求处理的太快就不太好展示自动扩缩的效果,那么就让单条请求处理的时间稍微长一些。Knative 官方 有一个 Autoscaler 的例子 , 这个例子中每一个请求会进行一些计算,把请求时间拉长,这样就能更容易的测试。你可以直接使用 registry.cn-hangzhou.aliyuncs.com/knative-sample/autoscale-go:0.1 这个镜像进行测试。
- 编写 Knative Service 文件 autoscale-go.yaml 如下:
复制代码
└─# cat autoscale-go.yaml apiVersion: serving.knative.dev/v1alpha1 kind:Service metadata: name: autoscale-go namespace:default spec: template: metadata: labels: app: autoscale-go annotations: autoscaling.knative.dev/target:"10" spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/autoscale-go:0.1
- 部署 autoscale-go
复制代码
└─# kubectlapply-f autoscale-go.yaml service.serving.knative.dev/autoscale-gocreated
run-test.sh 中 SVC_NAME
改成 autoscale-go
然后执行 run-test.sh ,如下:
复制代码
└─# ./run-test.sh IP_ADDRESS:39.97.31.219 NAME DOMAIN autoscale-goautoscale-go.default.example.com * Rebuilt URL to: http://39.97.31. 219/ * Trying39.97.31.219... * TCP_NODELAYset * Connected to39.97.31.219(39.97.31.219) port80(#0) > GET / HTTP/1.1 > Host:autoscale-go.default.example.com > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < content-length: 0 < date: Mon, 03 Jun 2019 05:05:38 GMT < server: istio-envoy < x-envoy-upstream-service-time: 2912 < * Connection #0 to host 39.97.31. 219 left intact {1} real 0m2.999s user 0m0.007s sys 0m0.008s {1}
可以看到 autoscale-go 已经可以提供服务了。
使用 hey 命令测试之前需要在本机进行 Host 绑定,对于 autoscale-go 来说要把 autoscale-go 的域名绑定到 Istio Gateway 的 IP 上,/etc/hosts 添加如下配置
复制代码
39.97.31. 219autoscale-go.default.example.com
- 使用 hey 进行测试:
复制代码
└─# hey -z30s -c50"http://autoscale-go.default.example.com?sleep=100′=10000&bloat=5"&& kubectlgetpods Summary: Total:30.1443secs Slowest:6.0173secs Fastest:0.1285secs Average:0.1717secs Requests/sec:290.4364 Total data:875284bytes Size/request:99bytes Response time histogram: 0.128[1] | 0.717[8704] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.306[0] | 1.895[0] | 2.484[0] | 3.073[0] | 3.662[0] | 4.251[0] | 4.840[0] | 5.428[0] | 6.017[50] | Latency distribution: 10%in0.1329secs 25%in0.1356secs 50%in0.1383secs 75%in0.1413secs 90%in0.1435secs 95%in0.1450secs 99%in0.1574secs Details (average, fastest, slowest): DNS+dialup:0.0002secs,0.1285secs,6.0173secs DNS-lookup:0.0000secs,0.0000secs,0.0036secs req write:0.0000secs,0.0000secs,0.0011secs resp wait:0.1713secs,0.1283secs,5.9780secs resp read:0.0001secs,0.0000secs,0.0066secs Status code distribution: [200]8755responses NAME READY STATUS RESTARTS AGE autoscale-go-zqcm2-deployment-6cf67b4545-2f2ck2/2Running028s autoscale-go-zqcm2-deployment-6cf67b4545-4xc9s2/2Running026s autoscale-go-zqcm2-deployment-6cf67b4545-6wt8r2/2Running028s autoscale-go-zqcm2-deployment-6cf67b4545-hdbnc2/2Running030s autoscale-go-zqcm2-deployment-6cf67b4545-w9pm72/2Running028s
可以看到此时 Knative 自动扩容出来了 5 个 Pod 处理请求。
总结
至此你已经完成了和 Knative Serving 的首次约会,也看到了这位白富美的真容。通过本篇文章你应该掌握
以下几点:
- 在阿里云容器服务上面快速搭建 Knative 集群的方法
- 理解 Knative 从零到一的含义,并且能够基于 helloworld-go 例子演示这个过程
- 理解 Knative 按需扩缩容的含义,并且能够基于 autoscale-go 例子演示这个过程
- 理解 Knative 按需扩容的原理,按需扩容不单单是用户发起 50 个并发、每一个 Pod 最多能够并发处理 10 个请求就一定需要创建 5 个 Pod 出来。如果请求的处理时间很短,一个 Pod 就能满足的情况下 Knative 是不会做无用的扩容的
Next
通过前面的例子相信你已经对 Knative Serving 有了更深刻的理解。但是你可能也会产生很多疑问:
- 这里面只是展示了 Serving 的 Hello World,Eventing、Build 能不能也来一些这样的例子?
- 为什么上面例子的域名都是 xx.example.com , 我能不能换成自己的域名?
- Knative 流量转发的细节是怎样的?queue 的作用是什么?
- 在前面的例子中,容器都是监听 8080 端口的,如果容器监听的不是 8080 端口那么需要怎么配置自定义的端口?
- Knative 默认扩缩容的策略是怎样的?
- hey 这个压测工具虽然展示了 Knative 自动扩容的过程,但是还不够直观、还有没有更好的工具来验证并发数、服务响应时间和 Knative 自动扩容 Pod 之间的关系?
- 如果 Service 有更新,如何进行流量灰度?
所有这些疑问我会在后续的一系列文章中都会一一讲解到,敬请期待后续的文章。