Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 Initial attempt to support distributed traces for the cloud lifecycle #138

Draft
wants to merge 36 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cdcb114
Initial attempt to support distributed traces for the cloud lifecycle
v1v Jun 24, 2021
38c6cbc
Noop
v1v Jun 24, 2021
79e16b3
Fix cloud onFailure with https://github.com/jenkinsci/jenkins/pull/4922
v1v Jun 24, 2021
5ec10da
Add specific plugin details and create started span
v1v Jun 24, 2021
301f2ae
Revert "Add specific plugin details and create started span"
v1v Jul 4, 2021
df3556e
Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lam…
v1v Jul 4, 2021
baa08c8
Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lam…
v1v Jul 4, 2021
091504e
Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisioner#lam…
v1v Jul 4, 2021
3fee581
Cloud Root naming strategy
v1v Jul 4, 2021
59c294f
Collect cloud plugin details
v1v Jul 4, 2021
be3c4b2
Add Google Cloud attributes
v1v Jul 4, 2021
4a22a50
Add InstanceConfiguration google configuration attributes
v1v Jul 4, 2021
e33cfc2
Add cloud name attribute
v1v Jul 4, 2021
57ef907
Refactor and use transform functions
v1v Jul 4, 2021
c89c6a5
Revert "Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisi…
v1v Jul 4, 2021
bc77989
Revert "Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisi…
v1v Jul 4, 2021
4a72a04
Revert "Bump dependency to fix the 'WARNING hudson.slaves.NodeProvisi…
v1v Jul 4, 2021
ba72571
Merge remote-tracking branch 'upstream/master' into feature/traces-fo…
v1v Jul 4, 2021
3de8781
Fix dependencies
v1v Jul 4, 2021
6d8bd76
Merge remote-tracking branch 'upstream/master' into feature/traces-fo…
v1v Jul 8, 2021
d28294a
Fix javadoc
v1v Jul 8, 2021
57ff1a6
Use object instead a cast
v1v Jul 12, 2021
4f3e52b
Add k8s span attributes for the cloud transactions
v1v Jul 12, 2021
d06274d
Add more logs and _onRollback logic
v1v Jul 12, 2021
bc2297a
Support onStarted with a list of plannedNodes and add more debug
v1v Jul 12, 2021
81104e5
Enrich containers/pods with a new Handler
v1v Jul 12, 2021
b2bfecb
Ensure the root transaction last for the whole lifecycle
v1v Jul 12, 2021
c525190
Revert "Ensure the root transaction last for the whole lifecycle"
v1v Jul 12, 2021
5ba4c0c
merge log traces
v1v Jul 12, 2021
9733536
Add attributes in the docs
v1v Jul 12, 2021
07c7c75
cosmetic log change
v1v Jul 13, 2021
873dd50
Add cloud name in the transactions if possible
v1v Jul 13, 2021
56efa12
Cosmetic log
v1v Jul 13, 2021
6409b2a
Cloud attributes should not use the label but the node
v1v Jul 13, 2021
a6dda42
Add started span
v1v Jul 13, 2021
1e885f5
Revert "Add started span"
v1v Jul 13, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,23 @@ In addition, if the backends were configured then there will be an environment v
| ci.pipeline.parameter.name | Name of the parameter | String |
| ci.pipeline.parameter.value | Value of the parameter. "Sensitive" values are redacted | String |

Cloud specific:

| Attribute | Description | Type |
|----------------------------------|--------------|------|
| ci.cloud.label | Labels attached to the agent | String |
| ci.cloud.name | The agent name | String |
| cloud.account.id | The cloud account ID the resource is assigned to | String |
| cloud.provider | Name of the cloud provider | String |
| cloud.name | Step id | String |
| cloud.project.id | Jenkins plugin for that particular step | String |
| cloud.machine.type | Jenkins plugin version | String |
| cloud.region | The geographical region the resource is running | String |
| cloud.availability_zone | The zone where the resource is running | String |
| cloud.runAsUser | The user | String |
| cloud.platform | The cloud platform in use. | String |


##### Spans

| Attribute | Description | Type |
Expand All @@ -125,6 +142,16 @@ In addition, if the backends were configured then there will be an environment v
| jenkins.url | Jenkins URL | String |
| jenkins.computer.name | Name of the agent | String |

Containers/Kubernetes specific:

| Attribute | Description | Type |
|----------------------------------|--------------|------|
| k8s.pod.name | The name of the Pod | String |
| container.image.name | The name of the image the container was built on | String |
| container.image.tag | Container image tag | String |
| container.name | The Container name | String |


### Metrics on Jenkins health indicators

