k8s

Docker入门基础之Client操作

目录

在有了之前的Dockerfile基础知识后,我们知道该如何编写一个简单的Dockfile,那么如果通过docker client命令来进行Image的构建呢,本节就来阐述一些docker client的一些使用;

在介绍docker client的各个操作命令之前先说明一下docker client操作的分类,按照操作的对象不同,可以分为以下几个大类:

你会发现docker client的很多命令的功能是重复的,例如docker build和docker image build,docker run和docker container run,参考Stack Overflow得知,这是由于历史原因导致的;最早docker的命令并没有按照操作对象进行分类管理,例如docker ps,docker build等,后来,docker client变得更加结构化,收敛到了几大类,同时为了向后兼容,所以看上去docker client的命令看上去比较乱;

1. docker镜像管理

docker build

前面在「基于Dockerfile的镜像构建」一节中,已经简单的说明了如何通过docker build进行镜像的构建,这里参考docker build手册再详细说明一下:

docker build 的语法,如下:

shell
docker build [OPTIONS] PATH | URL

docker build通过Dockerfile和构建上下文进行镜像的构建;构建的上下文是由PATH或URL指定位置的一个文件列表集合;所有构建过程都可以访问构建上下文的任何文件;

基于本地上下文构建

PATH用来指定本地文件系统的路径,会将该路径的文件列表作为构建上下文发送到docker daemon进行构建;如果没有指定Dockerfile会在PATH路径的根路径下查找;构建上下文是递归处理的,所以PATH会包含其所有的子目录,所以一般都是将Dockerfile放入一个空目录中,只添加构建所有需要的文件;

不要将根目录/作为PATH来进行构建,这将会把文件系统所有文件全部作为构建上下文传递给Docker daemon进行构建

基于远端上下文构建

URL指向远端的上下文资源,URL可以指向三种资源:

  • Git Repo

URL可以指向Git仓库;构建开始之前,Git Repo会被git pull拉取到本地的临时目录,Git仓库的所有的submodule会自动recursively拉取;当然提交历史会被忽略;然后,Git仓库的整个目录会被发送到docker daemon作为上下文进行镜像的构建;

Git Repo的URL的格式可以包含三个部分:仓库地址,分支名和用于构建上下文的仓库相对路径;如下是Repo URL的语法:

Build Syntax Suffix Commit Used Build Context Used
myrepo.git refs/heads/master /
myrepo.git#mytag refs/tags/mytag /
myrepo.git#mybranch refs/heads/mybranch /
myrepo.git#pull/42/head refs/pull/42/head /
myrepo.git#:myfolder refs/heads/master /myfolder
myrepo.git#master:myfolder refs/heads/master /myfolder
myrepo.git#mytag:myfolder refs/tags/mytag /myfolder
myrepo.git#mybranch:myfolder refs/heads/mybranch /myfolder
  • Tarball 文件

URL可以指向远端的压缩文件,docker build的时候会将URL发送到docker daemon ,由Docker daemon负责压缩包的下载和解压,并将其作为docker build的构建上下文进行构建;Tarbal文件必须是标准的tar文件格式,且压缩格式为xz,bzip2,gzip或者无压缩;使用语法格式如下:

text
$ docker build http://server/context.tar.gz
  • 文本文件

如果 URL 指向一个纯文本文件,那么该文本文件会被当做Dockerfile发送给docker daemon 用来进行镜像构建,且这种情况的构建是没有任何上下文的;

dockerfile
$ docker build http://server/Dockerfile.txt

这里和传入文本文件类似的,docker build支持从STDIN中读取数据,将其作为Dockerfile进行镜像的构建;如下:

dockerfile
$ docker build - < Dockerfile
$ cat Dockerfile|docker build -

docker build在将PATH所有文件列表,发送构建上下文的时候,如果根路径有.dockerignore文件,会将其中的文件忽略,不发送到docker daemon,以加速构建过程;

构建参数说明

下面简单的介绍一下docker build常用的OPTIONS选项,支持的所有OPTIONS列表可参考官方手册:

这里需要注意的是:对container有影响的参数,在docker build过程中只影响构建过程,并不会对最终构建的镜像有何影响;与之相对应的docker run可以对运行时的container的系统参数进行实际修改;

  • -f, --file

指定Dockerfile的路径,如果不指定,默认为PATH/Dockerfile,如果构建上下文为URL,那么Dockerfile就是相对URL构建上下文的根路径;如下:

shell
$ ls .
Dockerfile  helloworld  helloworld.go
$ docker build -f Dockerfile . # 等价于 docker build .
Sending build context to Docker daemon  2.045MB
Step 1/3 : FROM public.ecr.aws/lts/ubuntu
latest: Pulling from lts/ubuntu
63acfa137832: Pull complete 
Digest: sha256:62b8f60c5c8e1717f460bb7af05e558b74feb8ac460ff2abbdd3a98becdc15ce
Status: Downloaded newer image for public.ecr.aws/lts/ubuntu:latest
 ---> 3003ca74986b
Step 2/3 : COPY helloworld .
 ---> 36ae209bc43e
Step 3/3 : CMD ./helloworld
 ---> Running in bb925ccc1708
Removing intermediate container bb925ccc1708
 ---> 9576343a7c1d
Successfully built 9576343a7c1d
  • -t, --tag

为构建生成的镜像指定仓库名和tag,格式为name:tag, tag不写默认是latest;如果在镜像构建的时候不指定-t选项,构建的镜像的仓库名和tag都是<none>,如下,是上面执行docker build .构建的镜像的信息:

shell
$ docker images
REPOSITORY                                        TAG                 IMAGE ID            CREATED             SIZE
<none>                                            <none>              9576343a7c1d        4 minutes ago       79.9MB

