Kubernetes基于RabbitMQ队列长度指标进行HPA

2,452 阅读9分钟

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

上图是Prometheus-Operator官方提供的架构图,其中Operator是最核心的部分,作为一个控制器,他会去创建PrometheusServiceMonitorAlertManager以及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=true This flag enables, that a ServiceAccount token can be used to authenticate against the kubelet(s).
  • --authorization-mode=Webhook This 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 /metrics endpoint.

首先将代码拷贝下来,执行下面的命令

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

可以看到grafanaprometheus-adapter并没有暴露出端口,所以我们还要将这两个的端口暴露出去,以便我们能够访问。这里有两种方式

  1. 利用kubectl edit命令直接将service的类型改为NodePort,但是这样暴露出去的端口是随机的
  2. 修改原来的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

更改完就可以根据端口去访问grafanaprometheus了,比如查看 prometheus 的 targets 页面:

promethues-operator-targets

我们可以看到大部分的配置都是正常的,只有两三个没有管理到对应的监控目标,比如 kube-controller-managerkube-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-schedulertier=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 是否已经正常了

promethues-operator-kube-scheduler

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

下载如下资源

github.com/DirectXMan1…

生成证书

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中

总共分为以下三步:

  1. 部署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

  1. 创建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
  1. 创建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

再去prometheustarget页面查看就能看到有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_readyrabbitmq-exporter中定义的指标,更多的指标可以参考

github.com/kbudde/rabb…

有了数据编写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奔溃了。找了好多方法都不行,最后只能重启节点解决,如果大家知道好的解决办法可以留言告诉我,谢谢。