目录
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
3.7 日志收集
3.7.1 slf4j方式
3.7.2 logback方式
Spring-Boot-Admin(SBA)和Arthas集成部署到rancher环境,监控节点状态、jvm性能、日志收集等工作,留下本文记录搭建过程。
版本选择:
SBA版本跟随Spring Boot大版本一致,否则容易出一些奇葩问题
使用Spring initializer创建spring boot项目,选择ops下spring boot admin server
server:
port: 7000
spring:
application:
name: sba_arthas
在@SpringBootApplication入口类上添加注解@EnableAdminServer以启用SBA
此时没有服务注册进来
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.12.RELEASE
com.tsit
spring-boot-admin
0.0.1-SNAPSHOT
1.8
2.3.1
3.6.4
org.projectlombok
lombok
1.16.14
provided
org.springframework.boot
spring-boot-starter-web
de.codecentric
spring-boot-admin-starter-server
de.codecentric
spring-boot-admin-server-ui
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-test
test
com.taobao.arthas
arthas-common
${arthas.version}
com.taobao.arthas
arthas-tunnel-common
${arthas.version}
it.ozimov
embedded-redis
0.7.3
org.slf4j
slf4j-simple
org.springframework.boot
spring-boot-starter-data-redis
com.github.ben-manes.caffeine
caffeine
org.apache.commons
commons-pool2
org.apache.commons
commons-lang3
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
完整代码
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 的客户端
*
* @date: 2022年8月24日11:30:30
* @author: yzg
* @since: 1.0
* @version: 1.0
*/
@RequestMapping("/api/arthas")
@RestController
public class ArthasController {
@Autowired
private TunnelServer tunnelServer;
@RequestMapping(value = "/clients", method = RequestMethod.GET)
public Set getClients() {
Map 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 findAgent = tunnelServer.findAgent(agentId);
if (findAgent.isPresent()) {
String requestId = RandomStringUtils.random(20, true, true).toUpperCase();
ChannelHandlerContext agentCtx = findAgent.get().getChannelHandlerContext();
Promise 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 entry : simpleHttpResponse.getHeaders().entrySet()) {
bodyBuilder.header(entry.getKey(), entry.getValue());
}
ResponseEntity 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,可以比较一下两个不同
Arthas Console
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("");
server = list[i].toLowerCase();
$(uiSelect).append("");
}
}
/**
* 重置配置文件
*/
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/")
}
yml文件中定义的Arthas控制台外链地址如何定义,此处是重点
SBA启动后访问的页面是spring-boot-admin-server-ui依赖的页面,外链指向的地址是希望通过maven打包的方式将static静态资源打入到该目录下。
引入pom打包模块
${project.artifactId}
src/main/resources
${project.build.directory}/classes
**/*
true
src/main/resources/static
${project.build.directory}/classes/META-INF/spring-boot-admin-server-ui/extensions/arthas
false
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
logging:
file:
## 日志路径,默认文件名spring.log
path: /user/iot/manage
pattern:
file: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%thread]){faint} %clr(%logger{50}){cyan} %clr(LN:%L){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}'
添加logback-spring.xml文件
${APP_Name}
${CONSOLE_LOG_PATTERN}
utf8
${LOG_HOME}/iot-work.log
${LOG_HOME}/iot-work-%d{yyyy-MM-dd}.log
3
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n
10MB
添加application.yml中logging模块
management:
endpoint:
logfile:
## logback-spring.xml中定义的日志文件路径
external-file: /user/iot/work/logs/iot-work.log
logging:
config: classpath:logback-spring.xml
至此SBA集成Arthas搭建完成,下一章搭建客户端注册SBA,后续添加上DockerFile后打包部署到rancher上。
参考资料:https://blog.csdn.net/xiaoll880214/article/details/120191476?spm=1001.2014.3001.5502