文章

Docker

docker是真正划时代的技术。

  1. 服务部署演进
    1. 使用linux内核?
    2. docker为什么都要装个linux os?
    3. windows的docker怎么使用linux内核?
    4. docker用的是不同的linux distribution,为什么都能在同一个host kernel上跑起来?
    5. 优点
  2. 基本概念
    1. docker:容器化
    2. moby
    3. OCI
    4. CRI:标准
      1. containerd:docker提供的CRI实现
    5. kubernetes:容器编排
    6. rancher:kubernetes之上
      1. portainer:小而美
  3. 基础入门
    1. 安装
    2. 教程
    3. PWD
    4. image
    5. container
  4. docker engine
  5. image
    1. 命名
    2. build
    3. layer
    4. 相关命令
  6. container
    1. 启动
    2. 重连
    3. 重启
    4. 端口映射
    5. 默认指令:CMD vs. ENTRYPOINT
    6. 相关命令
  7. command
  8. 其他部分

服务部署演进

容器的演进:

container evolution

从GNU/Linux的层面来理解再合适不过了:

  • GNU:os套件;
  • Linux:内核;

Linux(内核)已经有了,为什么要再启动个Linux?只留下GNU不就得了!而所谓部署环境的不同,不就是GNU的不同嘛!

使用linux内核?

不再启动一个完整的os,只要保留了GNU。所以启动docker不是启动os,只是启动了一个进程,这个进程使用docker内部自己装的那些GNU,并在需要的时候调用操作系统的Linux内核。

如果以GUN/Linux来解释的话,可以认为docker装的是GUN,是除了内核的那一套文件,工具包。docker内的进程需要用内核的时候,用的是宿主内核。而宿主内核已经被加载进宿主的ram里了。所以docker还是只启动了一个进程。

在docker里和linux里执行cat /proc/version,结果是一样的

1
2
3
4
5
6
7
└> docker exec b47d5805606b cat /etc/apt/sources.list
deb http://deb.debian.org/debian jessie main
deb http://deb.debian.org/debian jessie-updates main
deb http://security.debian.org jessie/updates main

└> docker exec b47d5805606b cat /proc/version 
Linux version 5.10.0-8-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.46-5 (2021-09-23)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
└> cat /etc/apt/sources.list
# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free

deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free

└> cat /proc/version 
Linux version 5.10.0-8-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.46-5 (2021-09-23)

docker用的是jessie,debian是bullseye,但是二者输出的内核信息一毛一样。

  • https://www.zhihu.com/question/55143510

docker为什么都要装个linux os?

docker镜像更像是没有core的GNU。

windows的docker怎么使用linux内核?

用的是WSL2的真linux内核:

  • https://docs.docker.com/desktop/windows/wsl/

WSL2 Debian:

1
2
3
4
5
6
7
8
9
% sudo lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11
Codename:       bullseye

% cat /proc/version
Linux version 5.10.16.3-microsoft-standard-WSL2 (oe-user@oe-host) (x86_64-msft-linux-gcc (GCC) 9.3.0, GNU ld (GNU Binutils) 2.34.0.20200220) #1 SMP Fri Apr 2 22:23:49 UTC 2021

Windows上通过docker执行命令:

1
2
3
4
5
docker exec ece79f cat /etc/alpine-release
3.13.5

$ docker exec ece79f cat /proc/version
Linux version 5.10.16.3-microsoft-standard-WSL2 (oe-user@oe-host) (x86_64-msft-linux-gcc (GCC) 9.3.0, GNU ld (GNU Binutils) 2.34.0.20200220) #1 SMP Fri Apr 2 22:23:49 UTC 2021

docker用的是alpine,它和WSL2 Debian bullseye一样,用的都是这个WSL2的内核。

docker用的是不同的linux distribution,为什么都能在同一个host kernel上跑起来?

他们只用host的kernel,而所有的distribution的kernel基本都是一样的,也就带的系统软件不一样,用户能使用的功能不一样。但是如果host的kernel太老,没有docker distribution所需要的系统调用、driver等等,那就跑不起来了。

  • https://stackoverflow.com/a/56606244/4969542

优点

  • 方便部署
  • 资源管控,这个之前没太考虑过

在container一章有vm和container的介绍。

基本概念

docker:容器化

dock worker,码头工人,从船上装卸货物的人。所以docker的logo是一个类似船的鲸鱼,上面是一堆集装箱。

moby

因为docker项目要拆分为多个项目,项目之间有标准协议,每个项目是一个组件,组件可以被替换为第三方组件。所以原来的一个docker项目现在拆成了一堆,被放到了moby项目下。

moby的logo是一个鲸尾巴,之所以用moby,大概是因为Moby Dick这本书。而moby dick之所以翻译为白鲸记,是因为那个白鲸名字叫莫比迪克,所以moby也成为了鲸的代名词了吧。

OCI

Open Container Initiative,开放容器计划,旨在对容器基础架构的基础组件进行标准化。

竞争是好事,但关于标准的竞争一般不是。多种协议(如果不是从优化的角度提出多种协议)一般只会导致混乱。

CRI:标准

Container Runtime Interface,容器运行时标准规范。

containerd:docker提供的CRI实现

CRI的标准实现。

docker作为容器runtime技术是不完全兼容CRI的,但是docker公司开源的containerd是完全兼容CRI的容器运行时。

Containerd是一个轻量级的容器运行时,由Docker公司开发并开源。它提供了一个CRI(Container Runtime Interface)兼容的接口,用于管理和运行容器。Containerd支持多种容器格式,例如Docker、OCI(Open Container Initiative)和CRI-O等,可以与各种容器编排平台和工具集成使用。

虽然Docker和Containerd是两个不同的容器技术,但Docker公司将Containerd作为Docker引擎的一部分进行了开源,使得Docker引擎可以使用Containerd作为底层容器运行时。因此,在Docker 17.06及更高版本中,Docker引擎默认使用Containerd作为容器运行时

kubernetes:容器编排

helmsman or pilot,舵手。因为它是编排容器的。kubernetes和docker在解决两类完全不同的问题:

  • docker要解决的问题是:怎么容器化应用——将应用程序及其依赖项封装到一个独立的容器中,从而实现应用程序的可移植性和隔离性;
  • kubernetes要解决的问题是:怎么进行容器编排——自动化地管理容器的部署、伸缩、升级和故障恢复,从而简化容器化应用的管理;

所以docker是容器化技术的实现者,kubernetes是容器编排技术的实现者。kubernetes在docker之上,可以基于docker容器,也可以基于别的容器化运行时来运行容器,比如:

  • kubernetes 1.19+使用containerd作为默认的容器运行时。因为在过去的几年中,Kubernetes社区逐渐将重点放在了容器运行时的标准化和抽象化上,以支持更广泛的容器运行时选择。而Docker虽然是一个流行的容器运行时,但它在某些方面存在一些限制和问题,例如不支持CRI(Container Runtime Interface)标准和不易于管理容器资源等;
  • 在此之前,kubernetes使用docker作为默认的运行时;
  • kubernetes还可以使用:
    • CRI-O:CRI-O是一个轻量级的容器运行时,专门为Kubernetes设计,它支持Kubernetes的Container Runtime Interface(CRI)规范,可以与Kubernetes无缝集成;
    • rkt:rkt是另一个开源的容器运行时,它支持OCI容器规范,可以作为Kubernetes的容器运行时来运行容器;

2017年,kubernetes赢得了容器编排之战(战胜了apache mesos和docker swarm)

  1. rkt宣布放弃自己的容器编排系统fleet,使用kubernetes;
  2. rancher宣布完全基于kubernetes;
  3. apache mesos宣布集成kubernetes;
  4. docker之前只支持自己的docker swarm,现在也被迫同时支持kubernetes;

rancher:kubernetes之上

