Skip to content

Commit 2f549e6

Browse files
authored
[CONTINT-3542] Send in-<inode> using the cgroup controller when container-id cannot be retrieved
2 parents ed10c5c + 421ae2b commit 2f549e6

File tree

3 files changed

+203
-125
lines changed

3 files changed

+203
-125
lines changed

statsd/container_linux.go

+69-27
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"io"
1010
"os"
11+
"path"
1112
"regexp"
1213
"strings"
1314
"syscall"
@@ -20,8 +21,11 @@ const (
2021
// 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
2122
selfMountInfoPath = "/proc/self/mountinfo"
2223

23-
// mountsPath is the path to the file listing all the mount points
24-
mountsPath = "/proc/mounts"
24+
// defaultCgroupMountPath is the default path to the cgroup mount point.
25+
defaultCgroupMountPath = "/sys/fs/cgroup"
26+
27+
// cgroupV1BaseController is the controller used to identify the container-id for cgroup v1
28+
cgroupV1BaseController = "memory"
2529

2630
uuidSource = "[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}"
2731
containerSource = "[0-9a-f]{64}"
@@ -51,6 +55,9 @@ var (
5155
expContainerID = regexp.MustCompile(fmt.Sprintf(`(%s|%s|%s)(?:.scope)?$`, uuidSource, containerSource, taskSource))
5256

5357
cIDMountInfoRegexp = regexp.MustCompile(cIDRegexpStr)
58+
59+
// initContainerID initializes the container ID.
60+
initContainerID = internalInitContainerID
5461
)
5562

5663
// parseContainerID finds the first container ID reading from r and returns it.
@@ -109,55 +116,90 @@ func readMountinfo(path string) string {
109116
return parseMountinfo(f)
110117
}
111118

112-
// isCgroupV1 checks if Cgroup V1 is used
113-
func isCgroupV1(mountsPath string) bool {
114-
f, err := os.Open(mountsPath)
119+
func isHostCgroupNamespace() bool {
120+
fi, err := os.Stat("/proc/self/ns/cgroup")
115121
if err != nil {
116122
return false
117123
}
118-
defer f.Close()
119124

120-
scn := bufio.NewScanner(f)
125+
inode := fi.Sys().(*syscall.Stat_t).Ino
126+
127+
return inode == hostCgroupNamespaceInode
128+
}
129+
130+
// parseCgroupNodePath parses /proc/self/cgroup and returns a map of controller to its associated cgroup node path.
131+
func parseCgroupNodePath(r io.Reader) map[string]string {
132+
res := make(map[string]string)
133+
scn := bufio.NewScanner(r)
121134
for scn.Scan() {
122135
line := scn.Text()
123-
124-
tokens := strings.Fields(line)
125-
if len(tokens) >= 3 {
126-
fsType := tokens[2]
127-
if fsType == "cgroup" {
128-
return true
129-
}
136+
tokens := strings.Split(line, ":")
137+
if len(tokens) != 3 {
138+
continue
139+
}
140+
if tokens[1] == cgroupV1BaseController || tokens[1] == "" {
141+
res[tokens[1]] = tokens[2]
130142
}
131143
}
132-
133-
return false
144+
return res
134145
}
135146

136-
func isHostCgroupNamespace() bool {
137-
fi, err := os.Stat("/proc/self/ns/cgroup")
147+
// getCgroupInode returns the cgroup controller inode if it exists otherwise an empty string.
148+
// The inode is prefixed by "in-" and is used by the agent to retrieve the container ID.
149+
// For cgroup v1, we use the memory controller.
150+
func getCgroupInode(cgroupMountPath, procSelfCgroupPath string) string {
151+
// Parse /proc/self/cgroup to retrieve the paths to the memory controller (cgroupv1) and the cgroup node (cgroupv2)
152+
f, err := os.Open(procSelfCgroupPath)
138153
if err != nil {
139-
return false
154+
return ""
140155
}
156+
defer f.Close()
157+
cgroupControllersPaths := parseCgroupNodePath(f)
158+
// Retrieve the cgroup inode from /sys/fs/cgroup+controller+cgroupNodePath
159+
for _, controller := range []string{cgroupV1BaseController, ""} {
160+
cgroupNodePath, ok := cgroupControllersPaths[controller]
161+
if !ok {
162+
continue
163+
}
164+
inode := inodeForPath(path.Join(cgroupMountPath, controller, cgroupNodePath))
165+
if inode != "" {
166+
return inode
167+
}
168+
}
169+
return ""
170+
}
141171

142-
inode := fi.Sys().(*syscall.Stat_t).Ino
143-
144-
return inode == hostCgroupNamespaceInode
172+
// inodeForPath returns the inode for the provided path or empty on failure.
173+
func inodeForPath(path string) string {
174+
fi, err := os.Stat(path)
175+
if err != nil {
176+
return ""
177+
}
178+
stats, ok := fi.Sys().(*syscall.Stat_t)
179+
if !ok {
180+
return ""
181+
}
182+
return fmt.Sprintf("in-%d", stats.Ino)
145183
}
146184

147-
// initContainerID initializes the container ID.
185+
// internalInitContainerID initializes the container ID.
148186
// It can either be provided by the user or read from cgroups.
149-
func initContainerID(userProvidedID string, cgroupFallback bool) {
187+
func internalInitContainerID(userProvidedID string, cgroupFallback bool) {
150188
initOnce.Do(func() {
151189
if userProvidedID != "" {
152190
containerID = userProvidedID
153191
return
154192
}
155193

156194
if cgroupFallback {
157-
if isCgroupV1(mountsPath) || isHostCgroupNamespace() {
195+
isHostCgroupNs := isHostCgroupNamespace()
196+
if isHostCgroupNs {
158197
containerID = readContainerID(cgroupPath)
159-
} else {
160-
containerID = readMountinfo(selfMountInfoPath)
198+
return
199+
}
200+
containerID = readMountinfo(selfMountInfoPath)
201+
if containerID != "" {
202+
containerID = getCgroupInode(defaultCgroupMountPath, cgroupPath)
161203
}
162204
}
163205
})

statsd/container_stub.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
package statsd
55

6-
func initContainerID(userProvidedID string, cgroupFallback bool) {
6+
var initContainerID = func(userProvidedID string, cgroupFallback bool) {
77
initOnce.Do(func() {
88
if userProvidedID != "" {
99
containerID = userProvidedID

0 commit comments

Comments
 (0)