为了尽可能降低基础设施成本,我们可以在不使用某些资源时将其关闭。然而此时的挑战之处在于,决定在必要时该如何将资源自动打开。本文将介绍如何使用Linode Kubernetes Engine(LKE)部署一个Kubernetes集群,并使用Kubernetes Events-Driven Autoscaler(KEDA)将其收缩到“零”,然后恢复原状。
为何要收缩到零假设我们在Kubernetes上运行了一个常见的资源密集型应用,但我们只需要在工作时间里运行该应用。我们可能会希望在大家都下班后将其关闭,并在上班时间自动重新打开。
我们可能希望关闭无人使用的开发环境
此时,虽然可以使用CronJob来缩放实例,但这只是权宜之计,只能按照预先设定的时间表照计划运行。
周末会怎么办?公共假期又如何处理?如果整个团队都生病无法到岗呢?
与其编制一个不断增长的规则列表,不如根据流量来扩展我们的工作负载。当流量增加时,可以扩展副本数量;当没有流量时,可以将整个应用关闭。当应用关闭后又收到新的传入请求后,Kubernetes会启动至少一个副本来处理这些流量。
与其编制一个不断增长的规则列表,不如根据流量来扩展我们的工作负载。当流量增加时,可以扩展副本数量;当没有流量时,可以将整个应用关闭。当应用关闭后又收到新的传入请求后,Kubernetes会启动至少一个副本来处理这些流量。
将应用收缩至零有助于节约资源
下文我们将介绍该如何:
拦截去往应用程序的所有流量;
监控流量;并
设置Autoscaler调整副本数量或关闭应用。
为供大家参考,相关代码均已发布至LearnK8s GitHub。
创建集群
首先需要创建一个Kubernetes集群。可使用下列命令创建一个集群并保存kubeconfig文件。
bash
$ linode-cli lke cluster-create \
--label cluster-manager \
--region eu-west \
--k8s_version 1.23
$ linode-cli lke kubeconfig-view "insert cluster id here" --text | tail +2 | base64 -d > kubeconfig
我们可通过下列命令验证安装过程已成功完成:
bash
$ kubectl get pods -A --kubeconfig=kubeconfig
用环境变量导出kubeconfig文件通常是一种比较方便的做法。为此可以运行:
bash
$ export KUBECONFIG=${PWD}/kubeconfig
$ kubectl get pods
接着需要部署应用程序。
部署应用程序
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: podinfo
spec:
selector:
matchLabels:
app: podinfo
template:
metadata:
labels:
app: podinfo
spec:
containers:
- name: podinfo
image: stefanprodan/podinfo
ports:
- containerPort: 9898
---
apiVersion: v1
kind: Service
metadata:
name: podinfo
spec:
ports:
- port: 80
targetPort: 9898
selector:
app: podinfo
使用下列命令提交YAML文件:
terminal|command=1|title=bash
$ kubectl apply -f 1-deployment.yaml
随后即可访问该应用,为此请打开浏览器并访问localhost:8080。
bash
$ kubectl port-forward svc/podinfo 8080:80
接着应该就能看到这个应用了。
接下来需要安装KEDA,也就是本例中将会用到的Autoscaler。
KEDA:Kubernetes事件驱动的Autoscaler
Kubernetes提供的Horizontal Pod Autoscaler(HPA)可以作为控制器动态增减副本数量。然而HPA有一些不足之处:
- 无法拆箱即用,需要安装Metrics Server汇总和暴露指标。
- 无法缩放至零副本。
- 只能根据指标缩放副本,并且无法拦截HTTP流量。
好在并非只能使用官方提供的Autoscaler,我们还可以使用KEDA。KEDA是一种为下列三个组件打造的Autoscaler:
Scaler
Metrics Adapter
Controller
KEDA架构
Scaler类似于适配器,可以从数据库、消息代理、遥测系统等处收集指标。例如,HTTP Scaler这个适配器就可以拦截并收集HTTP流量。我们可以在这里看到一个使用RabbitMQ的Scaler范例。
Metrics Adapter负责以Kubernetes指标管道可以使用的格式导出Scaler所收集的指标。
最后,Controller可以将所有这些组件紧密结合在一起:
使用适配器收集指标,并将其暴露给指标API。
注册并管理KEDA指定的自定义资源定义(CRD),例如ScaledObject、TriggerAuthentication等。
代替我们创建并管理Horizontal Pod Autoscaler。
理论上的介绍就是这些了,一起看看它们实际上是如何起效的。
我们可以使用Helm快速安装Controller,详细的说明和介绍请参阅Helm官网。
bash
$ helm repo add kedacore https://kedacore.github.io/charts
$ helm install keda kedacore/keda
KEDA默认并不包含HTTP Scaler,因此需要单独安装:
bash
$ helm install http-add-on kedacore/keda-add-ons-http
随后就可以扩展我们的应用了。
定义Autoscaling策略
KEDA的HTTP加载项会暴露出一个CRD,借此我们可以描述应用程序的扩展方式。一起看一个例子:
yaml
kind: HTTPScaledObject
apiVersion: http.keda.sh/v1alpha1
metadata:
name: podinfo
spec:
host: example.com
targetPendingRequests: 100
scaleTargetRef:
deployment: podinfo
service: podinfo
port: 80
replicas:
min: 0
max: 10
该文件会指示拦截器将有关http://example.com的请求转发给podinfo服务。
KEDA和HTTP拦截器
其中还包含了需要扩展的部署的名称,本例中为podinfo。
使用下列命令将YAML提交至集群:
bash
$ kubectl apply -f scaled-object.yaml
提交了上述定义后,Pod被删除了!为何会这样?
在创建了HTTPScaledObject后,KEDA会立即将该部署收缩到零,因为目前没有流量。
为了进行扩展,我们必须向应用发出HTTP请求。试试看连接到该服务并发出一个请求。
bash
$ kubectl port-forward svc/podinfo 8080:80
这个命令被挂起了!
这种现象是合理的,因为目前没有可以为请求提供服务的Pod。但Kubernetes为何没有将该部署扩展为1?
测试KEDA拦截器
在使用Helm安装加载项时,会创建一个名为keda-add-ons-http-interceptor-proxy的Kubernetes服务。为了让自动扩展能够正常起效,HTTP流量必须首先通过该服务进行路由。我们可以用kubectl port-forward进行测试:
shell
$ kubectl port-forward svc/keda-add-ons-http-interceptor-proxy 8080:8080
这一次我们无法在浏览器中访问该URL。
一个KEDA HTTP拦截器可以处理多个部署,那么它如何知道要将流量路由到哪里?
yaml
kind: HTTPScaledObject
apiVersion: http.keda.sh/v1alpha1
metadata:
name: podinfo
spec:
host: example.com
targetPendingRequests: 100
scaleTargetRef:
deployment: podinfo
service: podinfo
port: 80
replicas:
min: 0
max: 10
针对这种情况,The HTTPScaledObject使用了一个host字段。在本例中,我们需要假装请求来自http://example.com。为此需要设置Host头:
bash
$ curl localhost:8080 -H 'Host: example.com'
我们将收到一个回应,尽管略微有些延迟。
检查Pod会发现,部署已经被扩展至一个副本:
bash
$ kubectl get pods
那么刚才到底发生了什么?
在将流量路由至KEDA的服务时,拦截器会追踪尚未收到回复的未决HTTP请求数量。KEDA Scaler会定期检查拦截器的队列大小,并存储相关指标信息。
KEDA Controller会监控指标,并根据需要增大或减小副本数量。本例中有一个未决请求,此时KEDA Controller将部署扩展为一个副本就已足够。
我们可以通过下列方式获取每个拦截器的未决HTTP请求队列状态:
bash
$ kubectl proxy &
$ curl -L localhost:8001/api/v1/namespaces/default/services/keda-add-ons-http-interceptor-admin:9090/proxy/queue
{"example.com":0,"localhost:8080":0}
由于这种设计的存在,我们必须慎重决定该用何种方式将流量路由给应用。KEDA只能在流量可被拦截的情况下才会对部署进行扩展。
如果有一个现有的入口Controller,并且希望使用该Controller将流量转发给应用,那么还需要修改入口清单,将流量转发给HTTP加载项服务。一起看一个例子。
将KEDA HTTP加载项与入口配合使用**
我们可以使用Helm安装Nginx-ingress controller:
bash
$ helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
随后写一个入口清单,将流量路由给podinfo:
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: podinfo
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: keda-add-ons-http-interceptor-proxy # <- this
port:
number: 8080
通过下列命令可以获取负载均衡器的IP地址:
bash
LB_IP=$(kubectl get services -l "app.kubernetes.io/component=controller" -o jsonpath="{.items[0].status.loadBalancer.ingress
[0].ip}" -n ingress-nginx)
最后使用下列命令向应用发出一个请求:
bash
curl $LB_IP -H "Host: example.com"
起作用了!如果等待足够长的时间,我们还将注意到,该部署最终被收缩到零。
这与Serverless on Kubernetes有什么区别?
这种配置与Kubernetes上的Serverless框架(如OpenFaaS)有一些显著区别:
- 如果使用KEDA,将无需调整应用架构,也不需要使用SDK来部署应用。
- Serverless框架负责路由和服务请求,我们只需要关注应用逻辑。
- 如果使用KEDA,所部署的是常规容器;如果使用Serverless框架,则并不总是如此。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。