Skip to content

Commit

Permalink
feat: add cri support to experimental otelcol log collector
Browse files Browse the repository at this point in the history
  • Loading branch information
swiatekm-sumo committed Jan 11, 2022
1 parent 9800605 commit 3323a12
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- feat: add CRI support to experimental otelcol log collector[#2017][#2017]
- feat: add batching to experimental otelcol log collector [#2018][#2018]
- feat: add experimental otelcol log collector [#1986][#1986]
- feat: add option to disable pod owners enrichment [#1959][#1959]
Expand Down Expand Up @@ -49,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#1994]: https://github.com/SumoLogic/sumologic-kubernetes-collection/pull/1994

[v2_3_2]: https://github.com/SumoLogic/sumologic-kubernetes-collection/releases/tag/v2.3.2
[#2017]: https://github.com/SumoLogic/sumologic-kubernetes-collection/pull/2017

## [v2.3.1][v2_3_1] - 2021-12-14

Expand Down
32 changes: 30 additions & 2 deletions deploy/helm/sumologic/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4188,7 +4188,35 @@ otellogs:
include_file_path: true
include_file_name: false
operators:

## Detect the container runtime log format
## Can be: docker-shim, CRI-O and containerd
- id: get-format
type: router
routes:
- output: parser-docker
expr: '$$body matches "^\\{"'
- output: parser-crio
expr: '$$body matches "^[^ Z]+ "'
- output: parser-containerd
expr: '$$body matches "^[^ Z]+Z"'
## Parse CRI-O format
- id: parser-crio
type: regex_parser
regex: '^(?P<time>[^ Z]+) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) (?P<log>.*)$'
output: extract-metadata-from-filepath
timestamp:
parse_from: time
layout_type: gotime
layout: '2006-01-02T15:04:05.000000000-07:00'
## Parse CRI-Containerd format
- id: parser-containerd
type: regex_parser
regex: '^(?P<time>[^ ^Z]+Z) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) (?P<log>.*)$'
output: extract-metadata-from-filepath
timestamp:
parse_from: time
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
## Parse docker-shim format
## parser-docker interprets the input string as JSON and moves the `time` field from the JSON to Timestamp field in the OTLP log
## record.
# Input Body (string): '{"log":"2001-02-03 04:05:06 first line\n","stream":"stdout","time":"2021-11-25T09:59:13.23887954Z"}'
Expand All @@ -4197,7 +4225,7 @@ otellogs:
# Output Timestamp: 2021-11-25 09:59:13.23887954 +0000 UTC
- id: parser-docker
type: json_parser
# output: join-multipart-entries
output: extract-metadata-from-filepath
timestamp:
parse_from: time
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
Expand Down
25 changes: 25 additions & 0 deletions tests/helm/logs_otc/static/basic.output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,32 @@ data:
include_file_name: false
include_file_path: true
operators:
- id: get-format
routes:
- expr: $$body matches "^\\{"
output: parser-docker
- expr: $$body matches "^[^ Z]+ "
output: parser-crio
- expr: $$body matches "^[^ Z]+Z"
output: parser-containerd
type: router
- id: parser-crio
output: extract-metadata-from-filepath
regex: ^(?P<time>[^ Z]+) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) (?P<log>.*)$
timestamp:
layout: "2006-01-02T15:04:05.000000000-07:00"
layout_type: gotime
parse_from: time
type: regex_parser
- id: parser-containerd
output: extract-metadata-from-filepath
regex: ^(?P<time>[^ ^Z]+Z) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) (?P<log>.*)$
timestamp:
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
parse_from: time
type: regex_parser
- id: parser-docker
output: extract-metadata-from-filepath
timestamp:
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
parse_from: time
Expand Down
152 changes: 152 additions & 0 deletions tests/integration/helm_otelcol_logs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package integration

import (
"context"
"fmt"
"testing"
"time"

"sigs.k8s.io/e2e-framework/pkg/envconf"
"sigs.k8s.io/e2e-framework/pkg/features"

terrak8s "github.com/gruntwork-io/terratest/modules/k8s"
"github.com/stretchr/testify/require"

corev1 "k8s.io/api/core/v1"
log "k8s.io/klog/v2"
"sigs.k8s.io/e2e-framework/klient/k8s"
"sigs.k8s.io/e2e-framework/klient/k8s/resources"
"sigs.k8s.io/e2e-framework/klient/wait"
"sigs.k8s.io/e2e-framework/klient/wait/conditions"

"github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal"
"github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/ctxopts"
"github.com/SumoLogic/sumologic-kubernetes-collection/tests/integration/internal/stepfuncs"
)

func Test_Helm_Otelcol_Logs(t *testing.T) {
const (
tickDuration = 3 * time.Second
waitDuration = 3 * time.Minute
logsGeneratorCount uint = 1000
)

featInstall := features.New("installation").
Assess("sumologic secret is created",
func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context {
terrak8s.WaitUntilSecretAvailable(t, ctxopts.KubectlOptions(ctx), "sumologic", 60, tickDuration)
secret := terrak8s.GetSecret(t, ctxopts.KubectlOptions(ctx), "sumologic")
require.Len(t, secret.Data, 2)
return ctx
}).
Assess("otelcol logs statefulset is ready",
stepfuncs.WaitUntilStatefulSetIsReady(
waitDuration,
tickDuration,
stepfuncs.WithNameF(
stepfuncs.ReleaseFormatter("%s-sumologic-otelcol-logs"),
),
stepfuncs.WithLabelsF(
stepfuncs.LabelFormatterKV{
K: "app",
V: stepfuncs.ReleaseFormatter("%s-sumologic-otelcol-logs"),
},
),
),
).
Assess("otelcol logs buffers PVCs are created and bound",
func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context {
res := envConf.Client().Resources(ctxopts.Namespace(ctx))
pvcs := corev1.PersistentVolumeClaimList{}
cond := conditions.
New(res).
ResourceListMatchN(&pvcs, 3,
func(object k8s.Object) bool {
pvc := object.(*corev1.PersistentVolumeClaim)
if pvc.Status.Phase != corev1.ClaimBound {
log.V(0).Infof("PVC %q not bound yet", pvc.Name)
return false
}
return true
},
resources.WithLabelSelector(
fmt.Sprintf("app=%s-sumologic-otelcol-logs", ctxopts.HelmRelease(ctx)),
),
)
require.NoError(t,
wait.For(cond,
wait.WithTimeout(waitDuration),
wait.WithInterval(tickDuration),
),
)
return ctx
}).
Assess("otelcol daemonset is ready",
stepfuncs.WaitUntilDaemonSetIsReady(
waitDuration,
tickDuration,
stepfuncs.WithNameF(
stepfuncs.ReleaseFormatter("%s-sumologic-otelcol-logs-collector"),
),
stepfuncs.WithLabelsF(
stepfuncs.LabelFormatterKV{
K: "app",
V: stepfuncs.ReleaseFormatter("%s-sumologic-otelcol-logs-collector"),
},
),
),
).
Feature()
featLogs := features.New("logs").
Setup(stepfuncs.GenerateLogsWithDeployment(
logsGeneratorCount,
internal.LogsGeneratorName,
internal.LogsGeneratorNamespace,
internal.LogsGeneratorImage,
)).
Assess("logs from log generator present", stepfuncs.WaitUntilExpectedLogsPresent(
logsGeneratorCount,
map[string]string{
"namespace": internal.LogsGeneratorName,
"pod_labels_app": internal.LogsGeneratorName,
},
internal.ReceiverMockNamespace,
internal.ReceiverMockServiceName,
internal.ReceiverMockServicePort,
waitDuration,
tickDuration,
)).
Assess("expected container log metadata is present", stepfuncs.WaitUntilExpectedLogsPresent(
logsGeneratorCount,
map[string]string{
"_collector": "kubernetes",
"namespace": internal.LogsGeneratorName,
"pod_labels_app": internal.LogsGeneratorName,
"container": internal.LogsGeneratorName,
"deployment": internal.LogsGeneratorName,
"replicaset": "",
"pod": "",
"k8s.pod.id": "",
"k8s.pod.pod_name": "",
// "k8s.container.id": "", // TODO: disable this for other tests, it's not reliable
"host": "",
"node": "",
},
internal.ReceiverMockNamespace,
internal.ReceiverMockServiceName,
internal.ReceiverMockServicePort,
waitDuration,
tickDuration,
)).
Teardown(
func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context {
opts := *ctxopts.KubectlOptions(ctx)
opts.Namespace = internal.LogsGeneratorNamespace
terrak8s.RunKubectl(t, &opts, "delete", "deployment", internal.LogsGeneratorName)
return ctx
}).
Teardown(stepfuncs.KubectlDeleteNamespaceOpt(internal.LogsGeneratorNamespace)).
Feature()

testenv.Test(t, featInstall, featLogs)
}
51 changes: 51 additions & 0 deletions tests/integration/internal/stepfuncs/assess_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,54 @@ func WaitUntilStatefulSetIsReady(
return ctx
}
}

// WaitUntilDaemonSetIsReady waits for a specified duration and checks with the
// specified tick interval whether the daemonset (as described by the provided options)
// is ready.
//
// Readiness for a daemonset is defined as having Status.NumberUnavailable == 0.
func WaitUntilDaemonSetIsReady(
waitDuration time.Duration,
tickDuration time.Duration,
opts ...Option,
) features.Func {
return func(ctx context.Context, t *testing.T, envConf *envconf.Config) context.Context {
ds := appsv1.DaemonSet{
ObjectMeta: v1.ObjectMeta{
Namespace: ctxopts.Namespace(ctx),
},
}

listOpts := []resources.ListOption{}
for _, opt := range opts {
opt.Apply(ctx, &ds)
listOpts = append(listOpts, opt.GetListOption(ctx))
}

res := envConf.Client().Resources(ctxopts.Namespace(ctx))
cond := conditions.
New(res).
ResourceListMatchN(&appsv1.DaemonSetList{Items: []appsv1.DaemonSet{ds}},
1,
func(obj k8s.Object) bool {
ds := obj.(*appsv1.DaemonSet)
log.V(5).InfoS("DaemonSet", "status", ds.Status)
if ds.Status.NumberUnavailable != 0 {
log.V(0).Infof("DaemonSet %q not yet fully ready", ds.Name)
return false
}
return true
},
listOpts...,
)

require.NoError(t,
wait.For(cond,
wait.WithTimeout(waitDuration),
wait.WithInterval(tickDuration),
),
)

return ctx
}
}
54 changes: 54 additions & 0 deletions tests/integration/values/values_helm_otelcol_logs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
sumologic:
setupEnabled: true
accessId: "dummy"
accessKey: "dummy"
endpoint: http://receiver-mock.receiver-mock:3000/terraform/api/

logs:
metadata:
provider: otelcol

metrics:
enabled: false

# We're using otelcol instead
fluent-bit:
enabled: false

fluentd:
events:
enabled: false

# Request less resources so that this fits on Github actions runners environment
metadata:
persistence:
size: 128Mi
logs:
logLevel: debug
statefulset:
resources:
requests:
cpu: 100m
memory: 128Mi

otellogs:
enabled: true
config:
service:
pipelines:
logs/containers:
receivers:
- filelog/containers
exporters:
- otlphttp
processors:
- filter/exclude_receiver_mock_container
processors:
# Filter out receiver-mock logs to prevent snowball effect
filter/exclude_receiver_mock_container:
logs:
exclude:
match_type: strict
record_attributes:
- key: k8s.container.name
value: receiver-mock

0 comments on commit 3323a12

Please sign in to comment.