SpringBoot admin+Eureka+釘釘通知 實現微服務監控

SpringBoot admin+Eureka+釘釘通知

1、效果

登陸帳號+密碼

監控服務

查看實時日誌

釘釘通知

2、什麼是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爲註冊的應用程序提供如下功能:html

  • 顯示健康情況java

  • 顯示詳細信息,例如react

  • JVM和內存指標web

  • micrometer.io指標spring

  • 數據源指標數據庫

  • 緩存指標apache

  • 顯示構建信息編號c#

  • 關注並下載日誌文件api

  • 查看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,......)

  • 狀態更改的事件日誌(非持久性)

3、原理

使用 Spring Boot Actuator 監控應用

4、集成 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 上註冊的信息,主動去註冊。

5、集成 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

6、通知

自定義通知+釘釘通知

一、建立釘釘機器人,拿到token,怎麼建立釘釘機器人,請自行google
二、下載sdk

釘釘官方提供了統一的SDK,使用SDK能夠便捷的調用服務端API,但沒有放到公共maven倉庫中,須要自行下載後導入到項目,或者上傳到本身的搭建的nexus私服中

三、自定義的通知器

經過擴展 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
相關文章
相關標籤/搜索