Rancher是一个基于Kubernetes的容器管理平台,它提供了一个可视化的界面来管理Kubernetes集群和应用程序,同时还提供了许多附加功能,例如负载均衡、监控、日志收集等。它为Kubernetes提供了更多的功能和工具,使得容器化应用的管理更加方便和高效。

rancher在1.x版本就能支持多种容器编排系统,但是在2017年宣布All-in-Kubernetes,从2.0版本开始,只支持kubernetes。这也加速了kubernetes赢得容器编排之战。

因为kubernetes适合大规模的容器编排,所以rancher一般在大规模的环境里使用。

portainer:小而美

Portainer是一个基于Docker的容器管理平台,可以帮助用户简化Docker容器的部署和管理。虽然Portainer提供了一些Kubernetes的集成和支持,但是Portainer本身并不是基于Kubernetes构建的。

portainer和rancher在逻辑上很像,但是因为不使用kubernetes,所以适合管理小规模服务,比如在自己的VPS上使用docker起几个容器,使用portainer管理非常不错。

基础入门

安装

  • install on Debian: https://docs.docker.com/engine/install/debian/
  • install on windows(backed on WSL2): https://docs.docker.com/desktop/windows/wsl/

教程

  • https://docs.docker.com/get-started/

PWD

play with docker:

  • https://labs.play-with-docker.com/

白嫖服务器部署docker服务!

image

镜像,类似于一个存在于光盘上的linux发行版。这里面装着GNU或者你自己打包进去的自己写的其他软件。但是它是无核的

Since the image contains the container’s filesystem, it must contain everything needed to run an application - all dependencies, configuration, scripts, binaries, etc. The image also contains other configuration for the container, such as environment variables, a default command to run, and other metadata.

镜像一般放到镜像管理仓库里进行管理,就像代码放到github上一样,docker官方提供了docker hub:

  • https://hub.docker.com/repositories

container

容器。容器从镜像创建,起来之后,其实就是一个linux进程!linux内核的cgroups本来就支持进程隔离,docker只是把这项技术变得更容易使用了。

Simply put, a container is simply another process on your machine that has been isolated from all other processes on the host machine. That isolation leverages kernel namespaces and cgroups, features that have been in Linux for a long time. Docker has worked to make these capabilities approachable and easy to use

容器关闭的时候和镜像有什么区别?可以理解为光盘里的linux发行版和你已经装到自己电脑上的linux的区别:后者已经有了你自己运行后的一些数据。

docker engine

image

镜像是无核的,只有一些必须的GNU组件。为了让容器小巧而运行迅速,镜像一般都很精简,只有很少的必须组件。通常只有一个bash,而不是好几种shell。

apline镜像只有3M,Debian只有50M,Debian-slim只有30M。

命名

1
<url><user><repo>:<tag>
  • url:第三方仓库地址,如果不指定,默认是官方仓库docker hub;
  • user:repo所属user,和github下的仓库结构一致,比如puppylpg/youtube-dl-webmocrosoft/powershell。但是docker的official image直接就在顶级目录下。比如debian,而非xxx/debian
  • repo: 镜像名称;
  • tag:镜像版本。如果不指定,默认是latest

比如:

  • es自己搭的hub的image:docker.elastic.co/elasticsearch/elasticsearch:7.12.1
  • docker hub offickal es image:elasticsearch:7.12.1
  • docker hub别人打的es image:bitnami/elasticsearch:latest

下载官方image名字很简单,最少只需要指定repo即可:

1
docker pull debian

latest标签就和master分支一样,只是一个默认时的名字,但是并不一定是最新的。比如alpine最新的标签是edge。

可以直接在命令行搜索docker hub

1
2
3
4
5
> docker search puppylpg
NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
puppylpg/get-started                                                              0
puppylpg/youtube-dl-web           A web wrapper for youtube-dl: https://github…   0
puppylpg/nginx-digest-auth-test   A container to combain nginx and http digest…   0

build

看官方文档即可:

  • https://docs.docker.com/engine/reference/commandline/build/
