这里要说一下Prometheus的检控指标从哪里来,它有3个渠道:html
主机监控,也就是部署了Node Exporter组件的主机,它以DaemonSet或者系统进程的形式运行,Prometheus会从这里获取关于宿主机相关的资源指标node
从Kubernetes自身组件,好比API Server或者Kubelet的/metrics,能够获取工做队列长度、请求QPS以及kubelet暴露出来的关于POD和容器的相关指标,其中容器的是经过Kubelet内置的cAdvisor服务暴露的,它是监控容器内部的。git
经过插件获取的,也就是Heapster的替代者Metrics Server,这个用来获取Pod和Node资源使用状况,它主要是用来供HPA使用。它的数据并非来自于Prometheus,不过Prometheus能够获取。github
今天咱们说的自定义API其实就是和Metrics Server的实现方式同样,都是经过注册API的形式来完成和Kubernetes的集成的,也就是在API Server增长本来没有的API。不过添加API还能够经过CRD的方式完成,不过咱们这里直说聚合方式。看下图:docker
实际上咱们访问API的时候访问的是一个aggregator的代理层,下面绿色的都是可用的后端。咱们访问上图中的2个URL实际上是被代理到不一样的后端,在这个机制下你能够添加更多的后端,而咱们今天要说的Custome-metrics-apiserver就是绿色线条的路径。若是你部署Metrics-server的话对于理解今天的内容也不会很难。json
首先在API Server中启用API聚合功能,在kube-apiserver启动参数中加入以下内容,其中证书名称换成你本身环境中的:后端
# CA根证书 --requestheader-client-ca-file= --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User # 在请求期间验证aggregator的客户端CA证书,我这里就是CA根证书 --proxy-client-cert-file= # 私钥,我这里配置的就是CA根证书私钥 --proxy-client-key-file=
另外在kube-controller-manager组件上也有一些参数能够配置,可是不是必须的:centos
# HPT控制器同步Pod副本数量的时间间隔,默认15秒 --horizontal-pod-autoscaler-sync-period=15s # 执行缩容的等待时间,默认5分钟 --horizontal-pod-autoscaler-downscale-stabilization=5m0s # 等待Pod到达Reday状态的延时,默认30秒 --horizontal-pod-autoscaler-initial-readiness-delay=30s
其次定义api,而后向API Server进行注册api
最后部署提供服务的应用。bash
本次演示使用的配置清单文件请提早下载。下面对每一个文件作一下简要说明。
custom-metrics-apiserver-auth-delegator-cluster-role-binding:
custom-metrics-apiserver-auth-reader-role-binding:
custom-metrics-apiserver-deployment:自定义指标提供者的容器
custom-metrics-apiserver-resource-reader-cluster-role-binding:
custom-metrics-apiserver-service:为deployment建立的service
custom-metrics-apiserver-service-account:建立服务帐号
custom-metrics-apiservice:须要注册的apiserver名称以及关联的service
custom-metrics-cluster-role:
custom-metrics-config-map:配置文件
custom-metrics-resource-reader-cluster-role:
hpa-custom-metrics-cluster-role-binding:
上面的文件看起来比较多,咱们合并一下,便于演示。
custom-metrics-apiserver-auth-delegator-cluster-role-binding、custom-metrics-apiserver-auth-reader-role-binding、custom-metrics-apiserver-resource-reader-cluster-role-binding、custom-metrics-apiserver-service-account、custom-metrics-cluster-role、custom-metrics-resource-reader-cluster-role、hpa-custom-metrics-cluster-role-binding,这几个合并到一个文件中,主要是服务帐号和受权类的。其余保持单独的。合并文件custom-metrics-apiserver-rabc.yaml内容以下:
# 建立名称空间 kind: Namespace apiVersion: v1 metadata: name: custom-metrics --- # 建立服务帐号 kind: ServiceAccount apiVersion: v1 metadata: name: custom-metrics-apiserver namespace: custom-metrics --- # 把系统角色system:auth-delegator绑定到服务帐号,使该服务帐号能够将认证请求转发到自定义的API上 apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: custom-metrics:system:auth-delegator roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - kind: ServiceAccount name: custom-metrics-apiserver namespace: custom-metrics --- # 建立集群角色custom-metrics-resource-reader apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: custom-metrics-resource-reader rules: - apiGroups: - "" resources: - namespaces - pods - services verbs: - get - list --- # 把集群角色custom-metrics-resource-reader绑定到服务帐号 apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: custom-metrics-resource-reader roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: custom-metrics-resource-reader subjects: - kind: ServiceAccount name: custom-metrics-apiserver namespace: custom-metrics --- # 把系统自带的角色extension-apiserver-authentication-reader绑定到服务帐号,容许服务帐号能够访问系统的ConfigMap extension-apiserver-authentication apiVersion: rbac.authorization.k8s.io/v1beta1 kind: RoleBinding metadata: name: custom-metrics-auth-reader namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: extension-apiserver-authentication-reader subjects: - kind: ServiceAccount name: custom-metrics-apiserver namespace: custom-metrics --- # 建立集群角色custom-metrics-server-resources apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: custom-metrics-server-resources rules: - apiGroups: - custom.metrics.k8s.io resources: ["*"] verbs: ["*"] --- # 把集群角色custom-metrics-server-resources绑定到系统自带的服务帐号horizontal-pod-autoscaler apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: hpa-controller-custom-metrics roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: custom-metrics-server-resources subjects: - kind: ServiceAccount name: horizontal-pod-autoscaler namespace: kube-system
源文件中使用的monitoring这个Namespace,可是它没有建立Namespace的内容,因此上面文件第一部分我本身增长了一个,而后我使用的Namespace是custom-metrics,并把源文件中的作了修改。
#!/bin/bash TEMP_WORK_DIR="/tmp/work" cd ${TEMP_WORK_DIR} cat << EOF > custom-metrics-apiserver-csr.json { "CN": "custom-metrics-apiserver", "hosts": [ "custom-metrics-apiserver.custom-metrics.svc" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "CN", "L": "Beijing", "ST": "Beijing" } ] } EOF # 会产生 custom-metrics-apiserver.pem custom-metrics-apiserver-key.pem cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes custom-metrics-apiserver-csr.json | cfssljson -bare custom-metrics-apiserver # 建立secret kubectl create secret generic cm-adapter-serving-certs --from-file=serving.crt=./custom-metrics-apiserver.pem --from-file=serving.key=./custom-metrics-apiserver-key.pem -n custom-metrics
ca-config.json文件内容
{ "signing": { "default": { "expiry": "87600h" }, "profiles": { "kubernetes": { "expiry": "87600h", "usages": [ "signing", "key encipherment", "server auth", "client auth" ] }, "etcd": { "expiry": "87600h", "usages": [ "signing", "key encipherment", "server auth", "client auth" ] }, "client": { "expiry": "87600h", "usages": [ "signing", "key encipherment", "client auth" ] } } } }
前提是你安装好了cfssl工具、配置好了kubectl环境变量以及在当前工做目录具备ca-config.json文件,这个文件不必定是这个名字。这个文件是我以前部署k8s集群时候使用的,因此如今继续使用,它用于配置证书的通用属性信息。
上图看到的TYPE是Opaque类型,由于Secret有4中类型Opaque是base64编码的Secret,用于存储密码和秘钥等加密比较弱。kubernetes.io/dockerconfigjson是用来存储私有docker仓库的认证信息。kubernetes.io/service-account-token是用来被service account使用的,是K8S用于验证service account的有效性的token。还有一种是tls专门用于加密证书文件的。
该文件的主要做用就是向Api server注册一个api,此API名称是关联到一个service名称上。
# 这个是向K8S apiserver注册本身的api以及版本 apiVersion: apiregistration.k8s.io/v1beta1 kind: APIService metadata: name: v1beta1.custom.metrics.k8s.io spec: # 该API所关联的service名称,也就是访问这个自定义API后,被转发到哪一个service来处理,custom-metrics-apiserver-service这里定义的 service: name: custom-metrics-apiserver namespace: custom-metrics # api所属的组名称 group: custom.metrics.k8s.io # api版本 version: v1beta1 insecureSkipTLSVerify: true groupPriorityMinimum: 100 versionPriority: 100
从下图能够看到这个api以及注册上了。
这个注册的api的其实就是hpa去api server访问这个注册的api,这个注册的api其实起到的是代理做用,把请求代理到某一个service上,这个service的endpoint其实就是提供检控数据的一个应用,也就是下面的适配器,适配器的做用就是从prometheus中查询数据而后从新组装成k8s能够识别的数据格式。
这里咱们应用剩余的三个配置清单。这里咱们主要说一下deployment文件。
apiVersion: apps/v1 kind: Deployment metadata: labels: app: custom-metrics-apiserver name: custom-metrics-apiserver namespace: custom-metrics spec: replicas: 1 selector: matchLabels: app: custom-metrics-apiserver template: metadata: labels: app: custom-metrics-apiserver name: custom-metrics-apiserver spec: serviceAccountName: custom-metrics-apiserver containers: - name: custom-metrics-apiserver image: quay.io/coreos/k8s-prometheus-adapter-amd64:v0.4.1 args: - /adapter - --secure-port=6443 # 这里是custom-metrics-apiserver链接k8s apiserver使用的证书和私钥,这个证书你能够本身生成,主要注意CN和hosts字段。另外使用k8s的CA根证书来签发 # CN能够写service名称,hosts写service的DNS名称,鉴于IP会变因此不建议写IP地址,而后使用sfssl来生成证书,最后建立secret。 # 该文件下面secretName中的名称。 - --tls-cert-file=/var/run/serving-cert/serving.crt - --tls-private-key-file=/var/run/serving-cert/serving.key - --logtostderr=true # 这个是链接prometheus的地址,它这里写的是prometheus service的域名 - --prometheus-url=http://prometheus.kube-system.svc:9090/ - --metrics-relist-interval=30s - --v=10 - --config=/etc/adapter/config.yaml ports: - containerPort: 6443 volumeMounts: - mountPath: /var/run/serving-cert name: volume-serving-cert readOnly: true - mountPath: /etc/adapter/ name: config readOnly: true volumes: - name: volume-serving-cert secret: secretName: cm-adapter-serving-certs - name: config configMap: name: adapter-config
首先进入这个POD看看证书有没有挂载上来
启动一个容器测试一下看看是否能够ping通prometheus的域名,经过下面的命令启动一个容器--rm是退出容器就删除该容器。kubectl run -it test --image=centos --rm /bin/bash
,以下图:
经过下面这个命令来查看自定义的api提供了哪些metrics,curl http://127.0.0.1:8080/apis/custom.metrics.k8s.io/v1beta1
按照网上的测试方法会获得这样的错误提示"message": "the server could not find the metric http_requests for pods",以下图:
若是这里的指标找不到,那你将没法经过这个指标来实现HPA,这是怎么回事呢?这个就跟你部署的那个deployment的具体镜像k8s-prometheus-adapter-amd64它使用的配置文件有关,也就是custom-metrics-config-map.yaml里面定义的rules,文件中全部的seriesQuery项在prometheus中查询后的结果都没有http_request_totals指标,因此也就确定找不到。
这里的原理就是adapter经过使用prometheus的查询语句来获取指标而后作一下修改最终把从新组装的指标和值经过本身的接口暴露,而后由自定义api代理到adapter的service上来获取这些指标。
既然样例中的规则缺乏了,那么咱们就本身拼凑一个(须要首先保障你的prometheus中能够搜索到http_requests_total这个指标),内容以下:
- seriesQuery: '{__name__=~"^http_requests_.*",kubernetes_pod_name!="",kubernetes_namespace!=""}' seriesFilters: [] resources: overrides: kubernetes_namespace: resource: namespace kubernetes_pod_name: resource: pod name: matches: ^(.*)_(total)$ as: "${1}" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
最终的custom-metrics-config-map.yaml就是下面这个
apiVersion: v1 kind: ConfigMap metadata: name: adapter-config namespace: custom-metrics data: config.yaml: | rules: - seriesQuery: '{__name__=~"^container_.*",container_name!="POD",namespace!="",pod_name!=""}' seriesFilters: [] resources: overrides: namespace: resource: namespace pod_name: resource: pod name: matches: ^container_(.*)_seconds_total$ as: "" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}[1m])) by (<<.GroupBy>>) - seriesQuery: '{__name__=~"^container_.*",container_name!="POD",namespace!="",pod_name!=""}' seriesFilters: - isNot: ^container_.*_seconds_total$ resources: overrides: namespace: resource: namespace pod_name: resource: pod name: matches: ^container_(.*)_total$ as: "" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}[1m])) by (<<.GroupBy>>) - seriesQuery: '{__name__=~"^container_.*",container_name!="POD",namespace!="",pod_name!=""}' seriesFilters: - isNot: ^container_.*_total$ resources: overrides: namespace: resource: namespace pod_name: resource: pod name: matches: ^container_(.*)$ as: "" metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>,container_name!="POD"}) by (<<.GroupBy>>) - seriesQuery: '{namespace!="",__name__!~"^container_.*"}' seriesFilters: - isNot: .*_total$ resources: template: <<.Resource>> name: matches: "" as: "" metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>) - seriesQuery: '{namespace!="",__name__!~"^container_.*"}' seriesFilters: - isNot: .*_seconds_total resources: template: <<.Resource>> name: matches: ^(.*)_total$ as: "" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>) - seriesQuery: '{__name__=~"^http_requests_.*",kubernetes_pod_name!="",kubernetes_namespace!=""}' seriesFilters: [] resources: overrides: kubernetes_namespace: resource: namespace kubernetes_pod_name: resource: pod name: matches: ^(.*)_(total)$ as: "${1}" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>) - seriesQuery: '{namespace!="",__name__!~"^container_.*"}' seriesFilters: [] resources: template: <<.Resource>> name: matches: ^(.*)_seconds_total$ as: "" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>) resourceRules: cpu: containerQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>) nodeQuery: sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>, id='/'}[1m])) by (<<.GroupBy>>) resources: overrides: instance: resource: node namespace: resource: namespace pod_name: resource: pod containerLabel: container_name memory: containerQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>}) by (<<.GroupBy>>) nodeQuery: sum(container_memory_working_set_bytes{<<.LabelMatchers>>,id='/'}) by (<<.GroupBy>>) resources: overrides: instance: resource: node namespace: resource: namespace pod_name: resource: pod containerLabel: container_name window: 1m
删除以前的config而后从新建立adapter再进行测试。
下面测试一下看看,发现已经有了。
curl http://127.0.0.1:8080/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_requests
当你访问这个注册的URL时候会被代理到后端adaptor上,而后这个adaptor会去prometheus里面查询具体的资源,好比上面/pods/*/http_requests,其资源就是pod,星号这是全部pod,的http_requests。而后按照k8s的格式进行返回。这也就是说你要访问的资源首先要在Prometheus中存在,而且该资源还须要被体如今http_requests_total指标中。因此咱们上面本身加的那段就是为了让adaptor能够去获取Prometheus的http_requests_total指标,而后这个指标中包含POD名称,以下图:
也就是说在prometheus中有指标是基础,而后adaptor才会按照它本身的配置规则去过滤,咱们上面添加的规则其实就是过滤规则。
这个app咱们用本身建立的景象
apiVersion: v1 kind: Service metadata: name: myapp-svc labels: appname: myapp-svc spec: type: ClusterIP ports: - name: http port: 5555 targetPort: 5555 selector: appname: myapp --- apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deploy-v1.0 labels: appname: myapp spec: replicas: 2 selector: matchLabels: appname: myapp release: 1.0.0 template: metadata: name: myapp labels: appname: myapp release: 1.0.0 annotations: prometheus.io/scrape: "true" prometheus.io/port: "5555" spec: containers: - name: myapp image: myapp:v1.0 imagePullPolicy: IfNotPresent resources: requests: cpu: "250m" memory: "128Mi" limits: cpu: "500m" memory: "256Mi" ports: - name: http containerPort: 5555 protocol: TCP livenessProbe: httpGet: path: /healthy port: http initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 2 readinessProbe: httpGet: path: /healthy port: http initialDelaySeconds: 20 periodSeconds: 10 revisionHistoryLimit: 10 strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 type: RollingUpdate
具体镜像制做过程参见使用Kubernetes演示金丝雀发布
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: myapp-custom-metrics-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp-deploy-v1.0 minReplicas: 2 maxReplicas: 10 metrics: - type: Pods pods: metricName: http_requests targetAverageValue: 5000m
自定义资源指标含义是基于myapp-deploy-v1.0这个deployment的所有pod来计算http_requests的平局值,若是达到5000m就进行扩容。
scaleTargetRef:这里定义的是自动扩容缩容的对象,能够是Deployment或者ReplicaSet,这里写具体的Deployment的名称。
metrics:这里是指标的目标值。在type中定义类型;经过target来定义指标的阈值,系统将在指标达到阈值的时候出发扩缩容操做。
metrics中的type有以下类型:
Resource:基于资源的指标,能够是CPU或者是内存,若是基于这个类型的指标来作只须要部署Metric-server便可,不须要部署自定义APISERVER。
Pods:基于Pod的指标,系统将对Deployment中的所有Pod副本指标进行平均值计算,若是是Pod则该指标必须来源于Pod自己。
Object:基于Ingress或者其余自定义指标,好比ServiceMonitor。它的target类型能够是Value或者AverageValue(根据Pod副本数计算平均值)。
这里说一下5000m是什么意思。自定义API SERVER收到请求后会从Prometheus里面查询http_requests_total的值,而后把这个值换算成一个以时间为单位的请求率。500m的m就是milli-requests,按照定义的规则metricsQuery中的时间范围1分钟,这就意味着过去1分钟内每秒若是达到500个请求则会进行扩容。
在一个HPA中能够定义了多种指标,若是定义多个系统将针对每种类型指标都计算Pod副本数量,取最大的进行扩缩容。换句话说,系统会根据CPU和pod的自定义指标计算,任何一个达到了都进行扩容。应用上面的HPA配置清单文件:
使用下面的命令进行测试:
ab -c 10 -n 10000 http://10.254.101.238:5555
10个用户,总共发10000个请求,平均到2个POD,每一个POD承受500,显然不会达到咱们设置的阈值。
能够看到已经扩容到3个了。从监控上也能够看到,以下图:
过了一会流量没有了它会自动缩容,以下图:
若是基于Service的话你能够改为service,以下:
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: myapp-custom-metrics-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp-deploy-v1.0 minReplicas: 2 maxReplicas: 10 metrics: - type: Object object: target: kind: Service # 这里是你本身的APP的service name: myapp-svc metricName: http_requests targetValue: 100
不过这个配置在个人环境中没法访问,curl http://127.0.0.1:8080/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/services/myapp-svc/http_requests
结果以下图:
由于上面的APP中的service没有配置被监控,另外在adaptor中的规则是针对pod名字的,因此这里要作一下修改。
说明,全部修改都是基于你的Prometheus中的k8s动态发现规则的配置。
首先修改myapp的service配置,而后就能够在k8s自动发现的角色endpoints里被发现:
apiVersion: v1 kind: Service metadata: name: myapp-svc labels: appname: myapp-svc annotations: prometheus.io/scrape: "true" # 新增内容 prometheus.io/port: "5555" # 新增内容 spec: type: ClusterIP ports: - name: http port: 5555 targetPort: 5555 selector: appname: myapp
重启应用,在Prometheus中查看
指标内容就比以前多一条:
而后配置适配器规则,增长以下内容:
- seriesQuery: '{__name__=~"^http_requests_.*",kubernetes_name!="",kubernetes_namespace!=""}' seriesFilters: [] resources: overrides: kubernetes_namespace: resource: namespace kubernetes_name: resource: service name: matches: ^(.*)_(total)$ as: "${1}" metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
kubernetes_name在prometheus中就是service的名称,这个是k8s中动态发现里面的relable修改的,咱们来测试一下新的条件是否能够找到:
应用这个配置,而后重建适配器。
再次经过该URL进行访问curl http://127.0.0.1:8080/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/services/myapp-svc/http_requests
,结果以下图:
关于这里的拍错须要说一下,就是你能够访问curl http://127.0.0.1:8080/apis/custom.metrics.k8s.io/v1beta1 > /tmp/metrics.txt
把它能使用资源列出来,若是没有service/http_requests则说明你没法访问那个URL,也就是说没有什么你就加什么就行了。
我这里就不作演示了,由于只要这个URL能够出来内容,那么针对Service的HPA就能够作。
参考: