runsisi's

technical notes

docker 基础

2019-01-12 runsisi#docker

由于有制作 Ubuntu 14.04 deb 包的需求,而我自己的机器要么是 16.04 要么是 18.04,对在这些机器上制作 14.04 的 deb 包又不是太放心,用 Vagrant 起的 Ubuntu 14.04 的虚机又实在是不堪承受编译打包这种高负荷的任务,因此不得不重新捡起几年前的那些 docker 知识,在此一并做个总结(不包括 UnionFS 等一系列的理论知识,在几年前就到处是资料,想来现在只会更多)。

宿主机以 Ubuntu 16.04 为例,CentOS 7.x 系列的操作差不太多,至于那些非 systemd 的老版本,文中的操作不一定适用。

docker 分 dockerd 守护进程和 docker 命令行两部分,docker 命令行通过 unix socket 与 dockerd 守护进程通信。由于 dockerd 创建的 unix socket 默认只有 root 和 docker 用户组的用户由读写(rw)权限,因此导致普通客户无法使用 docker 命令行,临时的解决方法使 chmod o+rw /var/run/docker.sock,当然重启 docker 服务就会失效,另一个方法使将普通用户加入 docker 用户组:

~$ sudo usermod -G docker -a runsisi
~$ id runsisi
uid=1000(runsisi) gid=1000(runsisi) groups=1000(runsisi),137(docker)
~$ groups runsisi
runsisi : runsisi docker

往用户组添加或删除用户也可以使用 gpasswd 命令:

~$ sudo gpasswd -d runsisi docker
Removing user runsisi from group docker
~$ sudo gpasswd -a runsisi docker
Adding user runsisi to group docker

注意将用户加入或移除 docker 用户组需要注销并重新登录才能生效,当然如果是 SSH 环境则只用断开当前连接然后重新连接即可。

在 OpenStack 环境中,创建虚机需要镜像或带系统的云盘,类似的,在 docker 环境中,创建容器需要有基础镜像,这个镜像可以参考网上的教程制作,也可以直接拉取网上已有的基础镜像(类似于在 Vagrant 环境下拉取别人制作的 box)。