如下在构建镜像的时候,指定镜像的仓库名和tag:

shell
$ docker build -t ccr.ccs.tencentyun.com/ns_gz_walkerdu/helloworld:v1 .
$ docker images
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
ccr.ccs.tencentyun.com/ns_gz_walkerdu/helloworld   v1                  9576343a7c1d        41 minutes ago      79.9MB

# 不写tag,默认构建的tag为latest
$ docker build -t ccr.ccs.tencentyun.com/ns_gz_walkerdu/helloworld .
$ docker images
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
ccr.ccs.tencentyun.com/ns_gz_walkerdu/helloworld   latest              9576343a7c1d        45 minutes ago      79.9MB
ccr.ccs.tencentyun.com/ns_gz_walkerdu/helloworld   v1                  9576343a7c1d        45 minutes ago      79.9MB
  • --ulimit

用来将构建过程中的每个step的container都设置ulimit系统资源的限制参数,参数的格式如下:

shell
<type>=<soft limit>[:<hard limit>]

如下,设置镜像中资源的栈大小,不过变更只影响构建过程,并不会对最终构建的镜像有何影响:

shell
$ docker build -t test/helloworld --ulimit stack=10240:10240 .

各种资源的类型名,在系统的/etc/security/limits.conf中有定义,如下:

shell
$ vim /etc/security/limits.conf
#<item> can be one of the following:
#        - core - limits the core file size (KB)
#        - data - max data size (KB)
#        - fsize - maximum filesize (KB)
#        - memlock - max locked-in-memory address space (KB)
#        - nofile - max number of open file descriptors
#        - rss - max resident set size (KB)
#        - stack - max stack size (KB)
#        - cpu - max CPU time (MIN)
#        - nproc - max number of processes
#        - as - address space limit (KB)
#        - maxlogins - max number of logins for this user
#        - maxsyslogins - max number of logins on the system
#        - priority - the priority to run user process with
#        - locks - max number of file locks the user can hold
#        - sigpending - max number of pending signals
#        - msgqueue - max memory used by POSIX message queues (bytes)
#        - nice - max nice priority allowed to raise to values: [-20, 19]
#        - rtprio - max realtime priority
  • --build-arg

前面在构建指令里有一个ARG指令,用来申明一个变量可以在后面的构建过程中使用的,该变量值,可以通过docker build命令在Image构建过程中进行修改; --build-arg传入的变量必须已经在Dockfile中通过ARG 指令定义,否则会在构建过程中触发Warning;具体可见ARG指令介绍的一节;

  • --add-host

在构建过程中,向构建的container的/etc/hosts中写入一个静态的host解析记录,注意:这里host的变更只影响构建过程,并不会对最终构建的镜像有何影响;可以参考github issue,如下:

shell
$ docker build -t test/helloworld --add-host=walkerdu:1.1.1.1 .

Step 5/6 : RUN cat /etc/hosts
 ---> Running in 85c90a53ae05
127.0.0.1       localhost
...
1.1.1.1 walkerdu
...

运行容器后,查看/etc/hosts的结果如下,docker build添加的host并没有影响到最终的镜像:

shell
$ docker run -d test/helloworld
$ docker exec -it 676c35929eed cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.10.4    db7c217d1b36
  • --target

在含有多个构建stage的Dockerfile构建的时候,可以使用--target选项来指定只进行某个stage的构建,其他的构建stage会被丢弃,如下:

dockerfile
FROM debian AS build-env
# ...
FROM alpine AS production-env
# ...

执行只构建第一个build-envstage的构建指令如下:

shell
$  docker build -t mybuildimage --target build-env .

docker tag

docker tag用于为一个Image对象打一个tag,其实就是给一个Image起另一个名字而已,格式如下:

shell
$ docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]

Image的名字是格式一般有两部分组成:注册中心和镜像名;中间用斜线分隔;注册中心名字是标准的DNS格式,可以包含主机的端口号;

Image的tag必须是由ASCII组成,可以包含:大小写字母,数字,下划线,点号,破折号;但是不能以点号,破折号开始,且长度不能超过128个字符长度;

打tag可以使用source image的Image ID,Image Name,如下简单示例:

shell
# 通过Image Name进行打tag操作
$ docker tag test/helloworld helloworld_tag1:v1
$ docker images
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
helloworld_tag1                                    v1                  156571ba9cfc        7 days ago          79.9MB
test/helloworld                                    latest              156571ba9cfc        7 days ago          79.9MB

# 通过Image ID进行打tag操作
$ docker tag 156571ba9cfc helloworld_tag2:v2
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
helloworld_tag1                                    v1                  156571ba9cfc        7 days ago          79.9MB
helloworld_tag2                                    v2                  156571ba9cfc        7 days ago          79.9MB
test/helloworld                                    latest              156571ba9cfc        7 days ago          79.9MB

# tag指向私有注册中心
$ docker tag 156571ba9cfc walkerdu.com:5050/test/helloworld:v1

docker save

docker的Image是由docker daemon集中化进行管理的,我们可以通过docker save/docker load进行镜像的迁移工作;docker save用于将Image的各个层的数据输出到一个tar的归档文件中;然后在需要的地方进行恢复;docker save的语法格式如下:

shell
$ docker save [OPTIONS] IMAGE [IMAGE...]

docker save默认会将tarred的镜像数据(所有layer的数据)输出到STDOUT中,可以通过-o, --output指定输出的tar文件名,也可以用重定向,管道进行操作,如下:

shell
$ docker save test/helloworld > helloworld.tar
$ tar -tvf helloworld.tar 
-rw-r--r-- 0/0            2170 2022-06-27 13:50 156571ba9cfca54ccb1492a20e9adc95318920559b679fcc40305e02b6c1e10e.json
drwxr-xr-x 0/0               0 2022-06-27 13:50 68c3a70ebf6e9af074b0b891cfb9fe6b417d2fc074cc29c90f842868296335a7/
-rw-r--r-- 0/0               3 2022-06-27 13:50 68c3a70ebf6e9af074b0b891cfb9fe6b417d2fc074cc29c90f842868296335a7/VERSION
-rw-r--r-- 0/0             406 2022-06-27 13:50 68c3a70ebf6e9af074b0b891cfb9fe6b417d2fc074cc29c90f842868296335a7/json
-rw-r--r-- 0/0        80342528 2022-06-27 13:50 68c3a70ebf6e9af074b0b891cfb9fe6b417d2fc074cc29c90f842868296335a7/layer.tar
drwxr-xr-x 0/0               0 2022-06-27 13:50 d3d1560b519c71f54e868a3a0818f0b844c938103707543661e0ec39175b3a47/
-rw-r--r-- 0/0               3 2022-06-27 13:50 d3d1560b519c71f54e868a3a0818f0b844c938103707543661e0ec39175b3a47/VERSION
-rw-r--r-- 0/0            1301 2022-06-27 13:50 d3d1560b519c71f54e868a3a0818f0b844c938103707543661e0ec39175b3a47/json
-rw-r--r-- 0/0         2060800 2022-06-27 13:50 d3d1560b519c71f54e868a3a0818f0b844c938103707543661e0ec39175b3a47/layer.tar
-rw-r--r-- 0/0             288 1970-01-01 08:00 manifest.json
-rw-r--r-- 0/0              98 1970-01-01 08:00 repositories

docker load

和docker save相对的是docker load,用于从tar归档文件中加载Image到docker host中进行集中化管理;docker load的语法格式如下:

shell
$ docker load [OPTIONS]

docker load支持从tar归档文件(支持gzip,bzip2,xz的压缩格式)或者STDIN进行镜像的恢复,恢复的Images信息包括repo名字和tag信息;docker load支持的OPTIONS有两个,如下:

  • -i, --input:输入的tar归档文件;
  • -q, --quiet:忽略load过程的输出信息

如下测试:

shell
$ docker load -i helloworld.tar
Loaded image: test/helloworld:latest

当load的时候出现Image的repo和tag和docker host存在重复的时候,会覆盖之前旧的Images;

docker export

docker export是将一个Container的文件系统导出为一个tar归档文件,命令格式如下,OPTION只支持-o, --output选项:

shell
$ docker export [OPTIONS] CONTAINER

和docker save的差别是:

  • docker save的操作对象是一个Image,docker export的操作对象是一个Container;

  • docker save会将Image的所有Layer信息,repo和tag信息都保存为tar归档文件;dokcer export会将Container的平铺开的整个文件系统进行归档打包,打包内容不包含任何的对应Image的结构信息;

下面是docker export测试,我们可以看到导出的归档文件就是整个文件系统,而docker save导出的结果在前面一节我们看了是包含Image层级信息的归档文件;

shell
$ docker export 7bd9f0152dff > helloworld_container.tar
# 可以看到归档文件是整个文件系统
$ tar -tvf helloworld_container.tar |head
-rwxr-xr-x 0/0               0 2022-07-06 07:18 .dockerenv
lrwxrwxrwx 0/0               0 2022-06-11 19:49 bin -> usr/bin
drwxr-xr-x 0/0               0 2022-04-18 18:28 boot/
drwxr-xr-x 0/0               0 2022-07-06 07:18 dev/
-rwxr-xr-x 0/0               0 2022-07-06 07:18 dev/console
drwxr-xr-x 0/0               0 2022-07-06 07:18 dev/pts/
drwxr-xr-x 0/0               0 2022-07-06 07:18 dev/shm/
drwxr-xr-x 0/0               0 2022-07-06 07:18 etc/
-rw------- 0/0               0 2022-06-11 19:49 etc/.pwd.lock
-rw-r--r-- 0/0            3028 2022-06-11 19:50 etc/adduser.conf

docker export导出的时候,不会将Container的volumes挂载的数据进行归档,只会归档底层挂载的目录;可以参考如何备份,恢复和迁移数据volumes

docker import

docker import用于从tar归档文件或者标准输入中创建一个新的Image;这里和docker load的区别是docker save & docker export之间的区别导致的;stackoverflow上也有关于docker load and import difference的讨论;

  • docker load会加载归档文件中的镜像,包括repo和tag名字,不做任何修改,而docker import会创建新的Image,如果不指定repo和tag名字,会默认为none;
  • docker image支持从docker save和docker export两类导出的归档数据中进行Image的创建;

docker import的格式如下:

shell
$ docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

docker import支持从三种源进行Image的构建,分别是:本地文件,URL,STDIN;

shell
# docker import helloworld.tar 
sha256:b8341bd5729572eca48873962f63bb4307ec1369c59d18263d4526c4728f1565
[root@VM-131-137-centos /data/home/walkerdu/soft/docker]# docker images
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
<none>                                             <none>              b8341bd57295        8 seconds ago       82.4MB

docker history

docker history用于查看一个Image的各个Layer的信息,Image是由一个个的Layer叠加而成的;每一层Layer都是上层Layer的Parent,docker history就可以展示这些Layer信息,及其是如何构建的,如下是命令的格式:

shell
$ docker history [OPTIONS] IMAGE

支持的选项有:

  • --format:格式化输出每一层的Image信息;具体参数值可以参考官方文档;
  • -H, --human:每一层的Image大小的输出用可读格式,默认为true;
  • --no-trunc:输出不截断,显示Image的完整的所有信息;
  • -q, --quiet:只输出每层的Image ID;

如下测试:

shell
$ docker history  test/helloworld
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
156571ba9cfc        8 days ago          /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "./he…   0B                  
a8531a366e53        8 days ago          /bin/sh -c cat /etc/security/limits.conf        0B                  
7a7dec1369d8        9 days ago          /bin/sh -c #(nop) COPY file:e562b4187dbcae3e…   2.06MB              
3003ca74986b        3 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           3 weeks ago         /bin/sh -c #(nop) ADD file:7b14f19bdf02562d0…   77.8MB              
<missing>           3 weeks ago         /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B

docker rmi

docker rmi用于从docker host的本地镜像仓库中删除指定Image,如果一个Image含有多个tags,必须指定对应的repo:tag进行指定删除;如果一个image只有一个tag,那么可以直接通过ImageID进行删除;docker rmi不会删除远端注册中心的镜像;docker rmi的命令格式如下:

shell
$ docker rmi [OPTIONS] IMAGE [IMAGE...]

OPTION支持的选项如下:

  • -f, --force:当Image有关联运行的Container,可以通过此选项强制删除;
  • --no-prune:不删除没有打tag的父镜像;

docker images

docker images用于列出docker host上所有的Image列表;命令格式如下:

shell
$ docker images [OPTIONS] [REPOSITORY[:TAG]]

可以指定显示特定repo和tag的Image;默认docker images会显示所有的顶层的Image,以及对应的repo,tags和对应的size;docker images支持的选项列表如下:

  • -a, --all:显示所有Images,包括中间层的Image,默认只会显示顶层的Image,不会展示中间层的Image,我们现在知道image的构造是一层一层堆叠的,为了加速Image的构建过程和减少Image的所占用大小;
  • --digests:显示Image的digest信息,也就是hash值;
  • -f, --filter:用于过滤特殊条件的Image,--filter支持的参数格式为"key=value"对,匹配多个参数需要多个--filter选项来标识,例如--filter "foo=bar" --filter "bob=alice",目前--filter支持的选项参数有以下几种:
    • dangling:取值为true/false;用来筛选没有打tag的Image;
    • label:使用方式为:label=<key>或者label=<key>=<value>,用来筛选特定label的Image;
    • before:使用方式为:before=<image-name>[:<tag>]或者before=<image-id>或者before=<image-name@digest>,用来过滤创建比指定Image早的Image;
    • after:使用方式和before一样,用来过滤比指定Image创建晚的Image;
    • reference:用来匹配特定repo和tag的Image;
  • --format:格式化输出Image的信息;
  • --no-trunc:输出不截断,显示Image的完整的所有信息;
  • -q, --quiet:只输出所有的Image ID;

2. docker容器生命管理

docker run

docker run用来运行一个container,其格式语法如下:

shell
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run命令运行一个container,其过程其实分了两步:

  • 基于一个image之上,创建一个可修改的container layer;
  • 然后通过特殊命令启动该container layer;

docker run等价于先调用API/containers/create,然后再调用/containers/(id)/start;且已经stop的container,可以通过docker start进行恢复,包括只之前running时发生的修改;具体相关docker run的手册说明可以详见官方指引;

docker run的构建参数OPTIONS选项列表,具体可参考官方文档,有一些是和docker build重合的,区别在于,他们在docker build中只影响构建镜像的过程,而docekr run的修改会影响运行的container;下面介绍一些比较重要的参数:

  • --name

给要运行的container,指定一个名字;如下:

shell
$ docker run -d --name walker test/helloworld 
$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                      NAMES
4113dceb42e0        test/helloworld      "/bin/sh -c ./hellow…"   3 seconds ago       Up 3 seconds                                   walker

如果运行container的时候,不指定名字,默认会生成一个随机的名字,但是这个随机的名字,看上去又有点生成规则。如下:

shell
# docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                      NAMES
9faf038fe924        test/helloworld      "/bin/sh -c ./hellow…"   3 seconds ago       Up 1 second                                    hardcore_stonebraker
5f7f11848962        test/helloworld      "/bin/sh -c ./hellow…"   26 seconds ago      Up 25 seconds                                  infallible_carson

经过请教Claude.AI得知,Docker生成的默认容器名称是两个英文单词拼接而成的。规则如下:

  1. 前缀单词,是从Docker内置的一个adjective(形容词)词库中随机选取的。这个词库包含许多常用的形容词,例如:admiring、desperate、silly、jovial、awesome等等。
  2. 后缀单词则,从Docker的另一个著名科学家名字词库中随机选取。这个词库包含众多著名科学家的名字,比如:einstein、feynman、hawking、curie等。

具体实现可以参考Docker的官方实现:moby names-generator.go

  • -it, --interactive, --tty

-it这两个选项一般会一起使用,--interactive选项是保持container的STDIN开启,这样运行的container才可以进行shell指令交互,进行相关调试;--tty选项为container提供一个伪终端(pseudo-TTY)绑定STDIN,这样才可以进行容器登录,并通过shell交互进行操作;

  • -d, -detach:后台运行容器,一般服务都是以后台daemon的方式运行;
  • --cidfile

运行container的时候,尝试创建一个新文件,并将container id写入其中,如果文件已经存在,将会返回错误,不会启动container,此cidfile在docker run执行结果后,就会关闭;该功能的目的是为了防止container多开,类似于进程通过pid文件来防多开的设计;如下测试:

shell
$ docker run --cidfile hw.cid -d test/helloworld
$ cat hw.cid 
35777669385a31e7d382a616b6b6d5363e600537de3f125854f83703dac75de2
# 再次running
$ docker run --cidfile hw.cid -d test/helloworld
docker: Container ID file found, make sure the other container isn't running or delete hw.cid.
  • -w, --workdir

设置container的工作目录,如果路径不存在,则会自动创建一个;这个参数会覆盖docker build镜像构建时,指定的container的工作目录;如下:

shell
# 修改镜像的工作目录,会发现CMD找不到容器启动要执行的exec文件了
$ docker run -w /tmp/hw_dir -it test/helloworld 
/bin/sh: 1: ./helloworld: not found

# 修改镜像的工作目录,执行pwd命令
$ docker run -w /tmp/hw_dir -it test/helloworld  pwd
/tmp/hw_dir

此选项的用途是创建一个特权容器;默认权限的容器运行时对宿主机来说是一个普通用户,无法访问很多资源,例如硬件资源;特权权限允许容器拥有宿主机的root权限;常用在Docker-in-Docker,在一个容器中允许另一个容器,但是有很多安全问题;参考文档;下面是关于特权权限的开启示例:

shell
$ docker run  -it test/helloworld  bash
root@5b12bfd00f49:/# mount -t tmpfs none /mnt
mount: /mnt: permission denied.

# 开启特权权限
$ docker run  --privileged -it test/helloworld  bash
root@d35cd8731f2d:/# mount -t tmpfs none /mnt
root@d35cd8731f2d:/# df -h
Filesystem      Size  Used Avail Use% Mounted on
...
none             16G     0   16G   0% /mnt
  • -v, --volume

将宿主机的目录挂载到Container的对应的目录中;在前面docker build的VOLUME指令中,我们知道VOLUME指令用来持久化数据的;前已经介绍了关于volume的使用有两种方法,现在又多了一种,可以挂载本地目录:

  1. docker run的时候不显示挂载volume,对于docker build中的挂载点,会使用匿名的volume,VOLUME指令默认在Container运行的时候创建的匿名Volume,虽然是持久化的,但是无法与其他Container共享;
  2. docker run显示指定有名的volume挂载,这种时候会在宿主机的指定路径下/var/lib/docker/volumes/vol_name/_data创建对应的名字的volume挂载到Container中;既做到了持久化,也可以和其他Container共享数据;
  3. docker run在volume挂载还可以指定本地目录挂载到Container中,如下:
shell
$ docker run -v /tmp/helloworld_dir:/walkerdu -w /walkerdu -it test/helloworld bash
root@acd7b86b4bce:/walkerdu# touch balabala


$ ls /tmp/helloworld_dir/
balabala

当要挂载的本地目录不存在时候会自动创建,我们可以看到通过volume来挂载目录到Container中的方式,也可以进行数据的持久化;

  • --mount

mount选项支持在Container中挂载volumes,宿主机目录,tmpfs;它的功能覆盖了上面介绍的--volume选项,只是语法格式不一样而已;详见官方关于VOLUME的说明;如下,mount指令由一组用逗号分隔的<key>=<value>组成;

shell
--mount '<key>=<value>, <key>=<value>...'

虽然官方没有计划弃用--volume(毕竟要向后兼容),但是官方建议使用--mount来进行volume挂载进行数据持久化;

  • --read-only:让Container的整个根文件系统变成只读,除了--volume和--mount指定挂载的目录;
  • -p, --expose

用于绑定本地主机端口和Container的端口,详见Docker NetWorking;格式如下:

shell
-p [host_ip:]host_port:container_port

主机IP可以不填,默认对所有网卡生效;在前面EXPOSE指令也有介绍,这里不再赘述;

--pull选项用于指定在准备运行Container的时候,如何拉取所需的镜像,如下三个选项参数:

参数值 说明
missing 如果docker deamon中不存在需要的Image,则进行拉取,否则使用已经cache的Image;默认采用此种方式
never 不拉取Image,如果docker deamon中没有,则报错
always 创建Container前,总是拉取最新的Image

用来在Container中设置环境变量,可以用来覆盖Image中docker build设置的同名环境变量,使用如下:

shell
$ cat env.txt 
ENV3=val3

$ docker run -e ENV1=val1 --env ENV2=val2 --env-file env.txt -it test/helloworld bash
root@7affea67ff79:/# env
...
ENV2=val2
ENV3=val3
ENV1=val1
...

注意:当选项只设置了环境变量的名字,没有设置value,不会覆盖Container中的环境变量;

用来设置Container的元数据即标签;语法格式和环境变量类似;详细参考官方Label说明;如下:

shell
$ cat label.txt 
label3=val3

$ docker run -l label1=val1 --label label2=val2 --label-file label.txt -d test/helloworld 
$ docker inspect 0f6861f0984e
...
"Labels": {
                "label1": "",
                "label2": "val2",
                "label3": "val3"
            }
...

注意:label可以只设置key,没有value,默认label的值为空串"",如果label的key有重复,那么后面的label会覆盖前面设置的label;

该参数用于设置Container在退出之后的重启策略,如下:

参数 说明
no Container退出后,不自动重启,默认选项
on-failure[:max-retries] 在Container退出后的exit code非0时,尝试进行重启,可以设置重试次数
unless-stopped 除非显示的stop Container,其他情况都重启
always 只要Container退出,就一直尝试重启

该选项用来docker stop Container的时候,发送给Container的信号,可以是信号名SIG<NAME>,也可以是信号值;如果不设置,默认是SIGTERM;

该选项用来设置在向Container发送stop信号后,等待多少秒超时还没有完成stop的话,就执行强杀,即发送SIGKILL信号;

如果--stop-timeout被设置为-1,那么会永远等待Container的结束;默认缺省值是有docker deamon决定的,Linux Container 10s,Windows Container 30s;

此参数用于限制Container最大允许使用的内存,Linux上是通过设置cgroup来实现;可以查看/sys/fs/cgroup/memory/memory.limit_in_bytes;

  • --cpus:使用的cpu核数
  • --sysctl:设置Container运行时的一些内核参数;

docker create