| Metrics | Unit | Label key | Label Value | Description |
Expand Down
31 changes: 30 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<properties>
<revision>0.17</revision>
<changelist>-SNAPSHOT</changelist>
<jenkins.version>2.235.5</jenkins.version>
<jenkins.version>2.249.3</jenkins.version>
<java.level>8</java.level>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>
<opentelemetry.version>1.2.0</opentelemetry.version>
Expand Down Expand Up @@ -61,6 +61,23 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- fix RequireUpperBoundDeps -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
<!-- fix RequireUpperBoundDeps caused when using the Kubernetes Jenkins plugin which it's optional -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.9</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>3.14.9</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -216,6 +233,18 @@
<artifactId>git</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>google-compute-engine</artifactId>
<version>4.3.8</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.csanchez.jenkins.plugins</groupId>
<artifactId>kubernetes</artifactId>
<version>1.27.7</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>github-branch-source</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.jenkins.plugins.opentelemetry.authentication.NoAuthentication;
import io.jenkins.plugins.opentelemetry.authentication.OtlpAuthentication;
import io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend;
import io.jenkins.plugins.opentelemetry.computer.CloudSpanNamingStrategy;
import io.jenkins.plugins.opentelemetry.job.SpanNamingStrategy;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import jenkins.model.GlobalConfiguration;
Expand Down Expand Up @@ -73,6 +74,8 @@ public class JenkinsOpenTelemetryPluginConfiguration extends GlobalConfiguration

private transient SpanNamingStrategy spanNamingStrategy;

private transient CloudSpanNamingStrategy cloudSpanNamingStrategy;

private transient ConcurrentMap<String, StepPlugin> loadedStepsPlugins = new ConcurrentHashMap<>();

private String serviceName;
Expand Down Expand Up @@ -226,6 +229,15 @@ public SpanNamingStrategy getSpanNamingStrategy() {
return spanNamingStrategy;
}

@Inject
public void setCloudSpanNamingStrategy(CloudSpanNamingStrategy cloudSpanNamingStrategy) {
this.cloudSpanNamingStrategy = cloudSpanNamingStrategy;
}

public CloudSpanNamingStrategy getCloudSpanNamingStrategy() {
return cloudSpanNamingStrategy;
}

@Nonnull
public ConcurrentMap<String, StepPlugin> getLoadedStepsPlugins() {
return loadedStepsPlugins;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright The Original Author or Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.jenkins.plugins.opentelemetry.computer;

import hudson.model.Node;
import hudson.slaves.Cloud;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;

import javax.annotation.Nonnull;

public interface CloudHandler {

boolean canAddAttributes(@Nonnull Cloud cloud);
boolean canAddAttributes(@Nonnull Node node);
void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span rootSpanBuilder) throws Exception;
void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull SpanBuilder rootSpanBuilder) throws Exception;
String getCloudName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The Original Author or Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.jenkins.plugins.opentelemetry.computer;

import com.google.common.annotations.VisibleForTesting;
import hudson.Extension;
import hudson.slaves.NodeProvisioner;
import jenkins.YesNoMaybe;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;

/**
* Use same root span name for all pull cloud labels
*/
@Extension(dynamicLoadable = YesNoMaybe.YES)
@Symbol("cloudSpanNamingStrategy")
public class CloudSpanNamingStrategy {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From:

image

To:

image


@DataBoundConstructor
public CloudSpanNamingStrategy() {
}

@Nonnull
public String getRootSpanName(@Nonnull NodeProvisioner.PlannedNode plannedNode) {
return getNodeRootSpanName(plannedNode.displayName);
}

@VisibleForTesting
@Nonnull
protected String getNodeRootSpanName(@Nonnull String displayName) {
// format: <namePrefix>-<id>
// e.g. "obs11-ubuntu-18-linux-beyyg2"
// remove last -<.*>
if (displayName.contains("-")) {
return displayName.substring(0, displayName.lastIndexOf("-")) + "-{id}";
}
return displayName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright The Original Author or Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.jenkins.plugins.opentelemetry.computer;

import com.google.jenkins.plugins.computeengine.ComputeEngineCloud;
import com.google.jenkins.plugins.computeengine.ComputeEngineInstance;
import com.google.jenkins.plugins.computeengine.InstanceConfiguration;
import hudson.Extension;
import hudson.model.Node;
import hudson.slaves.Cloud;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import jenkins.YesNoMaybe;

import javax.annotation.Nonnull;
import java.util.logging.Logger;

import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_PROVIDER;

/**
* Customization of spans for google cloud attributes.
*/
@Extension(optional = true, dynamicLoadable = YesNoMaybe.YES)
public class GoogleCloudHandler implements CloudHandler {
private final static Logger LOGGER = Logger.getLogger(GoogleCloudHandler.class.getName());

@Override
public boolean canAddAttributes(@Nonnull Cloud cloud) {
return cloud.getDescriptor() instanceof ComputeEngineCloud.GoogleCloudDescriptor;
}

@Override
public boolean canAddAttributes(@Nonnull Node node) {
return node instanceof ComputeEngineInstance;
}

@Override
public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull SpanBuilder rootSpanBuilder) throws Exception {
ComputeEngineCloud ceCloud = (ComputeEngineCloud) cloud;
rootSpanBuilder
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, ceCloud.getCloudName())
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PLATFORM, JenkinsOtelSemanticAttributes.GOOGLE_CLOUD_COMPUTE_ENGINE_PLATFORM)
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, ceCloud.getProjectId())
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, GOOGLE_CLOUD_PROVIDER);
}

