微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Spring-Boot-Admin集成arthas环境部署-服务端

目录

1.环境及版本使用

2.SBA环境搭建

2.1 SBA服务搭建

2.2 application.yml

2.3 SBA启动

3. SBA集成Arthas

 3.1 引入完整依赖

3.2 arthas源代码拷贝到SBA中

3.3 application.yml完整版

 3.4 SBA服务改造

3.5 Arthas外链设置 

 3.6 重新启动SBA并访问Arthas Console


1.环境及版本使用

Spring-Boot-Admin(SBA)和Arthas集成部署到rancher环境,监控节点状态、jvm性能、日志收集等工作,留下本文记录搭建过程。

版本选择:

  • Spring Boot:2.3.12.RELEASE
  • SBA:2.3.1
  • Arthas:3.6.4

SBA版本跟随Spring Boot大版本一致,否则容易出一些奇葩问题

2.SBA环境搭建

2.1 SBA服务搭建

使用Spring initializer创建spring boot项目,选择ops下spring boot admin server

2.2 application.yml

server:
  port: 7000
spring:
  application:
    name: sba_arthas

2.3 SBA启动

在@SpringBootApplication入口类上添加注解@EnableAdminServer以启用SBA

此时没有服务注册进来 

3. SBA集成Arthas

 3.1 引入完整依赖

<modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.tsit</groupId>
    <artifactId>spring-boot-admin</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot-admin.version>2.3.1</spring-boot-admin.version>
        <arthas.version>3.6.4</arthas.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.14</version>
            <scope>provided</scope>
        </dependency>
        <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>de.codecentric</groupId>
            <artifactId>spring-boot-admin-server-ui</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- arthas 集成需要 -->
        <dependency>
            <groupId>com.taobao.arthas</groupId>
            <artifactId>arthas-common</artifactId>
            <version>${arthas.version}</version>
        </dependency>
        <dependency>
            <groupId>com.taobao.arthas</groupId>
            <artifactId>arthas-tunnel-common</artifactId>
            <version>${arthas.version}</version>
        </dependency>
        <dependency>
            <groupId>it.ozimov</groupId>
            <artifactId>embedded-redis</artifactId>
            <version>0.7.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-simple</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

3.2 arthas源代码拷贝到SBA中

  • 拷贝后SBA目录结构如下

3.3 application.yml完整版

server:
  port: 7000

spring:
  application:
    name: sba_arthas
  ## 集成了spring security安全组件,定义登录SBA的账号密码,
  ## 后期注册到SBA的客户端也要设置此权限才能注册进来
  security:
    user:
      name: admin
      password: admin
  boot:
    admin:
      # SBA添加外链扩展页面,此处外链跳转Arthas控制台
      ui:
        external-views:
          - label: "Arthas Console"
            url: "./extensions/arthas/arthas.html"
            order: 1900
  # Arthas的缓存策略
  cache:
    type: caffeine
    cache-names: inMemoryClusterCache
    caffeine:
      spec: maximumSize=3000,expireAfteraccess=3600s

# 监控所有页面
management:
  endpoints:
    web:
      exposure:
        include: '*'
  metrics:
    tags:
      application: ${spring.application.name}
  ## 关闭rabbitmq,redis,es 健康检查
  health:
    redis:
      enabled: false
    rabbit:
      enabled: false
    elasticsearch:
      enabled: false
  # 总是显示服务健康细节
  endpoint:
    health:
      show-details: always
# arthas tunnel-server监听地址端口
arthas:
  server:
    host: 0.0.0.0
    port: ${PORT:7777}
  enableDetailPages: true

 3.4 SBA服务改造

完整代码

package com.example.sba_arthas.arthas.app.web;

import com.example.sba_arthas.arthas.AgentInfo;
import com.example.sba_arthas.arthas.TunnelServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.Set;

/**
 * 获取所有注册到 Arthas 的客户端 <br>
 *
 * @date: 2022年8月24日11:30:30 <br>
 * @author: yzg <br>
 * @since: 1.0 <br>
 * @version: 1.0 <br>
 */
@RequestMapping("/api/arthas")
@RestController
public class ArthasController {
	