1
docker build [OPTIONS] PATH | URL | -
  • 如果在Dockerfile同目录下:docker build .
  • 构建镜像并打标签:docker build -t puppylpg/repo:2.0 .

layer

镜像看起来是一个完整的东西,实际内部由很多layer组成,这些layer都是只读的,所以可以在不同镜像之间复用的。

有点儿类似git的commit。

使用docker image inspect可以检查image,也能看到里面的layer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
> docker inspect elasticsearch:7.17.2
[
    {
        "Id": "sha256:ff55ff4ded04f5512613ca71b29b0fdf093b6ba95aa4ed4264a18a8045ad0d59",
        "RepoTags": [
            "elasticsearch:7.17.2"
        ],
        "RepoDigests": [
            "elasticsearch@sha256:f51e653b5dfca16afef88d870b697e087e4b562c63c3272d6e8c3c92657110d9"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-03-28T17:38:20.456643057Z",
        "Container": "16bc75effd76d1d11c283d9a2c495dfa6f5fd9ac629112eb1006c5a44c476e51",
        "ContainerConfig": {
            "Hostname": "16bc75effd76",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "9200/tcp": {},
                "9300/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/share/elasticsearch/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "ELASTIC_CONTAINER=true"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"eswrapper\"]"
            ],
            "Image": "sha256:dc8ca8053a486e05c2624fd55443495d8c2e7f67efe139db52e013edc7c4cc64",
            "Volumes": null,
            "WorkingDir": "/usr/share/elasticsearch",
            "Entrypoint": [
                "/bin/tini",
                "--",
                "/usr/local/bin/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "org.label-schema.build-date": "2022-03-28T17:35:51.740591902Z",
                "org.label-schema.license": "Elastic-License-2.0",
                "org.label-schema.name": "Elasticsearch",
                "org.label-schema.schema-version": "1.0",
                "org.label-schema.url": "https://www.elastic.co/products/elasticsearch",
                "org.label-schema.usage": "https://www.elastic.co/guide/en/elasticsearch/reference/index.html",
                "org.label-schema.vcs-ref": "de7261de50d90919ae53b0eff9413fd7e5307301",
                "org.label-schema.vcs-url": "https://github.com/elastic/elasticsearch",
                "org.label-schema.vendor": "Elastic",
                "org.label-schema.version": "7.17.2",
                "org.opencontainers.image.created": "2022-03-28T17:35:51.740591902Z",
                "org.opencontainers.image.documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/index.html",
                "org.opencontainers.image.licenses": "Elastic-License-2.0",
                "org.opencontainers.image.revision": "de7261de50d90919ae53b0eff9413fd7e5307301",
                "org.opencontainers.image.source": "https://github.com/elastic/elasticsearch",
                "org.opencontainers.image.title": "Elasticsearch",
                "org.opencontainers.image.url": "https://www.elastic.co/products/elasticsearch",
                "org.opencontainers.image.vendor": "Elastic",
                "org.opencontainers.image.version": "7.17.2"
            }
        },
        "DockerVersion": "20.10.14",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "9200/tcp": {},
                "9300/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/share/elasticsearch/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "ELASTIC_CONTAINER=true"
            ],
            "Cmd": [
                "eswrapper"
            ],
            "Image": "sha256:dc8ca8053a486e05c2624fd55443495d8c2e7f67efe139db52e013edc7c4cc64",
            "Volumes": null,
            "WorkingDir": "/usr/share/elasticsearch",
            "Entrypoint": [
                "/bin/tini",
                "--",
                "/usr/local/bin/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "org.label-schema.build-date": "2022-03-28T17:35:51.740591902Z",
                "org.label-schema.license": "Elastic-License-2.0",
                "org.label-schema.name": "Elasticsearch",
                "org.label-schema.schema-version": "1.0",
                "org.label-schema.url": "https://www.elastic.co/products/elasticsearch",
                "org.label-schema.usage": "https://www.elastic.co/guide/en/elasticsearch/reference/index.html",
                "org.label-schema.vcs-ref": "de7261de50d90919ae53b0eff9413fd7e5307301",
                "org.label-schema.vcs-url": "https://github.com/elastic/elasticsearch",
                "org.label-schema.vendor": "Elastic",
                "org.label-schema.version": "7.17.2",
                "org.opencontainers.image.created": "2022-03-28T17:35:51.740591902Z",
                "org.opencontainers.image.documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/index.html",
                "org.opencontainers.image.licenses": "Elastic-License-2.0",
                "org.opencontainers.image.revision": "de7261de50d90919ae53b0eff9413fd7e5307301",
                "org.opencontainers.image.source": "https://github.com/elastic/elasticsearch",
                "org.opencontainers.image.title": "Elasticsearch",
                "org.opencontainers.image.url": "https://www.elastic.co/products/elasticsearch",
                "org.opencontainers.image.vendor": "Elastic",
                "org.opencontainers.image.version": "7.17.2"
            }
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 611152148,
        "VirtualSize": 611152148,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/960d6ce20d5737a53d669426f8e6fbbe2cbe6476c525ae70b93e0ffd318ef12c/diff:/var/lib/docker/overlay2/d1b96d500325364e5681588bfc9ee7db7d42c6ea03f452bb6a4ca8a33624cf74/diff:/var/lib/docker/overlay2/71b852247a8ca1f6c1b32b1618f644c28b3955b59db24bfa9f5c522eab0624c5/diff:/var/lib/docker/overlay2/d17e6f3e9cfcf9ea23e89413b2c2fba60def9745e61cbe4a4bf08ff98be87413/diff:/var/lib/docker/overlay2/dd57f7b88164f53559240da74bfd0162fa6bc3c9613c63bc14d505ba330984d7/diff:/var/lib/docker/overlay2/04e0bde992b9ec88d3e3d5ceb8d77d9e6c46c2ca7185257df7e49a6c22e97728/diff:/var/lib/docker/overlay2/13e6562841a6a35c77614cc929821581b3128bed444a805236965797199f25d1/diff:/var/lib/docker/overlay2/2174d62ee7a666288ff466f8b0ae5df952f5cbcecdf7760ca4c045496e5b1c18/diff",
                "MergedDir": "/var/lib/docker/overlay2/5a8d244c06078f7ad558b6b4a79e38b1dfbaa894dd4f32a86c7b3b59925dd0ed/merged",
                "UpperDir": "/var/lib/docker/overlay2/5a8d244c06078f7ad558b6b4a79e38b1dfbaa894dd4f32a86c7b3b59925dd0ed/diff",
                "WorkDir": "/var/lib/docker/overlay2/5a8d244c06078f7ad558b6b4a79e38b1dfbaa894dd4f32a86c7b3b59925dd0ed/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:867d0767a47c392f80acb51572851923d6d3e55289828b0cd84a96ba342660c7",
                "sha256:b5965a019fc65d7950827a769b7a9e49b53fcef2f55ed54595a5f8b409afb00a",
                "sha256:a64b4c94a654b7617e8fdf0c56702e1347765f8e8f22abfcf0cb1e8edcb961f5",
                "sha256:339b287ac1a76218be5c6216f4698adb48142bdb0484132afd58256c1af23b5e",
                "sha256:16bb710c979fc3824ccbd473b7de579dc47bb6f5cffccc2a5753eb5a4c313617",
                "sha256:0f3c2698b8d05dad9c748902696f9f4e961f26c1388571217e54287214728fa1",
                "sha256:d9e6d48b0a3b739f2e31470066d65f1e2617541e1c9bfb67c0e3d4d02805e784",
                "sha256:96115e941eecd52363a4908b3b076abe68b1ced66e9fe7a33635254393e17c39",
                "sha256:9753a5f19e15c70e1764231cfd4b0dc8289b8f06447b48a11320d37a25d4fa90"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

构建镜像的实质,就是在一个基础镜像的基础上,堆叠一层镜像层。增加、修改操作会产生新的镜像层

最基础的镜像层是谁?对于linux镜像来说,自然是linux发行版(no core)。比如Debian的layer只有一层:

1
2
3
4
5
6
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:655ed1b7a4286ce965b8942644f665a3aeafac315f023b3d75fabdbd4be12dd0"
            ]
        },

