在 Knative 上部署 12 要素应用程序

原文:Deploying 12 Factor Apps to Knative

作者:Dr Nic Williams

我尝试按照本文内容在 Azure 上使用 ACS-Engine 部署的 Kubernetes 上进行了测试,版本部分的案例得到了相反结果,不过还是按照原文发了出来,作为一个获取感性认识的入门还是不错的。

Google Next18 活动中,Google 宣称将会把 GKE 上的无服务器插件以 Knative 的名称进行开源。当时它被描述为无服务器平台的构建块,由此推断,Knative 可能需要 Google、Pivotal 或者 RedHat 的协助才能使用。这可能是开源的古怪时机。从我最初的摸索来看,Knative 能工作;当我把 Heroku/Cloud Foundry buildpacks 加入进来之后,整个系统变得越来越像 Heroku/Cloud Foundry,相对于原始 Kubernetes,我更加了解和喜爱这一系统。

本文中我们会将 Knative 安装到你自己的 Kubernetes 集群中(knctl install),然后用 Knative 做些有趣的事情(knctl deploy)。

Knative 能够为在 Kubernetes 集群上运行无状态应用的运维人员带来很多惊喜。对我来说,最引人入胜的一点就是伸缩性:在高负载时候进行扩容,没人喜欢你的应用了,就会一直缩容到 0。

下载和安装 Knative 客户端工具 knctl,然后就可以在你的 Kubernetes 上部署 Knative,然后发布你的应用了。

在 MacOS 中,可以利用我们的 Homebrew tap 进行安装:

brew install starkandwayne/kubernetes/knctl

这里我假设你再使用 Minikube。在 Minikube 中,可以使用 Node Port 代替 Load balancer:

minikube start --memory=8192 --cpus=3 \
  --kubernetes-version=v1.11.3 \
  --vm-driver=hyperkit \
  --bootstrapper=kubeadm

knctl install --node-ports --exclude-monitoring

可以参考 Knative 文档来获取在其它类型 Kubernetes 集群上进行部署的要点。

knctl install 命令可能要花上几分钟,甚至是十分钟或者更多。这个过程中需要下载大概一打镜像。不管是互联网带宽还是镜像尺寸都可能有变化,所以坐下放松一会,或者出去走走也好。

如果 knctl install 失败了,可能是你的 Internet 比较慢,Docker 镜像在命令超时之前还没能完成下载。运行 kubectl get pods --all-namespaces 直到所有 Pod 都在运行,然后再次运行 knctl install 命令继续完成安装过程。

现在你的 Kubernetes 就是无服务器架构了,不错吧。

可以运行 kubectl get pods --all-namespaces 看看原始的 Knative 的 Pod 们。

$ kubectl get pods --all-namespaces
NAMESPACE         NAME                                        READY   STATUS      RESTARTS   AGE
istio-system      istio-citadel-7d8f9748c5-zgm9x              1/1     Running     0          21m
istio-system      istio-cleanup-secrets-j4pkx                 0/1     Completed   0          21m
istio-system      istio-egressgateway-676c8546c5-dnwsd        1/1     Running     0          21m
istio-system      istio-galley-5669f7c9b-g774b                1/1     Running     0          21m
istio-system      istio-ingressgateway-5475685bbb-q5f2x       1/1     Running     0          21m
istio-system      istio-pilot-5795d6d695-9klrz                2/2     Running     0          21m
istio-system      istio-policy-7f945bf487-2wh88               2/2     Running     0          21m
istio-system      istio-sidecar-injector-d96cd9459-7knkm      1/1     Running     0          21m
istio-system      istio-statsd-prom-bridge-549d687fd9-lcmb7   1/1     Running     0          21m
istio-system      istio-telemetry-6c587bdbc4-t4jql            2/2     Running     0          21m
istio-system      knative-ingressgateway-7f4477dd99-n9wz2     1/1     Running     0          4m
knative-build     build-controller-7dcc4b7544-rkgwb           1/1     Running     0          4m
knative-build     build-webhook-fb6484576-sr4fk               1/1     Running     0          4m
knative-serving   activator-77d46b585d-b6g8n                  2/2     Running     0          4m
knative-serving   controller-85768cfd45-t8ktc                 1/1     Running     0          4m
knative-serving   webhook-56dd548f8-hjw44                     1/1     Running     0          4m
...

部署预构建的应用

接下来我们试试用一个现有的 Docker 镜像来作为自动伸缩的无状态应用运行到 Knative 上,在当前 Kubernetes 命令空间中:

kubectl create ns helloworld

knctl deploy \
      --namespace helloworld \
      --service hello \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET=Rev1

命令执行后会输出一些信息,表明 hello 服务已经创建,它的第一个版本 hello-00001 已经创建,并且被标记为 latestprevious(第一个版本)。

Name  hello

Waiting for new revision to be created...

Tagging new revision 'hello-00001' as 'latest'