docker create用于创建一个Container,在前面docker run介绍时,我们知道运行一个Container分为两步,第一步就是调用/containers/create的API在一个image之上,创建一个可修改的container layer;docker create就是干这个事情的:创建一个containr layer,然后不运行;

docker create创建完Container后,会在标准输出输出一个Container ID,此Container在创建完后的状态为Created,如下:

bash
$ docker create -e A=B test/helloworld
e05febc25ab27f127f2fe754ab17a5b2f5cef984fb8d9f694b1fdcb6d8babda0
$ docker ps -a|grep e05febc25ab2
e05febc25ab2        test/helloworld          "/bin/sh -c ./hellow…"   14 seconds ago      Created                                                 cranky_lichterman

docker create等价于docker run -d,除了它创建的Container不会自动启动运行;此种方式对于想要事先配置好Container,然后按需启动很有用处;

后面可以通过docker start命令来启动容器,如下:

shell
$ docker start -ai e05febc25ab2
2022-07-04 07:18:06 Hello World.
2022-07-04 07:18:07 Hello World.
...

docker create的命令格式如下,和docker run的OPTIONS基本是一样的,具体OPTIONS列表可以参考前一节,或者docker run指引;

shell
$ docker create [OPTIONS] IMAGE [COMMAND] [ARG...]

docker start

docker start用于启动没有运行(例如Created,Exited状态)的Container ,docker start的格式如下:

shell
$ docker start [OPTIONS] CONTAINER [CONTAINER...]

docker start的OPTIONS比较少,因为Container的相关参数在其创建的时候已经写入了Container中,docker start相对只是负责启动,有一些简单的输入输出的选项,可以参考docker start的手册说明;

需要注意的是:如上面命令格式,docker start后面除了OPTIONS的参数,只支持传入Container的名字,不支持其他的参数传入,可以传入多个Container,批量操作;

docker stop

docker stop用于stop处于running状态的Container,同样支持操作多个容器;命令格式如下:

shell
$ docker stop [OPTIONS] CONTAINER [CONTAINER...]

docker stop后,默认会向Container的主进程发送一个SIGTERM信号,如果在规定的优雅退出时间内,容器没有Stop,则会继续发送SIGKILL进行强杀;默认发送的信号在介绍docker run的时候已经介绍,可以通过两种方式进行修改:

  • STOPSIGNAL在docker build的时候设置
  • --stop-signal在docker run或者docker create的时候设置,会覆盖Dockfile的STOPSIGNAL设置;

docker stop支持的选项只有一个-t, --time,设置Container在收到停止信号后的优雅退出超时时间;我们知道docker run或者docker create的时候,可以通过--stop-timeout选项来设置Container的优雅退出超时时间;docker stop的超时时间设置优先级高于docker run设置的超时时间;如下测试:

shell
# 设置退出超时时间为20s
$ docker run --stop-timeout=20 -d test/helloworld
68c0539616cb4f8b458c7fe09436874a41061e17dcfbe67f2c87d0eb905661a0

# stop的时候设置超时退出时间为2s,可以看到2s后就强杀了
$ date && docker stop -t 2 68c0539616cb && date
Mon Jul  4 16:54:47 CST 2022
68c0539616cb
Mon Jul  4 16:54:49 CST 2022

docker restart

docker restart用于重启一个Container,等价于先docker stop,然后再docker start;格式 如下:

shell
$ docker restart [OPTIONS] CONTAINER [CONTAINER...]

目前支持的参数只有一个-t, --time,和docker stop一样,用于指定Container优雅退出的的超时时间;

docker pause/unpause

pause用于暂停容器中的所有进程,格式如下:

shell
$ docker pause CONTAINER [CONTAINER...]
$ docker unpause CONTAINER [CONTAINER...]

docker pause,在Linux中,通过cgroups的freezer cgroup来实现进程的暂停和恢复;Linux中,

  • 一般暂停进程都是通过发送SIGSTOP信号进行来实现,此信号会被进程观察到,虽然其不能忽略此信号也不能捕获该信号;
  • 另外一种方式暂停进程是通过freeze cgroup来实现,是进程无法感知的,也是不能被捕获的;

如下,登录容器查看输出结果,然后进行pause/unpause,输出如下:

shell
$ docker exec -it 68c0539616cb bash
root@68c0539616cb:/# tail -f helloworld.txt 
2022-07-04 11:44:14 Hello World.
2022-07-04 11:44:15 Hello World.
2022-07-04 11:44:32 Hello World.
2022-07-04 11:44:33 Hello World.

docker kill

docker kill用于kill正在运行的Container,格式如下:

shell
$ docker kill [OPTIONS] CONTAINER [CONTAINER...]

docker kill默认会向Container的主进程发送SIGKILL信号;可以通过--signal选项来设置发送给Container的信号值;这里的CONTAINER可以是Container ID,Container ID的前缀,也可以是Container Name;Container ID前缀匹配如果存在唯一的Container ID,则执行docker kill操作;

docker kill目前只支持--signal选项;

docker kill的使用方式和linux的kill命令很像,--signal可以传递任何预定义的信号值,让Container的主进程决定如何进行信号处理;这里需要注意:ENTERPOINT和CMD指令在shell模式运行的时候,是作为/bin/sh -c的子进程运行的,这种情况是无法接受docker命令的信号的;信号只会发送给Container的主进程(PID=1);

docker rm

docker rm用于删除Container,正常情况下Container在stop之后,是一直存在的,可以被再次运行,我们可以通过docker rm可以最终删除该Container,格式如下:

shell
$ docker rm [OPTIONS] CONTAINER [CONTAINER...]

docker rm支持的选项有如下三种:

  • -f, --force:强制(通过发送SIGKILL信号)将running的Container销毁;
  • -l, --link:用于删除Container之间默认的underlying桥接网络;
  • -v, --volumes:删除Container的时候,删除匿名volumes;

