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 }