본문 바로가기

IT Note/Cloud

Kubernetes Monitoring - Prometheus 실습

원문: 호롤리한하루/Kubernetes Monitoring - Prometheus 실습

Overview

이번 포스팅에서는 쿠버네티스 클러스터의 메트릭들을 프로메테우스로 수집하고 web UI를 통해 시각화 시키는 작업을 해보겠습니다.

참고 링크 : 쿠버네티스 시작하기(11) - Prometheus & Node-Exporter & AlertManager 연동

Prerequisites

먼저 쿠버네티스 클러스터를 생성해주세요.

참고링크 : 호롤리한하루/Install Kubernetes on CentOS/RHEL

본 실습에서 사용한 spec :
OS : CentOS v7.6
Arch : x86

Kubernetes : v1.16.2
Master : 4cpu, ram16G (1개)
Node : 4cpu, ram16G (2개)

Step

1. Prometheus 배포

namespace

제일먼저 해줘야 할 것은 프로메테우스관련 오브젝트들이 사용할 namespace를 생성해 주는 것입니다.

$ kubectl create ns monitoring

Cluster Role

다음으로는 프로메테우스 컨테이너가 쿠버네티스 api에 접근할 수 있는 권한을 부여해주기 위해 ClusterRole을 설정해주고 ClusterRoleBinding을 해줍니다.
생성된 ClusterRole은 monitoring namespace의 기본 서비스어카운트와 연동되어 권한을 부여해줍니다.

# prometheus-cluster-role.yaml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: prometheus
  namespace: monitoring
rules:
- apiGroups: [""]
  resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
  verbs: ["get", "list", "watch"]
- apiGroups:
  - extensions
  resources:
  - ingresses
  verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
- kind: ServiceAccount
  name: default
  namespace: monitoring

Configmap

프로메테우스가 기동되려면 환경설정파일이 필요한데 해당 환경설정 파일을 정의해주는 부분입니다.

data 밑에 prometheus.rulesprometheus.yml를 각각 정의하게 되어있습니다.

  • prometheus.rules : 수집한 지표에 대한 알람조건을 지정하여 특정 조건이 되면 AlertManager로 알람을 보낼 수 있음
  • prometheus.yml : 수집할 지표(metric)의 종류와 수집 주기등을 기입
# prometheus-config-map.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-server-conf
  labels:
    name: prometheus-server-conf
  namespace: monitoring
data:
  prometheus.rules: |-
    groups:
    - name: container memory alert
      rules:
      - alert: container memory usage rate is very high( > 55%)
        expr: sum(container_memory_working_set_bytes{pod!="", name=""})/ sum (kube_node_status_allocatable_memory_bytes) * 100 > 55
        for: 1m
        labels:
          severity: fatal
        annotations:
          summary: High Memory Usage on {{ $labels.instance }}
          identifier: "{{ $labels.instance }}"
          description: "{{ $labels.job }} Memory Usage: {{ $value }}"
    - name: container CPU alert
      rules:
      - alert: container CPU usage rate is very high( > 10%)
        expr: sum (rate (container_cpu_usage_seconds_total{pod!=""}[1m])) / sum (machine_cpu_cores) * 100 > 10
        for: 1m
        labels:
          severity: fatal
        annotations:
          summary: High Cpu Usage
  prometheus.yml: |-
    global:
      scrape_interval: 5s
      evaluation_interval: 5s
    rule_files:
      - /etc/prometheus/prometheus.rules
    alerting:
      alertmanagers:
      - scheme: http
        static_configs:
        - targets:
          - "alertmanager.monitoring.svc:9093"

    scrape_configs:
      - job_name: 'kubernetes-apiservers'

        kubernetes_sd_configs:
        - role: endpoints
        scheme: https

        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token

        relabel_configs:
        - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
          action: keep
          regex: default;kubernetes;https

      - job_name: 'kubernetes-nodes'

        scheme: https

        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token

        kubernetes_sd_configs:
        - role: node

        relabel_configs:
        - action: labelmap
          regex: __meta_kubernetes_node_label_(.+)
        - target_label: __address__
          replacement: kubernetes.default.svc:443
        - source_labels: [__meta_kubernetes_node_name]
          regex: (.+)
          target_label: __metrics_path__
          replacement: /api/v1/nodes/${1}/proxy/metrics


      - job_name: 'kubernetes-pods'

        kubernetes_sd_configs:
        - role: pod

        relabel_configs:
        - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
          action: keep
          regex: true
        - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
          action: replace
          target_label: __metrics_path__
          regex: (.+)
        - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
          action: replace
          regex: ([^:]+)(?::\d+)?;(\d+)
          replacement: $1:$2
          target_label: __address__
        - action: labelmap
          regex: __meta_kubernetes_pod_label_(.+)
        - source_labels: [__meta_kubernetes_namespace]
          action: replace
          target_label: kubernetes_namespace
        - source_labels: [__meta_kubernetes_pod_name]
          action: replace
          target_label: kubernetes_pod_name

      - job_name: 'kube-state-metrics'
        static_configs:
          - targets: ['kube-state-metrics.kube-system.svc.cluster.local:8080']

      - job_name: 'kubernetes-cadvisor'

        scheme: https

        tls_config:
          ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token

        kubernetes_sd_configs:
        - role: node

        relabel_configs:
        - action: labelmap
          regex: __meta_kubernetes_node_label_(.+)
        - target_label: __address__
          replacement: kubernetes.default.svc:443
        - source_labels: [__meta_kubernetes_node_name]
          regex: (.+)
          target_label: __metrics_path__
          replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

      - job_name: 'kubernetes-service-endpoints'

        kubernetes_sd_configs:
        - role: endpoints

        relabel_configs:
        - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
          action: keep
          regex: true
        - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
          action: replace
          target_label: __scheme__
          regex: (https?)
        - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
          action: replace
          target_label: __metrics_path__
          regex: (.+)
        - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
          action: replace
          target_label: __address__
          regex: ([^:]+)(?::\d+)?;(\d+)
          replacement: $1:$2
        - action: labelmap
          regex: __meta_kubernetes_service_label_(.+)
        - source_labels: [__meta_kubernetes_namespace]
          action: replace
          target_label: kubernetes_namespace
        - source_labels: [__meta_kubernetes_service_name]
          action: replace
          target_label: kubernetes_name

