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

Refactor use of system.hostfs to fix cgroup metrics #24334

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Fix panic due to unhandled DeletedFinalStateUnknown in k8s OnDelete {pull}23419[23419]
- Fix error loop with runaway CPU use when the Kafka output encounters some connection errors {pull}23484[23484]
- Allow configuring credential_profile_name and shared_credential_file when using role_arn. {pull}24174[24174]
- Fix issue discovering docker containers and metadata after reconnections {pull}24318[24318]
- Allow cgroup self-monitoring to see alternate `hostfs` paths {pull}24334[24334]

*Auditbeat*

Expand Down
12 changes: 11 additions & 1 deletion libbeat/cmd/instance/beat.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/elastic/beats/v7/libbeat/cloudid"
"github.com/elastic/beats/v7/libbeat/cmd/instance/metrics"
"github.com/elastic/beats/v7/libbeat/common"
"github.com/elastic/beats/v7/libbeat/common/cfgwarn"
"github.com/elastic/beats/v7/libbeat/common/file"
"github.com/elastic/beats/v7/libbeat/common/reload"
"github.com/elastic/beats/v7/libbeat/common/seccomp"
Expand Down Expand Up @@ -1083,13 +1084,22 @@ func initPaths(cfg *common.Config) error {
// the paths field. After we will unpack the complete configuration and keystore reference
// will be correctly replaced.
partialConfig := struct {
Path paths.Path `config:"path"`
Path paths.Path `config:"path"`
Hostfs string `config:"system.hostfs"`
jsoriano marked this conversation as resolved.
Show resolved Hide resolved
}{}

if paths.IsCLISet() {
cfgwarn.Deprecate("8.0.0", "This flag will be removed in the future and replaced by a config value.")
}

if err := cfg.Unpack(&partialConfig); err != nil {
return fmt.Errorf("error extracting default paths: %+v", err)
}

// Read the value for hostfs as `system.hostfs`
// In the config, there is no `path.hostfs`, as we're merely using the path struct to carry the hostfs variable.
partialConfig.Path.Hostfs = partialConfig.Hostfs
Copy link

Choose a reason for hiding this comment

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

Is this always safe? By introducing Hostfs to the Path struct we will allow users to overwrite the path in the configuration file already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe I'm missing something, but is that any different from -E flags?

Copy link

Choose a reason for hiding this comment

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

Paths are configured twice I think. Once via --path.X on startup + --system.hostfs.

Then the config file is read and the path settings from the configuration file are applied again. The -E flags for path settings only become effective during the second phase. That is why the shim bash scripts just use --path.X CLI flags and we do not attempt to overwrite the defaults in the config.

The thing I was wondering: What if path.hostfs is configured, but system.hostfs is not? In that case you will set path.hostfs to an empty string here. Is this on purpose? Why not:

if partialConfig.Path.Hostfs == "" {
  partialConfig.Path.Hostfs = partialConfig.Hostfs
}

If the forced overwrite is on purpose a comment explaining the why would be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, so, technically speaking, there is no path.hostfs. There's only system.hostfs, and we just expand the main Path struct type with hostfs, hence why that's being overwritten. Ideally it would only be path.hostfs, but I'm mostly worried about breaking changes here, since I'm considering this to be primarily a bugfix so we can properly set hostfs in the cgroup self-monitoring.

Copy link

Choose a reason for hiding this comment

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

I see. It is still kinda "obscure" and I'm sure someone (me?, future you?) might change the logic on purpose or by accident. Can you please add a code comment about the why? Having the why in the PR review it will be lost.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, good point.


if err := paths.InitPaths(&partialConfig.Path); err != nil {
return fmt.Errorf("error setting default paths: %+v", err)
}
Expand Down
2 changes: 2 additions & 0 deletions libbeat/cmd/instance/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/elastic/beats/v7/libbeat/metric/system/cpu"
"github.com/elastic/beats/v7/libbeat/metric/system/process"
"github.com/elastic/beats/v7/libbeat/monitoring"
"github.com/elastic/beats/v7/libbeat/paths"
"github.com/elastic/gosigar/cgroup"
)

Expand Down Expand Up @@ -285,6 +286,7 @@ func reportBeatCgroups(_ monitoring.Mode, V monitoring.Visitor) {
}

