github.com/moby/docker@v26.1.3+incompatible/daemon/info_unix.go (about)

     1  //go:build !windows
     2  
     3  package daemon // import "github.com/docker/docker/daemon"
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
    15  	"github.com/containerd/log"
    16  	"github.com/docker/docker/api/types"
    17  	containertypes "github.com/docker/docker/api/types/container"
    18  	"github.com/docker/docker/api/types/system"
    19  	"github.com/docker/docker/daemon/config"
    20  	"github.com/docker/docker/errdefs"
    21  	"github.com/docker/docker/pkg/rootless"
    22  	"github.com/docker/docker/pkg/sysinfo"
    23  	"github.com/pkg/errors"
    24  	rkclient "github.com/rootless-containers/rootlesskit/v2/pkg/api/client"
    25  )
    26  
    27  // fillPlatformInfo fills the platform related info.
    28  func (daemon *Daemon) fillPlatformInfo(ctx context.Context, v *system.Info, sysInfo *sysinfo.SysInfo, cfg *configStore) error {
    29  	v.CgroupDriver = cgroupDriver(&cfg.Config)
    30  	v.CgroupVersion = "1"
    31  	if sysInfo.CgroupUnified {
    32  		v.CgroupVersion = "2"
    33  	}
    34  
    35  	if v.CgroupDriver != cgroupNoneDriver {
    36  		v.MemoryLimit = sysInfo.MemoryLimit
    37  		v.SwapLimit = sysInfo.SwapLimit
    38  		v.KernelMemory = sysInfo.KernelMemory
    39  		v.KernelMemoryTCP = sysInfo.KernelMemoryTCP
    40  		v.OomKillDisable = sysInfo.OomKillDisable
    41  		v.CPUCfsPeriod = sysInfo.CPUCfs
    42  		v.CPUCfsQuota = sysInfo.CPUCfs
    43  		v.CPUShares = sysInfo.CPUShares
    44  		v.CPUSet = sysInfo.Cpuset
    45  		v.PidsLimit = sysInfo.PidsLimit
    46  	}
    47  	v.Runtimes = make(map[string]system.RuntimeWithStatus)
    48  	for n, p := range stockRuntimes() {
    49  		v.Runtimes[n] = system.RuntimeWithStatus{
    50  			Runtime: system.Runtime{
    51  				Path: p,
    52  			},
    53  			Status: daemon.runtimeStatus(ctx, cfg, n),
    54  		}
    55  	}
    56  	for n, r := range cfg.Config.Runtimes {
    57  		v.Runtimes[n] = system.RuntimeWithStatus{
    58  			Runtime: system.Runtime{
    59  				Path: r.Path,
    60  				Args: append([]string(nil), r.Args...),
    61  			},
    62  			Status: daemon.runtimeStatus(ctx, cfg, n),
    63  		}
    64  	}
    65  	v.DefaultRuntime = cfg.Runtimes.Default
    66  	v.RuncCommit.ID = "N/A"
    67  	v.ContainerdCommit.ID = "N/A"
    68  	v.InitCommit.ID = "N/A"
    69  
    70  	if err := populateRuncCommit(&v.RuncCommit, cfg); err != nil {
    71  		log.G(ctx).WithError(err).Warn("Failed to retrieve default runtime version")
    72  	}
    73  
    74  	if err := daemon.populateContainerdCommit(ctx, &v.ContainerdCommit); err != nil {
    75  		return err
    76  	}
    77  
    78  	if err := daemon.populateInitCommit(ctx, v, cfg); err != nil {
    79  		return err
    80  	}
    81  
    82  	// Set expected and actual commits to the same value to prevent the client
    83  	// showing that the version does not match the "expected" version/commit.
    84  
    85  	if v.CgroupDriver == cgroupNoneDriver {
    86  		if v.CgroupVersion == "2" {
    87  			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. Systemd is required to enable cgroups in rootless-mode.")
    88  		} else {
    89  			v.Warnings = append(v.Warnings, "WARNING: Running in rootless-mode without cgroups. To enable cgroups in rootless-mode, you need to boot the system in cgroup v2 mode.")
    90  		}
    91  	} else {
    92  		if !v.MemoryLimit {
    93  			v.Warnings = append(v.Warnings, "WARNING: No memory limit support")
    94  		}
    95  		if !v.SwapLimit {
    96  			v.Warnings = append(v.Warnings, "WARNING: No swap limit support")
    97  		}
    98  		if !v.KernelMemoryTCP && v.CgroupVersion == "1" {
    99  			// kernel memory is not available for cgroup v2.
   100  			// Warning is not printed on cgroup v2, because there is no action user can take.
   101  			v.Warnings = append(v.Warnings, "WARNING: No kernel memory TCP limit support")
   102  		}
   103  		if !v.OomKillDisable && v.CgroupVersion == "1" {
   104  			// oom kill disable is not available for cgroup v2.
   105  			// Warning is not printed on cgroup v2, because there is no action user can take.
   106  			v.Warnings = append(v.Warnings, "WARNING: No oom kill disable support")
   107  		}
   108  		if !v.CPUCfsQuota {
   109  			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs quota support")
   110  		}
   111  		if !v.CPUCfsPeriod {
   112  			v.Warnings = append(v.Warnings, "WARNING: No cpu cfs period support")
   113  		}
   114  		if !v.CPUShares {
   115  			v.Warnings = append(v.Warnings, "WARNING: No cpu shares support")
   116  		}
   117  		if !v.CPUSet {
   118  			v.Warnings = append(v.Warnings, "WARNING: No cpuset support")
   119  		}
   120  		// TODO add fields for these options in types.Info
   121  		if !sysInfo.BlkioWeight && v.CgroupVersion == "2" {
   122  			// blkio weight is not available on cgroup v1 since kernel 5.0.
   123  			// Warning is not printed on cgroup v1, because there is no action user can take.
   124  			// On cgroup v2, blkio weight is implemented using io.weight
   125  			v.Warnings = append(v.Warnings, "WARNING: No io.weight support")
   126  		}
   127  		if !sysInfo.BlkioWeightDevice && v.CgroupVersion == "2" {
   128  			v.Warnings = append(v.Warnings, "WARNING: No io.weight (per device) support")
   129  		}
   130  		if !sysInfo.BlkioReadBpsDevice {
   131  			if v.CgroupVersion == "2" {
   132  				v.Warnings = append(v.Warnings, "WARNING: No io.max (rbps) support")
   133  			} else {
   134  				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_bps_device support")
   135  			}
   136  		}
   137  		if !sysInfo.BlkioWriteBpsDevice {
   138  			if v.CgroupVersion == "2" {
   139  				v.Warnings = append(v.Warnings, "WARNING: No io.max (wbps) support")
   140  			} else {
   141  				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_bps_device support")
   142  			}
   143  		}
   144  		if !sysInfo.BlkioReadIOpsDevice {
   145  			if v.CgroupVersion == "2" {
   146  				v.Warnings = append(v.Warnings, "WARNING: No io.max (riops) support")
   147  			} else {
   148  				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.read_iops_device support")
   149  			}
   150  		}
   151  		if !sysInfo.BlkioWriteIOpsDevice {
   152  			if v.CgroupVersion == "2" {
   153  				v.Warnings = append(v.Warnings, "WARNING: No io.max (wiops) support")
   154  			} else {
   155  				v.Warnings = append(v.Warnings, "WARNING: No blkio throttle.write_iops_device support")
   156  			}
   157  		}
   158  	}
   159  	if !v.IPv4Forwarding {
   160  		v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled")
   161  	}
   162  	if !v.BridgeNfIptables {
   163  		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-iptables is disabled")
   164  	}
   165  	if !v.BridgeNfIP6tables {
   166  		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-ip6tables is disabled")
   167  	}
   168  	return nil
   169  }
   170  
   171  func (daemon *Daemon) fillPlatformVersion(ctx context.Context, v *types.Version, cfg *configStore) error {
   172  	if err := daemon.populateContainerdVersion(ctx, v); err != nil {
   173  		return err
   174  	}
   175  
   176  	if err := populateRuncVersion(cfg, v); err != nil {
   177  		log.G(ctx).WithError(err).Warn("Failed to retrieve default runtime version")
   178  	}
   179  
   180  	if err := populateInitVersion(ctx, cfg, v); err != nil {
   181  		return err
   182  	}
   183  
   184  	if err := daemon.fillRootlessVersion(ctx, v); err != nil {
   185  		if errdefs.IsContext(err) {
   186  			return err
   187  		}
   188  		log.G(ctx).WithError(err).Warn("Failed to fill rootless version")
   189  	}
   190  	return nil
   191  }
   192  
   193  func populateRuncCommit(v *system.Commit, cfg *configStore) error {
   194  	_, _, commit, err := parseDefaultRuntimeVersion(&cfg.Runtimes)
   195  	if err != nil {
   196  		return err
   197  	}
   198  	v.ID = commit
   199  	v.Expected = commit
   200  	return nil
   201  }
   202  
   203  func (daemon *Daemon) populateInitCommit(ctx context.Context, v *system.Info, cfg *configStore) error {
   204  	v.InitBinary = cfg.GetInitPath()
   205  	initBinary, err := cfg.LookupInitPath()
   206  	if err != nil {
   207  		log.G(ctx).WithError(err).Warnf("Failed to find docker-init")
   208  		return nil
   209  	}
   210  
   211  	rv, err := exec.CommandContext(ctx, initBinary, "--version").Output()
   212  	if err != nil {
   213  		if errdefs.IsContext(err) {
   214  			return err
   215  		}
   216  		log.G(ctx).WithError(err).Warnf("Failed to retrieve %s version", initBinary)
   217  		return nil
   218  	}
   219  
   220  	_, commit, err := parseInitVersion(string(rv))
   221  	if err != nil {
   222  		log.G(ctx).WithError(err).Warnf("failed to parse %s version", initBinary)
   223  		return nil
   224  	}
   225  	v.InitCommit.ID = commit
   226  	v.InitCommit.Expected = v.InitCommit.ID
   227  	return nil
   228  }
   229  
   230  func (daemon *Daemon) fillRootlessVersion(ctx context.Context, v *types.Version) error {
   231  	if !rootless.RunningWithRootlessKit() {
   232  		return nil
   233  	}
   234  	rlc, err := getRootlessKitClient()
   235  	if err != nil {
   236  		return errors.Wrap(err, "failed to create RootlessKit client")
   237  	}
   238  	rlInfo, err := rlc.Info(ctx)
   239  	if err != nil {
   240  		return errors.Wrap(err, "failed to retrieve RootlessKit version")
   241  	}
   242  	rlV := types.ComponentVersion{
   243  		Name:    "rootlesskit",
   244  		Version: rlInfo.Version,
   245  		Details: map[string]string{
   246  			"ApiVersion": rlInfo.APIVersion,
   247  			"StateDir":   rlInfo.StateDir,
   248  		},
   249  	}
   250  	if netDriver := rlInfo.NetworkDriver; netDriver != nil {
   251  		// netDriver is nil for the "host" network driver
   252  		// (not used for Rootless Docker)
   253  		rlV.Details["NetworkDriver"] = netDriver.Driver
   254  	}
   255  	if portDriver := rlInfo.PortDriver; portDriver != nil {
   256  		// portDriver is nil for the "implicit" port driver
   257  		// (used with "pasta" network driver)
   258  		//
   259  		// Because the ports are not managed via RootlessKit API in this case.
   260  		rlV.Details["PortDriver"] = portDriver.Driver
   261  	}
   262  	v.Components = append(v.Components, rlV)
   263  
   264  	switch rlInfo.NetworkDriver.Driver {
   265  	case "slirp4netns":
   266  		err = func() error {
   267  			rv, err := exec.CommandContext(ctx, "slirp4netns", "--version").Output()
   268  			if err != nil {
   269  				if errdefs.IsContext(err) {
   270  					return err
   271  				}
   272  				log.G(ctx).WithError(err).Warn("Failed to retrieve slirp4netns version")
   273  				return nil
   274  			}
   275  
   276  			_, ver, commit, err := parseRuntimeVersion(string(rv))
   277  			if err != nil {
   278  				log.G(ctx).WithError(err).Warn("Failed to parse slirp4netns version")
   279  				return nil
   280  			}
   281  			v.Components = append(v.Components, types.ComponentVersion{
   282  				Name:    "slirp4netns",
   283  				Version: ver,
   284  				Details: map[string]string{
   285  					"GitCommit": commit,
   286  				},
   287  			})
   288  			return nil
   289  		}()
   290  		if err != nil {
   291  			return err
   292  		}
   293  	case "vpnkit":
   294  		err = func() error {
   295  			out, err := exec.CommandContext(ctx, "vpnkit", "--version").Output()
   296  			if err != nil {
   297  				if errdefs.IsContext(err) {
   298  					return err
   299  				}
   300  				log.G(ctx).WithError(err).Warn("Failed to retrieve vpnkit version")
   301  				return nil
   302  			}
   303  			v.Components = append(v.Components, types.ComponentVersion{
   304  				Name:    "vpnkit",
   305  				Version: strings.TrimSpace(strings.TrimSpace(string(out))),
   306  			})
   307  			return nil
   308  		}()
   309  		if err != nil {
   310  			return err
   311  		}
   312  	}
   313  	return nil
   314  }
   315  
   316  // getRootlessKitClient returns RootlessKit client
   317  func getRootlessKitClient() (rkclient.Client, error) {
   318  	stateDir := os.Getenv("ROOTLESSKIT_STATE_DIR")
   319  	if stateDir == "" {
   320  		return nil, errors.New("environment variable `ROOTLESSKIT_STATE_DIR` is not set")
   321  	}
   322  	apiSock := filepath.Join(stateDir, "api.sock")
   323  	return rkclient.New(apiSock)
   324  }
   325  
   326  func fillDriverWarnings(v *system.Info) {
   327  	for _, pair := range v.DriverStatus {
   328  		if pair[0] == "Extended file attributes" && pair[1] == "best-effort" {
   329  			msg := fmt.Sprintf("WARNING: %s: extended file attributes from container images "+
   330  				"will be silently discarded if the backing filesystem does not support them.\n"+
   331  				"         CONTAINERS MAY MALFUNCTION IF EXTENDED ATTRIBUTES ARE MISSING.\n"+
   332  				"         This is an UNSUPPORTABLE configuration for which no bug reports will be accepted.\n", v.Driver)
   333  
   334  			v.Warnings = append(v.Warnings, msg)
   335  			continue
   336  		}
   337  	}
   338  }
   339  
   340  // parseInitVersion parses a Tini version string, and extracts the "version"
   341  // and "git commit" from the output.
   342  //
   343  // Output example from `docker-init --version`:
   344  //
   345  //	tini version 0.18.0 - git.fec3683
   346  func parseInitVersion(v string) (version string, commit string, err error) {
   347  	parts := strings.Split(v, " - ")
   348  
   349  	if len(parts) >= 2 {
   350  		gitParts := strings.Split(strings.TrimSpace(parts[1]), ".")
   351  		if len(gitParts) == 2 && gitParts[0] == "git" {
   352  			commit = gitParts[1]
   353  		}
   354  	}
   355  	parts[0] = strings.TrimSpace(parts[0])
   356  	if strings.HasPrefix(parts[0], "tini version ") {
   357  		version = strings.TrimPrefix(parts[0], "tini version ")
   358  	}
   359  	if version == "" && commit == "" {
   360  		err = errors.Errorf("unknown output format: %s", v)
   361  	}
   362  	return version, commit, err
   363  }
   364  
   365  // parseRuntimeVersion parses the output of `[runtime] --version` and extracts the
   366  // "name", "version" and "git commit" from the output.
   367  //
   368  // Output example from `runc --version`:
   369  //
   370  //	runc version 1.0.0-rc5+dev
   371  //	commit: 69663f0bd4b60df09991c08812a60108003fa340
   372  //	spec: 1.0.0
   373  func parseRuntimeVersion(v string) (runtime, version, commit string, err error) {
   374  	lines := strings.Split(strings.TrimSpace(v), "\n")
   375  	for _, line := range lines {
   376  		if strings.Contains(line, "version") {
   377  			s := strings.Split(line, "version")
   378  			runtime = strings.TrimSpace(s[0])
   379  			version = strings.TrimSpace(s[len(s)-1])
   380  			continue
   381  		}
   382  		if strings.HasPrefix(line, "commit:") {
   383  			commit = strings.TrimSpace(strings.TrimPrefix(line, "commit:"))
   384  			continue
   385  		}
   386  	}
   387  	if version == "" && commit == "" {
   388  		err = errors.Errorf("unknown output format: %s", v)
   389  	}
   390  	return runtime, version, commit, err
   391  }
   392  
   393  func parseDefaultRuntimeVersion(rts *runtimes) (runtime, version, commit string, err error) {
   394  	shim, opts, err := rts.Get(rts.Default)
   395  	if err != nil {
   396  		return "", "", "", err
   397  	}
   398  	shimopts, ok := opts.(*v2runcoptions.Options)
   399  	if !ok {
   400  		return "", "", "", fmt.Errorf("%s: retrieving version not supported", shim)
   401  	}
   402  	rt := shimopts.BinaryName
   403  	if rt == "" {
   404  		rt = defaultRuntimeName
   405  	}
   406  	rv, err := exec.Command(rt, "--version").Output()
   407  	if err != nil {
   408  		return "", "", "", fmt.Errorf("failed to retrieve %s version: %w", rt, err)
   409  	}
   410  	runtime, version, commit, err = parseRuntimeVersion(string(rv))
   411  	if err != nil {
   412  		return "", "", "", fmt.Errorf("failed to parse %s version: %w", rt, err)
   413  	}
   414  	return runtime, version, commit, err
   415  }
   416  
   417  func cgroupNamespacesEnabled(sysInfo *sysinfo.SysInfo, cfg *config.Config) bool {
   418  	return sysInfo.CgroupNamespaces && containertypes.CgroupnsMode(cfg.CgroupNamespaceMode).IsPrivate()
   419  }
   420  
   421  // Rootless returns true if daemon is running in rootless mode
   422  func Rootless(cfg *config.Config) bool {
   423  	return cfg.Rootless
   424  }
   425  
   426  func noNewPrivileges(cfg *config.Config) bool {
   427  	return cfg.NoNewPrivileges
   428  }
   429  
   430  func (daemon *Daemon) populateContainerdCommit(ctx context.Context, v *system.Commit) error {
   431  	rv, err := daemon.containerd.Version(ctx)
   432  	if err != nil {
   433  		if errdefs.IsContext(err) {
   434  			return err
   435  		}
   436  		log.G(ctx).WithError(err).Warnf("Failed to retrieve containerd version")
   437  		return nil
   438  	}
   439  	v.ID = rv.Revision
   440  	v.Expected = rv.Revision
   441  	return nil
   442  }
   443  
   444  func (daemon *Daemon) populateContainerdVersion(ctx context.Context, v *types.Version) error {
   445  	rv, err := daemon.containerd.Version(ctx)
   446  	if err != nil {
   447  		if errdefs.IsContext(err) {
   448  			return err
   449  		}
   450  		log.G(ctx).WithError(err).Warn("Failed to retrieve containerd version")
   451  		return nil
   452  	}
   453  
   454  	v.Components = append(v.Components, types.ComponentVersion{
   455  		Name:    "containerd",
   456  		Version: rv.Version,
   457  		Details: map[string]string{
   458  			"GitCommit": rv.Revision,
   459  		},
   460  	})
   461  	return nil
   462  }
   463  
   464  func populateRuncVersion(cfg *configStore, v *types.Version) error {
   465  	_, ver, commit, err := parseDefaultRuntimeVersion(&cfg.Runtimes)
   466  	if err != nil {
   467  		return err
   468  	}
   469  	v.Components = append(v.Components, types.ComponentVersion{
   470  		Name:    cfg.Runtimes.Default,
   471  		Version: ver,
   472  		Details: map[string]string{
   473  			"GitCommit": commit,
   474  		},
   475  	})
   476  	return nil
   477  }
   478  
   479  func populateInitVersion(ctx context.Context, cfg *configStore, v *types.Version) error {
   480  	initBinary, err := cfg.LookupInitPath()
   481  	if err != nil {
   482  		log.G(ctx).WithError(err).Warn("Failed to find docker-init")
   483  		return nil
   484  	}
   485  
   486  	rv, err := exec.CommandContext(ctx, initBinary, "--version").Output()
   487  	if err != nil {
   488  		if errdefs.IsContext(err) {
   489  			return err
   490  		}
   491  		log.G(ctx).WithError(err).Warnf("Failed to retrieve %s version", initBinary)
   492  		return nil
   493  	}
   494  
   495  	ver, commit, err := parseInitVersion(string(rv))
   496  	if err != nil {
   497  		log.G(ctx).WithError(err).Warnf("failed to parse %s version", initBinary)
   498  		return nil
   499  	}
   500  	v.Components = append(v.Components, types.ComponentVersion{
   501  		Name:    filepath.Base(initBinary),
   502  		Version: ver,
   503  		Details: map[string]string{
   504  			"GitCommit": commit,
   505  		},
   506  	})
   507  	return nil
   508  }
   509  
   510  // ociRuntimeFeaturesKey is the "well-known" key used for including the
   511  // OCI runtime spec "features" struct.
   512  //
   513  // see https://github.com/opencontainers/runtime-spec/blob/main/features.md
   514  const ociRuntimeFeaturesKey = "org.opencontainers.runtime-spec.features"
   515  
   516  func (daemon *Daemon) runtimeStatus(ctx context.Context, cfg *configStore, runtimeName string) map[string]string {
   517  	m := make(map[string]string)
   518  	if runtimeName == "" {
   519  		runtimeName = cfg.Runtimes.Default
   520  	}
   521  	if features := cfg.Runtimes.Features(runtimeName); features != nil {
   522  		if j, err := json.Marshal(features); err == nil {
   523  			m[ociRuntimeFeaturesKey] = string(j)
   524  		} else {
   525  			log.G(ctx).WithFields(log.Fields{"error": err, "runtime": runtimeName}).Warn("Failed to call json.Marshal for the OCI features struct of runtime")
   526  		}
   527  	}
   528  	return m
   529  }