和git版本管理类似,可以通过docker container prune命令来移除所有已经stop的Container;docker system prune可以移除整个docker无用的资源,例如stoped的Container, 未使用的images,以及网络;

3. docker容器操作

docker ps

docker ps用于查看Container的信息;名字类似linux中的ps命令,格式如下:

shell
$ docker ps [OPTIONS]

docker ps支持的OPTIONS列表如下:

  • -a, --all:列出所有Containers,默认只会列出running状态的Containers;
  • --foramt:格式化输出Container的信息;
  • -n, --last:最新的n个创建的Container;
  • -l, --latest:最新创建的容器;
  • --no-trunc:输出的容器信息不截断;
  • -q, --quiet:只输出Container ID信息;
  • -s, --size:显示总的文件大小;
  • -f, --filter:根据条件过滤出特定的Container列表,和docker images的--filter功能是一样的,不过Container的ps支持的filter参数值更多,同样--filter选项的参数格式为"key=value"对,匹配多个参数需要多个--filter选项来标识,例如--filter "foo=bar" --filter "bob=alice",docker ps --filter支持的选项参数有以下几种:如下:
    • id:显示指定的Container ID的Container;
    • name:显示指定的Container Name的Container;
    • label:过滤指定label的Container,label的值可以是一个key,也可以是key=value,例如--filter "label=color"或者--filter "label=color=blue";
    • exited:显示特定exit code的Container,需要结合--all选项一起使用;
    • status:显示特定状态的Container,Container的状态包括:created, restarting, running, removing, paused, exited, dead;
    • ancestor:查找祖先为给定Image的Containers,Image的参数可以为:<image-name>[:<tag>]或者<image-id>或者<image-name@digest>;
    • before :在特定Container之前创建的容器;
    • since:在特定Container之后创建的容器;
    • volume:查找挂载特定volume的Containers;
    • network:查找连接特定网络的的Containers;
    • publish 或expose:查找暴露特定端口的Container;
    • health:查找特定健康检测状态的Container,健康状态分为以下几类:startig, healthy, unhealthy, none;
    • is-task:判断是否某个服务的task;

docker top

docker top用于输出指定指定容器中运行的进程,命令格式如下,支持docker ps的选项:

shell
$ docker top CONTAINER [ps OPTIONS]

docker attach

docker attach用于attach到运行中的Container本地的标准输入,输出,错误流;格式如下:

shell
$ docker attach [OPTIONS] CONTAINER

通过docker attach可以查看到正在运行Container的输出,也可以通过交互的方式控制Container的1号进程;可以对同一个Container进行同时多个attach操作;

attach命令只会显示ENTRYPOINT/CMD入口进程的输出,attach命令挂载到Container上后,看上去会把自己挂住,其实只是没有和终端交互而已

可以通过CTRL-C来停止一个Container,CTRL-C会默认向Container发送SIGKILL信号,如果--sig-proxy选项置为true,那么CTRL-C会发送SIGINT信号;如果Container以-it方式启动,那么可以通过发送CTRL-p CTRL-q来detach一个Container,Container任然会继续运行;

shell
# attach到容器后,CTRL-C终止容器
$ docker run -d  test/helloworld
9c9a3ed3979b3aa5ca9cc5bb93644f494ee77e6caba61dec7bb34809e76b47ef
$ docker attach 9c9a3ed3979b
2022-07-11 08:09:48 Hello World.
2022-07-11 08:09:49 Hello World.
2022-07-11 08:09:50 Hello World.
^C

# attach到-it运行的容器后,通过CTRL-p CTRL-q来detach,不影响容器的运行
$ docker run -d -it test/helloworld
513f8ba87e0c1d96febb8ccab3a24eadefe01125ed223772c3f9358ccf4063aa
$ docker attach 513f8ba87e0c
2022-07-11 09:46:03 Hello World.
2022-07-11 09:46:04 Hello World.
2022-07-11 09:46:05 Hello World.
read escape sequence

docker attach支持的选项列表如下:

  • --detach-keys:覆盖detach的Container时候的按键序列;
  • --no-stdin:attach的时候不attach STDIN;
  • sig-proxy:attach后,将所有终端的控制信号直接发送给Container的1号进程;例如CTRL-C按照正常的SIGINT信号发送给Container;

docker commit

docker commit用于从Container中创建一个新的Image,命令格式如下:

shell
$ docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

docker commit可以从Writeable Layer的Container上进行Image的构建,基于此创建的Image会包含Container中的文件修改和设置,这样可以很方便的以交互的方式进行Container的调试,或者将工作环境切换到其他服务器;

需要注意的是:基于Container构建的Image,并不会包含挂载的volumes;

默认情况下:在执行docker commit构建的时候,Container会被暂停,以降低数据损坏的可能性;可以通过选项--pause=false来不暂停Container;

docker commit支持的选项有以下几种:

  • -a, --author,指定作者;
  • -c, --change:在创建Image的时候,通过该参数来使用Dockerfile支持的指令进行Image构建结果的修改;,支持的Dockerfile的指令有:CMD|ENTRYPOINT|ENV|EXPOSE|LABEL|ONBUILD|USER|VOLUME|WORKDIR;
  • -m, --message:commit消息;
  • -p, --pause:构建进项的时候是否暂停Container,默认为true;

4. docker registry操作

下面是一些对于Docker的注册中心的相关操作;

docker login

docker login用于登录指定的docker registry,命令格式如下:

shell
$ docker login [OPTIONS] [SERVER]

支持的选项如下:

  • -u, --username:登录用户名;

  • -p, --password:登录密码;

  • --password-stdin:从标准输入接收密码;