docker 镜像库,实际上类似于我们平时的 yum/apt 源,就是一个 docker 镜像仓库,在 docker 称为 registry,既然 yum/apt 可以有本地镜像(mirror),或者制作公司或个人的源,docker 的 registry 也是同样的道理,Docker Hub (https://hub.docker.com/) 是 docker 官方提供的镜像仓库,也是 docker 命令行默认访问的镜像仓库。

在拉取镜像之前需要解决一个问题:代理,docker 要从 Docker Hub 上拉取镜像自然需要访问网络,代理显然是企业环境无法回避的问题,由于 Ubuntu 18.04 上 docker 不再使用 /etc/default/docker 作为 docker.service 服务的环境变量文件,因此这里直接使用更推荐的方法。

首先创建 docker 服务的配置文件

~$ sudo mkdir /etc/systemd/system/docker.service.d
~$ sudo vi /etc/systemd/system/docker.service.d/xxx.conf
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:12345"
Environment="HTTPS_PROXY=http://proxy.example.com:12345"
Environment="NO_PROXY=hub.example.com,hub.runsisi.com"

查看配置是否生效并重启 docker 服务:

~$ sudo systemctl daemon-reload
~$ systemctl show docker --property Environment
Environment=HTTP_PROXY=http://proxy.example.com:12345 HTTPS_PROXY=http://proxy.example.com:12345 NO_PROXY=hub.example.com,hub.runsisi.com
~$ sudo systemctl restart docker

通过 docker info 命令可以查看到这些基本的信息:

~$ docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 2
Server Version: 17.03.2-ce
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: xfs
 Dirs: 9
 Dirperm1 Supported: true
...
Kernel Version: 4.15.0-34-generic
Operating System: Ubuntu 16.04.5 LTS
OSType: linux
Architecture: x86_64
CPUs: 16
Total Memory: 188.9 GiB
Name: ceph20
ID: JOMR:VPUG:OQPK:JDMI:YT6C:7W4D:IMN7:2NRE:ZNS2:A7L6:ENS2:NZXR
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Http Proxy: http://proxy.example.com:12345
Https Proxy: http://proxy.example.com:12345
No Proxy: hub.example.com,hub.runsisi.com
Registry: https://index.docker.io/v1/
...

docker 镜像

查看本地已有的镜像,这是一个新的 docker 环境,因此没有任何镜像:

~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

查看本地的容器(-a 显示所有的容器,包括已停止的),同样的,没有任何容器:

~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

有一个 docker search 的命令,可以在命令行上搜索 Docker Hub 上的镜像,聊胜于无,只是远远没有浏览器直接访问来的直接:

~$ docker search ubuntu
NAME                                                   DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
ubuntu                                                 Ubuntu is a Debian-based Linux operating s...   8904      [OK]
dorowu/ubuntu-desktop-lxde-vnc                         Ubuntu with openssh-server and NoVNC            253                  [OK]
rastasheep/ubuntu-sshd                                 Dockerized SSH service, built on top of of...   187                  [OK]
...

从 Docker Hub(即默认镜像仓库)拉取最新的 ubuntu 14.04 的镜像:

~$ docker pull ubuntu:14.04
14.04: Pulling from library/ubuntu
aa1a66b8583a: Pull complete
aaccc2e362b2: Pull complete
a53116a2808f: Pull complete
b3a7298e318c: Pull complete
Digest: sha256:f961d3d101e66017fc6f0a63ecc0ff15d3e7b53b6a0ac500cd1619ded4771bd6
Status: Downloaded newer image for ubuntu:14.04
~$ docker images -a
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              14.04               f17b6a61de28        2 weeks ago         188 MB

拉取早前版本的一个 ubuntu 14.04(即 trustry)的镜像:

~$ docker pull ubuntu:trusty-20180420
trusty-20180420: Pulling from library/ubuntu
324d088ce065: Pull complete
2ab951b6c615: Pull complete
9b01635313e2: Pull complete
04510b914a6c: Pull complete
83ab617df7b4: Pull complete
Digest: sha256:b8855dc848e2622653ab557d1ce2f4c34218a9380cceaa51ced85c5f3c8eb201
Status: Downloaded newer image for ubuntu:trusty-20180420
~$ docker images -a
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              14.04               f17b6a61de28        2 weeks ago         188 MB
ubuntu              trusty-20180420     8cef1fa16c77        7 months ago        223 MB

docker 镜像管理的很多概念与 git 非常相似,从上面的例子,我们接触到了 pull,显然还会有一个对应的 push 命令。每个镜像有一个唯一的 id,从上面的两个 pull 的例子可以看到,docker 镜像的名字分为两部分:镜像名字和标签(name:tag),中间以冒号分隔,tag 即类似于版本号、别名(alias)之类的东西,多个 name:tag 可以实际指向底层的同一个镜像,可以通过 docker tag 命令为本地的镜像创建新的标签:

~$ docker tag ubuntu:14.04 ubuntu:14.04-tag1
~$ docker tag f17b6a61de28 ubuntu:14.04-tag2
~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              14.04               f17b6a61de28        2 weeks ago         188 MB
ubuntu              14.04-tag1          f17b6a61de28        2 weeks ago         188 MB
ubuntu              14.04-tag2          f17b6a61de28        2 weeks ago         188 MB
ubuntu              trusty-20180420     8cef1fa16c77        7 months ago        223 MB

三个不同的 tag 对应了底层同一个 image(image id 相同),显然,这些操作和为 git tag 的行为一模一样。

删除镜像使用 docker rmi 命令(rm 被用来了作为删除容器的命令),显然只要还有 tag 在引用底层的镜像,镜像就不会被真正的删除:

~$ docker rmi ubuntu:14.04-tag2 ubuntu:14.04
Untagged: ubuntu:14.04-tag2
Untagged: ubuntu:14.04
~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              14.04-tag1          f17b6a61de28        2 weeks ago         188 MB
ubuntu              trusty-20180420     8cef1fa16c77        7 months ago        223 MB

也可以指定镜像 id 进行删除:

~$ docker rmi f17b6a61de28
Untagged: ubuntu:14.04-tag1
Untagged: ubuntu@sha256:f961d3d101e66017fc6f0a63ecc0ff15d3e7b53b6a0ac500cd1619ded4771bd6
Deleted: sha256:f17b6a61de28594fb3ec53b1cca7164fba66357d1635b414eeed4d586744342e
Deleted: sha256:62faa9fad606573b982c0444778746244947829aa8ebefbf29b3a5291875dc84
Deleted: sha256:5848a5ca21d07333dbdf428bbdde15d5c7cecc7614b24562b49b205d8d20199a
Deleted: sha256:cd509aa64a17350b03bf6af7f41d849fc273a0f2c9d1a309e897380617fca46e
Deleted: sha256:960c7c5516b277c5c23644b2cfb53d0106543eace96d517141611fa34e1b957c
~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              trusty-20180420     8cef1fa16c77        7 months ago        223 MB

如果有多个 tag 引用这个镜像,则直接使用镜像 id 进行删除会报错:

~$ docker rmi f17b6a61de28
Error response from daemon: conflict: unable to delete f17b6a61de28 (must be forced) - image is referenced in multiple repositories

docker 容器

使用 docker run 命令可以从 docker 镜像创建一个容器并运行指定的命令,但在创建容器之前需要理解容器的基本运行方式:

  1. 容器,实际上是一个进程容器,即隔离的进程运行环境 + 进程自身,在最初的容器哲学里,一个容器只运行一个应用进程,一组应用容器组成一个应用服务,这就是所谓的微服务架构;
  2. 容器有一个命令入口(entry),创建容器就会运行这个入口指定的应用进程,即容器进入运行状态;
  3. 一旦入口指定的应用进程退出,容器的运行就停止了,类似于编程语言中 main 函数返回,整个进程就结束了;

结合下面的实际操作应该可以比较容易的理解。

使用 docker create 命令可以从 docker 镜像创建一个容器,然后使用 docker start 可以启动该容器,但我们一般使用 docker run 直接创建并启动容器。

基于前面从 Docker Hub 拉取的 ubuntu 14.04 的镜像运行一个 ubuntu 14.04 的容器:

~$ docker run ubuntu:14.04

使用 docker ps 查看当前正在运行的容器:

~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

为何没有显示任何的信息?因为 docker ps 只能列出正在运行的容器,加上 -a 选项才能把已停止运行的容器也列出来:

~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
ca40774fbb1e        ubuntu:14.04        "/bin/bash"         6 seconds ago       Exited (0) 5 seconds ago                       unruffled_payne

注意上面的状态(STATUS)一栏显示该容器已经退出,即停止运行了。同时注意到上面的名字(NAMES)一栏,是一个随机字符串,我们可以在创建容器时指定名字:

~$ docker run --name xxx-14.04 ubuntu:14.04
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
1044b95a3b99        ubuntu:14.04        "/bin/bash"         3 seconds ago        Exited (0) 2 seconds ago                            xxx-14.04
ca40774fbb1e        ubuntu:14.04        "/bin/bash"         About a minute ago   Exited (0) About a minute ago                       unruffled_payne

不同的容器不能重名,显然这很好理解,因此下面针对容器的操作既可以使用容器 id 也可以使用容器名字:

~$ docker run --name xxx-14.04 --rm ubuntu:14.04
docker: Error response from daemon: Conflict. The container name "/xxx-14.04" is already in use by container 1044b95a3b99f10ddb44b8ec67acad6d9b44d60c390f2d3e7a11d435a1313b6c. You have to remove (or rename) that container to be able to reuse that name..

容器内的环境变量,可以在创建时用 -e, --env 选项指定(或者通过 --env-file 选项从文件中读取):

$ docker run -e http_proxy -e https_proxy -e no_proxy -it alpine

如果希望在容器运行结束的时候自动删除容器,可以增加 --rm 选项:

~$ docker run --name xxx-14.04-2 --rm ubuntu:14.04
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
1044b95a3b99        ubuntu:14.04        "/bin/bash"         34 seconds ago       Exited (0) 33 seconds ago                           xxx-14.04
ca40774fbb1e        ubuntu:14.04        "/bin/bash"         About a minute ago   Exited (0) About a minute ago                       unruffled_payne

删除容器,可以指定名字或者 id 进行删除:

~$ docker rm xxx-14.04 ca40774fbb1e
xxx-14.04
ca40774fbb1e
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

容器必须是已停止状态才能被删除,或者需要加上 -f 选项进行强制删除(注意例子中第二次尝试能删除成功是因为加了 -f 选项的原因,并不是因为指定了容器名字):

~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
0981f9e3396f        ubuntu:14.04        "/bin/bash"         2 seconds ago       Up 1 second                             nostalgic_newton
~$ docker rm 0981f9e3396f
Error response from daemon: You cannot remove a running container 0981f9e3396f9c5c7b0961dd6725efc1be45fcb05a492f4803755333c6d32b1d. Stop the container before attempting removal or use -f
~$ docker rm -f nostalgic_newton
nostalgic_newton
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

使用 docker stop 命令停止正在运行的容器:

~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
22f4a314cd98        ubuntu:14.04        "/bin/bash"         2 minutes ago       Up 2 minutes                            gracious_bose
~$ docker stop 22f4a314cd98
22f4a314cd98
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
22f4a314cd98        ubuntu:14.04        "/bin/bash"         3 minutes ago       Exited (0) 3 seconds ago                       gracious_bose

既然存在 stop,就存在 start 和 restart:

~$ docker start 22f4a314cd98
22f4a314cd98
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
22f4a314cd98        ubuntu:14.04        "/bin/bash"         8 minutes ago       Up 1 second                             gracious_bose
~$ docker restart 22f4a314cd98
22f4a314cd98
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
22f4a314cd98        ubuntu:14.04        "/bin/bash"         9 minutes ago       Up 2 seconds                            gracious_bose

注意到,前面所有的容器通过 docker ps 查看到它们的命令行(COMMAND)都是 /bin/bash,这实际上是因为 ubuntu:14.04 这个镜像默认的容器命令入口是 /bin/bash 程序,我们可以显式的指定这个入口:

~$ docker run ubuntu:14.04 /bin/bash
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
2bf826b4488d        ubuntu:14.04        "/bin/bash"         3 seconds ago       Exited (0) 2 seconds ago                       dazzling_einstein
~$ docker run ubuntu:14.04 lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.5 LTS
Release:        14.04
Codename:       trusty
~$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
a3cf4fd059d3        ubuntu:14.04        "lsb_release -a"    7 seconds ago       Exited (0) 6 seconds ago                        eloquent_kirch
2bf826b4488d        ubuntu:14.04        "/bin/bash"         25 seconds ago      Exited (0) 24 seconds ago                       dazzling_einstein

注意我们前面说过的,一旦容器的命令入口指定的进程退出,容器就会退出,显然这两个容器的命令入口都是一运行就很快结束的进程。

在平常的 docker 使用过程中,我们通常会把容器当虚机使用:

~$ docker run -it ubuntu:14.04 /bin/bash
root@5fe61440c1d2:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.5  0.0  18192  3300 ?        Ss   01:58   0:00 /bin/bash
root        16  0.0  0.0  15580  2060 ?        R+   01:58   0:00 ps aux

或者:

~$ docker run -dt ubuntu:14.04 /bin/bash
2284a56d572fdc4e1b19de19a812032979b44e8b0a958f6d101a7e498d82ba46
~$ docker exec -it 2284a5 /bin/bash
root@2284a56d572f:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.3  0.0  18180  3152 ?        Ss+  01:55   0:00 /bin/bash
root        16  1.5  0.0  18188  3256 ?        Ss   01:56   0:00 /bin/bash
root        31  0.0  0.0  15580  2176 ?        R+   01:56   0:00 ps aux

或者:

~$ docker run -idt ubuntu:14.04 /bin/bash
5c535902a65c1350eaaa08025b9b5652ab45e2afb5fee4741d1a4ee018ac4067
~$ docker attach 5c
root@5c535902a65c:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.4  0.0  18184  3156 ?        Ss   02:36   0:00 /bin/bash
root        16  0.0  0.0  15580  2128 ?        R+   02:36   0:00 ps aux

或者:

~$ docker run -id ubuntu:14.04 /bin/bash
cc69942631ffd66e6a67b4722bbc535991ccff72ea16a83b71997b3f28926250
~$ docker attach cc
ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  17972  2820 ?        Ss   02:37   0:00 /bin/bash
root         8  0.0  0.0  15580  2216 ?        R    02:38   0:00 ps aux

如果使用 -t 为容器进程分配了一个 tty,attach 之后(不管是 docker attach 命令,还是 docker run 的 -a 参数导致)需要使用 ctrl+p,ctrl+q 组合键 detach 退出,如果直接 ctrl+d 或者 exit 退出,会导致容器进程退出从而导致容器停止运行(当然可以通过 docker start 再次启动)。

docker attach

https://docs.docker.com/engine/reference/commandline/attach/

要理解上面的命令行参数,关键在如下的四个选项(-a 在上面的例子中没有使用到,但实际上是非常重要的选项):

-a, --attach list                           Attach to STDIN, STDOUT or STDERR (default [])
-i, --interactive                           Keep STDIN open even if not attached
-t, --tty                                   Allocate a pseudo-TTY
-d, --detach                                Run container in background and print container ID

这几个选项的内部处理逻辑:

  1. -a,attach 当前 docker 命令行的 stdio 作为容器命令入口进程的 stdio,可以同时 attach 多个标准 io 流(io 流的名字大写或小写无所谓),如:
~$ docker run -a stdout -i ubuntu:14.04 /bin/bash
~$ docker run -a stdout -a STDERR -i ubuntu:14.04 /bin/bash
~$ docker run -a stdout -a STDERR -a stdin ubuntu:14.04 /bin/bash

为何我们通常不使用 -a 选项?原因很简单,因为当我们没有显式的指定 -a 选项时,会默认 attach STDOUT 和 STDERR,相当于容器进程的标准输出流(stdout)和标准错误流(stderr)都会打印到 docker 命令运行所在的终端;

  1. -i,这个选项有两个作用,一是为容器进程创建标准输入的 pipe 管道,这样容器进程才有可能接收外部的输入(即 attach STDIN 才有可能),二是类似于显式指定了 -a STDIN 选项,即将当前 docker 命令行的标准输入作为容器进程的标准输入;
  2. -t,将当前 docker 命令行的 stdout 同时作为容器进程 stdout 和 stderr 的目的,如果不指定,则在 dockerd 一侧以 mux 的方式将 stdout 和 stderr 作为一条流在同一条 http/tcp 链路上返回,然后在 docker 命令行侧分别拷贝到 docker 命令行的 stdout 和 stderr(当然讨论的前提是将 docker 命令行的 stderr 进行 attach),此外,该选项最重要的一个作用,就是提供交互式的终端:
~$ docker run -it ubuntu
root@a3695af5c941:/# echo $-
himBH

~$ docker run -i ubuntu
echo $-
hB

~$ docker run -i ubuntu /bin/dash
echo $-
s

~$ docker run -it ubuntu /bin/dash
# echo $-
smi

How can I check in bash if a shell is running in interactive mode?

https://serverfault.com/questions/146745/how-can-i-check-in-bash-if-a-shell-is-running-in-interactive-mode

Interactive and non-interactive shells and scripts

https://www.tldp.org/LDP/abs/html/intandnonint.html

Which stream does Bash write its prompt to?

https://unix.stackexchange.com/questions/20826/which-stream-does-bash-write-its-prompt-to

  1. -d,不 attach 任何 stdio,容器进程在后台运行,后面如果有需要,可以 docker exec 在容器内创建 bash 之类的新的容器进程,也可以 docker attach 命令行的 stdio 到容器进程上;

pid 1 问题

A process running as PID 1 inside a container is treated specially by Linux: it ignores any signal with the default action. So, the process will not terminate on SIGINT or SIGTERM unless it is coded to do so.

在这种情况下,我们可以指定 --init 选项:

~$ docker run --init -it ubuntu:14.04
root@c467ef576c1c:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  4.0  0.0   1148     4 ?        Ss   01:39   0:00 /dev/init -- /bin/bash
root         7  0.0  0.0  18192  3360 ?        S    01:39   0:00 /bin/bash
root        17  0.0  0.0  15580  2052 ?        R+   01:39   0:00 ps aux
root@c467ef576c1c:/# /dev/init -h
init (tini version 0.13.0)
Usage: init [OPTIONS] PROGRAM -- [ARGS] | --version

Execute a program under the supervision of a valid init process (init)
...

My process became PID 1 and now signals behave strangely

https://hackernoon.com/my-process-became-pid-1-and-now-signals-behave-strangely-b05c52cc551c

docker run in foreground mode

https://docs.docker.com/engine/reference/run/#foreground

容器内部环境变量

有多种方式定义容器内部的环境变量:

  1. Dockerfile 中通过 ENV 指令定义;
  2. docker run 时通过 -e, --env 命令行选项指定;

既可以以 kv 键值对的形式直接指定:

~# docker run -it --rm -e http_proxy=http://10.120.155.xxx:12345 -e https_proxy=http://10.120.155.xxx:12345 ccc

也可以指定本机的环境变量:

~# docker run -it --rm -e http_proxy -e https_proxy ccc
  1. docker run 时通过 --env-file 命令行选项指定从本地文件中读取;

容器监听端口

上面通过 Dockerfile 创建的两个镜像,其命令入口都是 sshd,这实际上是一个支持 ssh 登陆的容器,如果要让容器外部能够访问容器内部的服务,需要在容器所在主机上建立端口映射:

~$ docker run -d -p 4567:22 runsisi:xxx
18bb357258027933fb235796e40f302dc3f8b3178a36031cadda51fbe509a82c

上面的 -p 选项将容器内的 22 端口映射到主机的 4567 端口。

然后在主机上访问容器内的 ssh 服务(注意使用的是主机的 ip):

~$ ssh -p 4567 10.120.155.xx
The authenticity of host '[10.120.155.xx]:4567 ([10.120.155.xx]:4567)' can't be established.
ECDSA key fingerprint is SHA256:HaBLdOfShk42e++O2ZX+rF4bPSDfLvNA6PCDsKyyHXc.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[10.120.155.xx]:4567' (ECDSA) to the list of known hosts.
runsisi@10.120.155.40's password:
Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.19.0-43-generic x86_64)
...
runsisi@18bb35725802:~$

