Skip to content

Commit

Permalink
Merge branch 'main' into rename_collections
Browse files Browse the repository at this point in the history
  • Loading branch information
grcevski committed Feb 6, 2024
2 parents 39b4e63 + aa3023b commit bf1d5f6
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 19 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/vale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Lint prose with Vale
on:
pull_request:
paths: ["docs/sources/**"]
workflow_dispatch:
jobs:
vale:
runs-on: ubuntu-latest
container:
image: grafana/vale:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run linter
run: >
cp /etc/vale/.vale.ini ./.vale.ini
&&
cp -R /etc/vale/dictionaries dictionaries
&&
vale
'--glob=*.md'
--minAlertLevel=warning
--output=/etc/vale/rdjsonl.tmpl
docs/sources
|
/bin/reviewdog
--conf=/etc/vale/.reviewdog.yml
--fail-on-error
--f=rdjsonl
--name=vale
--reporter=github-pr-review
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ all: generate build
.PHONY: compile
compile:
@echo "### Compiling project"
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -mod vendor -ldflags="-X 'main.Version=$(RELEASE_VERSION)'" -a -o bin/$(CMD) $(MAIN_GO_FILE)
CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -mod vendor -ldflags="-X 'main.Version=$(RELEASE_VERSION)'" -a -o bin/$(CMD) $(MAIN_GO_FILE)

.PHONY: dev
dev: prereqs generate compile-for-coverage
Expand All @@ -159,7 +159,7 @@ dev: prereqs generate compile-for-coverage
.PHONY: compile-for-coverage
compile-for-coverage:
@echo "### Compiling project to generate coverage profiles"
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -mod vendor -cover -a -o bin/$(CMD) $(MAIN_GO_FILE)
CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -mod vendor -cover -a -o bin/$(CMD) $(MAIN_GO_FILE)

.PHONY: test
test:
Expand Down Expand Up @@ -273,4 +273,4 @@ artifact: compile
.PHONE: clean-testoutput
clean-testoutput:
@echo "### Cleaning ${TEST_OUTPUT} folder"
rm -rf ${TEST_OUTPUT}/*
rm -rf ${TEST_OUTPUT}/*
9 changes: 6 additions & 3 deletions docs/sources/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ aliases:

## eBPF application auto-instrumentation

Instrumenting an application to obtain metrics and traces typically requires adding a language agent to the application deployment/packages. In some compiled languages like Go or Rust, tracepoints have to be manually added to the code. In both cases, the instrumented version of the application must be redeployed to the staging/production servers.
Instrumenting an application to obtain metrics and traces typically requires adding a language agent to the application deployment/packages.
In some compiled languages like Go or Rust, you must manually add tracepoints to the code.

Grafana Beyla is an eBPF-based application auto-instrumentation tool to easily get started with Application Observability. eBPF is used to automatically inspect application executables and the OS networking layer and capture trace spans related to web transactions and Rate-Errors-Duration (RED) metrics for Linux HTTP/S and gRPC services. All data capture occurs without any modifications to application code or configuration.
Grafana Beyla is an eBPF-based application auto-instrumentation tool to easily get started with Application Observability.
Beyla uses eBPF to automatically inspect application executables and the OS networking layer, and capture trace spans related to web transactions and Rate Errors Duration (RED) metrics for Linux HTTP/S and gRPC services.
All data capture occurs without any modifications to application code or configuration.

Beyla offers the following features:

Expand All @@ -37,7 +40,7 @@ Beyla offers the following features:

## Requirements

- Linux with Kernel 5.8 or higher with [BTF](https://www.kernel.org/doc/html/latest/bpf/btf.html)
- Linux with Kernel 5.8 or higher with BPF Type Format [(BTF)](https://www.kernel.org/doc/html/latest/bpf/btf.html)
enabled. BTF became enabled by default on most Linux distributions with kernel 5.14 or higher.
You can check if your kernel has BTF enabled by verifying if `/sys/kernel/btf/vmlinux` exists on your system.
If you need to recompile your kernel to enable BTF, the configuration option `CONFIG_DEBUG_INFO_BTF=y` must be
Expand Down
2 changes: 1 addition & 1 deletion docs/sources/distributed-traces.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Beyla will read any incoming trace context header values, track the Go program e

### Kernel integrity mode limitations

In order to write the `traceparent` value in outgoing HTTP/gRPC request headers, Beyla needs to write to the process memory using the [bpf_probe_write_user](https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html) eBPF helper. Since kernel 5.15 this helper is protected (and unavailable to BPF programs) if the Linux Kernel is running in `integrity` lockdown mode. Kernel integrity mode is typically enabled by default if the Kernel has [Secure Boot](https://wiki.debian.org/SecureBoot) enabled, but it can also be enabled manually.
In order to write the `traceparent` value in outgoing HTTP/gRPC request headers, Beyla needs to write to the process memory using the [bpf_probe_write_user](https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html) eBPF helper. Since kernel 5.14 (with fixes backported to the 5.10 series) this helper is protected (and unavailable to BPF programs) if the Linux Kernel is running in `integrity` lockdown mode. Kernel integrity mode is typically enabled by default if the Kernel has [Secure Boot](https://wiki.debian.org/SecureBoot) enabled, but it can also be enabled manually.

Beyla will automatically check if it can use the `bpf_probe_write_user` helper, and enable context propagation only if it's allowed by the kernel configuration. Verify the Linux Kernel lockdown mode by running the following command:

Expand Down
4 changes: 2 additions & 2 deletions pkg/internal/ebpf/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ func SupportsContextPropagation(log *slog.Logger) bool {
kernelMajor, kernelMinor := KernelVersion()
log.Debug("Linux kernel version", "major", kernelMajor, "minor", kernelMinor)

if kernelMajor < 5 || (kernelMajor == 5 && kernelMinor < 14) {
log.Debug("Found Linux kernel earlier than 5.14, trace context propagation is supported", "major", kernelMajor, "minor", kernelMinor)
if kernelMajor < 5 || (kernelMajor == 5 && kernelMinor < 10) {
log.Debug("Found Linux kernel earlier than 5.10, trace context propagation is supported", "major", kernelMajor, "minor", kernelMinor)
return true
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/internal/export/prom/prom.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ func labelNamesHTTPClient(cfg *PrometheusConfig, ctxInfo *global.ContextInfo) []
if ctxInfo.K8sEnabled {
names = appendK8sLabelNames(names)
}
if ctxInfo.ReportRoutes {
names = append(names, httpRouteKey)
}
return names
}

Expand All @@ -265,6 +268,9 @@ func (r *metricsReporter) labelValuesHTTPClient(span *request.Span) []string {
if r.ctxInfo.K8sEnabled {
values = appendK8sLabelValues(values, span)
}
if r.ctxInfo.ReportRoutes {
values = append(values, span.Route) // httpRouteKey
}
return values
}

Expand Down
23 changes: 18 additions & 5 deletions pkg/internal/helpers/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ type Info struct {
PIDNamespace uint32
}

// A docker cgroup entry is a string like:
// 0::/docker/<hex...>/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-besteffort.slice/
// kubelet-kubepods-besteffort-pod<hex...>.slice/cri-containerd-<hex...>.scope
// A docker cgroup entry is a string like - when running beyla as a process on the node:
// 0::/docker/<hex...>/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-besteffort.slice/kubelet-kubepods-besteffort-pod<hex...>.slice/cri-containerd-<hex...>.scope
// where the last <hex...> chain is the container ID inside its Pod
// The /docker/<hex...> part might not be visible inside the Pod (e.g. deploying Beyla
// as a sidecar). That's why we search for the "kubelet.slice" string.
var dockerCgroup = regexp.MustCompile(`^\d+:.*:.*/.*-([\da-fA-F]+)\.scope`)

// A k8s cgroup entry is a string like - when running beyla as daemonset in k8s:
// GKE: /kubepods/burstable/pod4a163a05-439d-484b-8e53-2968bc15824f/cde6dfaf5007ed65aad2d6aed72af91b0f3d95813492f773286e29ae145d20f4
// GKE-containerd: /kubepods/burstable/podb53dfa9e2dc9b890f7fadb2770857b03/bb959773d06ad1a07d469bced637bbc49b6f8573c493fd0548d7f5810eb3e5a8
// EKS: /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddde1244b_5bb5_4297_b544_85f3bc0d80bf.slice/cri-containerd-acb62391578595ec849d87d8556369cee7f935f0425097fd5f870df0e8aabd3c.scope
// Containerd: /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod7260904bbd08e72e4dff95d9fccd2ee8.slice/cri-containerd-d36686f9785534531160dc936aec9d711a26eb37f4fc7752a2ae27d0a24345c1.scope
var k8sCgroup = regexp.MustCompile(`^\d+:.*:/kubepods.*([0-9a-f]{64})`)

// InfoForPID returns the container ID and PID namespace for the given PID.
func InfoForPID(pid uint32) (Info, error) {
ns, err := namespaceFinder(int32(pid))
Expand All @@ -43,12 +47,21 @@ func InfoForPID(pid uint32) (Info, error) {
if err != nil {
return Info{}, fmt.Errorf("reading %s: %w", cgroupFile, err)
}
// We look for the docker cgroup entry first, as it's the most common
for _, cgroupEntry := range bytes.Split(cgroupBytes, []byte{'\n'}) {
submatches := dockerCgroup.FindSubmatch(cgroupEntry)
if len(submatches) < 2 {
continue
}
return Info{PIDNamespace: ns, ContainerID: string(submatches[1])}, nil
}
// If we didn't find a docker entry, we look for a k8s entry
for _, cgroupEntry := range bytes.Split(cgroupBytes, []byte{'\n'}) {
submatches := k8sCgroup.FindSubmatch(cgroupEntry)
if len(submatches) < 2 {
continue
}
return Info{PIDNamespace: ns, ContainerID: string(submatches[1])}, nil
}
return Info{}, fmt.Errorf("%s: couldn't find any docker entry for process with PID %d", cgroupFile, pid)
}
8 changes: 8 additions & 0 deletions pkg/internal/helpers/container/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ var fixturesWithContainer = map[uint32]string{
0::/system.slice/containerd.service`,
789: `0::/../cri-containerd-40c03570b6f4c30bc8d69923d37ee698f5cfcced92c7b7df1c47f6f7887378a9.scope`,
999: `0::/system.slice/docker-40c03570b6f4c30bc8d69923d37ee698f5cfcced92c7b7df1c47f6f7887378a9.scope/kubepods/burstable/podc55ba69a-e39f-44af-925d-c4794fd57878/264c1e319d1f6080a48a9fabcf9ac8fd9afd9a5930cf35e8d0eeb03b258c3152`,
// GKE cgroup entry
589: `0::/kubepods/burstable/pod4a163a05-439d-484b-8e53-2968bc15824f/40c03570b6f4c30bc8d69923d37ee698f5cfcced92c7b7df1c47f6f7887378a9`,
// GKE-containerd cgroup entry
689: `0::/kubepods/burstable/podb53dfa9e2dc9b890f7fadb2770857b03/40c03570b6f4c30bc8d69923d37ee698f5cfcced92c7b7df1c47f6f7887378a9`,
// EKS cgroup entry
788: `0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddde1244b_5bb5_4297_b544_85f3bc0d80bf.slice/cri-containerd-40c03570b6f4c30bc8d69923d37ee698f5cfcced92c7b7df1c47f6f7887378a9.scope`,
// Containerd cgroup entry
889: `0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod7260904bbd08e72e4dff95d9fccd2ee8.slice/cri-containerd-40c03570b6f4c30bc8d69923d37ee698f5cfcced92c7b7df1c47f6f7887378a9.scope`,
}

var fixturesWithoutContainer = map[uint32]string{
Expand Down
4 changes: 2 additions & 2 deletions test/integration/components/pingclient/pingclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func rtRequest(url string, counter int) {
func main() {
counter := 1
for {
regularGetRequest("https://grafana.com", counter)
rtRequest("https://grafana.com", counter)
regularGetRequest("https://grafana.com/oss/", counter)
rtRequest("https://grafana.com/oss/", counter)
time.Sleep(time.Second)
counter++
}
Expand Down
17 changes: 14 additions & 3 deletions test/integration/red_test_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/grafana/beyla/test/integration/components/prom"
)

func testClientWithMethodAndStatusCode(t *testing.T, method string, statusCode int, traceIDLookup string) {
func testClientWithMethodAndStatusCode(t *testing.T, method string, statusCode int, traces bool, traceIDLookup string) {
// Eventually, Prometheus would make this query visible
pq := prom.Client{HostPort: prometheusHostPort}
var results []prom.Result
Expand All @@ -27,6 +27,7 @@ func testClientWithMethodAndStatusCode(t *testing.T, method string, statusCode i
results, err = pq.Query(`http_client_request_duration_seconds_count{` +
fmt.Sprintf(`http_request_method="%s",`, method) +
fmt.Sprintf(`http_response_status_code="%d",`, statusCode) +
`http_route="/oss/",` +
`service_namespace="integration-test",` +
`service_name="pingclient"}`)
require.NoError(t, err)
Expand All @@ -40,6 +41,7 @@ func testClientWithMethodAndStatusCode(t *testing.T, method string, statusCode i
results, err = pq.Query(`http_client_request_body_size_bytes_count{` +
fmt.Sprintf(`http_request_method="%s",`, method) +
fmt.Sprintf(`http_response_status_code="%d",`, statusCode) +
`http_route="/oss/",` +
`service_namespace="integration-test",` +
`service_name="pingclient"}`)
require.NoError(t, err)
Expand All @@ -48,6 +50,10 @@ func testClientWithMethodAndStatusCode(t *testing.T, method string, statusCode i
assert.LessOrEqual(t, 1, val)
})

if !traces {
return
}

var trace jaeger.Trace
test.Eventually(t, testTimeout, func(t require.TestingT) {
resp, err := http.Get(jaegerQueryURL + fmt.Sprintf("?service=pingclient&operation=%s", method))
Expand Down Expand Up @@ -81,6 +87,11 @@ func testClientWithMethodAndStatusCode(t *testing.T, method string, statusCode i
}

func testREDMetricsForClientHTTPLibrary(t *testing.T) {
testClientWithMethodAndStatusCode(t, "GET", 200, "0000000000000000")
testClientWithMethodAndStatusCode(t, "OPTIONS", 204, "0000000000000001")
testClientWithMethodAndStatusCode(t, "GET", 200, true, "0000000000000000")
testClientWithMethodAndStatusCode(t, "OPTIONS", 204, true, "0000000000000001")
}

func testREDMetricsForClientHTTPLibraryNoTraces(t *testing.T) {
testClientWithMethodAndStatusCode(t, "GET", 200, false, "0000000000000000")
testClientWithMethodAndStatusCode(t, "OPTIONS", 204, false, "0000000000000001")
}
15 changes: 15 additions & 0 deletions test/integration/suites_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ func TestSuiteClient(t *testing.T) {
t.Run("BPF pinning folder unmounted", testBPFPinningUnmounted)
}

func TestSuiteClientPromScrape(t *testing.T) {
compose, err := docker.ComposeSuite("docker-compose-client.yml", path.Join(pathOutput, "test-suite-client-promscrape.log"))
compose.Env = append(compose.Env, `BEYLA_EXECUTABLE_NAME=pingclient`)
compose.Env = append(compose.Env,
`INSTRUMENTER_CONFIG_SUFFIX=-promscrape`,
`PROM_CONFIG_SUFFIX=-promscrape`,
)
require.NoError(t, err)
require.NoError(t, compose.Up())
t.Run("Client RED metrics", testREDMetricsForClientHTTPLibraryNoTraces)
t.Run("BPF pinning folder mounted", testBPFPinningMounted)
require.NoError(t, compose.Close())
t.Run("BPF pinning folder unmounted", testBPFPinningUnmounted)
}

// Same as Test suite, but the generated test image does not contain debug information
func TestSuite_NoDebugInfo(t *testing.T) {
compose, err := docker.ComposeSuite("docker-compose.yml", path.Join(pathOutput, "test-suite-nodebug.log"))
Expand Down

0 comments on commit bf1d5f6

Please sign in to comment.