github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/info.go (about)

     1  // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
     2  //go:build go1.19
     3  
     4  package daemon // import "github.com/docker/docker/daemon"
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"runtime"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/containerd/containerd/tracing"
    15  	"github.com/containerd/log"
    16  	"github.com/docker/docker/api"
    17  	"github.com/docker/docker/api/types"
    18  	"github.com/docker/docker/api/types/system"
    19  	"github.com/docker/docker/cli/debug"
    20  	"github.com/docker/docker/daemon/config"
    21  	"github.com/docker/docker/daemon/logger"
    22  	"github.com/docker/docker/dockerversion"
    23  	"github.com/docker/docker/pkg/fileutils"
    24  	"github.com/docker/docker/pkg/meminfo"
    25  	"github.com/docker/docker/pkg/parsers/kernel"
    26  	"github.com/docker/docker/pkg/parsers/operatingsystem"
    27  	"github.com/docker/docker/pkg/platform"
    28  	"github.com/docker/docker/pkg/sysinfo"
    29  	"github.com/docker/docker/registry"
    30  	metrics "github.com/docker/go-metrics"
    31  	"github.com/opencontainers/selinux/go-selinux"
    32  )
    33  
    34  func doWithTrace[T any](ctx context.Context, name string, f func() T) T {
    35  	_, span := tracing.StartSpan(ctx, name)
    36  	defer span.End()
    37  	return f()
    38  }
    39  
    40  // SystemInfo returns information about the host server the daemon is running on.
    41  //
    42  // The only error this should return is due to context cancellation/deadline.
    43  // Anything else should be logged and ignored because this is looking up
    44  // multiple things and is often used for debugging.
    45  // The only case valid early return is when the caller doesn't want the result anymore (ie context cancelled).
    46  func (daemon *Daemon) SystemInfo(ctx context.Context) (*system.Info, error) {
    47  	defer metrics.StartTimer(hostInfoFunctions.WithValues("system_info"))()
    48  
    49  	sysInfo := daemon.RawSysInfo()
    50  	cfg := daemon.config()
    51  
    52  	v := &system.Info{
    53  		ID:                 daemon.id,
    54  		Images:             daemon.imageService.CountImages(ctx),
    55  		IPv4Forwarding:     !sysInfo.IPv4ForwardingDisabled,
    56  		BridgeNfIptables:   !sysInfo.BridgeNFCallIPTablesDisabled,
    57  		BridgeNfIP6tables:  !sysInfo.BridgeNFCallIP6TablesDisabled,
    58  		Name:               hostName(ctx),
    59  		SystemTime:         time.Now().Format(time.RFC3339Nano),
    60  		LoggingDriver:      daemon.defaultLogConfig.Type,
    61  		KernelVersion:      kernelVersion(ctx),
    62  		OperatingSystem:    operatingSystem(ctx),
    63  		OSVersion:          osVersion(ctx),
    64  		IndexServerAddress: registry.IndexServer,
    65  		OSType:             runtime.GOOS,
    66  		Architecture:       platform.Architecture,
    67  		RegistryConfig:     doWithTrace(ctx, "registry.ServiceConfig", daemon.registryService.ServiceConfig),
    68  		NCPU:               doWithTrace(ctx, "sysinfo.NumCPU", sysinfo.NumCPU),
    69  		MemTotal:           memInfo(ctx).MemTotal,
    70  		GenericResources:   daemon.genericResources,
    71  		DockerRootDir:      cfg.Root,
    72  		Labels:             cfg.Labels,
    73  		ExperimentalBuild:  cfg.Experimental,
    74  		ServerVersion:      dockerversion.Version,
    75  		HTTPProxy:          config.MaskCredentials(getConfigOrEnv(cfg.HTTPProxy, "HTTP_PROXY", "http_proxy")),
    76  		HTTPSProxy:         config.MaskCredentials(getConfigOrEnv(cfg.HTTPSProxy, "HTTPS_PROXY", "https_proxy")),
    77  		NoProxy:            getConfigOrEnv(cfg.NoProxy, "NO_PROXY", "no_proxy"),
    78  		LiveRestoreEnabled: cfg.LiveRestoreEnabled,
    79  		Isolation:          daemon.defaultIsolation,
    80  		CDISpecDirs:        promoteNil(cfg.CDISpecDirs),
    81  	}
    82  
    83  	daemon.fillContainerStates(v)
    84  	daemon.fillDebugInfo(ctx, v)
    85  	daemon.fillAPIInfo(v, &cfg.Config)
    86  	// Retrieve platform specific info
    87  	if err := daemon.fillPlatformInfo(ctx, v, sysInfo, cfg); err != nil {
    88  		return nil, err
    89  	}
    90  	daemon.fillDriverInfo(v)
    91  	daemon.fillPluginsInfo(ctx, v, &cfg.Config)
    92  	daemon.fillSecurityOptions(v, sysInfo, &cfg.Config)
    93  	daemon.fillLicense(v)
    94  	daemon.fillDefaultAddressPools(ctx, v, &cfg.Config)
    95  
    96  	return v, nil
    97  }
    98  
    99  // SystemVersion returns version information about the daemon.
   100  //
   101  // The only error this should return is due to context cancellation/deadline.
   102  // Anything else should be logged and ignored because this is looking up
   103  // multiple things and is often used for debugging.
   104  // The only case valid early return is when the caller doesn't want the result anymore (ie context cancelled).
   105  func (daemon *Daemon) SystemVersion(ctx context.Context) (types.Version, error) {
   106  	defer metrics.StartTimer(hostInfoFunctions.WithValues("system_version"))()
   107  
   108  	kernelVersion := kernelVersion(ctx)
   109  	cfg := daemon.config()
   110  
   111  	v := types.Version{
   112  		Components: []types.ComponentVersion{
   113  			{
   114  				Name:    "Engine",
   115  				Version: dockerversion.Version,
   116  				Details: map[string]string{
   117  					"GitCommit":     dockerversion.GitCommit,
   118  					"ApiVersion":    api.DefaultVersion,
   119  					"MinAPIVersion": cfg.MinAPIVersion,
   120  					"GoVersion":     runtime.Version(),
   121  					"Os":            runtime.GOOS,
   122  					"Arch":          runtime.GOARCH,
   123  					"BuildTime":     dockerversion.BuildTime,
   124  					"KernelVersion": kernelVersion,
   125  					"Experimental":  fmt.Sprintf("%t", cfg.Experimental),
   126  				},
   127  			},
   128  		},
   129  
   130  		// Populate deprecated fields for older clients
   131  		Version:       dockerversion.Version,
   132  		GitCommit:     dockerversion.GitCommit,
   133  		APIVersion:    api.DefaultVersion,
   134  		MinAPIVersion: cfg.MinAPIVersion,
   135  		GoVersion:     runtime.Version(),
   136  		Os:            runtime.GOOS,
   137  		Arch:          runtime.GOARCH,
   138  		BuildTime:     dockerversion.BuildTime,
   139  		KernelVersion: kernelVersion,
   140  		Experimental:  cfg.Experimental,
   141  	}
   142  
   143  	v.Platform.Name = dockerversion.PlatformName
   144  
   145  	if err := daemon.fillPlatformVersion(ctx, &v, cfg); err != nil {
   146  		return v, err
   147  	}
   148  	return v, nil
   149  }
   150  
   151  func (daemon *Daemon) fillDriverInfo(v *system.Info) {
   152  	v.Driver = daemon.imageService.StorageDriver()
   153  	v.DriverStatus = daemon.imageService.LayerStoreStatus()
   154  
   155  	const warnMsg = `
   156  WARNING: The %s storage-driver is deprecated, and will be removed in a future release.
   157           Refer to the documentation for more information: https://docs.docker.com/go/storage-driver/`
   158  
   159  	switch v.Driver {
   160  	case "overlay":
   161  		v.Warnings = append(v.Warnings, fmt.Sprintf(warnMsg, v.Driver))
   162  	}
   163  
   164  	fillDriverWarnings(v)
   165  }
   166  
   167  func (daemon *Daemon) fillPluginsInfo(ctx context.Context, v *system.Info, cfg *config.Config) {
   168  	v.Plugins = system.PluginsInfo{
   169  		Volume:  daemon.volumes.GetDriverList(),
   170  		Network: daemon.GetNetworkDriverList(ctx),
   171  
   172  		// The authorization plugins are returned in the order they are
   173  		// used as they constitute a request/response modification chain.
   174  		Authorization: cfg.AuthorizationPlugins,
   175  		Log:           logger.ListDrivers(),
   176  	}
   177  }
   178  
   179  func (daemon *Daemon) fillSecurityOptions(v *system.Info, sysInfo *sysinfo.SysInfo, cfg *config.Config) {
   180  	var securityOptions []string
   181  	if sysInfo.AppArmor {
   182  		securityOptions = append(securityOptions, "name=apparmor")
   183  	}
   184  	if sysInfo.Seccomp && supportsSeccomp {
   185  		if daemon.seccompProfilePath != config.SeccompProfileDefault {
   186  			v.Warnings = append(v.Warnings, "WARNING: daemon is not using the default seccomp profile")
   187  		}
   188  		securityOptions = append(securityOptions, "name=seccomp,profile="+daemon.seccompProfilePath)
   189  	}
   190  	if selinux.GetEnabled() {
   191  		securityOptions = append(securityOptions, "name=selinux")
   192  	}
   193  	if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 {
   194  		securityOptions = append(securityOptions, "name=userns")
   195  	}
   196  	if Rootless(cfg) {
   197  		securityOptions = append(securityOptions, "name=rootless")
   198  	}
   199  	if cgroupNamespacesEnabled(sysInfo, cfg) {
   200  		securityOptions = append(securityOptions, "name=cgroupns")
   201  	}
   202  	if noNewPrivileges(cfg) {
   203  		securityOptions = append(securityOptions, "name=no-new-privileges")
   204  	}
   205  
   206  	v.SecurityOptions = securityOptions
   207  }
   208  
   209  func (daemon *Daemon) fillContainerStates(v *system.Info) {
   210  	cRunning, cPaused, cStopped := stateCtr.get()
   211  	v.Containers = cRunning + cPaused + cStopped
   212  	v.ContainersPaused = cPaused
   213  	v.ContainersRunning = cRunning
   214  	v.ContainersStopped = cStopped
   215  }
   216  
   217  // fillDebugInfo sets the current debugging state of the daemon, and additional
   218  // debugging information, such as the number of Go-routines, and file descriptors.
   219  //
   220  // Note that this currently always collects the information, but the CLI only
   221  // prints it if the daemon has debug enabled. We should consider to either make
   222  // this information optional (cli to request "with debugging information"), or
   223  // only collect it if the daemon has debug enabled. For the CLI code, see
   224  // https://github.com/docker/cli/blob/v20.10.12/cli/command/system/info.go#L239-L244
   225  func (daemon *Daemon) fillDebugInfo(ctx context.Context, v *system.Info) {
   226  	v.Debug = debug.IsEnabled()
   227  	v.NFd = fileutils.GetTotalUsedFds(ctx)
   228  	v.NGoroutines = runtime.NumGoroutine()
   229  	v.NEventsListener = daemon.EventsService.SubscribersCount()
   230  }
   231  
   232  func (daemon *Daemon) fillAPIInfo(v *system.Info, cfg *config.Config) {
   233  	const warn string = `
   234           Access to the remote API is equivalent to root access on the host. Refer
   235           to the 'Docker daemon attack surface' section in the documentation for
   236           more information: https://docs.docker.com/go/attack-surface/`
   237  
   238  	for _, host := range cfg.Hosts {
   239  		// cnf.Hosts is normalized during startup, so should always have a scheme/proto
   240  		proto, addr, _ := strings.Cut(host, "://")
   241  		if proto != "tcp" {
   242  			continue
   243  		}
   244  		if cfg.TLS == nil || !*cfg.TLS {
   245  			v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on http://%s without encryption.%s", addr, warn))
   246  			continue
   247  		}
   248  		if cfg.TLSVerify == nil || !*cfg.TLSVerify {
   249  			v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on https://%s without TLS client verification.%s", addr, warn))
   250  			continue
   251  		}
   252  	}
   253  }
   254  
   255  func (daemon *Daemon) fillDefaultAddressPools(ctx context.Context, v *system.Info, cfg *config.Config) {
   256  	_, span := tracing.StartSpan(ctx, "fillDefaultAddressPools")
   257  	defer span.End()
   258  	for _, pool := range cfg.DefaultAddressPools.Value() {
   259  		v.DefaultAddressPools = append(v.DefaultAddressPools, system.NetworkAddressPool{
   260  			Base: pool.Base,
   261  			Size: pool.Size,
   262  		})
   263  	}
   264  }
   265  
   266  func hostName(ctx context.Context) string {
   267  	ctx, span := tracing.StartSpan(ctx, "hostName")
   268  	defer span.End()
   269  	hostname := ""
   270  	if hn, err := os.Hostname(); err != nil {
   271  		log.G(ctx).Warnf("Could not get hostname: %v", err)
   272  	} else {
   273  		hostname = hn
   274  	}
   275  	return hostname
   276  }
   277  
   278  func kernelVersion(ctx context.Context) string {
   279  	ctx, span := tracing.StartSpan(ctx, "kernelVersion")
   280  	defer span.End()
   281  
   282  	var kernelVersion string
   283  	if kv, err := kernel.GetKernelVersion(); err != nil {
   284  		log.G(ctx).Warnf("Could not get kernel version: %v", err)
   285  	} else {
   286  		kernelVersion = kv.String()
   287  	}
   288  	return kernelVersion
   289  }
   290  
   291  func memInfo(ctx context.Context) *meminfo.Memory {
   292  	ctx, span := tracing.StartSpan(ctx, "memInfo")
   293  	defer span.End()
   294  
   295  	memInfo, err := meminfo.Read()
   296  	if err != nil {
   297  		log.G(ctx).Errorf("Could not read system memory info: %v", err)
   298  		memInfo = &meminfo.Memory{}
   299  	}
   300  	return memInfo
   301  }
   302  
   303  func operatingSystem(ctx context.Context) (operatingSystem string) {
   304  	ctx, span := tracing.StartSpan(ctx, "operatingSystem")
   305  	defer span.End()
   306  
   307  	defer metrics.StartTimer(hostInfoFunctions.WithValues("operating_system"))()
   308  
   309  	if s, err := operatingsystem.GetOperatingSystem(); err != nil {
   310  		log.G(ctx).Warnf("Could not get operating system name: %v", err)
   311  	} else {
   312  		operatingSystem = s
   313  	}
   314  	if inContainer, err := operatingsystem.IsContainerized(); err != nil {
   315  		log.G(ctx).Errorf("Could not determine if daemon is containerized: %v", err)
   316  		operatingSystem += " (error determining if containerized)"
   317  	} else if inContainer {
   318  		operatingSystem += " (containerized)"
   319  	}
   320  
   321  	return operatingSystem
   322  }
   323  
   324  func osVersion(ctx context.Context) (version string) {
   325  	ctx, span := tracing.StartSpan(ctx, "osVersion")
   326  	defer span.End()
   327  
   328  	defer metrics.StartTimer(hostInfoFunctions.WithValues("os_version"))()
   329  
   330  	version, err := operatingsystem.GetOperatingSystemVersion()
   331  	if err != nil {
   332  		log.G(ctx).Warnf("Could not get operating system version: %v", err)
   333  	}
   334  
   335  	return version
   336  }
   337  
   338  func getEnvAny(names ...string) string {
   339  	for _, n := range names {
   340  		if val := os.Getenv(n); val != "" {
   341  			return val
   342  		}
   343  	}
   344  	return ""
   345  }
   346  
   347  func getConfigOrEnv(config string, env ...string) string {
   348  	if config != "" {
   349  		return config
   350  	}
   351  	return getEnvAny(env...)
   352  }
   353  
   354  // promoteNil converts a nil slice to an empty slice.
   355  // A non-nil slice is returned as is.
   356  //
   357  // TODO: make generic again once we are a go module,
   358  // go.dev/issue/64759 is fixed, or we drop support for Go 1.21.
   359  func promoteNil(s []string) []string {
   360  	if s == nil {
   361  		return []string{}
   362  	}
   363  	return s
   364  }