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

Add container input, deprecate docker in favor of it #12162

Merged
merged 13 commits into from
May 20, 2019
2 changes: 1 addition & 1 deletion filebeat/autodiscover/builder/hints/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type config struct {

func defaultConfig() config {
rawCfg := map[string]interface{}{
"type": "docker",
"type": "container",
"containers": map[string]interface{}{
"paths": []string{
Copy link
Member

Choose a reason for hiding this comment

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

Can we use a less docker-specific path here? Or add a runtime option that sets up the proper defaults depending on the runtime?

Copy link
Contributor Author

@exekias exekias May 10, 2019

Choose a reason for hiding this comment

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

I'm currently working on a different PR to make autodiscover more CRI friendly, this is meant to keep it working as of now

// To be able to use this builder with CRI-O replace paths with:
Expand Down
3 changes: 3 additions & 0 deletions filebeat/docs/filebeat-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ You can configure {beatname_uc} to use the following inputs:

* <<{beatname_lc}-input-log>>
* <<{beatname_lc}-input-stdin>>
* <<{beatname_lc}-input-container>>
* <<{beatname_lc}-input-redis>>
* <<{beatname_lc}-input-udp>>
* <<{beatname_lc}-input-docker>>
Expand All @@ -57,6 +58,8 @@ include::inputs/input-log.asciidoc[]

include::inputs/input-stdin.asciidoc[]

include::inputs/input-container.asciidoc[]

include::inputs/input-redis.asciidoc[]

include::inputs/input-udp.asciidoc[]
Expand Down
64 changes: 64 additions & 0 deletions filebeat/docs/inputs/input-container.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
:type: container

[id="{beatname_lc}-input-{type}"]
=== Container input
dedemorton marked this conversation as resolved.
Show resolved Hide resolved

++++
<titleabbrev>Container</titleabbrev>
++++

Use the `container` input to read containers log files.

This input searches for container logs under the given path, and parse them into
common message lines, extracting timestamps too. Everything happens before line
filtering, multiline, and JSON decoding, so this input can be used in
combination with those settings.

Example configuration:

["source","yaml",subs="attributes"]
----
{beatname_lc}.inputs:
- type: container
paths: <1>
- '/var/lib/docker/containers/*/*.log'
----

<1> `path` is required. All other settings are optional.
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be paths instead?


==== Configuration options

The `container` input supports the following configuration options plus the
<<{beatname_lc}-input-{type}-common-options>> described later.

===== `stream`

Reads from the specified streams only: `all`, `stdout` or `stderr`. The default
is `all`.

===== `format`

Use the given format when reading the log file: `auto`, `docker` or `cri`. The
default is `auto`, it will automatically detect the format. To disable
autodetection set any of the other options.


The following input configures {beatname_uc} to read the `stdout` stream from
all containers under the default Kubernetes logs path:

[source,yaml]
----
- type: container
stream: stdout
paths:
- "/var/log/containers/*.log"
----

include::../inputs/input-common-harvester-options.asciidoc[]

include::../inputs/input-common-file-options.asciidoc[]

[id="{beatname_lc}-input-{type}-common-options"]
include::../inputs/input-common-options.asciidoc[]

:type!:
4 changes: 3 additions & 1 deletion filebeat/docs/inputs/input-docker.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<titleabbrev>Docker</titleabbrev>
++++

deprecated[7.2.0, Use `container` input instead.]

Use the `docker` input to read logs from Docker containers.

This input searches for container logs under its path, and parse them into
Expand Down Expand Up @@ -103,4 +105,4 @@ include::../inputs/input-common-file-options.asciidoc[]
[id="{beatname_lc}-input-{type}-common-options"]
include::../inputs/input-common-options.asciidoc[]

:type!:
:type!:
11 changes: 6 additions & 5 deletions filebeat/harvester/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import "github.com/elastic/beats/libbeat/common/match"

// Contains available input types
const (
LogType = "log"
StdinType = "stdin"
RedisType = "redis"
UdpType = "udp"
DockerType = "docker"
LogType = "log"
StdinType = "stdin"
RedisType = "redis"
UdpType = "udp"
exekias marked this conversation as resolved.
Show resolved Hide resolved
exekias marked this conversation as resolved.
Show resolved Hide resolved
exekias marked this conversation as resolved.
Show resolved Hide resolved
DockerType = "docker"
ContainerType = "container"
)

// MatchAny checks if the text matches any of the regular expressions
Expand Down
1 change: 1 addition & 0 deletions filebeat/include/list.go

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

31 changes: 31 additions & 0 deletions filebeat/input/container/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. 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 container

var defaultConfig = config{
Stream: "all",
Format: "auto",
}

type config struct {
// Stream can be all, stdout or stderr
Stream string `config:"stream"`

// Format can be auto, cri, json-file
Format string `config:"format"`
}
117 changes: 117 additions & 0 deletions filebeat/input/container/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. 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 container

import (
"fmt"
"strings"

"github.com/elastic/beats/filebeat/channel"
"github.com/elastic/beats/filebeat/input"
"github.com/elastic/beats/filebeat/input/log"
"github.com/elastic/beats/libbeat/common"

"github.com/pkg/errors"
)

func init() {
err := input.Register("container", NewInput)
if err != nil {
panic(err)
}
}

// NewInput creates a new container input
func NewInput(
cfg *common.Config,
outletFactory channel.Connector,
context input.Context,
) (input.Input, error) {
// Wrap log input with custom docker settings
config := defaultConfig
if err := cfg.Unpack(&config); err != nil {
return nil, errors.Wrap(err, "reading container input config")
}

if err := validateStream(config.Stream); err != nil {
return nil, err
}

if err := validateFormat(config.Format); err != nil {
return nil, err
}
Copy link

Choose a reason for hiding this comment

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

Better implement func (c *config) Validate() error . This will trigger validation on Unpack and adds some more context (file, full name of parent namespace including index) to the error message.


// Set partial line joining to true (both json-file and CRI)
if err := cfg.SetBool("docker-json.partial", -1, true); err != nil {
return nil, errors.Wrap(err, "update input config")
}

if err := cfg.SetBool("docker-json.cri_flags", -1, true); err != nil {
return nil, errors.Wrap(err, "update input config")
}

// Allow stream selection (stdout/stderr/all)
if err := cfg.SetString("docker-json.stream", -1, config.Stream); err != nil {
return nil, errors.Wrap(err, "update input config")
}

if err := cfg.SetString("docker-json.format", -1, config.Format); err != nil {
return nil, errors.Wrap(err, "update input config")
}

// Set symlinks to true as CRI-O paths could point to symlinks instead of the actual path.
if err := cfg.SetBool("symlinks", -1, true); err != nil {
return nil, errors.Wrap(err, "update input config")
}
Copy link

Choose a reason for hiding this comment

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

A many setters can be replaced by a single call to 'Merge':

err := cfg.Merge(common.MapStr{
    "docker-json.partial": true,
    "docker-json.cri_flags": true,
    ...
})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks for this, I didn't know it could be so simple! :godmode:


// Add stream to meta to ensure different state per stream
if config.Stream != "all" {
if context.Meta == nil {
context.Meta = map[string]string{}
}
context.Meta["stream"] = config.Stream
}

return log.NewInput(cfg, outletFactory, context)
}

func validateStream(val string) error {
if stringInSlice(val, []string{"all", "stdout", "stderr"}) {
return nil
}

return fmt.Errorf("Invalid value for stream: %s, supported values are: all, stdout, stderr", val)
}

func validateFormat(val string) error {
val = strings.ToLower(val)
if stringInSlice(val, []string{"auto", "docker", "cri"}) {
return nil
}

return fmt.Errorf("Invalid value for format: %s, supported values are: auto, docker, cri", val)
}

func stringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}
2 changes: 0 additions & 2 deletions filebeat/input/docker/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ type containers struct {
IDs []string `config:"ids"`
Path string `config:"path"`

Paths []string `config:"paths"`

// Stream can be all, stdout or stderr
Stream string `config:"stream"`
}
44 changes: 11 additions & 33 deletions filebeat/input/docker/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/elastic/beats/filebeat/input"
"github.com/elastic/beats/filebeat/input/log"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/common/cfgwarn"
"github.com/elastic/beats/libbeat/logp"

"github.com/pkg/errors"
Expand All @@ -45,25 +46,18 @@ func NewInput(
) (input.Input, error) {
logger := logp.NewLogger("docker")

cfgwarn.Deprecate("8.0.0", "'docker' input deprecated. Use 'container' input instead.")

// Wrap log input with custom docker settings
config := defaultConfig
if err := cfg.Unpack(&config); err != nil {
return nil, errors.Wrap(err, "reading docker input config")
}

// Docker input should make sure that no callers should ever pass empty strings as container IDs or paths
// Docker input should make sure that no callers should ever pass empty strings as container IDs
// Hence we explicitly make sure that we catch such things and print stack traces in the event of
// an invocation so that it can be fixed.
var ids, paths []string
for _, p := range config.Containers.Paths {
if p != "" {
paths = append(paths, p)
} else {
logger.Error("Docker paths can't be empty for Docker input config")
logger.Debugw("Empty path for Docker logfile was received", logp.Stack("stacktrace"))
}
}

var ids []string
for _, containerID := range config.Containers.IDs {
if containerID != "" {
ids = append(ids, containerID)
Expand All @@ -73,23 +67,12 @@ func NewInput(
}
}

if len(ids) == 0 && len(paths) == 0 {
return nil, errors.New("Docker input requires at least one entry under 'containers.ids' or 'containers.paths'")
}

// IDs + Path and Paths are mutually exclusive. Ensure that only one of them are set in a given configuration
if len(ids) != 0 && len(paths) != 0 {
return nil, errors.New("can not provide both 'containers.ids' and 'containers.paths' in the same input config")
if len(ids) == 0 {
return nil, errors.New("Docker input requires at least one entry under 'containers.ids''")
}

if len(ids) != 0 {
for idx, containerID := range ids {
cfg.SetString("paths", idx, path.Join(config.Containers.Path, containerID, "*.log"))
}
} else {
for idx, p := range paths {
cfg.SetString("paths", idx, p)
}
for idx, containerID := range ids {
cfg.SetString("paths", idx, path.Join(config.Containers.Path, containerID, "*.log"))
}

if err := checkStream(config.Containers.Stream); err != nil {
Expand All @@ -108,13 +91,8 @@ func NewInput(
return nil, errors.Wrap(err, "update input config")
}

if err := cfg.SetBool("docker-json.force_cri_logs", -1, config.CRIForce); err != nil {
return nil, errors.Wrap(err, "update input config")
}

if len(paths) != 0 {
// Set symlinks to true as CRI-O paths could point to symlinks instead of the actual path.
if err := cfg.SetBool("symlinks", -1, true); err != nil {
if config.CRIForce {
if err := cfg.SetString("docker-json.format", -1, "cri"); err != nil {
return nil, errors.Wrap(err, "update input config")
}
}
Expand Down
2 changes: 1 addition & 1 deletion filebeat/input/log/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ type config struct {
DockerJSON *struct {
Stream string `config:"stream"`
Partial bool `config:"partial"`
ForceCRI bool `config:"force_cri_logs"`
Format string `config:"format"`
CRIFlags bool `config:"cri_flags"`
} `config:"docker-json"`
}
Expand Down
Loading