	@Autowired
	private TunnelServer tunnelServer;
	
	@RequestMapping(value = "/clients", method = RequestMethod.GET)
	public Set<String> getClients() {
		Map<String, AgentInfo> agentInfoMap = tunnelServer.getAgentInfoMap();
		return agentInfoMap.keySet();
	}
	
	
}
package com.example.sba_arthas.config;

import com.example.sba_arthas.arthas.app.configuration.ArthasProperties;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrftokenRepository;

/**
 * @author :yzg
 * @date :Created in 2022/8/22 14:56
 * @description:
 * @modified By:
 * @version: $
 */

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	private final String admincontextpath;
	
	@Autowired
	private ArthasProperties arthasProperties;

	public SecurityConfig(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 + "/");
		
		// allow iframe
		if (arthasProperties.isEnableIframeSupport()) {
			http.headers().frameOptions().disable();
		}
		
		http.authorizeRequests()
				.antMatchers(admincontextpath + "/assets/**").permitAll()//Grants public access to all static assets and the login page.
				.antMatchers(admincontextpath + "/login").permitAll()
				.anyRequest().authenticated()//	Every other request must be authenticated.
				.and()
				.formLogin().loginPage(admincontextpath + "/login").successHandler(successHandler).and()//Configures login and logout.
				.logout().logoutUrl(admincontextpath + "/logout").and()
				.httpBasic().and()//Enables HTTP-Basic support. This is needed for the Spring Boot Admin Client to register.
				.csrf()
				.csrftokenRepository(CookieCsrftokenRepository.withHttpOnlyFalse())//	Enables CSRF-Protection using Cookies
				.ignoringAntMatchers(
						admincontextpath + "/instances",//	disables CRSF-Protection the endpoint the Spring Boot Admin Client uses to register.
						admincontextpath + "/actuator/**"//disables CRSF-Protection for the actuator endpoints.
				);
	}
}
package com.tsit.springbootadmin.arthas.app.web;

import com.alibaba.arthas.tunnel.common.MethodConstants;
import com.alibaba.arthas.tunnel.common.SimpleHttpResponse;
import com.alibaba.arthas.tunnel.common.URIConstans;
import com.tsit.springbootadmin.arthas.AgentInfo;
import com.tsit.springbootadmin.arthas.TunnelServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.Promise;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.http.ResponseEntity.BodyBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 代理http请求到具体的 arthas agent里
 * 
 * @author hengyunabc 2020-10-22
 *
 */
@RequestMapping(value = {"/extensions/arthas", "/"})
@Controller
public class ProxyController {
    private final static Logger logger = LoggerFactory.getLogger(ProxyController.class);

    @Autowired
	TunnelServer tunnelServer;

    @RequestMapping(value = "/proxy/{agentId}/**")
    @ResponseBody
    public ResponseEntity<?> execute(@PathVariable(name = "agentId", required = true) String agentId,
            HttpServletRequest request) throws InterruptedException, ExecutionException, TimeoutException {

        String fullPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
		fullPath = StringUtils.replace(fullPath, "/extensions/arthas", "");
		logger.info("fullPath:{}", fullPath);
        String targetUrl = fullPath.substring("/proxy/".length() + agentId.length());

        logger.info("http proxy, agentId: {}, targetUrl: {}", agentId, targetUrl);

        Optional<AgentInfo> findAgent = tunnelServer.findAgent(agentId);

        if (findAgent.isPresent()) {
            String requestId = RandomStringUtils.random(20, true, true).toupperCase();

            ChannelHandlerContext agentCtx = findAgent.get().getChannelHandlerContext();

            Promise<SimpleHttpResponse> httpResponsePromise = GlobalEventExecutor.INSTANCE.newPromise();

            tunnelServer.addProxyRequestPromise(requestId, httpResponsePromise);

            URI uri = UriComponentsBuilder.newInstance().scheme(URIConstans.RESPONSE).path("/")
                    .queryParam(URIConstans.METHOD, MethodConstants.HTTP_PROXY).queryParam(URIConstans.ID, agentId)
                    .queryParam(URIConstans.TARGET_URL, targetUrl).queryParam(URIConstans.PROXY_REQUEST_ID, requestId)
                    .build().toUri();

            agentCtx.channel().writeAndFlush(new TextWebSocketFrame(uri.toString()));
            logger.info("waitting for arthas agent http proxy, agentId: {}, targetUrl: {}", agentId, targetUrl);

            SimpleHttpResponse simpleHttpResponse = httpResponsePromise.get(15, TimeUnit.SECONDS);

            BodyBuilder bodyBuilder = ResponseEntity.status(simpleHttpResponse.getStatus());
            for (Entry<String, String> entry : simpleHttpResponse.getHeaders().entrySet()) {
                bodyBuilder.header(entry.getKey(), entry.getValue());
            }
            ResponseEntity<byte[]> responseEntity = bodyBuilder.body(simpleHttpResponse.getContent());
            return responseEntity;
        } else {
            logger.error("can not find agent by agentId: {}", agentId);
        }

        return ResponseEntity.notFound().build();
    }
}

 

