mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add the following options to "swarm init" and "swarm update": - --max-snapshots: Retain this many old Raft snapshots in addition to the latest one - --snapshot-interval: Number of log entries between Raft snapshots These options already existed in SwarmKit and the Docker API but were never exposed in the CLI. I'm adding them here to fix this oversight. --max-snapshots may be useful for debugging purposes and more conservative users who want to store rolling backups of old versions of the Raft state. --snapshot-interval is most useful for performance tuning. The default value of 10000 may not be ideal for some setups. There is also a LogEntriesForSlowFollowers option that is not exposed. I decided not to expose it along with these others because I don't think it's generally useful (and I'm not sure what I would call the CLI flag). But if people want, I can expose it for the sake of completeness. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
301 lines
11 KiB
Go
301 lines
11 KiB
Go
package system
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/cli"
|
|
"github.com/docker/docker/cli/command"
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
"github.com/docker/docker/utils"
|
|
"github.com/docker/docker/utils/templates"
|
|
"github.com/docker/go-units"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type infoOptions struct {
|
|
format string
|
|
}
|
|
|
|
// NewInfoCommand creates a new cobra.Command for `docker info`
|
|
func NewInfoCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
var opts infoOptions
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "info [OPTIONS]",
|
|
Short: "Display system-wide information",
|
|
Args: cli.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runInfo(dockerCli, &opts)
|
|
},
|
|
}
|
|
|
|
flags := cmd.Flags()
|
|
|
|
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runInfo(dockerCli *command.DockerCli, opts *infoOptions) error {
|
|
ctx := context.Background()
|
|
info, err := dockerCli.Client().Info(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if opts.format == "" {
|
|
return prettyPrintInfo(dockerCli, info)
|
|
}
|
|
return formatInfo(dockerCli, info, opts.format)
|
|
}
|
|
|
|
func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
|
|
fmt.Fprintf(dockerCli.Out(), "Containers: %d\n", info.Containers)
|
|
fmt.Fprintf(dockerCli.Out(), " Running: %d\n", info.ContainersRunning)
|
|
fmt.Fprintf(dockerCli.Out(), " Paused: %d\n", info.ContainersPaused)
|
|
fmt.Fprintf(dockerCli.Out(), " Stopped: %d\n", info.ContainersStopped)
|
|
fmt.Fprintf(dockerCli.Out(), "Images: %d\n", info.Images)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Server Version: %s\n", info.ServerVersion)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Storage Driver: %s\n", info.Driver)
|
|
if info.DriverStatus != nil {
|
|
for _, pair := range info.DriverStatus {
|
|
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", pair[0], pair[1])
|
|
|
|
// print a warning if devicemapper is using a loopback file
|
|
if pair[0] == "Data loop file" {
|
|
fmt.Fprintln(dockerCli.Err(), " WARNING: Usage of loopback devices is strongly discouraged for production use. Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.")
|
|
}
|
|
}
|
|
|
|
}
|
|
if info.SystemStatus != nil {
|
|
for _, pair := range info.SystemStatus {
|
|
fmt.Fprintf(dockerCli.Out(), "%s: %s\n", pair[0], pair[1])
|
|
}
|
|
}
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Logging Driver: %s\n", info.LoggingDriver)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Cgroup Driver: %s\n", info.CgroupDriver)
|
|
|
|
fmt.Fprintf(dockerCli.Out(), "Plugins: \n")
|
|
fmt.Fprintf(dockerCli.Out(), " Volume:")
|
|
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Volume, " "))
|
|
fmt.Fprintf(dockerCli.Out(), "\n")
|
|
fmt.Fprintf(dockerCli.Out(), " Network:")
|
|
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Network, " "))
|
|
fmt.Fprintf(dockerCli.Out(), "\n")
|
|
|
|
if len(info.Plugins.Authorization) != 0 {
|
|
fmt.Fprintf(dockerCli.Out(), " Authorization:")
|
|
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Authorization, " "))
|
|
fmt.Fprintf(dockerCli.Out(), "\n")
|
|
}
|
|
|
|
fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState)
|
|
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive {
|
|
fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID)
|
|
if info.Swarm.Error != "" {
|
|
fmt.Fprintf(dockerCli.Out(), " Error: %v\n", info.Swarm.Error)
|
|
}
|
|
fmt.Fprintf(dockerCli.Out(), " Is Manager: %v\n", info.Swarm.ControlAvailable)
|
|
if info.Swarm.ControlAvailable {
|
|
fmt.Fprintf(dockerCli.Out(), " ClusterID: %s\n", info.Swarm.Cluster.ID)
|
|
fmt.Fprintf(dockerCli.Out(), " Managers: %d\n", info.Swarm.Managers)
|
|
fmt.Fprintf(dockerCli.Out(), " Nodes: %d\n", info.Swarm.Nodes)
|
|
fmt.Fprintf(dockerCli.Out(), " Orchestration:\n")
|
|
taskHistoryRetentionLimit := int64(0)
|
|
if info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit != nil {
|
|
taskHistoryRetentionLimit = *info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit
|
|
}
|
|
fmt.Fprintf(dockerCli.Out(), " Task History Retention Limit: %d\n", taskHistoryRetentionLimit)
|
|
fmt.Fprintf(dockerCli.Out(), " Raft:\n")
|
|
fmt.Fprintf(dockerCli.Out(), " Snapshot Interval: %d\n", info.Swarm.Cluster.Spec.Raft.SnapshotInterval)
|
|
if info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots != nil {
|
|
fmt.Fprintf(dockerCli.Out(), " Number of Old Snapshots to Retain: %d\n", *info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots)
|
|
}
|
|
fmt.Fprintf(dockerCli.Out(), " Heartbeat Tick: %d\n", info.Swarm.Cluster.Spec.Raft.HeartbeatTick)
|
|
fmt.Fprintf(dockerCli.Out(), " Election Tick: %d\n", info.Swarm.Cluster.Spec.Raft.ElectionTick)
|
|
fmt.Fprintf(dockerCli.Out(), " Dispatcher:\n")
|
|
fmt.Fprintf(dockerCli.Out(), " Heartbeat Period: %s\n", units.HumanDuration(time.Duration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod)))
|
|
fmt.Fprintf(dockerCli.Out(), " CA Configuration:\n")
|
|
fmt.Fprintf(dockerCli.Out(), " Expiry Duration: %s\n", units.HumanDuration(info.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry))
|
|
if len(info.Swarm.Cluster.Spec.CAConfig.ExternalCAs) > 0 {
|
|
fmt.Fprintf(dockerCli.Out(), " External CAs:\n")
|
|
for _, entry := range info.Swarm.Cluster.Spec.CAConfig.ExternalCAs {
|
|
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", entry.Protocol, entry.URL)
|
|
}
|
|
}
|
|
}
|
|
fmt.Fprintf(dockerCli.Out(), " Node Address: %s\n", info.Swarm.NodeAddr)
|
|
}
|
|
|
|
if len(info.Runtimes) > 0 {
|
|
fmt.Fprintf(dockerCli.Out(), "Runtimes:")
|
|
for name := range info.Runtimes {
|
|
fmt.Fprintf(dockerCli.Out(), " %s", name)
|
|
}
|
|
fmt.Fprint(dockerCli.Out(), "\n")
|
|
fmt.Fprintf(dockerCli.Out(), "Default Runtime: %s\n", info.DefaultRuntime)
|
|
}
|
|
|
|
if info.OSType == "linux" {
|
|
if len(info.SecurityOptions) != 0 {
|
|
fmt.Fprintf(dockerCli.Out(), "Security Options:\n")
|
|
for _, o := range info.SecurityOptions {
|
|
switch o.Key {
|
|
case "Name":
|
|
fmt.Fprintf(dockerCli.Out(), " %s\n", o.Value)
|
|
case "Profile":
|
|
if o.Value != "default" {
|
|
fmt.Fprintf(dockerCli.Err(), " WARNING: You're not using the default seccomp profile\n")
|
|
}
|
|
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", o.Key, o.Value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Isolation only has meaning on a Windows daemon.
|
|
if info.OSType == "windows" {
|
|
fmt.Fprintf(dockerCli.Out(), "Default Isolation: %v\n", info.Isolation)
|
|
}
|
|
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Kernel Version: %s\n", info.KernelVersion)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Operating System: %s\n", info.OperatingSystem)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "OSType: %s\n", info.OSType)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Architecture: %s\n", info.Architecture)
|
|
fmt.Fprintf(dockerCli.Out(), "CPUs: %d\n", info.NCPU)
|
|
fmt.Fprintf(dockerCli.Out(), "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Name: %s\n", info.Name)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "ID: %s\n", info.ID)
|
|
fmt.Fprintf(dockerCli.Out(), "Docker Root Dir: %s\n", info.DockerRootDir)
|
|
fmt.Fprintf(dockerCli.Out(), "Debug Mode (client): %v\n", utils.IsDebugEnabled())
|
|
fmt.Fprintf(dockerCli.Out(), "Debug Mode (server): %v\n", info.Debug)
|
|
|
|
if info.Debug {
|
|
fmt.Fprintf(dockerCli.Out(), " File Descriptors: %d\n", info.NFd)
|
|
fmt.Fprintf(dockerCli.Out(), " Goroutines: %d\n", info.NGoroutines)
|
|
fmt.Fprintf(dockerCli.Out(), " System Time: %s\n", info.SystemTime)
|
|
fmt.Fprintf(dockerCli.Out(), " EventsListeners: %d\n", info.NEventsListener)
|
|
}
|
|
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Http Proxy: %s\n", info.HTTPProxy)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Https Proxy: %s\n", info.HTTPSProxy)
|
|
ioutils.FprintfIfNotEmpty(dockerCli.Out(), "No Proxy: %s\n", info.NoProxy)
|
|
|
|
if info.IndexServerAddress != "" {
|
|
u := dockerCli.ConfigFile().AuthConfigs[info.IndexServerAddress].Username
|
|
if len(u) > 0 {
|
|
fmt.Fprintf(dockerCli.Out(), "Username: %v\n", u)
|
|
}
|
|
fmt.Fprintf(dockerCli.Out(), "Registry: %v\n", info.IndexServerAddress)
|
|
}
|
|
|
|
// Only output these warnings if the server does not support these features
|
|
if info.OSType != "windows" {
|
|
if !info.MemoryLimit {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No memory limit support")
|
|
}
|
|
if !info.SwapLimit {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No swap limit support")
|
|
}
|
|
if !info.KernelMemory {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No kernel memory limit support")
|
|
}
|
|
if !info.OomKillDisable {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No oom kill disable support")
|
|
}
|
|
if !info.CPUCfsQuota {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu cfs quota support")
|
|
}
|
|
if !info.CPUCfsPeriod {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu cfs period support")
|
|
}
|
|
if !info.CPUShares {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu shares support")
|
|
}
|
|
if !info.CPUSet {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: No cpuset support")
|
|
}
|
|
if !info.IPv4Forwarding {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: IPv4 forwarding is disabled")
|
|
}
|
|
if !info.BridgeNfIptables {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: bridge-nf-call-iptables is disabled")
|
|
}
|
|
if !info.BridgeNfIP6tables {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: bridge-nf-call-ip6tables is disabled")
|
|
}
|
|
}
|
|
|
|
if info.Labels != nil {
|
|
fmt.Fprintln(dockerCli.Out(), "Labels:")
|
|
for _, attribute := range info.Labels {
|
|
fmt.Fprintf(dockerCli.Out(), " %s\n", attribute)
|
|
}
|
|
// TODO: Engine labels with duplicate keys has been deprecated in 1.13 and will be error out
|
|
// after 3 release cycles (1.16). For now, a WARNING will be generated. The following will
|
|
// be removed eventually.
|
|
labelMap := map[string]string{}
|
|
for _, label := range info.Labels {
|
|
stringSlice := strings.SplitN(label, "=", 2)
|
|
if len(stringSlice) > 1 {
|
|
// If there is a conflict we will throw out an warning
|
|
if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] {
|
|
fmt.Fprintln(dockerCli.Err(), "WARNING: labels with duplicate keys and conflicting values have been deprecated")
|
|
break
|
|
}
|
|
labelMap[stringSlice[0]] = stringSlice[1]
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild)
|
|
if info.ClusterStore != "" {
|
|
fmt.Fprintf(dockerCli.Out(), "Cluster Store: %s\n", info.ClusterStore)
|
|
}
|
|
|
|
if info.ClusterAdvertise != "" {
|
|
fmt.Fprintf(dockerCli.Out(), "Cluster Advertise: %s\n", info.ClusterAdvertise)
|
|
}
|
|
|
|
if info.RegistryConfig != nil && (len(info.RegistryConfig.InsecureRegistryCIDRs) > 0 || len(info.RegistryConfig.IndexConfigs) > 0) {
|
|
fmt.Fprintln(dockerCli.Out(), "Insecure Registries:")
|
|
for _, registry := range info.RegistryConfig.IndexConfigs {
|
|
if registry.Secure == false {
|
|
fmt.Fprintf(dockerCli.Out(), " %s\n", registry.Name)
|
|
}
|
|
}
|
|
|
|
for _, registry := range info.RegistryConfig.InsecureRegistryCIDRs {
|
|
mask, _ := registry.Mask.Size()
|
|
fmt.Fprintf(dockerCli.Out(), " %s/%d\n", registry.IP.String(), mask)
|
|
}
|
|
}
|
|
|
|
if info.RegistryConfig != nil && len(info.RegistryConfig.Mirrors) > 0 {
|
|
fmt.Fprintln(dockerCli.Out(), "Registry Mirrors:")
|
|
for _, mirror := range info.RegistryConfig.Mirrors {
|
|
fmt.Fprintf(dockerCli.Out(), " %s\n", mirror)
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(dockerCli.Out(), "Live Restore Enabled: %v\n", info.LiveRestoreEnabled)
|
|
|
|
return nil
|
|
}
|
|
|
|
func formatInfo(dockerCli *command.DockerCli, info types.Info, format string) error {
|
|
tmpl, err := templates.Parse(format)
|
|
if err != nil {
|
|
return cli.StatusError{StatusCode: 64,
|
|
Status: "Template parsing error: " + err.Error()}
|
|
}
|
|
err = tmpl.Execute(dockerCli.Out(), info)
|
|
dockerCli.Out().Write([]byte{'\n'})
|
|
return err
|
|
}
|