容器网络

查看 docker 内置的网络支持:

~$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
3de40fb3033e        bridge              bridge              local
faf6c0583806        host                host                local
2396df63ec39        none                null                local

容器启动时允许通过 --network string 选项指定容器网络,如果使用 host 类型的网络,则容器进程就和运行在 host 机器一样,此时不允许使用 -p 选项进行端口映射。

容器网络是一个很大的概念,更多的资料可以参考如下的链接:

https://docs.docker.com/network/

命令行 tips

在使用 docker 如果不显式指定 tag,默认使用 latest 作为 tag 名,即如果在 docker pull 拉取镜像、docker run 基于镜像创建容器、docker rmi 删除镜像时不显式指定一个 tag(即省略 :tag 部分),则会自动添加一个 latest 作为 tag。

在使用需要指定镜像 id 或 容器 id 的命令时,只要不存在歧义,则与 git 的 commit id 一样,该 id 字符串可以只截取前面的子字符串。

如果定义了环境变量 DOCKER_HIDE_LEGACY_COMMANDS 则 docker 的命令行会有如下的变化:

~$ docker verb ->
~$ docker noun verb

举例如下:

~$ docker images ->
~$ docker image ls
~$ docker stop ->
~$ docker container stop

代码实现

命令行选项实现变迁:

https://github.com/moby/moby
moby/runconfig/opts/parse.go
moby/cli/command/container/opts.go
https://github.com/docker/cli
cli/command/container/opts.go

Clean some stuff from runconfig that are cli only
https://github.com/moby/moby/pull/29683
Remove cmd/docker and other directories in cli/ in accordance with the new Moby project scope
https://github.com/moby/moby/pull/32694

docker run (v17.03.2-ce,仅 start 部分)

// https://github.com/moby/moby.git
cmd/docker/docker.go ->
cli/command/container/run.go
cmd/dockerd/docker.go ->
client/container_start.go
api/server/router/container/container_routes.go
daemon/start.go
libcontainerd/client_unix.go
libcontainerd/container_unix.go

docker run (7bfd8a7a)

大概流程如图所示:

docker

代码实现如下:

--- 0. docker run
[github.com/docker/cli]
cli/command/container/run.go
func runContainer

--- 1. create
[github.com/docker/cli]
cli/command/container/create.go
func createContainer

[github.com/moby/moby]
client/container_create.go
func (cli *Client) ContainerCreate
api/server/router/container/container_routes.go
func (s *containerRouter) postContainersCreate
daemon/create.go
func (daemon *Daemon) ContainerCreate
func (daemon *Daemon) containerCreate
func (daemon *Daemon) create
daemon/continer.go
func (daemon *Daemon) Register
container/stream/streams.go
func (c *Config) NewInputPipes