Tagging new revision 'hello-00001' as 'previous'

Succeeded

我们可以用一个 curl 请求,发送到 Knative 的路由层,来调用我们的 hello 服务:

$ knctl curl --namespace helloworld --service hello
Running: curl '-H' 'Host: hello.helloworld.example.com' 'http://192.168.64.8:32380'

Hello World: Rev1!

Succeeded

如果没有马上显示 Hello World: Rev1!,可能是因为你的系统还在下载应用镜像、可以稍后重试。

我使用的是 Minikube 中的 NodePort Ingress,这意味着我不能设置漂亮的 DNS 路由。以后我会讨论一下公共负载均衡、DNS、Knative 路由以及 https://github.com/cppforlife/kwt

knctl deploy 之后,我们的 Kubernetes 用单 Pod 的形式运行这一服务:

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          15s

Knative 中,服务的版本是一个重要特性。我们可以看到我们的唯一版本的服务正在处理 100% 的流量:

$ knctl revisions list --namespace helloworld --service hello
Revisions for service 'hello'

Name         Tags      Allocated Traffic %  Serving State  Annotations  Age
hello-00001  latest    100%                 Active         -            3m
             previous

1 revisions

下一步我们使用 knctl deploy 创建一个新的版本,然后把所有流量分配到新版本:

$ knctl deploy \
      --namespace helloworld \
      --service hello \
      --image gcr.io/knative-samples/helloworld-go \
      --env TARGET=Rev2

Name  hello

Waiting for new revision (after revision 'hello-00001') to be created...

Tagging new revision 'hello-00002' as 'latest'

Tagging older revision 'hello-00001' as 'previous'

Succeeded

现在请求被发送到了我们的新版本:

$ knctl revisions list --namespace helloworld --service hello
Revisions for service 'hello'

Name         Tags      Allocated Traffic %  Serving State  Annotations  Age
hello-00002  latest    100%                 Active         -            1m
hello-00001  previous  0%                   Active         -            10m

$ knctl curl --namespace helloworld --service hello
Running: curl '-H' 'Host: hello.helloworld.example.com' 'http://192.168.64.8:32380'

Hello World: Rev2!

部署第二个版本之后,起初两个版本的 Pod 都会持续运行:

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          1m
hello-00002-deployment-7874bf89b8-4b4k5   3/3     Running   0          29s

我们会看到 Knative 自动将没有路由指向的版本缩容到 0。

路由

下图展示了路由到服务某版本的过程中所涉及到的 Knative Serving 模块:

Object Model

版本是代码、依赖和配置的的只读快照。没有被路由引用的版本会被放弃,其中的 Kubernetes 资源会被删除。

我们可以看到当前的路由:

$ knctl routes list --namespace helloworld
Routes in namespace 'helloworld'

Name   Traffic         All Traffic Assigned  Ready  Domain                        Age
hello  100% -> hello:  true                  true   hello.helloworld.example.com  2h

缩容至 0

如果离开五分钟再回来,你会发现 hello-00002 Pod 正在被终结或者已经不见了:

$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS        RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running       0          6m
hello-00002-deployment-7874bf89b8-4b4k5   2/3     Terminating   0          5m

下一次 knctl curl,Knative 会动态的启动一个 Pod 来满足这一请求。

$ knctl curl --namespace helloworld --service hello
$ kubectl get pods --namespace helloworld
NAME                                      READY   STATUS    RESTARTS   AGE
hello-00001-deployment-5864685cbc-v8r7n   3/3     Running   0          8m
hello-00002-deployment-7874bf89b8-kfbm2   3/3     Running   0          10s

我还不太清楚为什么 hello-00001-deployment-... Pod 没有被缩容和终结。

未完待续

后续文章中将会尝试:

  • Knative Build 组件:使用 Dockerfile 或者 Cloud Foundry buildpack 自动从定制代码构建容器镜像(代码可以保存本地或者 Git 仓库之中)。
  • 为 Kubernetes 的负载均衡设置 DNS,从而为 Knative 路由和服务提供公共 URL。
  • 在不同版本之间进行流量分割(例如 10% 到最新版本,90% 到前一版本;然后将 100% 分配给新版本,老版本流量降低到 0%)。
  • Knative Eventing:在应用中进行 CloudEvents的绑定和分发。让你的服务更加“无服务器”。

社区

Knative 核心团队有自己的 Knative Slack,可以在 https://slack.knative.dev 申请加入。

knative-dev Group 中包含了总结和提议。

鸣谢

感谢 Google 的 Mark Chmarny,在 2018 Spring One 上首先回应了我的问题。

感谢 Pivotal 的 Dmitriy Kalinin 花时间帮助我将 Knative 运行起来,并给我展示了他的 knctl Knative CLI,以及 kwt Kubernetes Workstation Tools。相对于 YAML + kubectl 组合来说,一个好用的客户端工具更能够帮助 Knative 走向实用。

相关

comments powered by Disqus