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

feat: Collect Language Version in Runtime #1434

Merged
merged 16 commits into from
Aug 15, 2024
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,8 @@ cli-install:
@echo "Installing odigos from source. version: $(ODIGOS_CLI_VERSION)"
go run -tags=embed_manifests ./cli install --version $(ODIGOS_CLI_VERSION)


.PHONY: cli-upgrade
cli-upgrade:
@echo "Installing odigos from source. version: $(ODIGOS_CLI_VERSION)"
go run -tags=embed_manifests ./cli upgrade --version $(ODIGOS_CLI_VERSION) --yes
2 changes: 2 additions & 0 deletions api/config/crd/bases/odigos.io_instrumentedapplications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ spec:
- unknown
- ignored
type: string
runtimeVersion:
type: string
required:
- containerName
- language
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 13 additions & 12 deletions api/odigos/v1alpha1/instrumentedapplication_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,42 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

//+kubebuilder:object:generate=true
// +kubebuilder:object:generate=true
type ConfigOption struct {
OptionKey string `json:"optionKey"`
OptionKey string `json:"optionKey"`
SpanKind common.SpanKind `json:"spanKind"`
}

//+kubebuilder:object:generate=true
// +kubebuilder:object:generate=true
type InstrumentationLibraryOptions struct {
LibraryName string `json:"libraryName"`
Options []ConfigOption `json:"options"`
LibraryName string `json:"libraryName"`
Options []ConfigOption `json:"options"`
}

//+kubebuilder:object:generate=true
// +kubebuilder:object:generate=true
type EnvVar struct {
Name string `json:"name"`
Value string `json:"value"`
}

//+kubebuilder:object:generate=true
// +kubebuilder:object:generate=true
type RuntimeDetailsByContainer struct {
ContainerName string `json:"containerName"`
Language common.ProgrammingLanguage `json:"language"`
EnvVars []EnvVar `json:"envVars,omitempty"`
ContainerName string `json:"containerName"`
Language common.ProgrammingLanguage `json:"language"`
RuntimeVersion string `json:"runtimeVersion,omitempty"`
EnvVars []EnvVar `json:"envVars,omitempty"`
}

// +kubebuilder:object:generate=true
type OptionByContainer struct {
ContainerName string `json:"containerName"`
ContainerName string `json:"containerName"`
InstrumentationLibraries []InstrumentationLibraryOptions `json:"instrumentationsLibraries"`
}

// InstrumentedApplicationSpec defines the desired state of InstrumentedApplication
type InstrumentedApplicationSpec struct {
RuntimeDetails []RuntimeDetailsByContainer `json:"runtimeDetails,omitempty"`
Options []OptionByContainer `json:"options,omitempty"`
Options []OptionByContainer `json:"options,omitempty"`
}

// InstrumentedApplicationStatus defines the observed state of InstrumentedApplication
Expand Down
5 changes: 5 additions & 0 deletions common/lang_detection.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package common

type ProgramLanguageDetails struct {
Language ProgrammingLanguage
RuntimeVersion string
}

// +kubebuilder:validation:Enum=java;python;go;dotnet;javascript;mysql;unknown;ignored
type ProgrammingLanguage string

Expand Down
2 changes: 2 additions & 0 deletions helm/odigos/templates/crds/instrumentedapplications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ spec:
- unknown
- ignored
type: string
runtimeVersion:
type: string
required:
- containerName
- language
Expand Down
57 changes: 50 additions & 7 deletions odiglet/debug.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,52 @@
FROM python:3.11 AS python-builder
ARG ODIGOS_VERSION
WORKDIR /python-instrumentation
ADD odiglet/agents/python/requirements.txt .
RUN mkdir workspace && pip install --target workspace -r requirements.txt
COPY ../agents/python ./agents/python
RUN echo "VERSION = \"$ODIGOS_VERSION\";" > ./agents/python/configurator/version.py
RUN mkdir workspace && pip install ./agents/python/ --target workspace

FROM node:16 AS nodejs-builder

