文档章节

SpringBoot admin+Eureka+钉钉通知 实现微服务监控

小白的成长
 小白的成长
发布于 10/18 11:04
字数 1541
阅读 2237
收藏 49

SpringBoot admin+Eureka+钉钉通知

一、效果

登录账号+密码

监控服务

查看实时日志

钉钉通知

二、什么是Spring Boot Admin ?

Spring Boot Admin是一个开源社区项目,用于管理和监控SpringBoot应用程序。 应用程序作为Spring Boot Admin Client向为Spring Boot Admin Server注册(通过HTTP)或使用SpringCloud注册中心(例如Eureka,Consul)发现。 UI是的Vue.js应用程序,展示Spring Boot Admin Client的Actuator端点上的一些监控。服务端采用Spring WebFlux + Netty的方式。Spring Boot Admin为注册的应用程序提供以下功能:

  • 显示健康状况

  • 显示详细信息,例如

  • JVM和内存指标

  • micrometer.io指标

  • 数据源指标

  • 缓存指标

  • 显示构建信息编号

  • 关注并下载日志文件

  • 查看jvm system-和environment-properties

  • 查看Spring Boot配置属性

  • 支持Spring Cloud的postable / env-和/ refresh-endpoint

  • 轻松的日志级管理

  • 与JMX-beans交互

  • 查看线程转储

  • 查看http-traces

  • 查看auditevents

  • 查看http-endpoints

  • 查看计划任务

  • 查看和删除活动会话(使用spring-session)

  • 查看Flyway / Liquibase数据库迁移

  • 下载heapdump

  • 状态变更通知(通过电子邮件,Slack,Hipchat,......)

  • 状态更改的事件日志(非持久性)

三、原理

使用 Spring Boot Actuator 监控应用

四、集成 Eureka注册中心

1. 创建 eureka-server,自行google

2. 创建 spring-boot-admin

这是一个 Spring Boot Admin Server端。

pom.xml