相关命令

  • docker image
    • ls
      • -q:获取image id;
    • rm:该镜像产生的container如果存在,镜像不能被删除;
    • inspect(直接写docker inspect也行)
  • docker image rm $(docker image ls -q) -f:强制删除所有docker镜像;
  • docker build
  • docker search

container

可以使用一个image启动多个container。container关闭之后,依然存在,运行时产生的数据都在关闭后的container里,还可以再次启动。但是如果删掉container,啥都没了。

如果使用volume,即使容器没了,数据依然在。相当于外挂的硬盘。

启动

1
$ docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]

从image运行指定命令,比如:

1
docker container run -it ubuntu /bin/bash

运行了一个bash,这是容器里唯一的进程

1
2
3
4
root@19d4d7ba8603:/# ps -elf
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S root         1     0  0  80   0 -  1028 do_wai 15:01 pts/0    00:00:00 /bin/bash
0 R root        10     1  0  80   0 -  1475 -      15:01 pts/0    00:00:00 ps -elf

如果退出bash,唯一的进程结束了,container不运行进程就无法存在,所以也结束了。所以 命令(应用)结束了,container也就退出了

container结束,但container还在,可以看到多了一个关闭状态的container:

1
2
3
4
5
6
7
> docker container run -it debian ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr

> docker container ls -a
CONTAINER ID   IMAGE                                                  COMMAND                  CREATED          STATUS                      PORTS                               NAMES
ac35a6def0e3   debian                                                 "ls"                     28 seconds ago   Exited (0) 27 seconds ago                                       angry_chandrasekhar