deployment

프로메테우스 이미지를 담은 pod을 담은 deployment controller

# prometheus-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus-deployment
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus-server
  template:
    metadata:
      labels:
        app: prometheus-server
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:latest
          args:
            - "--config.file=/etc/prometheus/prometheus.yml"
            - "--storage.tsdb.path=/prometheus/"
          ports:
            - containerPort: 9090
          volumeMounts:
            - name: prometheus-config-volume
              mountPath: /etc/prometheus/
            - name: prometheus-storage-volume
              mountPath: /prometheus/
      volumes:
        - name: prometheus-config-volume
          configMap:
            defaultMode: 420
            name: prometheus-server-conf

        - name: prometheus-storage-volume
          emptyDir: {}

node exporter

프로메테우스가 수집하는 메트릭은 쿠버네티스에서 기본으로 제공하는 system metric만 수집하는게 아니라 그 외의 것들도 수집하기 때문에 수집역할을 하는 에이전트를 따로 두어야 합니다.

그 역할을 해주는게 node-exporter이고 각 노드에 하나씩 띄워야 하므로 DaemonSet으로 구성해주도록 합니다.

# prometheus-node-exporter.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
  labels:
    k8s-app: node-exporter
spec:
  selector:
    matchLabels:
      k8s-app: node-exporter
  template:
    metadata:
      labels:
        k8s-app: node-exporter
    spec:
      containers:
      - image: prom/node-exporter
        name: node-exporter
        ports:
        - containerPort: 9100
          protocol: TCP
          name: http
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: node-exporter
  name: node-exporter
  namespace: kube-system
spec:
  ports:
  - name: http
    port: 9100
    nodePort: 31672
    protocol: TCP
  type: NodePort
  selector:
    k8s-app: node-exporter

Service

마지막으로 프로메테우스 pod을 외부로 노출시키는 서비스를 구성해줍니다.

# prometheus-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: prometheus-service
  namespace: monitoring
  annotations:
      prometheus.io/scrape: 'true'
      prometheus.io/port:   '9090'
spec:
  selector:
    app: prometheus-server
  type: NodePort
  ports:
    - port: 8080
      targetPort: 9090
      nodePort: 30003

배포

한 번에 배포 :

$ kubectl apply -f prometheus-cluster-role.yaml
$ kubectl apply -f prometheus-config-map.yaml
$ kubectl apply -f prometheus-deployment.yaml
$ kubectl apply -f prometheus-node-exporter.yaml
$ kubectl apply -f prometheus-svc.yaml

prometheus pod도 정상적으로 동작하고, 현재 실습환경은 노드가 2개이므로 node-exporter도 2개 뜬 것을 확인할 수 있습니다.

$ kubectl get pod -n monitoring