arthas.html是拷贝的index.html,可以比较一下两个不同

<!doctype html>
<html lang="en">

<head>
    <!-- required Meta tags -->
    <Meta charset="utf-8">
    <Meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="bootstrap-4.2.1.min.css">
    <link rel="stylesheet" href="bootstrap-select.css">

    <link href="xterm.css" rel="stylesheet" />
    <link href="main.css" rel="stylesheet" />

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="jquery-3.3.1.min.js"></script>
    <script src="popper-1.14.6.min.js"></script>
    <script src="bootstrap-4.2.1.min.js"></script>
    <script src="xterm.js" type="text/javascript"></script>
    <script src="web-console.js"></script>
    <script src="arthas.js"></script>
    <script src="bootstrap-select.js"></script>


    <script type="text/javascript">
        window.addEventListener('resize', function () {
            if(ws !== undefined && ws !== null){
                let terminalSize = getTerminalSize();
                ws.send(JSON.stringify({ action: 'resize', cols: terminalSize.cols, rows: terminalSize.rows }));
                xterm.resize(terminalSize.cols, terminalSize.rows);
            }
        });
    </script>

    <title>Arthas Console</title>
</head>

<body>
    <nav class="navbar navbar-expand navbar-light bg-light flex-column flex-md-row bd-navbar">
        <a href="https://github.com/alibaba/arthas" target="_blank" title="" class="navbar-brand"><img src="logo.png"
                alt="Arthas" title="Welcome to Arthas web console" style="height: 25px;" class="img-responsive"></a>

        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="https://arthas.aliyun.com/doc" target="_blank">Documentation
                        <span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="https://arthas.aliyun.com/doc/arthas-tutorials.html" target="_blank">Online Tutorials</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="https://github.com/alibaba/arthas" target="_blank">Github</a>
                </li>
            </ul>
        </div>

        <form class="form-inline my-2 my-lg-0">
            <div class="col">
                <div class="input-group ">
                    <div class="input-group-prepend">
                        <span class="input-group-text" id="ip-addon">IP</span>
                    </div>
                    <input value="127.0.0.1" v-model="ip" type="text" class="form-control" name="ip" id="ip"
                        placeholder="please enter ip address" aria-label="ip" aria-describedby="ip-addon">
                </div>
            </div>

            <div class="col">
                <div class="input-group ">
                    <div class="input-group-prepend">
                        <span class="input-group-text" id="port-addon">Port</span>
                    </div>
                    <input value="7777" v-model="port" type="text" class="form-control" name="port" id="port"
                        placeholder="please enter port" aria-label="port" aria-describedby="port-addon">
                </div>
            </div>

            <div class="col">
                <select id="selectServer" data-type="btn-info" class="bootstrap-select"></select>