不过启动container最好自定义一个名字,方便关闭后下次继续启动它:

1
docker run --name my-es elasticsearch

重连

可以使用Ctrl + P + Q退出而不杀死bash(只对shell有用),从而不杀死docker。然后通过docker container exec连回容器:

1
2
3
4
5
6
g> docker container exec -it 19d4d7ba8603 bash
root@19d4d7ba8603:/# ps -elf
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S root         1     0  0  80   0 -  1028 core_s 15:01 pts/0    00:00:00 /bin/bash
4 S root        11     0  0  80   0 -  1028 do_wai 15:06 pts/1    00:00:00 bash
0 R root        19    11  0  80   0 -  1475 -      15:06 pts/1    00:00:00 ps -elf

此时容器里将会有两个bash进程,说明之前的bash进程还活着。即使退出当前bash,另一个bash依然不会退出,容器还活着:

1
2
3
4
5
root@19d4d7ba8603:/# exit
exit
PS C:\Users\puppylpg> docker container ps
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS         PORTS     NAMES
19d4d7ba8603   ubuntu    "/bin/bash"   7 minutes ago   Up 7 minutes             nostalgic_greider

只有使用docker container stop来关闭它。

重启

run命令后可以指定重启策略:

  • https://docs.docker.com/config/containers/start-containers-automatically/

策略:

  • --restart on-failure[:max-retries]异常退出(exit status不为0)则重启,可以指定重试--restart always--restart unless-stopped:就算是正常退出(status code = 0),也重启。但是如果是手动关停docker container stop,则不重启。always策略在docker daemon重启之后,也会重启,unless-stopped则不会;

注意,如果是通过手动关停,都不会自动重启,尊重用户的决策显然是第一位的

If you manually stop a container, its restart policy is ignored until the Docker daemon restarts or the container is manually restarted. This is another attempt to prevent a restart loop.

  • 注意事项:https://docs.docker.com/config/containers/start-containers-automatically/#restart-policy-details

端口映射

run命令的参数,-p <host>:<port>。把容器内的端口映射为host的端口。记住hc headcount,即可记住。

默认指令:CMD vs. ENTRYPOINT

只运行docker container run <image>,但不指定运行哪个app,默认运行容器打包时Dockerfile里的默认指令

  • entrypoint是入口指令,默认是/bin/sh -c,但是没有说使用sh运行哪个命令。entrypoint也可以使用ENTRYPOINT或者--entrypoint自定义
  • cmd是命令,默认是啥,具体需要看容器镜像

**加上才是run命令真正执行的默认指令。**

可以使用docker image inspect进行探究——

比如ubuntu镜像默认的CMD是:

1
2
3
            "Cmd": [
                "bash"
            ],

如果docker container run -it ubuntu <app>不指定最后的app,就是/bin/sh -c bash。如果指定app为sleep 1,那就是/bin/sh -c sleep 1

看Config,而不是ContainerConfig:https://stackoverflow.com/a/36216632/7676237

再比如elasticsearch:7.17.2容器,不仅指定了CMD,还修改了entrypoint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
            "Env": [
                "PATH=/usr/share/elasticsearch/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "ELASTIC_CONTAINER=true"
            ],
            "Cmd": [
                "eswrapper"
            ],
            "Image": "sha256:dc8ca8053a486e05c2624fd55443495d8c2e7f67efe139db52e013edc7c4cc64",
            "Volumes": null,
            "WorkingDir": "/usr/share/elasticsearch",
            "Entrypoint": [
                "/bin/tini",
                "--",
                "/usr/local/bin/docker-entrypoint.sh"
            ],

那么docker container run elasticsearch:7.17.2,执行的就是/bin/tini -- /usr/local/bin/docker-entrypoint.sh eswrapper

而eswrapper是啥玩意儿?看一下/usr/local/bin/docker-entrypoint.sh就知道了,是一个参数:

1
if [[ "$1" != "eswrapper" ]]; then

所以CMD也未必就真的是命令,只要和entrypoint组合起来是个有意义的东西就行。比如在这里,entrypoint已经指定完了命令(脚本),cmd就是脚本的参数。

但是,也可以显式用其他指令运行容器,比如对es镜像指定bash命令:

1
2
> docker container run --name bash-es -it elasticsearch:7.17.2 bash
root@064aeabd4a2a:/usr/share/elasticsearch#

这时候使用docker container inspect bash-es检查这个container的 运行时信息,发现启动这个容器的命令是bash,而不是默认的eswrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
> docker container inspect bash-es

            "Cmd": [
                "bash"
            ],
            "Image": "elasticsearch:7.17.2",
            "Volumes": null,
            "WorkingDir": "/usr/share/elasticsearch",
            "Entrypoint": [
                "/bin/tini",
                "--",
                "/usr/local/bin/docker-entrypoint.sh"
            ],

但是还有一个问题,es的image已经修改了entrypoint,也就是说现在运行的命令是/bin/tini -- /usr/local/bin/docker-entrypoint.sh bash,而不是/bin/sh -c bash,bash是怎么起来的?

再仔细看一下/usr/local/bin/docker-entrypoint.sh这个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Allow user specify custom CMD, maybe bin/elasticsearch itself
# for example to directly specify `-E` style parameters for elasticsearch on k8s
# or simply to run /bin/bash to check the image
if [[ "$1" != "eswrapper" ]]; then
  if [[ "$(id -u)" == "0" && $(basename "$1") == "elasticsearch" ]]; then
    # centos:7 chroot doesn't have the `--skip-chdir` option and
    # changes our CWD.
    # Rewrite CMD args to replace $1 with `elasticsearch` explicitly,
    # so that we are backwards compatible with the docs
    # from the previous Elasticsearch versions<6
    # and configuration option D:
    # https://www.elastic.co/guide/en/elasticsearch/reference/5.6/docker.html#_d_override_the_image_8217_s_default_ulink_url_https_docs_docker_com_engine_reference_run_cmd_default_command_or_options_cmd_ulink
    # Without this, user could specify `elasticsearch -E x.y=z` but
    # `bin/elasticsearch -E x.y=z` would not work.
    set -- "elasticsearch" "${@:2}"
    # Use chroot to switch to UID 1000 / GID 0
    exec chroot --userspec=1000:0 / "$@"
  else
    # User probably wants to run something else, like /bin/bash, with another uid forced (Openshift?)
    exec "$@"
  fi