NAME                                     READY   STATUS    RESTARTS   AGE
node-exporter-99w2v                      1/1     Running   0          18s
node-exporter-f9q7f                      1/1     Running   0          18s
prometheus-deployment-7bcb5ff899-h4rb7   1/1     Running   0          65s

ip:30003로 접근해보면 prometheus의 web ui로 접근할 수 있습니다.
쿠버네티스의 다양한 metric들을 그래프로 확인할 수 있습니다.

상단 메뉴의 Status > Targets를 들어가보면, 프로메테우스가 현재 모니터링하고있는 타겟을 확인할 수 있습니다.
근데 이 중 kube-state-metrics가 (0/1 up)으로 아직 올라가지 않은 것으로 표시됩니다.

kube-state-metrics는 쿠버네티스 클러스터 내 오브젝트(예를들면 Pod)에 대한 지표정보를 생성하는 서비스입니다.
따라서 Pod 상태정보를 모니터링하기 위해서는 kube-state-metrics가 떠 있어야 합니다.

Kube State Metrics 배포

첫번째로 배포할 것은 ClusterRole과 ClusterRoleBinding입니다.

# kube-state-cluster-role.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kube-state-metrics
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kube-state-metrics
subjects:
- kind: ServiceAccount
  name: kube-state-metrics
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kube-state-metrics
rules:
- apiGroups:
  - ""
  resources: ["configmaps", "secrets", "nodes", "pods", "services", "resourcequotas", "replicationcontrollers", "limitranges", "persistentvolumeclaims", "persistentvolumes", "namespaces", "endpoints"]
  verbs: ["list","watch"]
- apiGroups:
  - extensions
  resources: ["daemonsets", "deployments", "replicasets", "ingresses"]
  verbs: ["list", "watch"]
- apiGroups:
  - apps
  resources: ["statefulsets", "daemonsets", "deployments", "replicasets"]
  verbs: ["list", "watch"]
- apiGroups:
  - batch
  resources: ["cronjobs", "jobs"]
  verbs: ["list", "watch"]
- apiGroups:
  - autoscaling
  resources: ["horizontalpodautoscalers"]
  verbs: ["list", "watch"]
- apiGroups:
  - authentication.k8s.io
  resources: ["tokenreviews"]
  verbs: ["create"]
- apiGroups:
  - authorization.k8s.io
  resources: ["subjectaccessreviews"]
  verbs: ["create"]
- apiGroups:
  - policy
  resources: ["poddisruptionbudgets"]
  verbs: ["list", "watch"]
- apiGroups:
  - certificates.k8s.io
  resources: ["certificatesigningrequests"]
  verbs: ["list", "watch"]
- apiGroups:
  - storage.k8s.io
  resources: ["storageclasses", "volumeattachments"]
  verbs: ["list", "watch"]
- apiGroups:
  - admissionregistration.k8s.io
  resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"]
  verbs: ["list", "watch"]
- apiGroups:
  - networking.k8s.io
  resources: ["networkpolicies"]
  verbs: ["list", "watch"]

위의 ClusterRole과 연동될 서비스어카운트를 생성해줍니다.

# kube-state-svcaccount.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: kube-state-metrics
  namespace: kube-system

kube-state-metrics의 deployment도 구성해줍니다.

# kube-state-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: kube-state-metrics
  name: kube-state-metrics
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kube-state-metrics
  template:
    metadata:
      labels:
        app: kube-state-metrics
    spec:
      containers:
      - image: quay.io/coreos/kube-state-metrics:v1.8.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          timeoutSeconds: 5
        name: kube-state-metrics
        ports:
        - containerPort: 8080
          name: http-metrics
        - containerPort: 8081
          name: telemetry
        readinessProbe:
          httpGet:
            path: /
            port: 8081
          initialDelaySeconds: 5
          timeoutSeconds: 5
      nodeSelector:
        kubernetes.io/os: linux
      serviceAccountName: kube-state-metrics

마지막으로 kube-state-metrics의 서비스를 생성해주고 :

# kube-state-svc.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: kube-state-metrics
  name: kube-state-metrics
  namespace: kube-system
spec:
  clusterIP: None
  ports:
  - name: http-metrics
    port: 8080
    targetPort: http-metrics
  - name: telemetry
    port: 8081
    targetPort: telemetry
  selector:
    app: kube-state-metrics

배포해주면 끝입니다.

$ kubectl apply -f kube-state-cluster-role.yaml
$ kubectl apply -f kube-state-deployment.yaml
$ kubectl apply -f kube-state-svcaccount.yaml
$ kubectl apply -f kube-state-svc.yaml
$ kubectl get pod -n kube-system

