diff --git a/druid-admin/pom.xml b/druid-admin/pom.xml index aba972401..617fe62db 100644 --- a/druid-admin/pom.xml +++ b/druid-admin/pom.xml @@ -10,7 +10,7 @@ com.alibaba druid-admin - 1.2.12 + 1.2.22 druid-admin Demo project for Spring Boot @@ -66,7 +66,7 @@ com.alibaba druid - 1.2.12 + 1.2.22 @@ -83,6 +83,21 @@ org.apache.httpcomponents httpclient + + + + io.kubernetes + client-java + 16.0.3 + + + + + com.squareup.okhttp3 + okhttp + 3.14.9 + + diff --git a/druid-admin/src/main/java/com/alibaba/druid/admin/DruidAdminApplication.java b/druid-admin/src/main/java/com/alibaba/druid/admin/DruidAdminApplication.java index 073eee2ff..d6043e109 100644 --- a/druid-admin/src/main/java/com/alibaba/druid/admin/DruidAdminApplication.java +++ b/druid-admin/src/main/java/com/alibaba/druid/admin/DruidAdminApplication.java @@ -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; } diff --git a/druid-admin/src/main/java/com/alibaba/druid/admin/config/MonitorProperties.java b/druid-admin/src/main/java/com/alibaba/druid/admin/config/MonitorProperties.java index e537c6604..0e3a92ff5 100644 --- a/druid-admin/src/main/java/com/alibaba/druid/admin/config/MonitorProperties.java +++ b/druid-admin/src/main/java/com/alibaba/druid/admin/config/MonitorProperties.java @@ -29,4 +29,14 @@ public class MonitorProperties { * 访问路径 */ private String contextPath; + + /** + * k8s集群访问凭证 + */ + private String kubeConfigFilePath; + + /** + * k8s集群命名空间 + */ + private String k8sNamespace; } diff --git a/druid-admin/src/main/java/com/alibaba/druid/admin/model/dto/DataSourceResult.java b/druid-admin/src/main/java/com/alibaba/druid/admin/model/dto/DataSourceResult.java index 3a6188c77..db8d7985d 100644 --- a/druid-admin/src/main/java/com/alibaba/druid/admin/model/dto/DataSourceResult.java +++ b/druid-admin/src/main/java/com/alibaba/druid/admin/model/dto/DataSourceResult.java @@ -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") diff --git a/druid-admin/src/main/java/com/alibaba/druid/admin/service/K8sDiscoveryClient.java b/druid-admin/src/main/java/com/alibaba/druid/admin/service/K8sDiscoveryClient.java new file mode 100644 index 000000000..fdec5b0a2 --- /dev/null +++ b/druid-admin/src/main/java/com/alibaba/druid/admin/service/K8sDiscoveryClient.java @@ -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 getK8sPodsInfo(List 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 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 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)); + } + + +} diff --git a/druid-admin/src/main/java/com/alibaba/druid/admin/service/MonitorStatService.java b/druid-admin/src/main/java/com/alibaba/druid/admin/service/MonitorStatService.java index 9bdedb404..0218c5af7 100644 --- a/druid-admin/src/main/java/com/alibaba/druid/admin/service/MonitorStatService.java +++ b/druid-admin/src/main/java/com/alibaba/druid/admin/service/MonitorStatService.java @@ -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 getAllServiceNodeMap(){ + public Map getAllServiceNodeMap() { List services = discoveryClient.getServices(); + List 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 serviceNodes = new ArrayList<>(); for (String service : services) { List 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 getServiceAllNodeMap(Map parameters){ + public Map getServiceAllNodeMap(Map parameters) { String requestServiceName = parameters.get("serviceName"); List 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 serviceNodes = new ArrayList<>(); - for (String service : services) { List 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 parameters = getParameters(url); @@ -343,7 +359,13 @@ public class MonitorStatService implements DruidStatServiceMBean { } } List> 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 nodeContent = dataSourceResult.getContent(); diff --git a/druid-admin/src/main/java/com/alibaba/druid/admin/util/HttpUtil.java b/druid-admin/src/main/java/com/alibaba/druid/admin/util/HttpUtil.java index 17cd02cab..96687343a 100644 --- a/druid-admin/src/main/java/com/alibaba/druid/admin/util/HttpUtil.java +++ b/druid-admin/src/main/java/com/alibaba/druid/admin/util/HttpUtil.java @@ -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 get(String url, Class resultType) { CloseableHttpClient httpClient = HttpClients.createDefault(); + log.info(url); HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Content-type", "application/json"); CloseableHttpResponse response; diff --git a/druid-admin/src/main/resources/bootstrap.yml b/druid-admin/src/main/resources/bootstrap.yml index edf5222e1..8c1f1a1b1 100644 --- a/druid-admin/src/main/resources/bootstrap.yml +++ b/druid-admin/src/main/resources/bootstrap.yml @@ -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集群命名空间 diff --git a/druid-admin/src/main/resources/support/http/resources/js/common.js b/druid-admin/src/main/resources/support/http/resources/js/common.js index 65e64627b..6c15939ae 100644 --- a/druid-admin/src/main/resources/support/http/resources/js/common.js +++ b/druid-admin/src/main/resources/support/http/resources/js/common.js @@ -8,7 +8,7 @@ druid.common = function () { // only one page for now var sqlViewPage = 1; - var sqlViewPerPageCount = 1000000; + var sqlViewPerPageCount = 100; return { init: function () {