cgroups, err := cgroup.NewReaderOptions(cgroup.ReaderOptions{
RootfsMountpoint: paths.Paths.Hostfs,
IgnoreRootCgroups: true,
CgroupsHierarchyOverride: os.Getenv(libbeatMonitoringCgroupsHierarchyOverride),
})
Expand Down
1 change: 1 addition & 0 deletions libbeat/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func GenRootCmdWithSettings(beatCreator beat.Creator, settings instance.Settings
rootCmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.data"))
rootCmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.logs"))
rootCmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("path.home"))
rootCmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("system.hostfs"))
rootCmd.PersistentFlags().AddGoFlag(flag.CommandLine.Lookup("strict.perms"))
if f := flag.CommandLine.Lookup("plugin"); f != nil {
rootCmd.PersistentFlags().AddGoFlag(f)
Expand Down
6 changes: 2 additions & 4 deletions libbeat/docs/command-reference.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -704,12 +704,10 @@ the end of the file is reached. By default harvesters are closed after
`close_inactive` is reached.
endif::[]

ifeval::["{beatname_lc}"=="metricbeat"]
*`--system.hostfs MOUNT_POINT`*::

Specifies the mount point of the host's filesystem for use in monitoring a host
from within a container.
endif::[]
Specifies the mount point of the host's filesystem for use in monitoring a host.

fearful-symmetry marked this conversation as resolved.
Show resolved Hide resolved

ifeval::["{beatname_lc}"=="packetbeat"]
*`-t`*::
Expand Down
17 changes: 17 additions & 0 deletions libbeat/docs/shared-path-config.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,20 @@ Example:
------------------------------------------------------------------------------
path.logs: /var/log/beats
------------------------------------------------------------------------------

[float]
==== `system.hostfs`

Specifies the mount point of the host's filesystem for use in monitoring a host.
This can either be set in the config, or with the `--system.hostfs` CLI flag. This is used for cgroup self-monitoring.
ifeval::["{beatname_lc}"=="metricbeat"]
Comment on lines +111 to +115
Copy link
Member

Choose a reason for hiding this comment

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

If this setting is used for other beats too, I think we could add it as path.hostfs for the config file setting, as other beats don't have a system module. But we can leave it for a future change.

This is also used by the system module to read files from `/proc` and `/sys`.
endif::[]


Example:

[source,yaml]
------------------------------------------------------------------------------
path.logs: /var/log/beats
------------------------------------------------------------------------------
Comment on lines +119 to +125
Copy link
Member

Choose a reason for hiding this comment

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

@fearful-symmetry Is this the correct example? It seems to be a duplicate of the logs example one section up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops!

48 changes: 43 additions & 5 deletions libbeat/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
//
// path.config - Configuration files and Elasticsearch template default location
//
// system.hostfs - supplies an alternate filesystem root for containerized environments
Copy link
Member

Choose a reason for hiding this comment

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

Wdyt about calling it path.hostfs now that it is another configurable path, and not only used by the system module?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yah, it might be worth it to have a second one for backwards compatibility?

Copy link
Member

Choose a reason for hiding this comment

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

Yep, we would keep both at least till 8.0.

//
// These settings can be set via the configuration file or via command line flags.
// The CLI flags overwrite the configuration file options.
//
Expand All @@ -38,27 +40,45 @@
package paths

import (
"flag"
"fmt"
"os"
"path/filepath"
)

var (
// TODO: remove this flag in 8.0 since it should be replaced by system.hostfs configuration option (config.HostFS)
// HostFS is an alternate mountpoint for the filesytem root, for when metricbeat is running inside a container.
fearful-symmetry marked this conversation as resolved.
Show resolved Hide resolved
hostFS = flag.String("system.hostfs", "", "Mount point of the host's filesystem for use in monitoring a host from within a container")
)

// Path tracks user-configurable path locations and directories
type Path struct {
Home string
Config string
Data string
Logs string
Hostfs string
}

// FileType is an enumeration type representing the file types.
// Currently existing file types are: Home, Config, Data
type FileType string

const (
Home FileType = "home"
// Home is the "root" directory for the running beats instance
Home FileType = "home"
// Config is the path to the beat config
Config FileType = "config"
Data FileType = "data"
Logs FileType = "logs"
// Data is the path to the beat data directory
Data FileType = "data"
// Logs is the path to the beats logs directory
Logs FileType = "logs"
// Hostfs is an alternate path to the filesystem root,
// used for system metrics that interact with procfs and sysfs.
// Unlike the other values here, this corrisponds to `system.hostfs`
// and not `path.hostfs`.
Hostfs FileType = "hostfs"
)

// Paths is the Path singleton on which the top level functions from this
Expand Down Expand Up @@ -115,15 +135,31 @@ func (paths *Path) initPaths(cfg *Path) error {
paths.Logs = filepath.Join(paths.Home, "logs")
}

if *hostFS != "" {
paths.Hostfs = *hostFS
}

if paths.Hostfs == "" {
paths.Hostfs = "/"
}

return nil
}