shell
$ cat ~/my_password.txt | docker login --username foo --password-stdin

默认不指定SERVER,会登录到官方的Docker Hub注册中心;我们可以指定登录到任何公开或者私有的注册中心,登录后默认会将凭证以base64编码方式保存在$HOME/.docker/config.json (Linux)对应的auth字段中;Docker支持将登录的凭证存储在其他的凭证仓库,例如:操作系统本地的keychain,相对于Docker的本地config,将凭证存储在远端的仓库中会更加安全;

如果需要将凭证存储在远端仓库中,需要对应的远端仓库的helper或者说client程序来负责和Docker client进行存储和读取交互,目前支持的第三方凭证工具列表如下:

凭证的远端helper程序的配置如下:$HOME/.docker/config.json,对应的helper程序需要在$PATH路径中可以查找;

json
{
  "credsStore": "osxkeychain"
}

对应的helper程序的配置需要以docker-credential-为前缀,加上helper程序名字,例如上面的helper程序的配置文件需命名为:docker-credential-osxkeychain

docker pull

docker pull用于从registry拉取一个Images或者一整个repo;命令格式如下:

shell
$ docker pull [OPTIONS] NAME[:TAG|@DIGEST]

我们大部分的Images的最顶层都是从 Docker Hub注册中心拉取获得的,Docker Hub中包含了很多已经构建好的基础Images,我们都可以进行拉取使用,不需要我们自己进行单独的构建;docker pull拉取的时候Docker daemon默认会同时并发的拉取一个Image的三层镜像;如果要修改该设置,需要设置dockerd的--max-concurrent-downloads参数;

docker pull支持的选项如下:

  • -a, --all-tags:拉取一个repo下面的所有tag的Image;
  • --disable-content-trust:忽略Image的校验;
  • --platform:拉取特定平台的镜像,默认是根据Docker daemon的OS来自动匹配拉取;
  • -q, --quiet:隐藏详细信息输出;

如下测试,

shell
# 默认拉取ubuntu:latest的镜像
$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
405f018f9d1d: Pull complete 
Digest: sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac
Status: Downloaded newer image for ubuntu:latest

等价于:

shell
$ docker pull ubuntu:latest
$ docker pull ubuntu@sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac

默认情况,docker pull从Docker Hub中拉取Images,也可以显示的指定从特定的注册中心拉取镜像,如下从AWS的注册中心拉取ubuntu镜像:

shell
$ docker pull public.ecr.aws/lts/ubuntu
Using default tag: latest
latest: Pulling from lts/ubuntu
25dab5ae6e34: Pull complete 
Digest: sha256:3cd697a1c2c7bae4958052fd5a225a3227f5af5ab89cd608036c3ee210cd4fcf
Status: Downloaded newer image for public.ecr.aws/lts/ubuntu:latest

Docker daemon通过https://协议和注册中心通信,除非注册中心被设置允许通过不安全的http协议进行通信;具体可以参考如何设置insecure registries;

docker push

docker push用于向registry推送一个Images或者一整个repo;命令格式如下:

shell
$ docker push [OPTIONS] NAME[:TAG]

docker push和docker pull功能是一对,使用格式和语法基本一致,这里不再详述了;

docker push推送的时候Docker daemon默认会同时并发的推送一个Image的五层镜像;如果要修改该设置,需要设置dockerd的--max-concurrent-uploads参数;

docker search用从Docker Hub中搜索Images;不像docker pull/push可以从其他注册中心进行镜像操作,docker search只能从官方的Docker Hub进行Image的搜索;docker search命令的格式如下:

shell
$ docker search [OPTIONS] TERM

支持的OPTION列表如下:

  • -f, --filter:按照指定的flag进行过滤搜索;
  • --format:按照Go Template进行结果输出;
  • --limit:最大搜索结果,默认25个;
  • --no-trunc:输出结果不截断;

5. docker其他操作

docker inspect

docker inspect用于查看Docker对象的详细底层结构控制信息;指令的格式如下:

shell
$ docker inspect [OPTIONS] NAME|ID [NAME|ID...]

docker inspect默认输出的信息是JSON数组格式化的;它支持的OPTIONS列表如下:

  • -f, --format:按照指定的Go模板进行格式化输出;这个还是比较常用的,关于Go模块,这篇翻译Go Template不错,可以参考;
  • -s, --size:针对Container对象,输出总文件的大小;
  • --type:如果出现相同名的对象,需要指定要查看对象的类型,例如--type=container或者--type=image,当然最好的还是通过归类的对象的inspect命令进行信息的查看,例如docker container inspect;

docker events

docker events用于从docker deamon中获取实时的事件消息;对于不同的Docker对象会有不同种类的事件类型,具体的事件类型可以参考官方手册关于各种对象的事件列表;

问题汇总

docker.sock权限问题

bash
$docker run -d -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/create?name=rabbitmq: dial unix /var/run/docker.sock: connect: permission denied.

我们看一下docker.sock文件的权限如下:

bash
$ll /var/run/docker.sock
srw-rw---- 1 root docker 0 Jul 15 12:51 /var/run/docker.sock

该文件是docker安装时创建的sock文件,它的用途是:

Docker.sock is a Unix socket that enables the Docker server-side daemon, dockerd, to communicate with its command-line interface via a REST API.

比较安全的解决方法是,参考Stack Overflow,docker默认只能以root权限运行,如果想以普通用户运行docker进程,需要将该用户加入docker group,如下流程:

bash
# Create the docker group if it does not exist
$ sudo groupadd docker
# Add your user to the docker group.
$ sudo usermod -aG docker $USER
# Run the following command or Logout and login again and run (that doesn't work you may need to reboot your machine first)
$ newgrp docker
# Check if docker can be run without root
$ docker run hello-world

评论