mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
These comments were added to enforce using the correct import path for
our packages ("github.com/docker/docker", not "github.com/moby/moby").
However, when working in go module mode (not GOPATH / vendor), they have
no effect, so their impact is limited.
Remove these imports in preparation of migrating our code to become an
actual go module.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
429 lines
14 KiB
Go
429 lines
14 KiB
Go
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
|
//go:build go1.23
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/v2/pkg/tracing"
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/api"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/system"
|
|
"github.com/docker/docker/cmd/dockerd/debug"
|
|
"github.com/docker/docker/daemon/config"
|
|
"github.com/docker/docker/daemon/internal/filedescriptors"
|
|
"github.com/docker/docker/daemon/logger"
|
|
"github.com/docker/docker/dockerversion"
|
|
"github.com/docker/docker/internal/metrics"
|
|
"github.com/docker/docker/internal/platform"
|
|
"github.com/docker/docker/pkg/meminfo"
|
|
"github.com/docker/docker/pkg/parsers/kernel"
|
|
"github.com/docker/docker/pkg/parsers/operatingsystem"
|
|
"github.com/docker/docker/pkg/sysinfo"
|
|
"github.com/docker/docker/registry"
|
|
"github.com/opencontainers/selinux/go-selinux"
|
|
)
|
|
|
|
func doWithTrace[T any](ctx context.Context, name string, f func() T) T {
|
|
_, span := tracing.StartSpan(ctx, name)
|
|
defer span.End()
|
|
return f()
|
|
}
|
|
|
|
// SystemInfo returns information about the host server the daemon is running on.
|
|
//
|
|
// The only error this should return is due to context cancellation/deadline.
|
|
// Anything else should be logged and ignored because this is looking up
|
|
// multiple things and is often used for debugging.
|
|
// The only case valid early return is when the caller doesn't want the result anymore (ie context cancelled).
|
|
func (daemon *Daemon) SystemInfo(ctx context.Context) (*system.Info, error) {
|
|
defer metrics.StartTimer(metrics.HostInfoFunctions.WithValues("system_info"))()
|
|
|
|
sysInfo := daemon.RawSysInfo()
|
|
cfg := daemon.config()
|
|
|
|
v := &system.Info{
|
|
ID: daemon.id,
|
|
Images: daemon.imageService.CountImages(ctx),
|
|
IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled,
|
|
Name: hostName(ctx),
|
|
SystemTime: time.Now().Format(time.RFC3339Nano),
|
|
LoggingDriver: daemon.defaultLogConfig.Type,
|
|
KernelVersion: kernelVersion(ctx),
|
|
OperatingSystem: operatingSystem(ctx),
|
|
OSVersion: osVersion(ctx),
|
|
IndexServerAddress: registry.IndexServer,
|
|
OSType: runtime.GOOS,
|
|
Architecture: platform.Architecture(),
|
|
RegistryConfig: doWithTrace(ctx, "registry.ServiceConfig", daemon.registryService.ServiceConfig),
|
|
NCPU: doWithTrace(ctx, "runtime.NumCPU", runtime.NumCPU),
|
|
MemTotal: memInfo(ctx).MemTotal,
|
|
GenericResources: daemon.genericResources,
|
|
DockerRootDir: cfg.Root,
|
|
Labels: cfg.Labels,
|
|
ExperimentalBuild: cfg.Experimental,
|
|
ServerVersion: dockerversion.Version,
|
|
HTTPProxy: config.MaskCredentials(getConfigOrEnv(cfg.HTTPProxy, "HTTP_PROXY", "http_proxy")),
|
|
HTTPSProxy: config.MaskCredentials(getConfigOrEnv(cfg.HTTPSProxy, "HTTPS_PROXY", "https_proxy")),
|
|
NoProxy: getConfigOrEnv(cfg.NoProxy, "NO_PROXY", "no_proxy"),
|
|
LiveRestoreEnabled: cfg.LiveRestoreEnabled,
|
|
Isolation: daemon.defaultIsolation,
|
|
CDISpecDirs: promoteNil(cfg.CDISpecDirs),
|
|
}
|
|
|
|
daemon.fillContainerStates(v)
|
|
daemon.fillDebugInfo(ctx, v)
|
|
daemon.fillContainerdInfo(v, &cfg.Config)
|
|
daemon.fillAPIInfo(v, &cfg.Config)
|
|
|
|
// Retrieve platform specific info
|
|
if err := daemon.fillPlatformInfo(ctx, v, sysInfo, cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
daemon.fillDriverInfo(v)
|
|
daemon.fillPluginsInfo(ctx, v, &cfg.Config)
|
|
daemon.fillSecurityOptions(v, sysInfo, &cfg.Config)
|
|
daemon.fillLicense(v)
|
|
daemon.fillDefaultAddressPools(ctx, v, &cfg.Config)
|
|
daemon.fillFirewallInfo(v)
|
|
daemon.fillDiscoveredDevicesFromDrivers(ctx, v, &cfg.Config)
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// SystemVersion returns version information about the daemon.
|
|
//
|
|
// The only error this should return is due to context cancellation/deadline.
|
|
// Anything else should be logged and ignored because this is looking up
|
|
// multiple things and is often used for debugging.
|
|
// The only case valid early return is when the caller doesn't want the result anymore (ie context cancelled).
|
|
func (daemon *Daemon) SystemVersion(ctx context.Context) (types.Version, error) {
|
|
defer metrics.StartTimer(metrics.HostInfoFunctions.WithValues("system_version"))()
|
|
|
|
kernelVer := kernelVersion(ctx)
|
|
cfg := daemon.config()
|
|
|
|
v := types.Version{
|
|
Components: []types.ComponentVersion{
|
|
{
|
|
Name: "Engine",
|
|
Version: dockerversion.Version,
|
|
Details: map[string]string{
|
|
"GitCommit": dockerversion.GitCommit,
|
|
"ApiVersion": api.DefaultVersion,
|
|
"MinAPIVersion": cfg.MinAPIVersion,
|
|
"GoVersion": runtime.Version(),
|
|
"Os": runtime.GOOS,
|
|
"Arch": runtime.GOARCH,
|
|
"BuildTime": dockerversion.BuildTime,
|
|
"KernelVersion": kernelVer,
|
|
"Experimental": strconv.FormatBool(cfg.Experimental),
|
|
},
|
|
},
|
|
},
|
|
|
|
// Populate deprecated fields for older clients
|
|
Version: dockerversion.Version,
|
|
GitCommit: dockerversion.GitCommit,
|
|
APIVersion: api.DefaultVersion,
|
|
MinAPIVersion: cfg.MinAPIVersion,
|
|
GoVersion: runtime.Version(),
|
|
Os: runtime.GOOS,
|
|
Arch: runtime.GOARCH,
|
|
BuildTime: dockerversion.BuildTime,
|
|
KernelVersion: kernelVer,
|
|
Experimental: cfg.Experimental,
|
|
}
|
|
|
|
v.Platform.Name = dockerversion.PlatformName
|
|
|
|
if err := daemon.fillPlatformVersion(ctx, &v, cfg); err != nil {
|
|
return v, err
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
func (daemon *Daemon) fillDriverInfo(v *system.Info) {
|
|
v.Driver = daemon.imageService.StorageDriver()
|
|
v.DriverStatus = daemon.imageService.LayerStoreStatus()
|
|
|
|
const warnMsg = `
|
|
WARNING: The %s storage-driver is deprecated, and will be removed in a future release.
|
|
Refer to the documentation for more information: https://docs.docker.com/go/storage-driver/`
|
|
|
|
switch v.Driver {
|
|
case "overlay":
|
|
v.Warnings = append(v.Warnings, fmt.Sprintf(warnMsg, v.Driver))
|
|
}
|
|
|
|
fillDriverWarnings(v)
|
|
}
|
|
|
|
func (daemon *Daemon) fillPluginsInfo(ctx context.Context, v *system.Info, cfg *config.Config) {
|
|
v.Plugins = system.PluginsInfo{
|
|
Volume: daemon.volumes.GetDriverList(),
|
|
Network: daemon.GetNetworkDriverList(ctx),
|
|
|
|
// The authorization plugins are returned in the order they are
|
|
// used as they constitute a request/response modification chain.
|
|
Authorization: cfg.AuthorizationPlugins,
|
|
Log: logger.ListDrivers(),
|
|
}
|
|
}
|
|
|
|
func (daemon *Daemon) fillSecurityOptions(v *system.Info, sysInfo *sysinfo.SysInfo, cfg *config.Config) {
|
|
var securityOptions []string
|
|
if sysInfo.AppArmor {
|
|
securityOptions = append(securityOptions, "name=apparmor")
|
|
}
|
|
if sysInfo.Seccomp && supportsSeccomp {
|
|
if daemon.seccompProfilePath != config.SeccompProfileDefault {
|
|
v.Warnings = append(v.Warnings, "WARNING: daemon is not using the default seccomp profile")
|
|
}
|
|
securityOptions = append(securityOptions, "name=seccomp,profile="+daemon.seccompProfilePath)
|
|
}
|
|
if selinux.GetEnabled() {
|
|
securityOptions = append(securityOptions, "name=selinux")
|
|
}
|
|
if uid, gid := daemon.idMapping.RootPair(); uid != 0 || gid != 0 {
|
|
securityOptions = append(securityOptions, "name=userns")
|
|
}
|
|
if Rootless(cfg) {
|
|
securityOptions = append(securityOptions, "name=rootless")
|
|
}
|
|
if cgroupNamespacesEnabled(sysInfo, cfg) {
|
|
securityOptions = append(securityOptions, "name=cgroupns")
|
|
}
|
|
if noNewPrivileges(cfg) {
|
|
securityOptions = append(securityOptions, "name=no-new-privileges")
|
|
}
|
|
|
|
v.SecurityOptions = securityOptions
|
|
}
|
|
|
|
func (daemon *Daemon) fillContainerStates(v *system.Info) {
|
|
cRunning, cPaused, cStopped := metrics.StateCtr.Get()
|
|
v.Containers = cRunning + cPaused + cStopped
|
|
v.ContainersPaused = cPaused
|
|
v.ContainersRunning = cRunning
|
|
v.ContainersStopped = cStopped
|
|
}
|
|
|
|
// fillDebugInfo sets the current debugging state of the daemon, and additional
|
|
// debugging information, such as the number of Go-routines, and file descriptors.
|
|
//
|
|
// Note that this currently always collects the information, but the CLI only
|
|
// prints it if the daemon has debug enabled. We should consider to either make
|
|
// this information optional (cli to request "with debugging information"), or
|
|
// only collect it if the daemon has debug enabled. For the CLI code, see
|
|
// https://github.com/docker/cli/blob/v20.10.12/cli/command/system/info.go#L239-L244
|
|
func (daemon *Daemon) fillDebugInfo(ctx context.Context, v *system.Info) {
|
|
v.Debug = debug.IsEnabled()
|
|
v.NFd = filedescriptors.GetTotalUsedFds(ctx)
|
|
v.NGoroutines = runtime.NumGoroutine()
|
|
v.NEventsListener = daemon.EventsService.SubscribersCount()
|
|
}
|
|
|
|
// fillContainerdInfo provides information about the containerd configuration
|
|
// for debugging purposes.
|
|
func (daemon *Daemon) fillContainerdInfo(v *system.Info, cfg *config.Config) {
|
|
if cfg.ContainerdAddr == "" {
|
|
return
|
|
}
|
|
v.Containerd = &system.ContainerdInfo{
|
|
Address: cfg.ContainerdAddr,
|
|
Namespaces: system.ContainerdNamespaces{
|
|
Containers: cfg.ContainerdNamespace,
|
|
Plugins: cfg.ContainerdPluginNamespace,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (daemon *Daemon) fillAPIInfo(v *system.Info, cfg *config.Config) {
|
|
const warn string = `
|
|
Access to the remote API is equivalent to root access on the host. Refer
|
|
to the 'Docker daemon attack surface' section in the documentation for
|
|
more information: https://docs.docker.com/go/attack-surface/`
|
|
|
|
if cfg.CorsHeaders != "" {
|
|
v.Warnings = append(v.Warnings, `DEPRECATED: The "api-cors-header" config parameter and the dockerd "--api-cors-header" option will be removed in the next release. Use a reverse proxy if you need CORS headers.`)
|
|
}
|
|
|
|
for _, host := range cfg.Hosts {
|
|
// cnf.Hosts is normalized during startup, so should always have a scheme/proto
|
|
proto, addr, _ := strings.Cut(host, "://")
|
|
if proto != "tcp" {
|
|
continue
|
|
}
|
|
const removal = "In future versions this will be a hard failure preventing the daemon from starting! Learn more at: https://docs.docker.com/go/api-security/"
|
|
if cfg.TLS == nil || !*cfg.TLS {
|
|
v.Warnings = append(v.Warnings, fmt.Sprintf("[DEPRECATION NOTICE]: API is accessible on http://%s without encryption.%s\n%s", addr, warn, removal))
|
|
continue
|
|
}
|
|
if cfg.TLSVerify == nil || !*cfg.TLSVerify {
|
|
v.Warnings = append(v.Warnings, fmt.Sprintf("[DEPRECATION NOTICE]: API is accessible on https://%s without TLS client verification.%s\n%s", addr, warn, removal))
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (daemon *Daemon) fillDefaultAddressPools(ctx context.Context, v *system.Info, cfg *config.Config) {
|
|
_, span := tracing.StartSpan(ctx, "fillDefaultAddressPools")
|
|
defer span.End()
|
|
for _, pool := range cfg.DefaultAddressPools.Value() {
|
|
v.DefaultAddressPools = append(v.DefaultAddressPools, system.NetworkAddressPool{
|
|
Base: pool.Base.String(),
|
|
Size: pool.Size,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (daemon *Daemon) fillFirewallInfo(v *system.Info) {
|
|
if daemon.netController == nil {
|
|
return
|
|
}
|
|
v.FirewallBackend = daemon.netController.FirewallBackend()
|
|
}
|
|
|
|
func hostName(ctx context.Context) string {
|
|
ctx, span := tracing.StartSpan(ctx, "hostName")
|
|
defer span.End()
|
|
hn, err := os.Hostname()
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Warn("Could not get hostname")
|
|
return ""
|
|
}
|
|
return hn
|
|
}
|
|
|
|
func kernelVersion(ctx context.Context) string {
|
|
ctx, span := tracing.StartSpan(ctx, "kernelVersion")
|
|
defer span.End()
|
|
|
|
var ver string
|
|
if kv, err := kernel.GetKernelVersion(); err != nil {
|
|
log.G(ctx).WithError(err).Warn("Could not get kernel version")
|
|
} else {
|
|
ver = kv.String()
|
|
}
|
|
return ver
|
|
}
|
|
|
|
func memInfo(ctx context.Context) *meminfo.Memory {
|
|
ctx, span := tracing.StartSpan(ctx, "memInfo")
|
|
defer span.End()
|
|
|
|
mi, err := meminfo.Read()
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Error("Could not read system memory info")
|
|
return &meminfo.Memory{}
|
|
}
|
|
return mi
|
|
}
|
|
|
|
func operatingSystem(ctx context.Context) (operatingSystem string) {
|
|
ctx, span := tracing.StartSpan(ctx, "operatingSystem")
|
|
defer span.End()
|
|
|
|
defer metrics.StartTimer(metrics.HostInfoFunctions.WithValues("operating_system"))()
|
|
|
|
if s, err := operatingsystem.GetOperatingSystem(); err != nil {
|
|
log.G(ctx).WithError(err).Warn("Could not get operating system name")
|
|
} else {
|
|
operatingSystem = s
|
|
}
|
|
if inContainer, err := operatingsystem.IsContainerized(); err != nil {
|
|
log.G(ctx).WithError(err).Error("Could not determine if daemon is containerized")
|
|
operatingSystem += " (error determining if containerized)"
|
|
} else if inContainer {
|
|
operatingSystem += " (containerized)"
|
|
}
|
|
|
|
return operatingSystem
|
|
}
|
|
|
|
func osVersion(ctx context.Context) (version string) {
|
|
ctx, span := tracing.StartSpan(ctx, "osVersion")
|
|
defer span.End()
|
|
|
|
defer metrics.StartTimer(metrics.HostInfoFunctions.WithValues("os_version"))()
|
|
|
|
version, err := operatingsystem.GetOperatingSystemVersion()
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Warn("Could not get operating system version")
|
|
return ""
|
|
}
|
|
|
|
return version
|
|
}
|
|
|
|
func getEnvAny(names ...string) string {
|
|
for _, n := range names {
|
|
if val := os.Getenv(n); val != "" {
|
|
return val
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getConfigOrEnv(config string, env ...string) string {
|
|
if config != "" {
|
|
return config
|
|
}
|
|
return getEnvAny(env...)
|
|
}
|
|
|
|
// promoteNil converts a nil slice to an empty slice of that type.
|
|
// A non-nil slice is returned as is.
|
|
func promoteNil[S ~[]E, E any](s S) S {
|
|
if s == nil {
|
|
return S{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// fillDiscoveredDevicesFromDrivers iterates over registered device drivers
|
|
// and calls their ListDevices method (if available) to populate system info.
|
|
func (daemon *Daemon) fillDiscoveredDevicesFromDrivers(ctx context.Context, v *system.Info, cfg *config.Config) {
|
|
ctx, span := tracing.StartSpan(ctx, "daemon.fillDiscoveredDevicesFromDrivers")
|
|
defer span.End()
|
|
|
|
// Make sure v.DiscoveredDevices is initialized to an empty slice instead of nil.
|
|
// This ensures that the JSON output is always a valid array, even if no devices are discovered.
|
|
v.DiscoveredDevices = []system.DeviceInfo{}
|
|
|
|
for driverName, driver := range deviceDrivers {
|
|
if driver.ListDevices == nil {
|
|
log.G(ctx).WithField("driver", driverName).Trace("Device driver does not implement ListDevices method.")
|
|
continue
|
|
}
|
|
|
|
ls, err := driver.ListDevices(ctx, cfg)
|
|
if err != nil {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"driver": driverName,
|
|
"error": err,
|
|
}).Warn("Failed to list devices for driver")
|
|
v.Warnings = append(v.Warnings, fmt.Sprintf("Failed to list devices from driver '%s': %v", driverName, err))
|
|
continue
|
|
}
|
|
|
|
if len(ls.Warnings) > 0 {
|
|
v.Warnings = append(v.Warnings, ls.Warnings...)
|
|
}
|
|
|
|
for _, device := range ls.Devices {
|
|
device.Source = driverName
|
|
v.DiscoveredDevices = append(v.DiscoveredDevices, device)
|
|
}
|
|
}
|
|
}
|