// IsCLISet returns true if the CLI system.hostfs value has been set
func IsCLISet() bool {
if *hostFS != "" {
return true
}
return false
}

// Resolve resolves a path to a location in one of the default
// folders. For example, Resolve(Home, "test") returns an absolute
// path for "test" in the home path.
func (paths *Path) Resolve(fileType FileType, path string) string {
// absolute paths are not changed
if filepath.IsAbs(path) {
// absolute paths are not changed for non-hostfs file types, since hostfs is a little odd
if filepath.IsAbs(path) && fileType != Hostfs {
return path
}

Expand All @@ -136,6 +172,8 @@ func (paths *Path) Resolve(fileType FileType, path string) string {
return filepath.Join(paths.Data, path)
case Logs:
return filepath.Join(paths.Logs, path)
case Hostfs:
return filepath.Join(paths.Hostfs, path)
default:
panic(fmt.Sprintf("Unknown file type: %s", fileType))
}
Expand Down
26 changes: 17 additions & 9 deletions metricbeat/module/system/diskio/diskio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,23 @@ import (

"github.com/stretchr/testify/assert"

"github.com/elastic/beats/v7/libbeat/paths"
mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing"
"github.com/elastic/beats/v7/metricbeat/module/system"
)

func setHostfs(pathString string) {
path := paths.Path{
Hostfs: pathString,
}
paths.InitPaths(&path)
}

func TestDataNameFilter(t *testing.T) {
oldFS := system.HostFS
newFS := "_meta/testdata"
system.HostFS = &newFS
oldFS := paths.Paths.Hostfs
setHostfs("_meta/testdata")

defer func() {
system.HostFS = oldFS
setHostfs(oldFS)
}()

conf := map[string]interface{}{
Expand All @@ -51,11 +58,11 @@ func TestDataNameFilter(t *testing.T) {
}

func TestDataEmptyFilter(t *testing.T) {
oldFS := system.HostFS
newFS := "_meta/testdata"
system.HostFS = &newFS
oldFS := paths.Paths.Hostfs
setHostfs("_meta/testdata")

defer func() {
system.HostFS = oldFS
setHostfs(oldFS)
}()

conf := map[string]interface{}{
Expand All @@ -67,6 +74,7 @@ func TestDataEmptyFilter(t *testing.T) {
data, errs := mbtest.ReportingFetchV2Error(f)
assert.Empty(t, errs)
assert.Equal(t, 10, len(data))

}

func TestFetch(t *testing.T) {
Expand Down
9 changes: 2 additions & 7 deletions metricbeat/module/system/entropy/entropy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import (

"github.com/elastic/beats/v7/libbeat/common"
"github.com/elastic/beats/v7/libbeat/common/cfgwarn"
"github.com/elastic/beats/v7/libbeat/paths"
"github.com/elastic/beats/v7/metricbeat/mb"
"github.com/elastic/beats/v7/metricbeat/module/system"
)

// init registers the MetricSet with the central registry as soon as the program
Expand All @@ -55,12 +55,7 @@ type MetricSet struct {
func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
cfgwarn.Beta("The system entropy metricset is beta.")

systemModule, ok := base.Module().(*system.Module)
if !ok {
return nil, errors.New("unexpected module type")
}

totalPath := path.Join(systemModule.HostFS, "/proc/sys/kernel/random")
totalPath := paths.Resolve(paths.Hostfs, "/proc/sys/kernel/random")

return &MetricSet{
BaseMetricSet: base,
Expand Down
22 changes: 17 additions & 5 deletions metricbeat/module/system/entropy/entropy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ import (

"github.com/stretchr/testify/assert"

"github.com/elastic/beats/v7/libbeat/paths"
mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing"
"github.com/elastic/beats/v7/metricbeat/module/system"
)

func TestData(t *testing.T) {
testdata := "./_meta/testdata"
system.HostFS = &testdata
testPath := paths.Path{
Hostfs: "./_meta/testdata",
}

if err := paths.InitPaths(&testPath); err != nil {
t.Errorf("error setting default paths: %+v", err)
t.FailNow()
}
f := mbtest.NewReportingMetricSetV2Error(t, getConfig())
err := mbtest.WriteEventsReporterV2Error(f, t, ".")
if err != nil {
Expand All @@ -39,8 +45,14 @@ func TestData(t *testing.T) {
}

func TestFetch(t *testing.T) {
testdata := "./_meta/testdata"
system.HostFS = &testdata
testPath := paths.Path{
Hostfs: "./_meta/testdata",
}

if err := paths.InitPaths(&testPath); err != nil {
t.Errorf("error setting default paths: %+v", err)
t.FailNow()
}
f := mbtest.NewReportingMetricSetV2Error(t, getConfig())
events, errs := mbtest.ReportingFetchV2Error(f)

Expand Down
5 changes: 2 additions & 3 deletions metricbeat/module/system/filesystem/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ package filesystem
import (
"bufio"
"os"
"path"
"path/filepath"
"strings"
"time"

"runtime"

"github.com/elastic/beats/v7/libbeat/common"
"github.com/elastic/beats/v7/metricbeat/module/system"
"github.com/elastic/beats/v7/libbeat/paths"
sigar "github.com/elastic/gosigar"
)

Expand Down Expand Up @@ -194,7 +193,7 @@ func BuildTypeFilter(ignoreType ...string) Predicate {
func DefaultIgnoredTypes() (types []string) {
// If /proc/filesystems exist, default ignored types are all marked
// as nodev
fsListFile := path.Join(*system.HostFS, "/proc/filesystems")
fsListFile := paths.Resolve(paths.Hostfs, "/proc/filesystems")
if f, err := os.Open(fsListFile); err == nil {
scanner := bufio.NewScanner(f)
for scanner.Scan() {
Expand Down
5 changes: 3 additions & 2 deletions metricbeat/module/system/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/elastic/beats/v7/libbeat/common"
"github.com/elastic/beats/v7/libbeat/logp"
"github.com/elastic/beats/v7/libbeat/metric/system/process"
"github.com/elastic/beats/v7/libbeat/paths"
"github.com/elastic/beats/v7/metricbeat/mb"
"github.com/elastic/beats/v7/metricbeat/mb/parse"
"github.com/elastic/beats/v7/metricbeat/module/system"
Expand Down Expand Up @@ -83,8 +84,8 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) {

if runtime.GOOS == "linux" {
if config.Cgroups == nil || *config.Cgroups {
debugf("process cgroup data collection is enabled, using hostfs='%v'", systemModule.HostFS)
m.cgroup, err = cgroup.NewReader(systemModule.HostFS, true)
debugf("process cgroup data collection is enabled, using hostfs='%v'", paths.Paths.Hostfs)
m.cgroup, err = cgroup.NewReader(paths.Paths.Hostfs, true)
if err != nil {
if err == cgroup.ErrCgroupsMissing {
logp.Warn("cgroup data collection will be disabled: %v", err)
Expand Down
8 changes: 2 additions & 6 deletions metricbeat/module/system/raid/raid.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import (
"github.com/prometheus/procfs"

"github.com/elastic/beats/v7/libbeat/common"
"github.com/elastic/beats/v7/libbeat/paths"
"github.com/elastic/beats/v7/metricbeat/mb"
"github.com/elastic/beats/v7/metricbeat/mb/parse"
"github.com/elastic/beats/v7/metricbeat/module/system"
"github.com/elastic/beats/v7/metricbeat/module/system/raid/blockinfo"
)

Expand All @@ -45,10 +45,6 @@ type MetricSet struct {

// New creates a new instance of the raid metricset.
func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
systemModule, ok := base.Module().(*system.Module)
if !ok {
return nil, errors.New("unexpected module type")
}

// Additional configuration options
config := struct {
Expand All @@ -60,7 +56,7 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
}

if config.MountPoint == "" {
config.MountPoint = systemModule.HostFS
config.MountPoint = paths.Paths.Hostfs
}

mountPoint := filepath.Join(config.MountPoint, procfs.DefaultMountPoint)
Expand Down
Loading