<!--                <div class="input-group ">-->
<!--                    <div class="input-group-prepend">-->
<!--                        <span class="input-group-text" id="agentId-addon">AgentId</span>-->
<!--                    </div>-->
<!--                    <input value="" v-model="agentId" type="text" class="form-control" name="agentId" id="agentId"-->
<!--                        placeholder="please enter agentId" aria-label="agentId" aria-describedby="agentId-addon">-->
<!--                </div>-->
            </div>

            <div class="col-inline">
                <button title="connect" type="button" class="btn btn-info form-control" onclick="startConnect()">Connect</button>
                <button title="disconnect" type="button" class="btn btn-info form-control" onclick="disconnect()">disconnect</button>
                <a id="arthasOutputA" target="_blank" href="arthas-output/" class="btn btn-info" role="button" onclick="updateArthasOutputLink()">Arthas Output</a>
            </div>

        </form>

    </nav>

    <div class="container-fluid px-0">
        <div class="col px-0" id="terminal-card">
            <div id="terminal"></div>
        </div>
    </div>

    <div title="fullscreen" id="fullSc" class="fullSc">
        <button id="fullScBtn" onclick="xtermFullScreen()"><img src="fullsc.png"></button>
    </div>
</body>

</html>
var registerapplications = null;
var applications = null;
$(document).ready(function () {
    reloadRegisterapplications();
    reloadApplications();
});

/**
 * 获取注册的arthas客户端
 */
function reloadRegisterapplications() {
    var result = reqSync("/api/arthas/clients", "get");
    registerapplications = result;
    initSelect("#selectServer", registerapplications, "");
}

function reloadAgent(){
    reloadRegisterapplications();
    reloadApplications();
}

/**
 * 获取注册的应用
 */
function reloadApplications() {
    applications = reqSync("/api/applications", "get");
    console.log(applications)
}

/**
 * 初始化下拉选择框
 */
function initSelect(uiSelect, list, key) {
    $(uiSelect).html('');
    var server;
    for (var i = 0; i < list.length; i++) {
        //server = list[i].toLowerCase().split("@");
        //if ("phantom-admin" === server[0]) continue;
        //$(uiSelect).append("<option value=" + list[i].toLowerCase() + ">" + server[0] + "</option>");
        server = list[i].toLowerCase();
        $(uiSelect).append("<option value=" + server + ">" + server + "</option>");
    }
}

/**
 * 重置配置文件
 */
function release() {
    var currentServer = $("#selectServer").text();
    for (var i = 0; i < applications.length; i++) {
        serverId = applications[i].id;
        serverName = applications[i].name.toLowerCase();
        console.log(serverId + "/" + serverName);
        if (currentServer === serverName) {
            var result = reqSync("/api/applications/" +serverId+ "/env/reset", "post");
            alert("env reset success");
        }
    }
}

function reqSync(url, method) {
    var result = null;
    $.ajax({
        url: url,
        type: method,
        async: false, //使用同步的方式,true为异步方式
        headers: {
            'Content-Type': 'application/json;charset=utf8;',
        },
        success: function (data) {
            // console.log(data);
            result = data;
        },
        error: function (data) {
            console.log("error");
        }
    });
    return result;
}
function updateArthasOutputLink() {
    $('#arthasOutputA').prop("href", "proxy/" + $("#selectServer").val() + "/arthas-output/")
}

 

3.5 Arthas外链设置 

yml文件中定义的Arthas控制台外链地址如何定义,此处是重点

SBA启动后访问的页面是spring-boot-admin-server-ui依赖的页面外链指向的地址是希望通过maven打包的方式将static静态资源打入到该目录下。

引入pom打包模块

<build>
        <finalName>${project.artifactId}</finalName>
        <resources>
            <!-- 指定 src/main/resources下所有文件文件夹为资源文件 -->
            <resource>
                <directory>src/main/resources</directory>
                <targetPath>${project.build.directory}/classes</targetPath>
                <includes>
                    <include>**/*</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <!-- 通过 Maven Resource 的指定配置打入指定目录,实现 SBA 启动时的自定义加载 ,通过application配置 外链-->
            <resource>
                <directory>src/main/resources/static</directory>
                <targetPath>${project.build.directory}/classes/meta-inf/spring-boot-admin-server-ui/extensions/arthas
                </targetPath>
                <filtering>false</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

 3.6 重新启动SBA并访问Arthas Console

至此SBA集成Arthas搭建完成,下一章搭建客户端注册SBA,后续添加上DockerFile后打包部署到rancher上。

参考资料:https://blog.csdn.net/xiaoll880214/article/details/120191476?spm=1001.2014.3001.5502 

原文地址:https://www.jb51.cc/wenti/3284646.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