這裏要說一下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就能夠作。
參考: