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  }