@Override
public void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span span) throws Exception {
ComputeEngineInstance instance = (ComputeEngineInstance) node;
InstanceConfiguration configuration = instance.getCloud().getInstanceConfigurationByDescription(instance.getNodeDescription());
if (configuration != null) {
span
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ACCOUNT_ID, configuration.getServiceAccountEmail())
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_MACHINE_TYPE, transformMachineType(configuration.getMachineType()))
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_REGION, transformRegion(configuration.getRegion()))
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_RUN_AS_USER, configuration.getRunAsUser())
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_ZONE, transformZone(configuration.getZone()));
}
}

@Override
public String getCloudName() {
return GOOGLE_CLOUD_PROVIDER;
}

protected String transformRegion(String region) {
// f.e: "https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a"
return transform(region);
}

protected String transformMachineType(String machineType) {
// f.e: "https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a/machineTypes/n2-standard-2"
return transform(machineType);
}

protected String transformZone(String zone) {
// f.e: "https://www.googleapis.com/compute/v1/projects/project-name/zones/us-central1-a"
return transform(zone);
}

private String transform(String value) {
if (value.contains("/")) {
return value.substring(value.lastIndexOf("/") + 1, value.length());
}
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright The Original Author or Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.jenkins.plugins.opentelemetry.computer;

import hudson.Extension;
import hudson.model.Node;
import hudson.slaves.Cloud;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import jenkins.YesNoMaybe;
import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud;
import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave;
import org.csanchez.jenkins.plugins.kubernetes.PodTemplate;

import javax.annotation.Nonnull;

import java.util.logging.Level;
import java.util.logging.Logger;

import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.K8S_CLOUD_PROVIDER;

/**
* Customization of spans for kubernetes cloud attributes.
*/
@Extension(optional = true, dynamicLoadable = YesNoMaybe.YES)
public class KubernetesCloudHandler implements CloudHandler {
private final static Logger LOGGER = Logger.getLogger(KubernetesCloudHandler.class.getName());

@Nonnull
@Override
public boolean canAddAttributes(@Nonnull Cloud cloud) {
return cloud.getDescriptor() instanceof KubernetesCloud.DescriptorImpl;
}

@Override
public boolean canAddAttributes(@Nonnull Node node) {
return node instanceof KubernetesSlave;
}

@Override
public void addCloudAttributes(@Nonnull Cloud cloud, @Nonnull SpanBuilder rootSpanBuilder) throws Exception {
KubernetesCloud k8sCloud = (KubernetesCloud) cloud;
rootSpanBuilder
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_NAME, k8sCloud.getDisplayName())
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROJECT_ID, k8sCloud.getDisplayName())
.setAttribute(JenkinsOtelSemanticAttributes.CLOUD_PROVIDER, K8S_CLOUD_PROVIDER)
.setAttribute(JenkinsOtelSemanticAttributes.K8S_NAMESPACE_NAME, k8sCloud.getNamespace());
}

@Override
public void addCloudSpanAttributes(@Nonnull Node node, @Nonnull Span span) throws Exception {
KubernetesSlave instance = (KubernetesSlave) node;
span
.setAttribute(JenkinsOtelSemanticAttributes.K8S_POD_NAME, instance.getPodName());
PodTemplate podTemplate = instance.getTemplateOrNull();
if (podTemplate != null) {
// TODO: add resourceLimit attributes to detect misbehaviours?
span
.setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_NAME, getImageName(podTemplate.getImage()))
.setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_IMAGE_TAG, getImageTag(podTemplate.getImage()))
.setAttribute(JenkinsOtelSemanticAttributes.CONTAINER_NAME, podTemplate.getName());
} else {
LOGGER.log(Level.FINE, () -> "There is no podTemplate for the existing node.");
}
}

@Override
public String getCloudName() {
return K8S_CLOUD_PROVIDER;
}

protected String getImageName(String image) {
if (image.contains(":")) {
return image.substring(0, image.lastIndexOf(":"));
}
return image;
}

protected String getImageTag(String image) {
if (image.contains(":")) {
return image.substring(image.lastIndexOf(":") + 1);
}
return "latest";
}
}
Loading