--- 2. attach
[github.com/docker/cli]
cli/command/container/run.go
func attachContainer

[github.com/moby/moby]
client/container_attach.go
func (cli *Client) ContainerAttach
api/server/router/container/container_routes.go
func (s *containerRouter) postContainersAttach
daemon/attach.go
func (daemon *Daemon) ContainerAttach
container/stream/attach.go
func (c *Config) AttachStreams
func (daemon *Daemon) containerAttach
container/stream/attach.go
func (c *Config) CopyStreams

[github.com/docker/cli]
cli/command/container/hijack.go
func (h *hijackedIOStreamer) stream

--- 3. start
[github.com/moby/moby]
client/container_start.go
func (cli *Client) ContainerStart
api/server/router/container/container_routes.go
func (s *containerRouter) postContainersStart
daemon/start.go
func (daemon *Daemon) ContainerStart
func (daemon *Daemon) containerStart
libcontainerd/client_daemon.go
func (c *client) Create

[github.com/containerd/containerd]
client.go
func (c *Client) NewContainer

[github.com/moby/moby]
libcontainerd/client_daemon.go
func (c *client) Start
t, err = ctr.ctr.NewTask
  func (c *client) createIO
  func (container *Container) InitializeStdio
    container/stream/streams.go
    func (c *Config) CopyToPipe
t.Start(ctx)

参考资料

Docker run vs exec: reference and key differences

https://medium.com/the-code-review/docker-run-vs-exec-deep-dive-into-their-differences-19a1041735a3

Docker ARG, ENV and .env - a Complete Guide

https://vsupalov.com/docker-arg-env-variable-guide/

Docker Tip #33: Should You Use the Volume or Mount Flag?

https://nickjanetakis.com/blog/docker-tip-33-should-you-use-the-volume-or-mount-flag