一. tunnel的基础使用
tunnel 通过使用server/client管理不同机器的JVM进程。
1.1 使用agent方式,添加arthas探针。
tunnel-server 是管理agentId的方式,先要认识如何通过agent运行项目。
1.1.1 配置java VM OPTIONS
java -javaagent:/tmp/arthas-agent.jar -jar demo.jar
运行后会把项目的类信息通过ArthasClassloader 加载到内存中,AgentBootstrap提供监听线程获取类信息。
1.1.2 springboot-starter 注入
现在使用的是3.6.3版本,因为该版本开始添加了用户权限控制,这个方式更适合springboot项目
<dependency>
<groupId>com.github.hiwepy</groupId>
<artifactId>redisson-plus-spring-boot-starter</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
当使用starter包要配置,访问地址,有两种方式
1.1.2.1 使用yaml配置
arthas:
# 通过http访问的端口
http-port: 8987
# 通过telnet访问的端口
telnet-port: 6736
session-timeout: 2000
# 绑定的ip
ip: 0.0.0.0
且可以用到redis存储管理tunnel-server信息
<dependency>
<groupId>com.zengtengpeng</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>1.0.12</version>
</dependency>
springboot注入信息配置
import com.alibaba.arthas.spring.ArthasProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.SocketUtils;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* @Author: eliooyang
*/
@Slf4j
@Configuration
@ConditionalOnClass(value = ArthasProperties.class)
public class ArthasConfigMapConfig {
@Value("${spring.name:default_null}")
private String serviceName;
@Value("${server.port:8080}")
private String serverPort;
@Autowired
private Environment environment;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Bean("arthasConfigMap")
public Map<String, String> arthasConfigMap(@Autowired ArthasProperties arthasProperties) {
Map<String, String> map = new HashMap<>(8);
// 接收环境变量参数,否则随机生成
String agentId = environment.getProperty("arthas.agentId");
//优先使用环境变量arthas.name,其次使用服务名+随机数
if (StringUtils.isEmpty(agentId)) {
Random secureRandom = new SecureRandom();
agentId = serviceName + ":" + secureRandom.nextInt(1000);
}
map.put("agent-id", agentId);
//优先从配置中获取tunnel-server,如果没有,则从redis中获取,如果redis没有则使用默认
String tunnelServer = arthasProperties.getTunnelServer();
if (tunnelServer == null) {
String tunnelServerKey = "arthas.tunnel-server:" + agentId;
tunnelServer = redisTemplate.opsForValue().get(tunnelServerKey);
if (tunnelServer == null) {
tunnelServer = "ws://127.0.0.1:7777/ws";
redisTemplate.opsForValue().set(tunnelServerKey, tunnelServer);
}
}
map.put("tunnel-server", tunnelServer);
Integer serverPortInt = null;
if (StringUtils.isNotBlank(serverPort)) {
serverPortInt = Integer.parseInt(serverPort);
}
// 优先从环境变量中获取,其次从配置文件中获取,否则为serverPort+1,否则随机(值为0),若值为-1则不监听端口
String httpPort = environment.getProperty("arthas.httpPort");
if (httpPort == null) {
if (arthasProperties.getHttpPort() == 0) {
if (serverPortInt != null) {
map.put("http-port", "" + (serverPortInt + 1));
} else {
map.put("http-port", "" + SocketUtils.findAvailableTcpPort());
}
} else {
map.put("http-port", "" + arthasProperties.getHttpPort());
}
} else {
map.put("http-port", httpPort);
}
// 优先读取环境变量接口
String telnetPort = environment.getProperty("arthas.telnetPort");
if (StringUtils.isEmpty(telnetPort)) {
if (arthasProperties.getTelnetPort() == 0) {
if (serverPortInt != null) {
map.put("telnet-port", "" + (serverPortInt + 2));
} else {
map.put("telnet-port", "" + SocketUtils.findAvailableTcpPort());
}
} else {
map.put("telnet-port", "" + arthasProperties.getHttpPort());
}
} else {
map.put("telnet-port", telnetPort);
}
// 如果有配置target-ip,那么使用,否则默认127.0.0.1
if (StringUtils.isNotBlank(arthasProperties.getIp())) {
map.put("ip", arthasProperties.getIp());
}
// 鉴权用的用户名,默认arthas,targetIp非127.0.0.1才生效
if (StringUtils.isNotBlank(arthasProperties.getUsername())) {
map.put("username", arthasProperties.getUsername());
} else {
map.put("username", "username");
}
log.debug("username:{}", map.get("username"));
// 鉴权用的密码,targetIp非127.0.0.1才生效
if (StringUtils.isNotBlank(arthasProperties.getPassword())) {
map.put("password", arthasProperties.getPassword());
} else {
map.put("password", "pwd");
}
// 失效命令,设置为空,为了不禁用stop命令
map.put("disabledCommands", "");
log.info("server info:{}", map);
return map;
}
}
可以通过访问:127.0.0.1:8563 自己定义的端口,在浏览器访问,主要是为了提供tunnel-server服务给到管理
1.1.2 非springboot的Java项目,注入方式
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-agent-attach</artifactId>
<version>${arthas.version}</version>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-packaging</artifactId>
<version>${arthas.version}</version>
</dependency>
1.2 运行 tunnel-server
java -jar arthas-tunnel-server.jar
默认情况下,arthas tunnel server 的 web 端口是8080,arthas agent 连接的端口是7777。启动后访问 http://127.0.0.1:8080,通过 agentId 连接到已注册的
1.2.1 tunnel-server 链接方式
1.2.1.1 sh方式,需要安装telnet
指定ws链接方式
as.sh --tunnel-server 'ws://127.0.0.1:7777/ws' --app-name xxx
可以通过 --agent-id 指定对应的探针
./as.sh --tunnel-server --agent-id <agentId>
1.2.2 集群内指定服务监控
tunnel-server 调用流程,可以通过redis统一管理ip+port信息,如果有服务粒度的划分,根据需求进行细化。
二. tunnel-server-web 产品化
2.1 需求脑爆
- 应用agentId同步到redis中,生命周期:正常->不可用->下线。不可用状态主要针对一些已经故障的应用。
- 应用的agent端口的占用问题,服务下线,端口占用取消,arthas会有线程持续运行着,需要优化。
- tunnel-server端对agent的管理需要有权限控制。
- 支持编译代码功能,以及代码回滚
- 服务性能监听与告警,设置告警策略与等级,告警巡检形式,同步信息形式。
- 等等…
注: 项目还在编码,后续分享