Skip to content

Commit

Permalink
send in-inode when container-id cannot be retrieved
Browse files Browse the repository at this point in the history
  • Loading branch information
AliDatadog committed Nov 28, 2023
1 parent 54ec306 commit 17adae9
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 40 deletions.
5 changes: 3 additions & 2 deletions statsd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ var (
)

// getContainerID returns the container ID configured at the client creation
// It can either be auto-discovered with origin detection or provided by the user.
// User-defined container ID is prioritized.
// It can be provided by the user or read from cgroups. If the container ID
// is not available, it returns the cgroup inode prefixed by "in-" if available.
// If the cgroup inode is not available, it returns an empty string.
func getContainerID() string {
return containerID
}
79 changes: 59 additions & 20 deletions statsd/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
"path"
"regexp"
"strings"
"syscall"
Expand All @@ -17,6 +18,9 @@ const (
// cgroupPath is the path to the cgroup file where we can find the container id if one exists.
cgroupPath = "/proc/self/cgroup"

// cgroupRootPath is the default path to the cgroup root path
cgroupRootPath = "/sys/fs/cgroup"

// selfMountinfo is the path to the mountinfo path where we can find the container id in case cgroup namespace is preventing the use of /proc/self/cgroup
selfMountInfoPath = "/proc/self/mountinfo"

Expand Down Expand Up @@ -48,29 +52,62 @@ var (
)

// parseContainerID finds the first container ID reading from r and returns it.
func parseContainerID(r io.Reader) string {
scn := bufio.NewScanner(r)
for scn.Scan() {
path := expLine.FindStringSubmatch(scn.Text())
if len(path) != 2 {
// invalid entry, continue
continue
}
if parts := expContainerID.FindStringSubmatch(path[1]); len(parts) == 2 {
return parts[1]
}
func parseContainerID(line string) string {
path := expLine.FindStringSubmatch(line)
if len(path) != 2 {
// invalid entry, continue
return ""
}
if parts := expContainerID.FindStringSubmatch(path[1]); len(parts) == 2 {
return parts[1]
}
return ""
}

// readContainerID attempts to return the container ID from the provided file path or empty on failure.
func readContainerID(fpath string) string {
// parseCgroupNodePath parses the cgroup root path from a line formatted as
// 0::<path> where <path> is the cgroup node prefixed by /sys/fs/cgroup
func parseCgroupNodePath(line string) string {
if strings.HasPrefix(line, "0::") {
// return root cgroup
return line[3:]
}
return ""
}

// readContainerIDOrCgroupInode attempts to return the container ID from the provided file path
// or the cgroup inode if the container ID is not available. Otherwise, it returns an empty string.
func readContainerIDOrCgroupInode(cgroupPrefix, fpath string) string {
f, err := os.Open(fpath)
if err != nil {
return ""
}
defer f.Close()
return parseContainerID(f)

cgroupNodePath := ""

scn := bufio.NewScanner(f)
for scn.Scan() {
line := scn.Text()
// Return the container id if found
if containerID := parseContainerID(line); containerID != "" {
return containerID
}
cgroupNodePath = parseCgroupNodePath(line)
}

// Assumes the file is not a symlink
fi, err := os.Stat(path.Clean(cgroupPrefix + cgroupNodePath))
if err != nil {
return ""
}
inode := fi.Sys().(*syscall.Stat_t).Ino
// Inode 0 indicates that there is no inode.
// Inode 1 indicates a bad block
// Inode 2 indicates starting of fs nodes
if inode > 2 {
return fmt.Sprintf("in-%d", inode)
}
return ""
}

// Parsing /proc/self/mountinfo is not always reliable in Kubernetes+containerd (at least)
Expand Down Expand Up @@ -138,21 +175,23 @@ func isHostCgroupNamespace() bool {
return inode == hostCgroupNamespaceInode
}

// initContainerID initializes the container ID.
// It can either be provided by the user or read from cgroups.
func initContainerID(userProvidedID string, cgroupFallback bool) {
// initContainerIDOrCgroupInode initializes the container ID or the cgroup inode.
// The container ID can either be provided by the user or read from cgroups.
// The cgroup inode can only be auto discovered.
func initContainerIDOrCgroupInode(userProvidedID string, cgroupFallback bool) {
initOnce.Do(func() {
if userProvidedID != "" {
containerID = userProvidedID
return
}

if cgroupFallback {
if isCgroupV1(mountsPath) || isHostCgroupNamespace() {
containerID = readContainerID(cgroupPath)
} else {
if !(isCgroupV1(mountsPath) || isHostCgroupNamespace()) {
containerID = readMountinfo(selfMountInfoPath)
}
if containerID == "" {
containerID = readContainerIDOrCgroupInode(cgroupRootPath, cgroupPath)
}
}
})
}
2 changes: 1 addition & 1 deletion statsd/container_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

package statsd

func initContainerID(userProvidedID string, cgroupFallback bool) {
func initContainerIDOrCgroupInode(userProvidedID string, cgroupFallback bool) {
initOnce.Do(func() {
if userProvidedID != "" {
containerID = userProvidedID
Expand Down
80 changes: 64 additions & 16 deletions statsd/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,88 @@ import (
"io"
"io/ioutil"
"os"
"path"
"strings"
"syscall"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseContainerID(t *testing.T) {
for input, expectedResult := range map[string]string{
`other_line
10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
9:cpuset:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
8:pids:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
7:freezer:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
6:cpu,cpuacct:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
5:perf_event:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
4:blkio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
3:devices:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa
2:net_cls,net_prio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa`: "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"9:cpuset:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"8:pids:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"7:freezer:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"6:cpu,cpuacct:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"5:perf_event:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"4:blkio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"3:devices:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"2:net_cls,net_prio:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa": "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa",
"10:hugetlb:/kubepods": "",
"11:hugetlb:/ecs/55091c13-b8cf-4801-b527-f4601742204d/432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da": "432624d2150b349fe35ba397284dea788c2bf66b885d14dfc1569b01890ca7da",
"1:name=systemd:/docker/34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376": "34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376",
"1:name=systemd:/uuid/34dc0b5e-626f-2c5c-4c51-70e34b10e765": "34dc0b5e-626f-2c5c-4c51-70e34b10e765",
"1:name=systemd:/ecs/34dc0b5e626f2c5c4c5170e34b10e765-1234567890": "34dc0b5e626f2c5c4c5170e34b10e765-1234567890",
"1:name=systemd:/docker/34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376.scope": "34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376",
`1:name=systemd:/nope
2:pids:/docker/34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376
3:cpu:/invalid`: "34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376",
"2:pids:/docker/34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376": "34dc0b5e626f2c5c4c5170e34b10e7654ce36f0fcd532739f4445baabea03376",
} {
id := parseContainerID(strings.NewReader(input))
id := parseContainerID(input)
assert.Equal(t, expectedResult, id)
}
}

// In order to test with the actual /sys/fs/cgroup file, an integration test running in a container is necessary.
// When ran on host, /sys/fs/cgroup's inode is 2.
func TestDefaultCase(t *testing.T) {
// Create a dummy cgroup node
dummyCgroupNode, err := ioutil.TempDir(os.TempDir(), "sysfscgroup-") // ex: 0::/tmp/sysfscgroup-4222027333/
assert.NoError(t, err)
defer os.Remove(dummyCgroupNode)

// Create the file corresponding to /proc/self/cgroup redirecting to the cgroup node
tmpFile, err := ioutil.TempFile(os.TempDir(), "procselfcgroup-")
assert.NoError(t, err)
defer os.Remove(tmpFile.Name())

// Write the path to the cgroup node in the tmp file
procFileContent := `other_line
0::` + dummyCgroupNode
_, err = io.WriteString(tmpFile, procFileContent)
assert.NoError(t, err)
err = tmpFile.Close()
assert.NoError(t, err)

// Verify that readContainerIDOrCgroupInode returns the inode of dummyCgroupNode
inode := readContainerIDOrCgroupInode("", tmpFile.Name())

fi, err := os.Stat(path.Clean(dummyCgroupNode))
assert.NoError(t, err)
expectedInode := fi.Sys().(*syscall.Stat_t).Ino
assert.Equal(t, fmt.Sprintf("in-%d", expectedInode), inode)
}

func TestContainerIDIsReturned(t *testing.T) {
// Create the file corresponding to /proc/self/cgroup redirecting to the cgroup node
tmpFile, err := ioutil.TempFile(os.TempDir(), "fake-cgroup-")
assert.NoError(t, err)
defer os.Remove(tmpFile.Name())

// Write the path to the cgroup node in the tmp file
procFileContent := `other_line
0::/
10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa`
_, err = io.WriteString(tmpFile, procFileContent)
assert.NoError(t, err)
err = tmpFile.Close()
assert.NoError(t, err)

// Verify that readContainerIDOrCgroupInode returns the cid
cid := readContainerIDOrCgroupInode("", tmpFile.Name())
assert.Equal(t, "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa", cid)
}

func TestReadContainerID(t *testing.T) {
cid := "8c046cb0b72cd4c99f51b5591cd5b095967f58ee003710a45280c28ee1a9c7fa"
cgroupContents := "10:hugetlb:/kubepods/burstable/podfd52ef25-a87d-11e9-9423-0800271a638e/" + cid
Expand All @@ -57,7 +105,7 @@ func TestReadContainerID(t *testing.T) {
err = tmpFile.Close()
assert.NoError(t, err)

actualCID := readContainerID(tmpFile.Name())
actualCID := readContainerIDOrCgroupInode("", tmpFile.Name())
assert.Equal(t, cid, actualCID)
}

Expand Down
2 changes: 1 addition & 1 deletion statsd/statsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ func newWithWriter(w io.WriteCloser, o *Options, writerName string) (*Client, er
}

if !hasEntityID {
initContainerID(o.containerID, isOriginDetectionEnabled(o, hasEntityID))
initContainerIDOrCgroupInode(o.containerID, isOriginDetectionEnabled(o, hasEntityID))
}

if o.maxBytesPerPayload == 0 {
Expand Down
8 changes: 8 additions & 0 deletions statsd/test_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type testServer struct {
tags string
namespace string
containerID string
inode string

aggregation bool
extendedAggregation bool
Expand Down Expand Up @@ -328,6 +329,13 @@ func (ts *testServer) getContainerID() string {
return "|c:" + ts.containerID
}

func (ts *testServer) getCgroupInode() string {
if ts.inode == "" {
return ""
}
return "|e:" + ts.inode
}

func (ts *testServer) getFinalTelemetryTags() string {
base := "|#"
if ts.tags != "" {
Expand Down

0 comments on commit 17adae9

Please sign in to comment.