Kubernetes基于RabbitMQ队列长度指标进行HPA
这是在之前在工作中的一个需求,能够监控RabbitMQ中队列的长度,并且能也能够根据队列的长度自动的进行对应Pod的扩缩容。
网上很多的教程都是基于CPU和内存的HPA,相对简单。
在尝试了很多的方法后最终也是实现了这个需求。由于网上还没有完整的文档,所以这篇部署的文档分享出来,提供给大家参考也顺便记录一下
需要的组件
-
Prometheus,用来监控整个集群的资源,然后将监控的数据接口注册到k8s 中的 api server中,能够通过api server 访问到监控数据 -
custom-metrics,提供自定义指标,可以被k8s获取 -
RabbitMQ,一个消息队列
-
RabbitMQ-exporter
,用来收集RabbitMQ`的信息
部署Prometheus
部署Promentheys有很多种方法,我们选择最快速方便的一种,既利用kube-prometheus。
kube-prometheus是Prometheus-Operator的升级版本
Operator是由CoreOS公司开发的,用来扩展 Kubernetes API,特定的应用程序控制器,它用来创建、配置和管理复杂的有状态应用,如数据库、缓存和监控系统。Operator基于 Kubernetes 的资源和控制器概念之上构建,但同时又包含了应用程序特定的一些专业知识,比如创建一个数据库的Operator,则必须对创建的数据库的各种运维方式非常了解,创建Operator的关键是CRD(自定义资源)的设计。
Prometheus-Operator架构图
上图是Prometheus-Operator官方提供的架构图,其中Operator是最核心的部分,作为一个控制器,他会去创建Prometheus、ServiceMonitor、AlertManager以及PrometheusRule4个CRD资源对象,然后会一直监控并维持这4个资源对象的状态。
其中创建的prometheus这种资源对象就是作为Prometheus Server存在,而ServiceMonitor就是exporter的各种抽象,exporter是用来提供专门提供metrics数据接口的工具,Prometheus就是通过ServiceMonitor提供的metrics数据接口去 pull 数据的,当然alertmanager这种资源对象就是对应的AlertManager的抽象,而PrometheusRule是用来被Prometheus实例使用的报警规则文件。
这样我们要在集群中监控什么数据,就变成了直接去操作 Kubernetes 集群的资源对象了。上图中的 Service 和 ServiceMonitor 都是 Kubernetes 的资源,一个 ServiceMonitor 可以通过 labelSelector 的方式去匹配一类 Service,Prometheus 也可以通过 labelSelector 去匹配多个ServiceMonitor。
下面就开始部署
就按照官方文档的步骤来进行:github.com/coreos/kube…
特别需要注意的就是文档开头提示的你的k8s集群必须要满足的要求
This means the kubelet configuration must contain these flags:
--authentication-token-webhook=trueThis flag enables, that aServiceAccounttoken can be used to authenticate against the kubelet(s).--authorization-mode=WebhookThis flag enables, that the kubelet will perform an RBAC request with the API to determine, whether the requesting entity (Prometheus in this case) is allow to access a resource, in specific for this project the/metricsendpoint.
首先将代码拷贝下来,执行下面的命令
kubectl create -f manifests/setup
until kubectl get servicemonitors --all-namespaces ; do date; sleep 1; echo ""; done
kubectl create -f manifests/
等待一段时间后,部署完成会创建一个名为monitoring的namespace,kube-prometheus安装的所有组件都在该命令空间下
test@max-master:~$ kubectl get pod -n monitoring
NAME READY STATUS RESTARTS AGE
alertmanager-main-0 2/2 Running 0 10d
alertmanager-main-1 2/2 Running 0 5h22m
alertmanager-main-2 2/2 Running 0 10d
grafana-5566bbccc-fq487 1/1 Running 0 6h10m
kube-state-metrics-5b667f584-wwprp 1/1 Running 0 10d
node-exporter-2cqlj 2/2 Running 2 10d
node-exporter-5x2s7 2/2 Running 0 10d
node-exporter-642v5 2/2 Running 2 10d
prometheus-adapter-68698bc948-cvhlz 1/1 Running 0 6h10m
prometheus-k8s-0 3/3 Running 0 10d
prometheus-k8s-1 3/3 Running 1 5h22m
prometheus-operator-7457c79db5-mccvt 1/1 Running 0 10d
可以发现他也同样安装好了grafana
查看创建的Service
test@max-master:~$ kubectl get svc -n monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
alertmanager-main ClusterIP 10.107.209.135 <none> 9093/TCP 10d
alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 10d
custom-metrics-apiserver ClusterIP 10.99.254.50 <none> 443/TCP 8d
grafana NodePort 10.104.75.233 <none> 3000/TCP 10d
kube-state-metrics ClusterIP None <none> 8080/TCP,8081/TCP 10d
node-exporter ClusterIP None <none> 9100/TCP 10d
prometheus-adapter ClusterIP 10.109.29.134 <none> 443/TCP 10d
prometheus-k8s ClusterIP 10.97.68.82 <none> 9090/TCP 10d
prometheus-operated ClusterIP None <none> 9090/TCP 10d
prometheus-operator ClusterIP None <none> 8080/TCP 10d
可以看到grafana和prometheus-adapter并没有暴露出端口,所以我们还要将这两个的端口暴露出去,以便我们能够访问。这里有两种方式
- 利用kubectl edit命令直接将service的类型改为NodePort,但是这样暴露出去的端口是随机的
- 修改原来的yaml文件,可以指定暴露出去的端口
我们这里使用第2中方式
# 修改 kube-prometheus/manifests/grafana-service.yaml 文件为以下内容
apiVersion: v1
kind: Service
metadata:
labels:
app: grafana
name: grafana
namespace: monitoring
spec:
selector:
app: grafana
ports:
- name: http
port: 3000
protocol: TCP
targetPort: 3000
nodePort: 3000
type: NodePort
# 修改 kube-prometheus/manifests/prometheus-service.yaml 文件为以下内容
apiVersion: v1
kind: Service
metadata:
labels:
prometheus: k8s
name: prometheus-k8s
namespace: monitoring
spec:
ports:
- name: web
port: 9090
targetPort: 9090
nodePort: 9090
selector:
app: prometheus
prometheus: k8s
sessionAffinity: ClientIP
type: NodePort
# 应用这两个文件
kubectl apply -f prometheus-service.yaml
kubectl apply -f grafana-service.yaml
更改完就可以根据端口去访问grafana和prometheus了,比如查看 prometheus 的 targets 页面:
我们可以看到大部分的配置都是正常的,只有两三个没有管理到对应的监控目标,比如 kube-controller-manager 和 kube-scheduler 这两个系统组件,这就和 ServiceMonitor 的定义有关系了,我们先来查看下 kube-scheduler 组件对应的 ServiceMonitor 资源的定义:(prometheus-serviceMonitorKubeScheduler.yaml)
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
k8s-app: kube-scheduler
name: kube-scheduler
namespace: monitoring
spec:
endpoints:
- interval: 30s # 每30s获取一次信息
port: http-metrics # 对应service的端口名
jobLabel: k8s-app
namespaceSelector: # 表示去匹配某一命名空间中的service,如果想从所有的namespace中匹配用any: true
matchNames:
- kube-system
selector: # 匹配的 Service 的labels,如果使用mathLabels,则下面的所有标签都匹配时才会匹配该service,如果使用matchExpressions,则至少匹配一个标签的service都会被选择
matchLabels:
k8s-app: kube-scheduler
上面是一个典型的 ServiceMonitor 资源文件的声明方式,上面我们通过selector.matchLabels在 kube-system 这个命名空间下面匹配具有k8s-app=kube-scheduler这样的 Service,但是我们系统中根本就没有对应的 Service,所以我们需要手动创建一个 Service:(prometheus-kubeSchedulerService.yaml)
apiVersion: v1
kind: Service
metadata:
namespace: kube-system
name: kube-scheduler
labels:
k8s-app: kube-scheduler
spec:
selector:
component: kube-scheduler
ports:
- name: http-metrics
port: 10251
targetPort: 10251
protocol: TCP
10251是
kube-scheduler组件 metrics 数据所在的端口,10252是kube-controller-manager组件的监控数据所在端口。
其中最重要的是上面 labels 和 selector 部分,labels 区域的配置必须和我们上面的 ServiceMonitor 对象中的 selector 保持一致,selector下面配置的是component=kube-scheduler,为什么会是这个 label 标签呢,我们可以去 describe 下 kube-scheduelr 这个 Pod:
$ kubectl describe pod kube-scheduler-master -n kube-system
Name: kube-scheduler-master
Namespace: kube-system
Node: master/10.151.30.57
Start Time: Sun, 05 Aug 2018 18:13:32 +0800
Labels: component=kube-scheduler
tier=control-plane
......
我们可以看到这个 Pod 具有component=kube-scheduler和tier=control-plane这两个标签,而前面这个标签具有更唯一的特性,所以使用前面这个标签较好,这样上面创建的 Service 就可以和我们的 Pod 进行关联了,直接创建即可:
$ kubectl create -f prometheus-kubeSchedulerService.yaml
$ kubectl get svc -n kube-system -l k8s-app=kube-scheduler
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-scheduler ClusterIP 10.102.119.231 <none> 10251/TCP 18m
之后会看到kube-scheduler的状态还是未正常运行,这是因为kube-scheduler 默认是绑定在127.0.0.1上面的,而上面我们这个地方是想通过节点的 IP 去访问,所以访问被拒绝了,我们只要把 kube-scheduler 绑定的地址更改成0.0.0.0即可满足要求,由于 kube-scheduler 是以静态 Pod 的形式运行在集群中的,所以我们只需要更改静态 Pod 目录下面对应的 YAML 文件即可:
$ ls /etc/kubernetes/manifests/
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
将 kube-scheduler.yaml 文件中-command的--address地址更改成0.0.0.0:
containers:
- command:
- kube-scheduler
- --leader-elect=true
- --kubeconfig=/etc/kubernetes/scheduler.conf
- --address=0.0.0.0
修改完成后我们将该文件从当前文件夹中移除,隔一会儿再移回该目录,就可以自动更新了,然后再去看 prometheus 中 kube-scheduler 这个 target 是否已经正常了
Kube-contronal-manager也是同样的步骤,我就只把yaml文件给出来
# prometheus-kubeControllManagerService.yaml
apiVersion: v1
kind: Service
metadata:
namespace: kube-system
name: kube-controller-manager
labels:
k8s-app: kube-controller-manager
spec:
selector:
component: kube-controller-manager
ports:
- name: http-metrics
port: 10252
targetPort: 10252
protocol: TCP
应用这个文件后,整个Prometheus就部署好了
安装custom-metrics-apiserver
下载如下资源
生成证书
umask 077;openssl genrsa -out serving.key 2048
证书请求
touch /home/test/.rnd
openssl req -new -key serving.key -out serving.csr -subj "/CN=serving"
签署证书
openssl x509 -req -in serving.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out serving.crt -days 3650
上述步骤生成:
serving.key serving.csr serving.crt
创建secret
kubectl create secret generic cm-adapter-serving-certs --from-file=serving.crt=./serving.crt --from-file=serving.key=./serving.key -n monitoring
kubectl get secrets -n monitoring
cm-adapter-serving-certs Opaque 2 2s
修改文件
将manifests yaml中的namespace替换为monitoring
sed -i 's/namespace\: custom-metrics/namespace\: monitoring/g' ./*
修改custom-metrics-apiserver-deployment.yaml
--prometheus-url=http://prometheus.prom.svc:9090/ 改为 --prometheus-url=http://prometheus-k8s.monitoring.svc:9090/
创建服务
kubectl apply -f ./manifests
用下面的命令请求这个api,就会看到对应的数据
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
安装rabbitmq-exporter
安装rabbitmq就不做介绍了。直接使用官方的镜像就可以了
下面我们开始安装RabbitMQ-exporter,将他的数据推送到Prometheus中
总共分为以下三步:
- 部署
rabbitmq-exporter插件,获取rabbitmq的信息
kubectl create -f exporter-deployment.yaml
# exporter-deployment.yaml,要记得修改RabbitMQ的配置
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: rabbitmq-exporter
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: rabbitmq-exporter
spec:
containers:
- name: rabbitmq-exporter
image: kbudde/rabbitmq-exporter
env:
- name: PUBLISH_PORT
value: "9099"
- name: RABBIT_CAPABILITIES
value: "bert,no_sort"
- name: RABBIT_USER
value: "user"
- name: RABBIT_PASSWORD
value: "password"
- name: RABBIT_URL
value: http://localhost:15672
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9099
- 创建
rabbitmq-exporter对应的service,能够让prometheus通过该service获取到rabbitmq的信息
kubectl create -f exporter-svc.yaml
# exporter-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: rabbitmq-exporter
namespace: default
labels:
k8s-app: rabbitmq-exporter
spec:
selector:
k8s-app: rabbitmq-exporter
type: ClusterIP
clusterIP: None
ports:
- name: api
port: 9099
protocol: TCP
在创建好 service 后,我们就可以测试了
test@max-master:~/gfs-k8s$ kubectl get endpoints
NAME ENDPOINTS
rabbitmq-exporter 10.244.1.223:9099
# 访问对应到的ip和端口,看到以下信息就表示部署成功了,能够通过service获取到mq的信息
curl http://10.244.1.223:9099/metrics
# HELP rabbitmq_queue_messages_ready_ram Number of messages from messages_ready which are resident in ram.
# TYPE rabbitmq_queue_messages_ready_ram gauge
rabbitmq_queue_messages_ready_ram{cluster="rabbit@rabbitmq-d8b6cc756-s5m85",durable="true",policy="",queue="2bac6d7195f01ed379f6819ca77d4954",self="1",vhost="/"} 201
rabbitmq_queue_messages_ready_ram{cluster="rabbit@rabbitmq-d8b6cc756-s5m85",durable="true",policy="",queue="44882ab9f68e10ec6556dc42d150974a",self="1",vhost="/"} 0
rabbitmq_queue_messages_ready_ram{cluster="rabbit@rabbitmq-d8b6cc756-s5m85",durable="true",policy="",queue="49a35e386b824bf825a8adca5014563a",self="1",vhost="/"} 0
rabbitmq_queue_messages_ready_ram{cluster="rabbit@rabbitmq-d8b6cc756-s5m85",durable="true",policy="",queue="9343296e1e8a14a292fb0d351b7d5a21",self="1",vhost="/"} 0
rabbitmq_queue_messages_ready_ram{cluster="rabbit@rabbitmq-d8b6cc756-s5m85",durable="true",policy="",queue="b37ed0539555e5b5f5d16093c1d956c1",self="1",vhost="/"} 0
rabbitmq_queue_messages_ready_ram{cluster="rabbit@rabbitmq-d8b6cc756-s5m85",durable="true",policy="",queue="test",self="1",vhost="/"} 0
- 创建
ServiceMonitor,能够让prometheus知道要去取rabbitmq的信息
kubectl create -f monitoring.yaml
# monitoring.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: rabbitmq-exporter
namespace: monitoring
labels:
k8s-app: rabbitmq-exporter
namespace: monitoring
spec:
jobLabel: k8s-app
endpoints:
- port: api
interval: 30s
scheme: http
selector:
matchLabels:
k8s-app: rabbitmq-exporter
namespaceSelector:
matchNames:
- default
注意下面的配置,你要采集的数据在哪个namespace下,就写他的namespace
namespaceSelector:
matchNames:
- default
再去prometheus的target页面查看就能看到有rabbitmq-exporter了,就部署成功了
HPA配置
之前我们已经安装好了依赖的组件,那我们怎么才能够通过custom-service访问到rabbitmq的数据呢
比如我们访问名为test-1的队列的信息:
test@max-master:~/kube-prometheus/manifests$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/services/rabbitmq-exporter/rabbitmq_queue_messages_ready?metricLabelSelector=queue%3Dtest-1 | jq .
{
"kind": "MetricValueList",
"apiVersion": "custom.metrics.k8s.io/v1beta1",
"metadata": {
"selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/services/rabbitmq-exporter/rabbitmq_queue_messages_ready"
},
"items": [
{
"describedObject": {
"kind": "Service",
"namespace": "default",
"name": "rabbitmq-exporter",
"apiVersion": "/v1"
},
"metricName": "rabbitmq_queue_messages_ready",
"timestamp": "2020-02-21T09:19:41Z",
"value": "0",
"selector": null
}
]
}
可以看到等待被处理的消息是0。
其中rabbitmq_queue_messages_ready是rabbitmq-exporter中定义的指标,更多的指标可以参考
有了数据编写HPA的yaml文件就很简单了,下面的yaml配置的是:监控名为test-1的队列,如果其中等待被处理的消息超过了10条,就对名为test-nginx的deployment进行动态扩缩容
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: test-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: extensions/v1beta1
kind: Deployment
name: test-nginx # 需要扩缩容的deployment名字
minReplicas: 1
maxReplicas: 100
metrics:
- type: Object
object:
metric:
name: "rabbitmq_queue_messages_ready" # 需要监控的指标
selector:
matchLabels:
"queue": "test-1"
describedObject:
apiVersion: "v1"
kind: Service
name: rabbitmq-exporter # 从哪里获取指标
target:
type: Value
value: 10
在后面的测试当中,发现如果Pod启动的数量过多,服务器资源耗尽,就会出现节点NotReady的问题,排查后是因为org.freedesktop.systemd1奔溃了。找了好多方法都不行,最后只能重启节点解决,如果大家知道好的解决办法可以留言告诉我,谢谢。