<!--?xml version="1.0" encoding="UTF-8"?-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>
    <parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>2.1.6.RELEASE</version>
        <relativepath /> <!-- lookup parent from repository -->
    </parent>
    <packaging>jar</packaging>
    <artifactid>spring-boot-admin</artifactid>
    <name>spring-boot-admin</name>
    <description>Spring Boot Admin Server端</description>

     <properties>
        <java.version>1.8</java.version>
        <spring-boot-admin.version>2.1.6</spring-boot-admin.version>
        <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>
        <dependency>
            <groupid>de.codecentric</groupid>
            <artifactid>spring-boot-admin-starter-server</artifactid>
        </dependency>
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-netflix-eureka-client</artifactid>
        </dependency>

        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-security</artifactid>
        </dependency>
        <dependency>
            <groupid>org.jolokia</groupid>
            <artifactid>jolokia-core</artifactid>
        </dependency>
    </dependencies>

    <dependencymanagement>
        <dependencies>
            <dependency>
                <groupid>org.springframework.cloud</groupid>
                <artifactid>spring-cloud-dependencies</artifactid>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupid>de.codecentric</groupid>
                <artifactid>spring-boot-admin-dependencies</artifactid>
                <version>${spring-boot-admin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencymanagement>

    <build>
        <plugins>
            <plugin>
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-maven-plugin</artifactid>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

spring:
  application:
    name: admin-server
server:
  port: 1300
eureka:
  client:
    registryFetchIntervalSeconds: 5
    service-url:
      defaultZone: ${EUREKA_SERVICE_URL:http://localhost:8761}/eureka/
  instance:
    leaseRenewalIntervalInSeconds: 10
    health-check-url-path: /actuator/health

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS

启动类 SpringbootAdminServerApplication

@SpringBootApplication
@EnableAdminServer
@EnableEurekaClient
public class ScAdminServerApplication {

    public static void main(String[] args) {
        SpringApplication.run( ScAdminServerApplication.class, args );
    }

}

3. 被监控端

被监控端需要放开端点

application.yml

spring:
  application:
    name: admin-client
eureka:
  instance:
    leaseRenewalIntervalInSeconds: 10
    health-check-url-path: /actuator/health

  client:
    registryFetchIntervalSeconds: 5
    service-url:
      defaultZone: ${EUREKA_SERVICE_URL:http://localhost:8761}/eureka/
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS
server:
  port: 8762

admin 会自己拉取 Eureka 上注册的信息,主动去注册。

五、集成 Spring Security

Web应用程序中的身份验证和授权有多种方法,因此Spring Boot Admin不提供默认方法。默认情况下,spring-boot-admin-server-ui提供登录页面和注销按钮。我们结合 Spring Security 实现需要用户名和密码登录的安全认证。

springboot-admin工程的pom文件需要增加以下的依赖:

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-security</artifactid>
</dependency>

在 spirngboot-admin工的配置文件 application.yml 中配置 spring security 的用户名和密码,这时需要在服务注册时带上 metadata-map 的信息,如下:

spring:
  security:
    user:
      name: "admin"
      password: "admin"
      
eureka:
  instance:
    metadata-map:
      user.name: ${spring.security.user.name}
      user.password: ${spring.security.user.password}
      startup: ${random.int}    #needed to trigger info and endpoint update after restart

写一个配置类SecuritySecureConfig继承WebSecurityConfigurerAdapter,配置如下:

/**
 * security配置
 * @author wangjiafang
 * @date 2019/10/10
 */
@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {

    private final String adminContextPath;

    public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
        this.adminContextPath = adminServerProperties.getContextPath();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(adminContextPath + "/");

        http.authorizeRequests()
                .antMatchers(adminContextPath + "/assets/**").permitAll()
                .antMatchers(adminContextPath + "/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
                .logout().logoutUrl(adminContextPath + "/logout").and()
                .httpBasic().and()
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringRequestMatchers(
                        new AntPathRequestMatcher(adminContextPath + "/instances", HttpMethod.POST.toString()),
                        new AntPathRequestMatcher(adminContextPath + "/instances/*", HttpMethod.DELETE.toString()),
                        new AntPathRequestMatcher(adminContextPath + "/actuator/**")
                );
        // @formatter:on
    }
}

重新访问 http:localhost:1300 会出现登录界面,密码是配置文件中配置好的,账号 admin 密码 admin

六、通知

自定义通知+钉钉通知

1、创建钉钉机器人,拿到token,怎么创建钉钉机器人,请自行google
2、下载sdk

钉钉官方提供了统一的SDK,使用SDK可以便捷的调用服务端API,但没有放到公共maven仓库中,需要自行下载后导入到项目,或者上传到自己的搭建的nexus私服中

3、自定义的通知器

通过扩展 AbstractEventNotifier或AbstractStatusChangeNotifier。在springboot-admin-server工程中编写一个自定义的通知器:


import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.taobao.api.ApiException;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;


/**
 * 钉钉通知
 * @author wangjiafang
 * @date 2019/10/10
 */
@Component
@Slf4j
public class CustomNotifier  extends AbstractEventNotifier {

    /**
     * 消息模板
     */
    private static final String template = "服务名:%s(%s) \n状态:%s(%s) \n服务ip:%s";

    @Value("${spring.admin.ding-talk-token}")
    private String dingTalkToken;


    public CustomNotifier(InstanceRepository repository) {
        super(repository);
    }

    @Override
    protected Mono<void> doNotify(InstanceEvent event, Instance instance) {
        return Mono.fromRunnable(() -&gt; {
            if (event instanceof InstanceStatusChangedEvent) {
                log.info("Instance {} ({}) is {}", instance.getRegistration().getName(), event.getInstance(),
                        ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());


                String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
                String messageText = null;
                switch (status) {
                    // 健康检查没通过
                    case "DOWN":
                        log.info("发送 健康检查没通过 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"健康检查没通过",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    // 服务离线
                    case "OFFLINE":
                        log.info("发送 服务离线 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务离线",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    //服务上线
                    case "UP":
                        log.info("发送 服务上线 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务上线",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    // 服务未知异常
                    case "UNKNOWN":
                        log.info("发送 服务未知异常 的通知!");
                        messageText = String.format(template, instance.getRegistration().getName(),event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),"服务未知异常",instance.getRegistration().getServiceUrl());
                        this.sendMessage(messageText);
                        break;
                    default:
                        break;
                }
            } else {
                log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
                    event.getType());
            }
        });
    }

    /**
     * 发送消息
     * @param messageText
     */
    private void sendMessage(String messageText){
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?access_token="+dingTalkToken);
        OapiRobotSendRequest request = new OapiRobotSendRequest();

        request.setMsgtype("text");
        OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
        text.setContent(messageText);
        request.setText(text);

        try {
            client.execute(request);
        } catch (ApiException e) {
            log.info("[ERROR] sendMessage", e);
        }
    }
}

七 查看实时日志

要在springbootadmin面板中查看实时日志,需要指定项目中日志输出的地址,比如我的日志是在/logs/项目名称/项目名称-info.log下

logging:
  file: /logs/${spring.application.name}/${spring.application.name}-info.log

© 著作权归作者所有

小白的成长
粉丝 6
博文 39
码字总数 30248
作品 0
深圳
程序员
私信 提问
加载中

评论(7)

dongjunv5
dongjunv5
还可以加个邮件通知
小白的成长
小白的成长 博主
嗯嗯,个的觉得,邮件通知不是很及时,因为我公司使用的是钉钉沟通,一楼说的钉钉的ding这个思路道是挺好的,能比较及时的通知到个人
很拽De土豆
很拽De土豆
邮件个人感觉也不好。以前公司也是用的邮件。但是邮件这种东西自己搭建需要成本而且容易出问题,用qq邮箱这种还会被拦截。钉钉简单高效,就是担心钉钉以后收费或者不开放了。
dongjunv5
dongjunv5
邮件通知会发送到公司邮箱,这个比较正式;或者也可以企业内部自建应用,获取key,secret,然后给个人发送消息
小白的成长
小白的成长 博主
钉钉确定会有这这方面的原因,从这个月初开始到现在都新建自定义的机器人,连个通知也没有,保障点的方按还是多渠道通知
Kit_lee
Kit_lee
最可惜的是只能依赖钉钉机器人,如果能直接给企业内部发ding就好了,不过得注册企业用户
小白的成长
小白的成长 博主
是的,ding这个功能比较醒目,可以集成一下
恒宇少年/spring-boot-chapter

简书整套文档以及源码解析 专题 专题名称 专题描述 001 Spring Boot 核心技术 讲解SpringBoot一些企业级层面的核心组件 002 Spring Cloud 核心技术 对Spring Cloud核心技术全面讲解 003 Quer...

恒宇少年
2018/04/19
0
0
SpringBootBucket 2.0.4 发布,代号“傲娇的小二晶”

SpringBootBucket 自从1.0.0版本发布后就有好多人喜欢,目前码云上面star数量接近1.2k。上个月还收到了红薯签名的1000 star奖杯,这个我自己也觉得很惊讶。 由于SpringBoot 1.x官方将终止维护...

一刀
2018/09/16
3K
4
springboot情操陶冶-初识springboot

前言:springboot由于其轻便和去配置化等的特性已经被广泛应用,基于时代潮流以及不被鄙视,笔者于是开辟此篇开始认识springboot 前话 springboot是基于spring而开发的轻量级框架,所以在学习...

南柯问天
2018/07/24
0
0
springboot 详解 (六)servlet & scheduled & listener

---------------------------------------------------------------------------------------------------------------- springboot 详解 (一) helloworld 下载demo springboot 详解 (二)......

curiousby
2016/11/17
0
0
SpringBootBucket 1.0.0 发布,SprintBoot 全家桶

Spring Boot 现在已经成为Java 开发领域的一颗璀璨明珠,它本身是包容万象的,可以跟各种技术集成。 本项目对目前Web开发中常用的各个技术,通过和SpringBoot的集成,并且对各种技术通过“一...

一刀
2018/03/05
9.1K
17

没有更多内容

加载失败,请刷新页面

加载更多

DDD(十)--仓储

1、引言 DDD中的Repository(仓储):协调领域和数据映射层,利用类似与集合的接口来访问领域对象。——《领域驱动设计-软件核心复杂性应对之道》 仓储是DDD中产生的概念,也就是说,如果应...

MrYuZixian
12分钟前
5
0
Jenkins的多种迁移方法

说明 Jenkins有时需要进行迁移,主目录会发生改变,本文主要讲解如何更改主目录。由于jenkins安装方式的不同,主目录也不一样。 本测试环境:Centos7.6 X64。注意:在更改主目录之前,请一定...

Elson
13分钟前
5
0
好程序员web前端教程分享前端javascript练习题三

好程序员web前端教程分享前端javascript练习题三,cookie 一周内免登录 样式代码: <form action=""> 姓名:<input type="text" id="usename"/><br /> 密码:<input type="text" i="mima"/>......

好程序员官网
32分钟前
5
0
Table 信息转成pojo属性

import com.google.common.base.CaseFormat;import java.sql.*;/** * @author: liyhu * @date: 2019/11/22 */public class TableToPojo { static String url="jdbc:mys......

暗中观察
今天
8
0
Access数据库-C#操作类

//Access数据库-C# 操作类 代码using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Data.OleDb;using System.Data;namespace XXX{......

芳缘
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部