fi

它支持启动任意命令:如果指定的参数不是eswrapper,那指定啥命令,就运行啥命令……牛逼……

参阅

  • https://stackoverflow.com/a/21564990/7676237

所以这么一说,并不是所有的镜像都支持bash……比如我自己打包的这个镜像:

1
2
3
4
5
6
7
8
9
10
11
> docker image inspect puppylpg/youtube-dl-web:1.0.0

            "Cmd": null,
            "ArgsEscaped": true,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "/app",
            "Entrypoint": [
                "python",
                "bin/youtube-dl"
            ],

自定义了entrypoint,且没有cmd。所以它的默认命令是python bin/youtube-dl

如果我妄图以bash启动它,发现它启动的不是bash,还是它自己(python bin/youtube-dl):

1
2
3
4
5
6
7
8
9
10
11
> docker container run --name wtf -it puppylpg/youtube-dl-web:1.0.0 bash
[04/Sep/2022:16:26:04] ENGINE Listening for SIGTERM.
[04/Sep/2022:16:26:04] ENGINE Listening for SIGHUP.
[04/Sep/2022:16:26:04] ENGINE Listening for SIGUSR1.
[04/Sep/2022:16:26:04] ENGINE Bus STARTING
CherryPy Checker:
The Application mounted at '' has an empty config.

[04/Sep/2022:16:26:04] ENGINE Started monitor thread 'Autoreloader'.
[04/Sep/2022:16:26:05] ENGINE Serving on http://0.0.0.0:8080
[04/Sep/2022:16:26:05] ENGINE Bus STARTED

虽然可以inspect container,发现cmd确实是bash:

1
2
3
4
5
6
7
8
9
10
11
12
> docker container inspect wtf

            "Cmd": [
                "bash"
            ],
            "Image": "puppylpg/youtube-dl-web:1.0.0",
            "Volumes": null,
            "WorkingDir": "/app",
            "Entrypoint": [
                "python",
                "bin/youtube-dl"
            ],

但是根据上面的分析可以推测,它现在启动的命令是:python bin/youtube-dl bash,相当于给命令最后加了个bash参数,并没有什么用,所以这个镜像不可能进入bash。

相关命令

  • docker container
    • run
      • --name:自定义一个名字
      • -it:交互式(keep STDIN open)、终端;
      • --restart:重启策略
      • -p <host>:<port>
    • exec:连接回正在运行的容器,格式和run指定一样;
    • start
    • stop:向容器内的PID=1的进程发送SIGTERM,如果10s还没结束,会发送SIGKILL
    • rm
    • ls
      • -a:否则只显示运行中的容器;
    • inspect:运行时细节,比如用哪个指令启动的

command

  • docker build -t .
  • docker run -d -p : puppylpg/xxx (自动pull)
    • -w : sets the “working directory” or the current directory that the command will run from
    • -v :: mount named volume
    • -v:: bind mount
    • –network : connect to network
    • –network-alias : 类似于在这个网络上,该container的host name;
  • docker ps
  • docker stop <>
  • docker rm <>
  • docker rm -f <>
  • docker image ls
  • docker login -u puppylpg
  • docker tag getting-started-puppy puppylpg/getting-started-puppy
  • docker push puppylpg/getting-started-puppy
  • docker exec ls /
  • docker volume create :这句不写也行。使用named volume的时候,docker发现没有,会自动创建
  • docker run -dp 3000:3000 -v : getting-started
  • docker logs -f
  • docker network create

其他部分

单列成章:

本文由作者按照 CC BY 4.0 进行授权