NAME                                       READY   STATUS    RESTARTS   AGE
...
kube-state-metrics-59bd4d9d-nbfrq          1/1     Running   0          50s

다시 프로메테우스의 웹으로 돌아가서 Target을 확인해보면 kube-state-metrics가 정상적으로 실행되고 있는 것을 확인할 수 있습니다.

2. Grafana 연동

그라파나는 수집 지표 정보를 분석 및 시각화 시키는 오픈소스 툴입니다.
주로 데이터를 시각화 하기 위한 대시보드로 주로 사용됩니다.

위에서 모니터링툴인 프로메테우스를 사용해 쿠버네티스의 metric들을 수집하는 것까지 했는데 이번엔 그라파나를 붙여서 프로메테우스의 데이터들을 시각화시켜보도록 하겠습니다.

먼저 그라파나 pod과 svc를 생성해줍니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      name: grafana
      labels:
        app: grafana
    spec:
      containers:
      - name: grafana
        image: grafana/grafana:latest
        ports:
        - name: grafana
          containerPort: 3000
        env:
        - name: GF_SERVER_HTTP_PORT
          value: "3000"
        - name: GF_AUTH_BASIC_ENABLED
          value: "false"
        - name: GF_AUTH_ANONYMOUS_ENABLED
          value: "true"
        - name: GF_AUTH_ANONYMOUS_ORG_ROLE
          value: Admin
        - name: GF_SERVER_ROOT_URL
          value: /
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: monitoring
  annotations:
      prometheus.io/scrape: 'true'
      prometheus.io/port:   '3000'
spec:
  selector:
    app: grafana
  type: NodePort
  ports:
    - port: 3000
      targetPort: 3000
      nodePort: 30004
$ kubectl apply -f grafana.yaml

$ kubectl get pod -n monitoring
NAME                                     READY   STATUS    RESTARTS   AGE
grafana-799c99855d-kxhkm                 1/1     Running   0          16s
node-exporter-99w2v                      1/1     Running   0          66m
node-exporter-f9q7f                      1/1     Running   0          66m
prometheus-deployment-7bcb5ff899-h4rb7   1/1     Running   0          67m

datasource 추가

ip:30004로 접속해보면 다음 화면을 보실 수 있습니다.
이제 그라파나와 프로메테우스를 연동시켜줄겁니다.

Add data source로 이동 :

시계열 데이터모델을 지원하는 여러 db를 확인할 수 있고, 우리는 프로메테우스를 사용할 것이니 프로메테우스를 선택 :

연동할 프로메테우스의 정보를 기입해주어야 합니다.
url은 서비스의 internal ip를 참고해서 적어주도록 합니다.

$ kubectl get svc -n monitoring

NAME                 TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
grafana              NodePort   10.101.152.163   <none>        3000:30004/TCP   6m43s
prometheus-service   NodePort   10.101.196.111   <none>        8080:30003/TCP   73m

Save & Test 버튼을 눌렀을 때 "Data source is working"메세지가 떠야합니다.

해당 메세지가 뜨면 연동은 성공!

연동을 했으니 프로메테우스의 데이터들을 시각화 해줄 dashboard를 만들어야합니다.
PromQL을 통해 사용자가 직접 원하는 대시보드를 만들수도있지만,
남이 만든것을 쉽게 가져다가 사용할수도 있습니다.

dashboard 추가

이번 실습에서는 PromQL을 작성하는게 아니라 다른사람이 만든 대시보드를 import해보도록 하겠습니다.

Grafana페이지로 이동 ->

Grafana > Dashboard로 이동해서 kubernetes를 검색합니다.

아무거나 마음에 드는것을 고르고 copy id를 눌러 대시보드의 id를 복사합니다.

그라파나 UI로 돌아와서 Import Dashboard로 이동해 복사한 번호를 붙여넣기 합니다.

항목입력해주고 Import해주면 예쁜 dashboard를 확인할 수 있습니다!

Deployment memory usage가 N/A로 뜨는 이유는 해당 dashboard의 PromQL이 저희의 prometheus와 맞지 않아서인데요... PromQL을 수정해주면 잘 뜬다고 하는데 한번 도전해보시기 바랍니다.

마치며

이렇게 쿠버네티스-프로메테우스-그라파나 가 완성되었습니다.
모니터링 구조의 기초 뼈대는 갖춰진 셈이니 이제 상황과 요구에 맞게 추가하시고 수정하시면 될 것 같습니다.


반응형