######### Node.js Native Community Agent #########
#
# The Node.js agent is built in multiple stages so it can be built with either upstream
# @odigos/opentelemetry-node or with a local clone to test changes during development.
# The implemntation is based on the following blog post:
# https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/

# The first build stage 'nodejs-agent-native-community-clone' clones the agent sources from github main branch.
FROM alpine AS nodejs-agent-native-community-clone
RUN apk add git
WORKDIR /src
ARG NODEJS_AGENT_VERSION=main
RUN git clone https://github.com/odigos-io/opentelemetry-node.git && cd opentelemetry-node && git checkout $NODEJS_AGENT_VERSION

# The second build stage 'nodejs-agent-native-community-src' prepares the actual code we are going to compile and embed in odiglet.
# By default, it uses the previous 'nodejs-agent-native-community-src' stage, but one can override it by setting the
# --build-context nodejs-agent-native-community-src=../opentelemetry-node flag in the docker build command.
# This allows us to nobe the agent sources and test changes during development.
# The output of this stage is the resolved source code to be used in the next stage.
FROM scratch AS nodejs-agent-native-community-src
COPY --from=nodejs-agent-native-community-clone /src/opentelemetry-node /

# The third build stage 'nodejs-agent-native-community-builder' compiles the agent sources and prepares the final output.
# it COPY from the previous 'nodejs-agent-native-community-src' stage, so it can be used with either the upstream or local sources.
# The output of this stage is the compiled agent code in:
# - package source code in '/nodejs-instrumentation/build/src' directory.
# - all required dependencies in '/nodejs-instrumentation/prod_node_modules' directory.
# These artifacts are later copied into the odiglet final image to be mounted into auto-instrumented pods at runtime.
FROM node:18 AS nodejs-agent-native-community-builder
ARG ODIGOS_VERSION
WORKDIR /nodejs-instrumentation
COPY odiglet/agents/nodejs .
RUN npm install
COPY --from=nodejs-agent-native-community-src /package.json /yarn.lock ./
# prepare the production node_modules content in a separate directory
RUN yarn --production --frozen-lockfile
RUN mv node_modules ./prod_node_modules
# install all dependencies including dev so we can yarn compile
RUN yarn --frozen-lockfile
COPY --from=nodejs-agent-native-community-src / ./
# inject the actual version into the agent code
RUN echo "export const VERSION = \"$ODIGOS_VERSION\";" > ./src/version.ts
RUN yarn compile

FROM busybox:1.36.1 AS dotnet-builder
WORKDIR /dotnet-instrumentation
Expand All @@ -23,13 +63,14 @@ RUN ARCH_SUFFIX=$(cat /tmp/arch_suffix) && \
unzip opentelemetry-dotnet-instrumentation-linux-glibc-${ARCH_SUFFIX}.zip && \
rm opentelemetry-dotnet-instrumentation-linux-glibc-${ARCH_SUFFIX}.zip

FROM --platform=$BUILDPLATFORM keyval/odiglet-base:v1.5 as builder
FROM --platform=$BUILDPLATFORM keyval/odiglet-base:v1.5 AS builder
WORKDIR /go/src/github.com/odigos-io/odigos
# Copyy local modules required by the build
COPY api/ api/
COPY common/ common/
COPY k8sutils/ k8sutils/
COPY procdiscovery/ procdiscovery/
COPY opampserver/ opampserver/
WORKDIR /go/src/github.com/odigos-io/odigos/odiglet
COPY odiglet/ .

Expand All @@ -52,7 +93,9 @@ RUN chmod 644 /instrumentations/java/javaagent.jar
COPY --from=python-builder /python-instrumentation/workspace /instrumentations/python

# NodeJS
COPY --from=nodejs-builder /nodejs-instrumentation/build/workspace /instrumentations/nodejs
COPY --from=nodejs-agent-native-community-builder /nodejs-instrumentation/build/src /instrumentations/nodejs
COPY --from=nodejs-agent-native-community-builder /nodejs-instrumentation/prod_node_modules /instrumentations/nodejs/node_modules


