Allow querying logs only by instance to simplify frontend query (#9157)

This commit is contained in:
kezhenxu94 2022-06-01 20:49:31 +08:00 committed by GitHub
parent 5b5b3d58da
commit b69e3f403e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 133 additions and 47 deletions

View File

@ -11,8 +11,13 @@ feature is disabled by default, please read the configuration documentation to e
As the name indicates, this feature only works for Kubernetes Pods.
SkyWalking OAP lists the Kubernetes namespaces, services, Pods and containers in the UI for users to select,
users can select the same and UI should fetch the logs in a given interval and display the logs in UI.
SkyWalking OAP collects and saves the service instance's namespace and Pod name in ther serivce instance's
properties, named `namespace` and `pod`, users can select the same and UI should fetch the logs by service
instance in a given interval and display the logs in UI, OAP receives the query and checks the instance's
properties and use the `namespace` and `pod` to locate the Pod and query the logs.
If you want to register a service instance that has on demand logs available, you should add `namespace`
and `pod` in the service instance properties, so that you can query the real time logs from that Pod.
That said, in order to make this feature work properly, you should in advance configure the cluster role for
OAP to list/get namespaces, services, pods and pods/log.

View File

@ -134,7 +134,7 @@ catalog:
- name: "Log Collecting And Analysis"
path: "/en/setup/backend/log-analyzer"
- name: "On Demand Pod Logs"
path: "/en/setup/backend/on-demand-pod-logg"
path: "/en/setup/backend/on-demand-pod-log"
- name: "Extension"
catalog:
- name: "Metrics Exporter"

View File

@ -175,6 +175,17 @@ public class InstanceTraffic extends Metrics {
}
public static class PropertyUtil {
/**
* `namespace` and `pod` are key properties that help "on demand Pod logs"
* to locate the corresponding Pod in Kubernetes, when language agent is
* registering a new service instance that is supposed to work in terms of
* "on demand Pod logs", the agent should also fill in these 2 properties.
*
* @since 9.1.0
*/
public static final String NAMESPACE = "namespace";
public static final String POD = "pod";
public static final String LANGUAGE = "language";
public static final String IPV4 = "ipv4";
public static final String IPV4S = "ipv4s";

View File

@ -78,6 +78,7 @@ public class GraphQLQueryProvider extends ModuleProvider {
@Override
public void prepare() throws ServiceNotProvidedException {
final MetadataQueryV2 metadataQueryV2 = new MetadataQueryV2(getManager());
schemaBuilder.file("query-protocol/common.graphqls")
.resolvers(new Query(), new Mutation(), new HealthQuery(getManager()))
.file("query-protocol/metadata.graphqls")
@ -118,14 +119,14 @@ public class GraphQLQueryProvider extends ModuleProvider {
.file("query-protocol/event.graphqls")
.resolvers(new EventQuery(getManager()))
.file("query-protocol/metadata-v2.graphqls")
.resolvers(new MetadataQueryV2(getManager()))
.resolvers(metadataQueryV2)
.file("query-protocol/ebpf-profiling.graphqls")
.resolvers(new EBPFProcessProfilingQuery(getManager()), new EBPFProcessProfilingMutation(getManager()));
if (config.isEnableOnDemandPodLog()) {
schemaBuilder
.file("query-protocol/ondemand-pod-log.graphqls")
.resolvers(new OndemandLogQuery());
.resolvers(new OndemandLogQuery(metadataQueryV2));
}
schemaBuilder.scalars(ExtendedScalars.GraphQLLong);

View File

@ -23,9 +23,9 @@ import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
@ -34,64 +34,87 @@ import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import org.apache.skywalking.oap.query.graphql.type.InternalLog;
import org.apache.skywalking.oap.query.graphql.type.LogAdapter;
import org.apache.skywalking.oap.query.graphql.type.OndemandContainergQueryCondition;
import org.apache.skywalking.oap.query.graphql.type.OndemandLogQueryCondition;
import org.apache.skywalking.oap.server.core.analysis.IDManager;
import org.apache.skywalking.oap.server.core.analysis.manual.instance.InstanceTraffic.PropertyUtil;
import org.apache.skywalking.oap.server.core.query.input.Duration;
import org.apache.skywalking.oap.server.core.query.type.Attribute;
import org.apache.skywalking.oap.server.core.query.type.Log;
import org.apache.skywalking.oap.server.core.query.type.Logs;
import org.apache.skywalking.oap.server.core.query.type.ServiceInstance;
import org.apache.skywalking.oap.server.library.util.StringUtil;
import graphql.kickstart.tools.GraphQLQueryResolver;
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.V1Container;
import io.kubernetes.client.openapi.models.V1Namespace;
import io.kubernetes.client.openapi.models.V1NamespaceList;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1PodSpec;
import io.kubernetes.client.util.Config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
public class OndemandLogQuery implements GraphQLQueryResolver {
private final Gson gson = new Gson();
private final Type responseType = new TypeToken<Map<String, Object>>() {
}.getType();
private CoreV1Api kApi;
public List<String> listNamespaces() throws IOException {
try {
final V1NamespaceList nsList =
kApi().listNamespace(null, null, null, null, null, null, null, null, null, null);
return nsList
.getItems()
.stream()
.map(V1Namespace::getMetadata)
.filter(Objects::nonNull)
.map(V1ObjectMeta::getName)
.collect(Collectors.toList());
} catch (ApiException e) {
log.error("Failed to list namespaces from Kubernetes, {}", e.getResponseBody(), e);
private final MetadataQueryV2 metadataQuery;
Map<String, Object> responseBody = gson.fromJson(e.getResponseBody(), responseType);
String message = responseBody.getOrDefault("message", e.getCode()).toString();
throw new RuntimeException(message);
}
public List<String> listContainers(final OndemandContainergQueryCondition condition)
throws IOException {
final ServiceInstance instance =
metadataQuery.getInstance(condition.getServiceInstanceId());
final Map<String, String> attributesMap = convertInstancePropertiesToMap(instance);
final String ns = attributesMap.get(PropertyUtil.NAMESPACE);
final String pod = attributesMap.get(PropertyUtil.POD);
return listContainers(ns, pod);
}
public List<String> listContainers(final OndemandLogQueryCondition condition)
public Logs ondemandPodLogs(final OndemandLogQueryCondition condition)
throws IOException {
final String ns = condition.getNamespace();
final IDManager.ServiceInstanceID.InstanceIDDefinition instanceIDDefinition =
IDManager.ServiceInstanceID.analysisId(condition.getServiceInstanceId());
final String instanceName = instanceIDDefinition.getName();
final ServiceInstance instance =
metadataQuery.getInstance(condition.getServiceInstanceId());
final Map<String, String> attributesMap = convertInstancePropertiesToMap(instance);
final String ns = attributesMap.get(PropertyUtil.NAMESPACE);
final String pod = attributesMap.get(PropertyUtil.POD);
return ondemandPodLogs(ns, pod, condition);
}
protected Map<String, String> convertInstancePropertiesToMap(final ServiceInstance instance) {
if (instance == null) {
return Collections.emptyMap();
}
final List<Attribute> attributes = instance.getAttributes();
if (attributes == null || attributes.isEmpty()) {
return Collections.emptyMap();
}
final Map<String, String> attributesMap =
attributes
.stream()
.collect(Collectors.toMap(Attribute::getName, Attribute::getValue));
if (!attributesMap.containsKey(PropertyUtil.NAMESPACE)
|| !attributesMap.containsKey(PropertyUtil.POD)) {
return Collections.emptyMap();
}
return attributesMap;
}
public List<String> listContainers(
final String namespace,
final String podName) throws IOException {
try {
final V1Pod pod = kApi().readNamespacedPod(instanceName, ns, null);
if (Strings.isNullOrEmpty(namespace) || Strings.isNullOrEmpty(podName)) {
return Collections.emptyList();
}
final V1Pod pod = kApi().readNamespacedPod(podName, namespace, null);
final V1PodSpec spec = pod.getSpec();
if (isNull(spec)) {
throw new RuntimeException(String.format("No spec: %s:%s", ns, instanceName));
return Collections.emptyList();
}
final List<String> containers = spec.getContainers().stream()
@ -114,23 +137,19 @@ public class OndemandLogQuery implements GraphQLQueryResolver {
}
}
public Logs ondemandPodLogs(OndemandLogQueryCondition condition)
throws IOException {
final String ns = condition.getNamespace();
final IDManager.ServiceInstanceID.InstanceIDDefinition instanceIDDefinition =
IDManager.ServiceInstanceID.analysisId(condition.getServiceInstanceId());
final String instanceName = instanceIDDefinition.getName();
public Logs ondemandPodLogs(
final String namespace,
final String podName,
final OndemandLogQueryCondition condition) throws IOException {
try {
final V1Pod pod = kApi().readNamespacedPod(instanceName, ns, null);
final V1Pod pod = kApi().readNamespacedPod(podName, namespace, null);
final V1ObjectMeta podMetadata = pod.getMetadata();
if (isNull(podMetadata)) {
throw new RuntimeException(
String.format("No such instance: %s:%s", ns, instanceName));
return new Logs();
}
final V1PodSpec spec = pod.getSpec();
if (isNull(spec)) {
throw new RuntimeException(String.format("No spec: %s:%s", ns, instanceName));
return new Logs();
}
final Duration duration = new Duration();

View File

@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.skywalking.oap.query.graphql.type;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OndemandContainergQueryCondition {
private String serviceInstanceId;
}

View File

@ -28,9 +28,7 @@ import lombok.Setter;
@Getter
@Setter
public class OndemandLogQueryCondition {
private String namespace;
private String container;
private String serviceId;
private String serviceInstanceId;
private Duration duration;
private List<String> keywordsOfContent = Collections.emptyList();

@ -1 +1 @@
Subproject commit edf5871a3a2e8f7300eb06175240b56c21072fe9
Subproject commit faf3a768087825258f64dd782a4dc405dc271cf8

View File

@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
filter: "{ tags -> tags.job_name in [ 'kubernetes-cadvisor', 'kube-state-metrics' ] }" # The OpenTelemetry job name
expSuffix: |-
service(['cluster' , 'service'], '::', Layer.K8S_SERVICE)
.instance(['cluster', 'service'], '::', ['pod'], '', Layer.K8S_SERVICE, { tags -> ['pod': tags.pod, 'namespace': tags.namespace] })
metricPrefix: k8s_service_instance
metricsRules:
- name: pod_instance_status
exp: kube_pod_status_phase.retagByK8sMeta('service' , K8sRetagType.Pod2Service , 'pod' , 'namespace').tagNotEqual('service' , '').sum(['cluster', 'namespace', 'service' , 'pod'])