小哥之哥 小哥之哥
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

小哥之哥

运维扫地僧
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Kubertenes

  • Prometheus

    • Prometheus介绍
    • Prometheus安装部署
    • PromQL学习
    • kube-prometheus 实战指南
    • Prometheus高可用-Thanos
    • Prometheus监控JVM实战
      • Alertmanager报警历史持久化
      • Kubernetes监控的自定义部署实践【未完整版】
      • Prometheus远程存储之VictoriaMetrics
      • Prometheus数据迁移至VMstorage
    • Docker

    • 数据库

    • 运维利器

    • 运维
    • Prometheus
    tchua
    2023-07-10
    目录

    Prometheus监控JVM实战

    # 一、概述


    我们知道,一款产品的生命周期,不仅仅是前期的规划设计,再到后续的开发,应用的运维也是重中之重。在运维过程中,监控则更是占据重要位置,它可以提供关键的运行时信息和指标,有助于优化应用程序的性能、稳定性和可伸缩性。

    关于java的监控,这里有两种方式,一种是使用JMX Exporter,一种是服务主动暴露actuator端点,提供Prometheus进行指标抓取。

    # 二、JMX Exporter方式


    JMX Exporter 是一个用于将 Java Management Extensions (JMX) 指标导出为 Prometheus 可以抓取的格式的工具。JMX 是 Java 平台提供的一种管理和监控应用程序的标准方式,它允许我们在应用程序中公开各种指标和管理操作。

    JMX Exporter 的作用是通过连接到应用程序的 JMX 接口,并将 JMX 指标转换为 Prometheus 可以解析的指标格式。这使得 Prometheus 可以定期抓取这些指标并进行长期存储、查询和可视化。

    # 2.1 安装准备

    这里我们使用JVM 进程内启动的方式,就是通过 javaagent 的形式运行 JMX-Exporter 的 jar 包,然后读取jvm的数据指标进行相关的指标暴露,基于这种方式,我们需要把JMX-Exporter的jar包放置基础镜像中,这样我们每个应用都可以在启动的时候,直接配置参数就可以实现指标的暴露。

    关于 JMX-Exporter的下载,参考官方Github地址:点我查看 (opens new window)

    # 2.2 jar包下载
    [root@localhost sale-toc]# wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.19.0/jmx_prometheus_javaagent-0.19.0.jar
    
    1
    # 2.3 配置文件准备

    jmx-config.yaml

    lowercaseOutputLabelNames: true
    lowercaseOutputName: true
    whitelistObjectNames: ["java.lang:type=OperatingSystem"]
    blacklistObjectNames: []
    rules:
      - pattern: 'java.lang<type=OperatingSystem><>(committed_virtual_memory|free_physical_memory|free_swap_space|total_physical_memory|total_swap_space)_size:'
        name: os_$1_bytes
        type: GAUGE
        attrNameSnakeCase: true
      - pattern: 'java.lang<type=OperatingSystem><>((?!process_cpu_time)\w+):'
        name: os_$1
        type: GAUGE
        attrNameSnakeCase: true
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    参数解释

    lowercaseOutputLabelNames:指定输出的标签名称是否转换为小写

    lowercaseOutputName: 指定是否将输出的指标名称转换为小写

    whitelistObjectNames:只有符合指定 ObjectName 的 MBean 才会被暴露

    pattern: 用于匹配要暴露的 MBean 的 ObjectName

    rules:定义了两个规则来转换 JMX 指标为 Prometheus 指标。

    • 第一个规则使用正则表达式匹配 java.lang<type=OperatingSystem> 的 MBean,并将匹配的属性转换为以 os_ 开头的指标名称,后跟 _bytes。attrNameSnakeCase: true 将属性名称转换为蛇形命名法。
    • 第二个规则使用正则表达式匹配 java.lang<type=OperatingSystem> 的 MBean 中除了 process_cpu_time 以外的所有属性,并将属性转换为以 os_ 开头的指标名称。同样,attrNameSnakeCase: true 将属性名称转换为蛇形命名法。
    # 2.4 镜像构建
    Dockerfile文件
    FROM openjdk:17-jdk-alpine3.14
    
    LABEL maintainer="tchuaxiaohua@163.com"
    
    ENV appPort 9021
    ENV appName sale-toc-1.0-SNAPSHOT.jar
    
    COPY ${appName} /apps/
    COPY jmx_prometheus_javaagent-0.19.0.jar /jmx/
    COPY jmx-config.yaml /jmx/
    
    COPY entrypoint.sh /usr/bin/entrypoint.sh
    
    RUN chmod +x /usr/bin/entrypoint.sh
    
    RUN apk --update add tzdata && \
        cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
        echo "Asia/Shanghai" > /etc/timezone && \
        apk del tzdata && \
        rm -rf /var/cache/apk/*
    
    EXPOSE ${appPort}
    
    ENTRYPOINT ["entrypoint.sh","start"]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    docker build -t registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v2 .
    docker push registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v2
    
    1
    2
    entrypoint.sh 脚本内容
    #!/bin/sh
    
    # 应用名
    PROG_NAME=$0
    ACTION=$1
    APP_HOME=/apps # 应用部署家目录
    JAR_NAME="sale-toc-1.0-SNAPSHOT.jar" # jar包的名字
    JAVA_OPTS="-Xms512M -Xmx512M --spring.profiles.active=dev"
    
    # 判断目录是否存在
    if [ ! -d "$APP_HOME" ]; then
        mkdir -p ${APP_HOME}
    fi
    
    usage() {
        echo "Usage: $PROG_NAME {start|stop|restart}"
        exit 2
    }
    
    start_application() {
        echo "starting java process"
        java -jar ${APP_HOME}/${JAR_NAME} ${JAVA_OPTS}
        echo "started java process"
    }
    
    stop_application() {
       checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'run.sh'| awk '{print$2}'`
       
       if [[ ! $checkjavapid ]];then
          echo -e "\rno java process"
          return
       fi
    
       echo "stop java process"
       times=60
       for e in $(seq 60)
       do
            sleep 1
            COSTTIME=$(($times - $e ))
            checkjavapid=`ps -ef | grep java | grep ${APP_NAME} | grep -v grep |grep -v 'run.sh'| awk '{print$2}'`
            if [[ $checkjavapid ]];then
                kill -9 $checkjavapid
                echo -e  "\r-- stopping java lasts `expr $COSTTIME` seconds."
            else
                echo -e "\rjava process has exited"
                break;
            fi
       done
       echo ""
    }
    start() {
        start_application
    }
    stop() {
        stop_application
    }
    case "$ACTION" in
        start)
            start
        ;;
        stop)
            stop
        ;;
        restart)
            stop
            start
        ;;
        *)
            usage
        ;;
    esac
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71

    关于Dockerfile的更多应用,参考Java应用Dockerfile编辑 (opens new window)

    # 2.5 配置启动参数

    启动参数我们可以在两个地方配置,一个是在构建镜像的时候,直接把启动参数放到应用的启动脚本entrypoint.sh之中,另一种就是启动的时候配置环境变量JAVA_TOOL_OPTIONS,JAVA_TOOL_OPTIONS 是 JVM 工具选项的标准环境变量,启动的时候会读取该变量。

    • pod资源清单文件

    注意添加JAVA_TOOL_OPTIONS变量时,jmx配置文件及jar包路径,8088指的时暴露的指标获取端口

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sale-toc-service
      namespace: dev
      labels:
        app: sale-toc
    spec:
      revisionHistoryLimit: 10
      replicas: 1
      selector:
        matchLabels:
          app: sale-toc
      template:
        metadata:
          annotations:
            prometheus.io/scrape: "true"
            prometheus.io/port: "8088"
            prometheus.io/path: "/"
          labels:
            team: tfgol
            app: sale-toc
        spec:
          imagePullSecrets:
          - name: tfgol-registry
          containers:
          - name: sale-toc
            image: registry.cn-hangzhou.aliyuncs.com/tfgol-dev/auto-toc:v2
            imagePullPolicy: Always
            env:
            - name: JAVA_TOOL_OPTIONS
              value: '-javaagent:/jmx/jmx_prometheus_javaagent-0.19.0.jar=8088:/jmx/jmx-config.yaml'
            resources:
              limits:
                memory: 1024Mi
              requests:
                memory: 512Mi
            livenessProbe:
              tcpSocket:
                port: 9201
              initialDelaySeconds: 10
              timeoutSeconds: 3
              periodSeconds: 10
              successThreshold: 1
              failureThreshold: 5
            startupProbe:
              tcpSocket:
                port: 9201
              initialDelaySeconds: 25
              timeoutSeconds: 3
              periodSeconds: 10
              successThreshold: 1
              failureThreshold: 10
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53

    注意

    上面清单文件中,我们在pod模板中定义了annotations,新增了Prometheus相关的标签,这主要是为了后面注册时,我们会根据自标签过滤:

    annotations:
     prometheus.io/scrape: "true"
     prometheus.io/port: "8088"
     prometheus.io/path: "/"
    
    1
    2
    3
    4

    除了上面在启动时,配置系统变量的方式,我们还可以直接修改entrypoint.sh脚本,把启动参数添加至脚本中,修改启动参数:

    # 添加变量
    JAVA_TOOL_OPTIONS="-javaagent:/jmx/jmx_prometheus_javaagent-0.19.0.jar=8088:/jmx/jmx-config.yaml"
    
    # 修改启动函数中的启动命令 把JAVA_TOOL_OPTIONS参数添加上
    start_application() {
        echo "starting java process"
        java -jar ${JAVA_TOOL_OPTIONS} ${APP_HOME}/${JAR_NAME} ${JAVA_OPTS}
        echo "started java process"
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 2.6 查看指标

    上面我们应用资源清单文件之后,待pod启动完成,可以查看下podIP,然后直接访问${podip}:8088端口,即可查看指标数据

    image-20230707104622110

    注意

    JMX Exporter 有两种方式使用:作为 Java 代理(in-process)或作为独立的进程,上面我们是基于Java 代理模式,当然官方也比较推荐使用这种模式。

    另一种作为独立进程,等于我们需要为没一个应用都起一个独立的jmx进程,这增加了复杂度,因为多了一个进程对于维护也不方便,因此生产中建议都直接使用Java代理的模式。

    # 三、 Spring Actuator方式


    Spring Boot Actuator 是一个用于监控和管理 Spring Boot 应用程序的模块,它提供了许多有用的特性,包括指标收集、健康检查、日志查看等,Actuator 提供了一组默认的 HTTP 端点,我们可以通过 HTTP 请求访问这些端点来获取应用程序的信息和指标。

    此种方式因涉及到项目pom文件修改建议与开发人员一起配置

    # 3.1 修改pom依赖
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>io.micrometer</groupId>
      <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    
    1
    2
    3
    4
    5
    6
    7
    8
    # 3.2 修改配置

    编辑 resources 目录下的 application.yml 文件,修改 actuator 相关的配置来暴露 Prometheus 协议的指标数据

    management:
      endpoints:
        web:
          exposure:
            include: prometheus
      metrics:
        distribution:
          slo:
            http:
              server:
                requests: 1ms,5ms,10ms,50ms,100ms,200ms,500ms,1s,5s
        tags:
          application: ${spring.application.name}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 3.3 打包启动

    上面配置之后,如果本地有环境可以调试,启动项目之后,直接访问http://localhost:port/context-path/actuator/prometheus 访问到 Prometheus 协议的指标数据,说明相关的依赖配置已经正确,如果想要发生生产,则根据实际情况构建镜像发布即可,这种模式使用的端口直接就是应用的端口。

    # 四、接入Prometheus


    上面无论哪种方式,应用启动之后,通过访问指定的端点信息都可以访问到应用的jvm指标数据,这里我们以jmx exporter方式,说明下接入Prometheus的步骤。

    # 4.1 自定义应用监控

    准备Prometheus文件,把pod中暴露的数据接入至Prometheus,更多方式参考Kube-Prometheus自定义告警 (opens new window)

    - job_name: 'bsd-tfgol-jvm'
      metrics_path: "/"
      scrape_interval: 30s
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      tls_config:
        insecure_skip_verify: true
      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
        target_label: __address__
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: namespace
      - source_labels: [__meta_kubernetes_pod_label_app]
        action: replace
        target_label: app_name
      - source_labels: [__meta_kubernetes_pod_label_team]
        action: replace
        regex: (.*)
        target_label: team
      - source_labels: [__meta_kubernetes_pod_name]
        action: replace
        regex: (.*)
        target_label: pod
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    配置说明

    relabel_configs: 这里是用来过滤哪些pod可以注册至该job下。

    __meta_kubernetes*: k8s中几乎所有标签都是以此开头,后面跟的是所属资源

    # 4.2 配置文件注册至Prometheus

    这里操作演示是基于Kube-Prometheus在看k8s集群中部署的监控系统

    # 创建secret
    kubectl create secret generic tfgol-jvm --from-file=tfgol-jvm.yaml -n monitoring
    # 注册至prometheus
    vim prometheus-prometheus.yaml # 新增
    additionalScrapeConfigs:
        name: tfgol-jvm
        key: tfgol-jvm.yaml
    # 激活配置
    kubectl apply -f prometheus-prometheus.yaml
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    注意

    如果使用的Prometheus版本比较新,可能会出现以下权限错误:

    ts=2023-07-07T08:20:32.971Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg="pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: Failed to watch *v1.Endpoints: failed to list *v1.Endpoints: endpoints is forbidden: User \"system:serviceaccount:monitoring:prometheus-k8s\" cannot list resource \"endpoints\" in API group \"\" at the cluster scope"
    ts=2023-07-07T08:20:56.421Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg="pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: failed to list *v1.Pod: pods is forbidden: User \"system:serviceaccount:monitoring:prometheus-k8s\" cannot list resource \"pods\" in API group \"\" at the cluster scope"
    ts=2023-07-07T08:20:56.421Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg="pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: Failed to watch *v1.Pod: failed to list *v1.Pod: pods is forbidden: User \"system:serviceaccount:monitoring:prometheus-k8s\" cannot list resource \"pods\" in API group \"\" at the cluster scope"
    
    1
    2
    3

    这是因为对应的sa没有权限,修改下Prometheus角色权限即可,prometheus-clusterRole.yaml:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      labels:
        app.kubernetes.io/component: prometheus
        app.kubernetes.io/instance: k8s
        app.kubernetes.io/name: prometheus
        app.kubernetes.io/part-of: kube-prometheus
        app.kubernetes.io/version: 2.42.0
      name: prometheus-k8s
    rules:
    - apiGroups:
      - ""
      resources:
      - nodes/metrics
      - endpoints
      - pods
      - services
      verbs:
      - get
      - list
    - nonResourceURLs:
      - /metrics
      verbs:
      - get
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # 4.3 查看job注册

    可以看到pod实例中的jvm指标已经正常采集过来

    image-20230710092315766

    # 4.4 告警规则

    告警规则我们直接基于operator,把资源清单应用即可,需要主要prometheus: k8s和role: alert-rules必须要有

    apiVersion: monitoring.coreos.com/v1
    kind: PrometheusRule
    metadata:
      labels:
        prometheus: k8s
        role: alert-rules
      name: bsd-tfgol-jvm
      namespace: monitoring
    spec:
      groups:
      - name: bsd-tfgol-jvm-rules
        rules:
        - alert: 堆内存使用率
          expr: sum(jvm_memory_used_bytes{area="heap"}) by(instance,app_name,namespace) / sum(jvm_memory_max_bytes{area="heap"}) by(instance,app_name,namespace) * 100 > 95 
          for: 2m
          labels:
            severity: warning
          annotations:
            description: '环境:{{ $labels.namespace }},应用:{{ $labels.app_name }} 堆内存使用率超过95%,当前值:{{ $value | printf "%.2f" }}'
        - alert: 应用线程阻塞预警
          expr: sum(jvm_threads_states_threads{state="blocked",job="bsd-channel-jvm",namespace="prod"})by (pod,application,namespace) > 0 
          for: 2m
          labels:
            severity: warning
          annotations:
            description: '应用{{ $labels.pod }}当前有{{ $value | printf "%.2f" }}个线程处于blocked状态'
        - alert: 应用等待线程预警
          expr: sum(jvm_threads_states_threads{state="timed-waiting",job="bsd-channel-jvm",namespace="prod"})by (pod,application,namespace) > 500
          for: 10s
          labels:
            severity: warning
          annotations:
            description: '应用{{ $labels.pod }}当前有{{ $value | printf "%.2f" }}个线程处于等待状态'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    # 4.5 grafana模版导入

    注: 如果你是基于jmx,则可以使用模版8563如果是基于spring actuator则可以使用官网模板4701

    image-20230710115237285

    image-20230710115301412

    image-20230710115317716

    编辑 (opens new window)
    #Prometheus
    上次更新: 2023/07/10, 11:58:13
    Prometheus高可用-Thanos
    Alertmanager报警历史持久化

    ← Prometheus高可用-Thanos Alertmanager报警历史持久化→

    最近更新
    01
    cert-manager自动签发Lets Encrypt
    09-05
    02
    Docker构建多架构镜像
    08-02
    03
    Prometheus数据迁移至VMstorage
    08-01
    更多文章>
    Theme by Vdoing | Copyright © 2023-2024 |豫ICP备2021026650号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式