mirror of https://github.com/alibaba/druid.git
druid-admin
Feature: add support for services in k8s cluster without a discovery service like eureka, nacos... Fix: 1. fix fastjson error when serialize large object. 2. fix an int overflow error
This commit is contained in:
parent
b713c5ae4e
commit
32f2e26fb6
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-admin</artifactId>
|
||||
<version>1.2.12</version>
|
||||
<version>1.2.22</version>
|
||||
<name>druid-admin</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
|||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>1.2.12</version>
|
||||
<version>1.2.22</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -83,6 +83,21 @@
|
|||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/io.kubernetes/client-java -->
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java</artifactId>
|
||||
<version>16.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>3.14.9</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
|
|
|
@ -30,6 +30,12 @@ public class DruidAdminApplication {
|
|||
if (properties.getLoginPassword() != null) {
|
||||
registrationBean.addInitParameter("loginPassword", properties.getLoginPassword());
|
||||
}
|
||||
if (properties.getKubeConfigFilePath() != null) {
|
||||
registrationBean.addInitParameter("kubeConfigFilePath", properties.getKubeConfigFilePath());
|
||||
}
|
||||
if (properties.getK8sNamespace() != null) {
|
||||
registrationBean.addInitParameter("k8sNamespace", properties.getK8sNamespace());
|
||||
}
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,4 +29,14 @@ public class MonitorProperties {
|
|||
* 访问路径
|
||||
*/
|
||||
private String contextPath;
|
||||
|
||||
/**
|
||||
* k8s集群访问凭证
|
||||
*/
|
||||
private String kubeConfigFilePath;
|
||||
|
||||
/**
|
||||
* k8s集群命名空间
|
||||
*/
|
||||
private String k8sNamespace;
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ public class DataSourceResult {
|
|||
@JSONField(name = "RemoveAbandoned")
|
||||
private boolean RemoveAbandoned;
|
||||
@JSONField(name = "ClobOpenCount")
|
||||
private int ClobOpenCount;
|
||||
private long ClobOpenCount;
|
||||
@JSONField(name = "BlobOpenCount")
|
||||
private int BlobOpenCount;
|
||||
@JSONField(name = "KeepAliveCheckCount")
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package com.alibaba.druid.admin.service;
|
||||
|
||||
import com.alibaba.druid.admin.model.ServiceNode;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.Configuration;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Pod;
|
||||
import io.kubernetes.client.openapi.models.V1PodList;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import io.kubernetes.client.util.ClientBuilder;
|
||||
import io.kubernetes.client.util.KubeConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class K8sDiscoveryClient {
|
||||
|
||||
public Map<String, ServiceNode> getK8sPodsInfo(List<String> serviceNames, String kubeConfigFilePath, String namespace) throws IOException, ApiException {
|
||||
InputStream inputStream = new ClassPathResource(kubeConfigFilePath).getInputStream();
|
||||
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
ApiClient client = ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(reader)).build();
|
||||
Configuration.setDefaultApiClient(client);
|
||||
CoreV1Api api = new CoreV1Api();
|
||||
List<ServiceNode> serviceNodes = new ArrayList<>();
|
||||
V1PodList podList = api.listNamespacedPod(namespace, null, null, null, null, null, null, null, null, null, null);
|
||||
for (String serviceName : serviceNames) {
|
||||
V1Service service = api.readNamespacedService(serviceName, namespace, null);
|
||||
List<V1Pod> servicePods = podList.getItems().stream().filter(i -> Objects.requireNonNull(Objects.requireNonNull(i.getMetadata()).getName()).startsWith(serviceName)).collect(Collectors.toList());
|
||||
for (V1Pod pod : servicePods) {
|
||||
String podId = pod.getMetadata().getUid();
|
||||
String podIp = pod.getStatus().getPodIP();
|
||||
Integer port = Objects.requireNonNull(Objects.requireNonNull(service.getSpec()).getPorts()).stream().filter(i -> serviceName.equalsIgnoreCase(i.getName())).findFirst().get().getPort();
|
||||
ServiceNode serviceNode = new ServiceNode();
|
||||
serviceNode.setId(podId);
|
||||
serviceNode.setPort(port);
|
||||
serviceNode.setAddress(podIp);
|
||||
serviceNode.setServiceName(serviceName);
|
||||
serviceNodes.add(serviceNode);
|
||||
MonitorStatService.serviceIdMap.put(podId, serviceNode);
|
||||
log.info("pod info: " + serviceNode);
|
||||
}
|
||||
}
|
||||
return serviceNodes.stream().collect(Collectors.toMap(i -> i.getServiceName() + "-" + i.getAddress() + "-" + i.getPort(),
|
||||
Function.identity(), (v1, v2) -> v2));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -2,12 +2,7 @@ package com.alibaba.druid.admin.service;
|
|||
|
||||
import com.alibaba.druid.admin.config.MonitorProperties;
|
||||
import com.alibaba.druid.admin.model.ServiceNode;
|
||||
import com.alibaba.druid.admin.model.dto.ConnectionResult;
|
||||
import com.alibaba.druid.admin.model.dto.DataSourceResult;
|
||||
import com.alibaba.druid.admin.model.dto.SqlDetailResult;
|
||||
import com.alibaba.druid.admin.model.dto.SqlListResult;
|
||||
import com.alibaba.druid.admin.model.dto.WallResult;
|
||||
import com.alibaba.druid.admin.model.dto.WebResult;
|
||||
import com.alibaba.druid.admin.model.dto.*;
|
||||
import com.alibaba.druid.admin.util.HttpUtil;
|
||||
import com.alibaba.druid.stat.DruidStatServiceMBean;
|
||||
import com.alibaba.druid.support.http.stat.WebAppStatManager;
|
||||
|
@ -18,18 +13,19 @@ import com.alibaba.druid.util.StringUtils;
|
|||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -44,7 +40,7 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
public static final int RESULT_CODE_ERROR = -1;
|
||||
|
||||
private static final int DEFAULT_PAGE = 1;
|
||||
private static final int DEFAULT_PER_PAGE_COUNT = Integer.MAX_VALUE;
|
||||
private static final int DEFAULT_PER_PAGE_COUNT = 1000;
|
||||
private static final String ORDER_TYPE_DESC = "desc";
|
||||
private static final String ORDER_TYPE_ASC = "asc";
|
||||
private static final String DEFAULT_ORDER_TYPE = ORDER_TYPE_ASC;
|
||||
|
@ -60,13 +56,25 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
@Autowired
|
||||
private MonitorProperties monitorProperties;
|
||||
|
||||
@Autowired
|
||||
private K8sDiscoveryClient k8sDiscoveryClient;
|
||||
|
||||
/**
|
||||
* 获取所有服务信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, ServiceNode> getAllServiceNodeMap(){
|
||||
public Map<String, ServiceNode> getAllServiceNodeMap() {
|
||||
List<String> services = discoveryClient.getServices();
|
||||
List<String> applications = monitorProperties.getApplications();
|
||||
String kubeConfig = monitorProperties.getKubeConfigFilePath();
|
||||
if (CollectionUtils.isEmpty(services) && !Strings.isNullOrEmpty(kubeConfig)) {
|
||||
try {
|
||||
return k8sDiscoveryClient.getK8sPodsInfo(applications, kubeConfig, monitorProperties.getK8sNamespace());
|
||||
} catch (IOException | ApiException e) {
|
||||
log.error("get k8s resource fail: ", e);
|
||||
}
|
||||
}
|
||||
List<ServiceNode> serviceNodes = new ArrayList<>();
|
||||
for (String service : services) {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(service);
|
||||
|
@ -79,7 +87,7 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
int port = instance.getPort();
|
||||
String serviceId = instance.getServiceId();
|
||||
// 根据前端参数采集指定的服务
|
||||
if (monitorProperties.getApplications().contains(serviceId)) {
|
||||
if (applications.contains(serviceId)) {
|
||||
ServiceNode serviceNode = new ServiceNode();
|
||||
serviceNode.setId(instanceId);
|
||||
serviceNode.setPort(port);
|
||||
|
@ -100,11 +108,18 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
public Map<String, ServiceNode> getServiceAllNodeMap(Map<String, String> parameters){
|
||||
public Map<String, ServiceNode> getServiceAllNodeMap(Map<String, String> parameters) {
|
||||
String requestServiceName = parameters.get("serviceName");
|
||||
List<String> services = discoveryClient.getServices();
|
||||
String kubeConfig = monitorProperties.getKubeConfigFilePath();
|
||||
if (CollectionUtils.isEmpty(services) && !Strings.isNullOrEmpty(kubeConfig)) {
|
||||
try {
|
||||
return k8sDiscoveryClient.getK8sPodsInfo(Lists.newArrayList(requestServiceName), kubeConfig, monitorProperties.getK8sNamespace());
|
||||
} catch (IOException | ApiException e) {
|
||||
log.error("get k8s resource fail: ", e);
|
||||
}
|
||||
}
|
||||
List<ServiceNode> serviceNodes = new ArrayList<>();
|
||||
|
||||
for (String service : services) {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(service);
|
||||
for (ServiceInstance instance : instances) {
|
||||
|
@ -130,6 +145,7 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
return serviceNodes.stream().collect(Collectors.toMap(i -> i.getServiceName() + "-" + i.getAddress() + "-" + i.getPort(),
|
||||
Function.identity(), (v1, v2) -> v2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String service(String url) {
|
||||
Map<String, String> parameters = getParameters(url);
|
||||
|
@ -343,7 +359,13 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
}
|
||||
}
|
||||
List<Map<String, Object>> maps = comparatorOrderBy(arrayMap, parameters);
|
||||
String jsonString = JSON.toJSONString(maps);
|
||||
String jsonString = JSON.toJSONString(
|
||||
maps,
|
||||
JSONWriter.Feature.LargeObject,
|
||||
JSONWriter.Feature.ReferenceDetection,
|
||||
JSONWriter.Feature.BrowserCompatible,
|
||||
JSONWriter.Feature.BrowserSecure
|
||||
);
|
||||
JSONArray objects = JSON.parseArray(jsonString);
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("ResultCode", RESULT_CODE_SUCCESS);
|
||||
|
@ -351,7 +373,6 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
return jsonObject.toJSONString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 数据源监控
|
||||
*
|
||||
|
@ -368,6 +389,7 @@ public class MonitorStatService implements DruidStatServiceMBean {
|
|||
String serviceName = serviceNode.getServiceName();
|
||||
|
||||
String url = "http://" + serviceNode.getAddress() + ":" + serviceNode.getPort() + "/druid/datasource.json";
|
||||
log.info("request url: " + url);
|
||||
DataSourceResult dataSourceResult = HttpUtil.get(url, lastResult.getClass());
|
||||
if (dataSourceResult != null) {
|
||||
List<DataSourceResult.ContentBean> nodeContent = dataSourceResult.getContent();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.alibaba.druid.admin.util;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
|
@ -15,9 +16,11 @@ import java.nio.charset.StandardCharsets;
|
|||
* @author linchtech
|
||||
* @date 2020-09-16 16:12
|
||||
**/
|
||||
@Slf4j
|
||||
public class HttpUtil {
|
||||
public static <T> T get(String url, Class<T> resultType) {
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
log.info(url);
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
httpGet.setHeader("Content-type", "application/json");
|
||||
CloseableHttpResponse response;
|
||||
|
|
|
@ -31,6 +31,10 @@ eureka:
|
|||
|
||||
monitor:
|
||||
applications: #需要监控的服务名spring.application.name
|
||||
- a
|
||||
- b
|
||||
login-username: admin #监控页面的登录用户名和密码
|
||||
login-password: 123456
|
||||
kube-config-file-path: # k8s集群访问凭证,相对路径,默认resources目录
|
||||
k8s-namespace: # k8s集群命名空间
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ druid.common = function () {
|
|||
|
||||
// only one page for now
|
||||
var sqlViewPage = 1;
|
||||
var sqlViewPerPageCount = 1000000;
|
||||
var sqlViewPerPageCount = 100;
|
||||
|
||||
return {
|
||||
init: function () {
|
||||
|
|
Loading…
Reference in New Issue