github.com/waldiirawan/apm-agent-go/v2@v2.2.2/internal/apmhostutil/container_linux.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 //go:build linux 19 // +build linux 20 21 package apmhostutil 22 23 import ( 24 "bufio" 25 "errors" 26 "io" 27 "os" 28 "regexp" 29 "strings" 30 "sync" 31 32 "github.com/waldiirawan/apm-agent-go/v2/model" 33 ) 34 35 var ( 36 cgroupContainerInfoOnce sync.Once 37 cgroupContainerInfoError error 38 kubernetes *model.Kubernetes 39 container *model.Container 40 41 kubepodsRegexp = regexp.MustCompile( 42 "" + 43 `(?:^/kubepods[\S]*/pod([^/]+)$)|` + 44 `(?:kubepods[^/]*-pod([^/]+)\.slice)`, 45 ) 46 47 containerIDRegexp = regexp.MustCompile( 48 "" + 49 "^[[:xdigit:]]{64}$|" + 50 "^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,}$|" + 51 "^[[:xdigit:]]{32}-[[:digit:]]{10}$", 52 ) 53 ) 54 55 func containerInfo() (*model.Container, error) { 56 container, _, err := cgroupContainerInfo() 57 return container, err 58 } 59 60 func kubernetesInfo() (*model.Kubernetes, error) { 61 _, kubernetes, err := cgroupContainerInfo() 62 if err == nil && kubernetes == nil { 63 return nil, errors.New("could not determine kubernetes info") 64 } 65 return kubernetes, err 66 } 67 68 func cgroupContainerInfo() (*model.Container, *model.Kubernetes, error) { 69 cgroupContainerInfoOnce.Do(func() { 70 cgroupContainerInfoError = func() error { 71 f, err := os.Open("/proc/self/cgroup") 72 if err != nil { 73 return err 74 } 75 defer f.Close() 76 77 c, k, err := readCgroupContainerInfo(f) 78 if err != nil { 79 return err 80 } 81 if c == nil { 82 return errors.New("could not determine container info") 83 } 84 container = c 85 kubernetes = k 86 return nil 87 }() 88 }) 89 return container, kubernetes, cgroupContainerInfoError 90 } 91 92 func readCgroupContainerInfo(r io.Reader) (*model.Container, *model.Kubernetes, error) { 93 var container *model.Container 94 var kubernetes *model.Kubernetes 95 s := bufio.NewScanner(r) 96 for s.Scan() { 97 // split the line according to the format "hierarchy-ID:controller-list:cgroup-path" 98 fields := strings.SplitN(s.Text(), ":", 3) 99 if len(fields) != 3 { 100 continue 101 } 102 103 // extract cgroup-path 104 cgroupPath := fields[2] 105 106 // split based on the last occurrence of the colon character, if such exists, in order 107 // to support paths of containers created by containerd-cri, where the path part takes 108 // the form: <dirname>:cri-containerd:<container-ID> 109 idx := strings.LastIndex(cgroupPath, ":") 110 if idx == -1 { 111 // if colon char is not found within the path, the split is done based on the 112 // last occurrence of the slash character 113 if idx = strings.LastIndex(cgroupPath, "/"); idx == -1 { 114 continue 115 } 116 } 117 118 dirname, basename := cgroupPath[:idx], cgroupPath[idx+1:] 119 120 // If the basename ends with ".scope", check for a hyphen and remove everything up to 121 // and including that. This allows us to match .../docker-<container-id>.scope as well 122 // as .../<container-id>. 123 if strings.HasSuffix(basename, ".scope") { 124 basename = strings.TrimSuffix(basename, ".scope") 125 126 if hyphen := strings.Index(basename, "-"); hyphen != -1 { 127 basename = basename[hyphen+1:] 128 } 129 } 130 if match := kubepodsRegexp.FindStringSubmatch(dirname); match != nil { 131 // By default, Kubernetes will set the hostname of 132 // the pod containers to the pod name. Users that 133 // override the name should use the Downard API to 134 // override the pod name. 135 hostname, _ := os.Hostname() 136 uid := match[1] 137 if uid == "" { 138 // Systemd cgroup driver is being used, 139 // so we need to unescape '_' back to '-'. 140 uid = strings.Replace(match[2], "_", "-", -1) 141 } 142 kubernetes = &model.Kubernetes{ 143 Pod: &model.KubernetesPod{ 144 Name: hostname, 145 UID: uid, 146 }, 147 } 148 // We don't check the contents of the last path segment 149 // when we've matched "^/kubepods"; we assume that it is 150 // a valid container ID. 151 container = &model.Container{ID: basename} 152 } else if containerIDRegexp.MatchString(basename) { 153 container = &model.Container{ID: basename} 154 } 155 } 156 if err := s.Err(); err != nil { 157 return nil, nil, err 158 } 159 return container, kubernetes, nil 160 }