# .NET
COPY --from=dotnet-builder /dotnet-instrumentation /instrumentations/dotnet
Expand Down
20 changes: 11 additions & 9 deletions odiglet/pkg/kube/runtime_details/inspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ func runtimeInspection(pods []corev1.Pod, ignoredContainers []string) ([]odigosv
continue
}

var lang common.ProgrammingLanguage
programLanguageDetails := common.ProgramLanguageDetails{Language: common.UnknownProgrammingLanguage}
var inspectProc *procdiscovery.Details
var detectErr error

for _, proc := range processes {
lang, detectErr = inspectors.DetectLanguage(proc)
if detectErr == nil && lang != common.UnknownProgrammingLanguage {
programLanguageDetails, detectErr = inspectors.DetectLanguage(proc)
if detectErr == nil && programLanguageDetails.Language != common.UnknownProgrammingLanguage {
inspectProc = &proc
break
}
Expand All @@ -108,22 +108,24 @@ func runtimeInspection(pods []corev1.Pod, ignoredContainers []string) ([]odigosv
envs := make([]odigosv1.EnvVar, 0)
if inspectProc == nil {
log.Logger.V(0).Info("unable to detect language for any process", "pod", pod.Name, "container", container.Name, "namespace", pod.Namespace)
lang = common.UnknownProgrammingLanguage
programLanguageDetails.Language = common.UnknownProgrammingLanguage
} else {
if len(processes) > 1 {
log.Logger.V(0).Info("multiple processes found in pod container, only taking the first one with detected language into account", "pod", pod.Name, "container", container.Name, "namespace", pod.Namespace)
}

// Convert map to slice for k8s format
envs = make([]odigosv1.EnvVar, 0, len(inspectProc.Envs))
for envName, envValue := range inspectProc.Envs {
envs = make([]odigosv1.EnvVar, 0, len(inspectProc.Environments.DetailedEnvs))
for envName, envValue := range inspectProc.Environments.OverwriteEnvs {
envs = append(envs, odigosv1.EnvVar{Name: envName, Value: envValue})
}
}

resultsMap[container.Name] = odigosv1.RuntimeDetailsByContainer{
ContainerName: container.Name,
Language: lang,
EnvVars: envs,
ContainerName: container.Name,
Language: programLanguageDetails.Language,
RuntimeVersion: programLanguageDetails.RuntimeVersion,
EnvVars: envs,
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions procdiscovery/pkg/inspectors/dotnet/dotnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ const (
dotnet = "DOTNET"
)

func (d *DotnetInspector) Inspect(p *process.Details) (common.ProgrammingLanguage, bool) {
func (d *DotnetInspector) Inspect(p *process.Details) (common.ProgramLanguageDetails, bool) {
var programLanguageDetails common.ProgramLanguageDetails
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/environ", p.ProcessID))
yodigos marked this conversation as resolved.
Show resolved Hide resolved
if err == nil {
environ := string(data)
if strings.Contains(environ, aspnet) || strings.Contains(environ, dotnet) {
return common.DotNetProgrammingLanguage, true
programLanguageDetails.Language = common.DotNetProgrammingLanguage
return programLanguageDetails, true
}
}

return "", false
return programLanguageDetails, false
}
14 changes: 10 additions & 4 deletions procdiscovery/pkg/inspectors/golang/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ import (

type GolangInspector struct{}

func (g *GolangInspector) Inspect(p *process.Details) (common.ProgrammingLanguage, bool) {
func (g *GolangInspector) Inspect(p *process.Details) (common.ProgramLanguageDetails, bool) {
var programLanguageDetails common.ProgramLanguageDetails
file := fmt.Sprintf("/proc/%d/exe", p.ProcessID)
_, err := buildinfo.ReadFile(file)
buildInfo, err := buildinfo.ReadFile(file)
if err != nil {
return "", false
return programLanguageDetails, false
}

return common.GoProgrammingLanguage, true
programLanguageDetails.Language = common.GoProgrammingLanguage
if buildInfo != nil {
programLanguageDetails.RuntimeVersion = buildInfo.GoVersion
}

return programLanguageDetails, true
}
15 changes: 11 additions & 4 deletions procdiscovery/pkg/inspectors/java/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ type JavaInspector struct{}

const processName = "java"

func (j *JavaInspector) Inspect(p *process.Details) (common.ProgrammingLanguage, bool) {
if strings.Contains(p.ExeName, processName) || strings.Contains(p.CmdLine, processName) {
return common.JavaProgrammingLanguage, true
func (j *JavaInspector) Inspect(proc *process.Details) (common.ProgramLanguageDetails, bool) {
var programLanguageDetails common.ProgramLanguageDetails

if strings.Contains(proc.ExeName, processName) || strings.Contains(proc.CmdLine, processName) {
programLanguageDetails.Language = common.JavaProgrammingLanguage
blumamir marked this conversation as resolved.
Show resolved Hide resolved
if value, exists := proc.GetDetailedEnvsValue(process.JavaVersionConst); exists {
programLanguageDetails.RuntimeVersion = value
}

return programLanguageDetails, true
}

return "", false
return programLanguageDetails, false
}
31 changes: 18 additions & 13 deletions procdiscovery/pkg/inspectors/langdetect.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (e ErrLanguageDetectionConflict) Error() string {
}

type inspector interface {
Inspect(process *process.Details) (common.ProgrammingLanguage, bool)
Inspect(process *process.Details) (common.ProgramLanguageDetails, bool)
}

var inspectorsList = []inspector{
Expand All @@ -37,23 +37,28 @@ var inspectorsList = []inspector{
// DetectLanguage returns the detected language for the process or
// common.UnknownProgrammingLanguage if the language could not be detected, in which case error == nil
// if error or language detectors disagree common.UnknownProgrammingLanguage is also returned
func DetectLanguage(process process.Details) (common.ProgrammingLanguage, error) {
detectedLanguage := common.UnknownProgrammingLanguage
func DetectLanguage(process process.Details) (common.ProgramLanguageDetails, error) {
detectedProgramLanguageDetails := common.ProgramLanguageDetails{
Language: common.UnknownProgrammingLanguage,
}

for _, i := range inspectorsList {
language, detected := i.Inspect(&process)
languageDetails, detected := i.Inspect(&process)
if detected {
if detectedLanguage == common.UnknownProgrammingLanguage {
detectedLanguage = language
if detectedProgramLanguageDetails.Language == common.UnknownProgrammingLanguage {
detectedProgramLanguageDetails = languageDetails
continue
}
return common.UnknownProgrammingLanguage, ErrLanguageDetectionConflict{
languages: [2]common.ProgrammingLanguage{
detectedLanguage,
language,
},
}
return common.ProgramLanguageDetails{
Language: common.UnknownProgrammingLanguage,
}, ErrLanguageDetectionConflict{
languages: [2]common.ProgrammingLanguage{
detectedProgramLanguageDetails.Language,
languageDetails.Language,
},
}
}
}

return detectedLanguage, nil
return detectedProgramLanguageDetails, nil
}
8 changes: 5 additions & 3 deletions procdiscovery/pkg/inspectors/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ type MySQLInspector struct{}

const MySQLProcessName = "mysqld"

func (j *MySQLInspector) Inspect(p *process.Details) (common.ProgrammingLanguage, bool) {
func (j *MySQLInspector) Inspect(p *process.Details) (common.ProgramLanguageDetails, bool) {
var programLanguageDetails common.ProgramLanguageDetails
if strings.HasSuffix(p.ExeName, MySQLProcessName) || strings.HasSuffix(p.CmdLine, MySQLProcessName) {
return common.MySQLProgrammingLanguage, true
programLanguageDetails.Language = common.MySQLProgrammingLanguage
return programLanguageDetails, true
}

return "", false
return programLanguageDetails, false
}
Loading
Loading