从查看 docker 服务 restart 次数说起->
从查看 docker 服务 restart 次数说起->
ARCHIVE 这篇写于 2019 年,距今 7 年多了。里面的版本、链接和做法可能都已经过时,看的时候留个心眼。
背景
为什么我要知道查看 docker 服务的 restart 次数呢?
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
13c212bc5cf1 redis:4-alpine "docker-entrypoint.s…" 4 weeks ago Up 13 days 0.0.0.0:6380->6379/tcp dev_redis_1
我发现每次调用一个会 panic 的 Docker 服务后,它的 STATUS 时间是显示 Up 几秒钟之前,但是 CREATED 的时间一直没有变化的。
从这里我联想到,那我是否可以查看每个 docker 容器的重启次数呢?
当然,这里会有一个前提是,你的 docker 容器是设置了 restart: Always 或者: restart: on-failure: 3 这类的重启策略
重启策略:
- no
- on-failure
- unless-stopped
- always
查看方法:
$ docker inspect -f "{{ .RestartCount }}" docker-service-name
102
输出的结果就是重启的次数。
为什么是这样写的呢?
首先,我们来看看 docker command help
$ docker help
...
inspect Return low-level information on Docker objects # 返回 Docker 对象中的低级信息。
...
docker inspect container_id
➭ docker inspect dev_db_1
[
{
"Id": "xxx",
"Created": "2019-01-27T09:59:20.849355325Z",
"Path": "docker-entrypoint.sh",
"Args": [
"--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 3359,
"ExitCode": 0,
"Error": "",
"StartedAt": "2019-01-30T16:59:13.975716147Z",
"FinishedAt": "2019-01-30T16:44:04.737912071Z"
},
"Image": "sha256:xxx",
"ResolvConfPath": "/var/lib/docker/containers/xxx/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/xxx/hostname",
"HostsPath": "/var/lib/docker/containers/xxx/hosts",
"LogPath": "/var/lib/docker/containers/xxx/xx-json.log",
"Name": "/dev_db_1",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": [
".../dev/config/mysql_init.sql:/docker-entrypoint-initdb.d/mysql_init.sql:rw",
"dev_data_db:/var/lib/mysql:rw"
],
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "dev_default",
"PortBindings": {
"3306/tcp": [
{
"HostIp": "",
"HostPort": "3306"
}
]
},
"RestartPolicy": {
"Name": "",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": [],
"CapAdd": null,
"CapDrop": null,
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "shareable",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": null,
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": null,
"DeviceCgroupRules": null,
"DiskQuota": 0,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": 0,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/xxx-init/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/xxx/diff",
"MergedDir": "/var/lib/docker/overlay2/xxx/merged",
"UpperDir": "/var/lib/docker/overlay2/xxx/diff",
"WorkDir": "/var/lib/docker/overlay2/xxx/work"
},
"Name": "overlay2"
},
"Mounts": [
{
"Type": "bind",
"Source": ".../dev/config/mysql_init.sql",
"Destination": "/docker-entrypoint-initdb.d/mysql_init.sql",
"Mode": "rw",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "dev_data_db",
"Source": "/var/lib/docker/volumes/dev_data_db/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "rw",
"RW": true,
"Propagation": ""
}
],
"Config": {
"Hostname": "xxx",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"3306/tcp": {},
"33060/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"MYSQL_ROOT_PASSWORD=xxx",
"MYSQL_DATABASE=xxx",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.7",
"MYSQL_MAJOR=5.7",
"MYSQL_VERSION=5.7.23-1debian9"
],
"Cmd": [
"--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci"
],
"Image": "mysql:5.7",
"Volumes": {
"/docker-entrypoint-initdb.d/mysql_init.sql": {},
"/var/lib/mysql": {}
},
"WorkingDir": "",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {
"com.docker.compose.config-hash": "xxx",
"com.docker.compose.container-number": "1",
"com.docker.compose.oneoff": "False",
"com.docker.compose.project": "dev",
"com.docker.compose.service": "db",
"com.docker.compose.version": "1.23.2"
}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "xxx",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"3306/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "3306"
}
],
"33060/tcp": null
},
"SandboxKey": "/var/run/docker/netns/xxx",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {
"dev_default": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"db",
"xxx"
],
"NetworkID": "xxx",
"EndpointID": "xxx",
"Gateway": "172.20.0.1",
"IPAddress": "172.20.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "xxx",
"DriverOpts": null
}
}
}
}
]
这里的所有 Docker 对象的低级别的配置信息。
如何获取某个配置呢?
这么多的信息,我是否可以获取指定的配置呢?提供了 format 方法。
format 的语法是 golang 的 text/template。
Docker inspect 源码阅读
github.com/docker/cli/cli/command/system/inspect.go
// NewInspectCommand creates a new cobra.Command for `docker inspect`
func NewInspectCommand(dockerCli command.Cli) *cobra.Command {
var opts inspectOptions
cmd := &cobra.Command{
Use: "inspect [OPTIONS] NAME|ID [NAME|ID...]",
Short: "Return low-level information on Docker objects",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.ids = args
return runInspect(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
flags.StringVar(&opts.inspectType, "type", "", "Return JSON for specified type")
flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes if the type is container")
return cmd
}
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
var elementSearcher inspect.GetRefFunc
switch opts.inspectType {
case "", "container", "image", "node", "network", "service", "volume", "task", "plugin", "secret":
elementSearcher = inspectAll(context.Background(), dockerCli, opts.size, opts.inspectType)
default:
return errors.Errorf("%q is not a valid value for --type", opts.inspectType)
}
return inspect.Inspect(dockerCli.Out(), opts.ids, opts.format, elementSearcher)
}
// ...
func inspectContainers(ctx context.Context, dockerCli command.Cli, getSize bool) inspect.GetRefFunc {
return func(ref string) (interface{}, []byte, error) {
return dockerCli.Client().ContainerInspectWithRaw(ctx, ref, getSize)
}
}
// ContainerInspectWithRaw returns the container information and its raw representation.
func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) {
if containerID == "" {
return types.ContainerJSON{}, nil, objectNotFoundError{object: "container", id: containerID}
}
query := url.Values{}
if getSize {
query.Set("size", "1")
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
if err != nil {
return types.ContainerJSON{}, nil, wrapResponseError(err, serverResp, "container", containerID)
}
defer ensureReaderClosed(serverResp)
body, err := ioutil.ReadAll(serverResp.body)
if err != nil {
return types.ContainerJSON{}, nil, err
}
var response types.ContainerJSON
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
}
它在 Linux 的路径是:/var/lib/docker/containers/containerID/config.v2.json。
更多实战模板,可以看参考资料3。
docker inspect --format '{{$e := . }}{{with .NetworkSettings}} {{$e.Name}}
{{range $index, $net := .Networks}}{{$index}} {{.IPAddress}}
{{end}}{{end}}' $(docker ps -q)
查看当前所有的 docker 服务的重启次数,服务名称,创建时间,最后一次启动时间:
$ docker inspect -f "RestartCount:{{.RestartCount}}, ServiceName: {{.Config.Hostname}}, Created: {{.Created}}, LastUp: {{.State.StartedAt}}" $(docker ps -q)
参考资料
茶歇驿站
一个可以让你停下来看一看,在茶歇之余给你帮助的小站,这里的内容主要是后端技术,个人管理,团队管理,以及其他个人杂想。