操作Docker容器
写在前面
在前面我们学习了docker镜像相关的知识,接下来再来学习另一个核心概念—容器。容器是镜像的一个运行实例,所不同的是,镜像是静态的只读文件,而容器则是带有运行时需要的可写文件层,同时容器中的应用进程处于运行状态。
可以将其类比到面向对象编程中的对象和类的关系,其中类实例化出对象,正如这里的容器是镜像的一个运行实例一般。
如果认为虚拟机是模拟运行的一整套操作系统,包括内核、应用运行环境、其他系统环境以及跑在上面的应用;那么Docker容器就是独立运行的一个或者一组运用,以及它们必需的运行环境。
接下将围绕容器来进行学习,主要包含创建、启动、终止、删除容器,进入容器内执行操作、通过导入导出操作实现容器迁移等内容。
创建容器
用户可以理解为对容器的操作就像直接操作应用一般,非常简单且快速。
新建容器
开发者可以使用docker create [container]
命令来新建一个容器。
举个例子,开发者可以使用ubuntu:latest镜像创建一个容器,使用的命令如下:
1 | [root@envythink ~]# docker create -it ubuntu:latest |
但是请注意,使用docker create [container]
命令新建的容器处于停止状态,开发者可以使用docker start [container]
命令来启动它。
细心的朋友肯定发现我们在docker create [container]
命令中加入了-it
参数,其实这就是选项参数。选项参数包括三大类:与容器运行模式相关、与容器环境配置相关、与容器资源限制和安全保护相关。接下来笔者从网上和书籍中贴了几张选项参数的图片,这些需要平时去记忆:
(1)create命令与容器运行模式相关的选项如下所示:
(2)create命令与容器环境和配置相关的选项如下所示:
(3)create命令与容器资源限制和安全保护相关的选项如下所示:
其他选项还包括:
-l
或者--label=[]
,它表示以键值对方式指定容器的标签信息;--label-file=[]
,它表示从文件中读取标签信息。
启动容器
在前面提到过开发者可以使用docker start [container]
命令来启动一个已经创建的容器。
举个例子,如开发者可以启动之前基于ubuntu:latest创建的容器,使用的命令如下:
1 | [root@envythink ~]# docker ps -a |
请注意这里可以使用CONTAINER ID来唯一标识容器,或者可以标识唯一容器的一小部分,最后使用docker ps
来查看正在运行的容器实例。
新建并启动容器
是不是觉得前面的docker create [container]
命令非常鸡肋,创建了容器居然不去运行,还需要借助于docker start [container]
命令来启动,其实在实际工作中这种场景也是存在的。但是docker也提供了另一个将两者功能合一的命令docker run [container]
,docker run [container]
命令可以直接新建并启动容器。也就是说docker run [container]
命令等价于先执行docker create [container]
命令,再执行docker start [container]
命令。
举个例子,下面的命令将会输出一个“hello,world”,然后容器就自动终止运行:
1 | [root@envythink ~]# docker run ubuntu:latest /bin/echo "hello,world" |
不过这看起来与本地直接执行/bin/echo "hello,world"
感觉没什么区别,但实际上区别大着呢。
当开发者使用docker run [container]
来创建并启动容器时,Docker在后台运行的标准操作如下所示:
(1)检查本地是否存在指定的镜像,如果不存在就从Docker Hub公有仓库下载;
(2)利用镜像创建一个容器,并启动该容器;
(3)分配一个文件系统给容器,并在只读的镜像层外面挂载一层可读写层;
(4)从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去;
(5)从网桥的地址池配置一个IP地址给容器;
(6)执行用户指定的应用程序;
(7)执行完毕后容器被自动终止运行。
下面的命令用来启动一个bash终端,并运行用户进行交互:
1 | [root@envythink ~]# docker run -it ubuntu:latest /bin/bash |
其中的-t
选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入中,而-i
选项则让容器的标准输入保持打开,获取更多命令可以使用man docker-run
命令来查看。
可以看到前面笔者在容器内的交互模式下执行了echo "hello,beijing"
命令且进行了输出。如果用户在容器内使用ps命令查看进程,可以发现该容器内只运行了bash应用,并没有运行其他的无法进程,且开发者可以使用Ctrl+d
或者exit命令来退出容器:
1 | [root@envythink ~]# docker run -it ubuntu:latest /bin/bash |
请注意这个root@3b25fe814a4d
中@
后面一串字符串是新创建的容器的id。
对于所创建的bash容器来说,当用户使用exit命令退出bash进程之后,容器也会自动退出,这是因为对于容器来说,当其中的应用退出后,容器的使命就完成了,因此没有继续运行的必要。
开发者可以使用docker container wait CONTAINER [CONTAINER...]
子命令来等待容器退出,并打印退出返回结果。
但是在某些时候,开发者执行docker run [container]
命令的时候可能会出错,进而导致无法正常执行,容器会出错直接退出并默认返回命令的退出错误码,这里笔者列举3种常见的错误代码:
(1)125,这是docker daemon执行出错,像指定了不支持的docker命令参数时就会出现这个问题;
(2)126,表示指定的命令无法执行,像权限出错就会出现这个问题;
(3)127,表示内的命令无法找到。
守护态运行
通常而言,我们都希望docker容器在后台以守护态(Daemonized)形式运行,此时开发者可以通过在创建容器的时候添加-d
参数来实现这一目的:
1 | [root@envythink ~]# docker run -d ubuntu:latest /bin/sh -c "while true;do echo 'hello,world';sleep 1;done" |
可以看到上面的命令就会使新创建的容器在后台运行,容器启动后会返回一个唯一的id,开发者可以通过docker ps
或者docker container ls
命令来查看容器信息:
1 | [root@envythink ~]# docker ps |
查看容器输出
开发者如果想要获取容器的输出信息,可以使用docker logs [container]
命令,请注意该命令还支持以下6个选项参数:
(1)-details
表示打印详细信息;
(2)-f
或者-follow
表示持续保持输出;
(3)-since string
表示输出从某个时间开始的日志;
(4)-tail string
表示输出最近的若干日志;
(5)-t
或者-timestamps
表示显示时间戳信息;
(6)-until string
表示输出某个时间之前的日志。
停止容器
在学习完创建和运行容器之后,接下来开始学习如何停止容器,这里主要学习docker容器的pause/unpause
、stop/prune
命令。
暂停容器
开发者可以使用docker pause CONTAINER [CONTAINER...]
命令来暂停一个运行中的容器。
举个例子,使用之前的ubuntu:latest镜像启动一个别名为envy的容器,并将其暂停,相应的命令如下:
1 | [root@envythink ~]# docker run --name envy -d -it ubuntu /bin/bash |
请注意开发者如果想恢复处于paused状态的容器,可以使用docker unpause CONTAINER [CONTAINER...]
命令。
终止容器
前面学习的是如何暂停容器,接下来开始学习如何终止容器的运行,那么可以使用docker stop [container]
命令。注意该命令也可以携带参数-t
或者--time=10
,表示先向容器发送SIGTERM信息,等待一段超时时间后(默认为10秒钟),再发送SIGKILL信号来终止容器运行:
1 | [root@envythink ~]# docker ps |
请注意尽管容器被终止运行了,但是它默认还是存在的,此时如果开发者想要自动清除所有处于停止状态的容器,可以使用docker container prune
命令。前面大家也都看到了docker stop [container]
是一种缓慢终止容器运行的命令,如果开发者想要立即强制终止容器的运行,可以使用docker kill [container]
命令来直接发送SIGKILL信息进而强行终止容器的运行。
请注意,当docker容器中指定的应用终止时,容器也会自动终止,这里的指定是指该容器只允许了一个应用的容器。前面我们都是启动了一个只有终端应用在运行的容器,开发者通过使用exit或者Ctrl+d组合键来退出终端时,那么所创建的容器也会立即终止运行,处于stopped状态。
在前面我们使用docker ps
命令来查看正在运行的容器实例信息;使用docker ps -a
命令来查看所有容器的信息,无论是否正在运行,其实还可以使用docker ps -qa
命令来查看所有容器的ID,无论是否正在运行:
1 | [root@envythink ~]# docker ps -qa |
同样开发者可以使用docker start [container]
命令来将处于终止状态的容器重新启动:
1 | [root@envythink ~]# docker run --name envy -d -it ubuntu /bin/bash |
可以看到处于暂停状态的容器,使用docker ps
命令还是可以看到它在运行,只是状态变成了Paused,而处于终止状态的容器,使用docker ps
命令是无法看到它的信息,因为它是真的没有在运行。
还有一个命令是docker restart [container]
,它会将一个处于运行态的容器先终止,然后再重新启动:
1 | [root@envythink ~]# docker ps |
发现问题了么,其实上面那个命令就是重启容器罢了,因此STATUS的时间会清零。
进入容器
在前面我们讲过,如果在创建容器的时候添加-d
参数,那么容器启动后就会进入后台,用户是无法看到容器中的信息,也无法进行操作,此时如果开发者还是想进入容器进行操作,可以使用官方推荐的attach或者exec命令。
attach命令
attach是Docker自带的命令,其使用的格式如下:
1 | docker attach [container] [--detach-keys[=[]]]] [--no-stdin] [--sig-proxy[=true]] CONTAINER |
可以看到这个命令中有三个选项参数:
(1)--detach-keys[=[]]
,它表示指定退出attach模式的快捷键序列,默认是CTRL-p CTRL-q;
(2)--no-stdin=true|false
,它表示是否关闭标准输入,默认是保持打开;
(3)--sig-proxy=true|false
,它表示是否代理收到的系统信号给应用进程,默认为true。
举个例子,新建一个别名为envy的容器,使其在后台运行,然后进入到该容器中:
1 | [root@envythink ~]# docker run -itd --name envy ubuntu:latest |
但是请注意,当多个窗口同时attach同一个容器的时候,所有窗口都会同步显示,且当某个窗口因执行某些命令而阻塞时,此时其他窗口也无法执行任何操作,这是它的一个缺点。
exec命令
docker1.3开始提供了另一个比较方便的命令exec,可以在运行中的容器内直接执行任意命令。其使用的格式如下:
1 | docker exec [container] [-d|--detach] [--detach-keys[=[]]]] [-i|--interactive] [--privileged] [-t|--tty] [-u|--user[=USER]] |
可以看到这个命令中有很多个选项参数,其中较为重要的如下:
(1)-d|--detach
,表示在容器中后台执行命令;
(2)--detach-keys=""
,表示指定将容器切回后台的按键;
(3)-e|--env=[]
,表示指定环境变量列表;
(4)-i|--interactive=true|false
,表示打开标准输入接受用户输入命令,默认值为false;
(5)--privileged=true|false
,表示是否给执行命令以最高权限,默认值为false;
(6)-t|--tty=true|false
,表示是否分配伪终端,默认值为false;
(7)-u|--user=""
,表示执行命令的用户名或ID。
还记得前面刚创建的envy容器么,接下来尝试进入到该容器中,并启动一个bash:
1 | [root@envythink ~]# docker ps |
可以看到这个命令会打开一个新的bash终端,能在不影响容器内其他应用的前提下,用户可以与容器进行交互。
请注意,笔者推荐通过指定-it
参数来保持标准输入打开,且分配一个伪终端,然后通过exec命令对容器执行操作。
既然前面都已经进入到容器内,那么就可以尝试查看容器内的用户和进程信息:
1 | root@8d4d82f1ae23:/# w |
删除容器
开发者可以使用docker rm [container]
命令来删除处于终止或退出状态的容器,其对应的格式为docker rm [container] [-f|--force] [-l|--link] [-v|--volumes] CONTAINER [CONTAINER...]
。解释一下上述命令中的选项参数:
(1)-f, --force[=false]
,是否强行终止并删除一个正处于运行状态的容器。
(2)-l, --link[=false]
,表示删除容器的链接,但是保留容器;
(3)-v, --volumes[=false]
,表示删除容器挂载的数据卷。
举个例子查看正在运行的容器,并强制删除该容器:
1 | [root@envythink ~]# docker ps |
请注意,默认情况下使用docker rm [container]
命令只能删除已经处于终止或者退出状态的容器,无法删除正处于运行状态的容器。但是当我们在docker rm [container]
命令内添加了-f=true
参数时,Docker就会先发送一个SIGKILL信号给容器,来终止其中的应用,之后就强制删除该容器。
导入和导出容器
在实际工作中,我们经常需要将一个容器从A系统迁移到B系统中,此时可以使用Docker自身提供的导入和导出功能。
导出容器为文件
所谓的导出容器,是指将一个已经创建的容器到出为一个文件,注意此时无论该容器是否处于运行状态,开发者都可以使用docker export [container]
命令来进行导出,其对应的格式为docker export [container] [-o|--output[=""]] CONTAINER
。其中的-o
选项参数用于指定导出的tar文件的名称,也可以直接通过重定向来实现。
接下来举个例子,分别演示导出一个正在运行的别名为lichee的容器为the_run_exapmle.tar
文件;一个已经停止运行的别名为envy的容器为the_stop_example.tar
文件,相应的操作命令如下:
1 | [root@envythink envythink]# docker ps -a |
之后开发者就可以将导出的两个tar文件迁拷贝到其他机器上,然后再通过即将介绍的导入命令导入到系统中,进而实现容器的迁移。
导入容器为镜像
之前导出的容器tar文件可以通过docker import [container]
命令导入变成镜像,其对应的格式为docker import [-c|--change[=]] [-m|--message[=MESSAGE]] file |URL|- [REPOSITORY[:TAG]]
。解释一下上述命令中的选项参数:
(1)-c|--change[=]
,表示在导入容器的同时执行对容器进行修改的Dockerfile指令;
(2)-m|--message[=MESSAGE]
,表示导入镜像时设置的提交信息;
举个例子,将之前我们导出的the_stop_example.tar文件导入到系统中,并成为一个example/ubuntu:v1.0的镜像:
1 | [root@envythink envythink]# docker import the_stop_example.tar example/ubuntu:v1.0 |
还记得之前在学习docker镜像的时候,我们使用docker load [image]
命令来导入一个镜像,这个看似与这里介绍的docker import [container]
命令相似,其实差别很大。
docker import [container]
命令用于导入一个容器快照文件到本地镜像库。容器快照文件将丢弃所有的历史记录和元数据信息,也就是仅仅保存容器当时的快照状态,体积较小,且导入时可以重新指定标签等元数据信息;
docker load [image]
命令用于导入镜像存储文件到本地镜像库。镜像存储文件将保存完整记录,包括所有的历史记录和元数据信息,它的体积更大。
查看容器
现在来学习几个和查看容器相关的命令,如inspect、top和stats等。
inspect查看容器详情信息
开发者如果想查看容器的详情信息,可以使用docker inspect [OPTIONS] NAME|ID [NAME|ID...]
命令,注意此命令会以JSON格式来返回容器的具体信息,如容器ID,创建时间、路径、状态、镜像、配置等各项信息。
举个例子,使用docker inspect
命令来查看之前创建的别名为lichee的容器的信息:
1 | [root@envythink envythink]# docker inspect lichee |
注意上面也可以使用容器的ID,其查询结果为(部分):
top查看容器内进程
开发者如果想查看一个处于运行状态的容器内的进程,可以使用docker top CONTAINER
命令,这个命令和Linux系统中的top命令非常相似,会打印出容器内的进程信息,包括PID、用户、时间和命令等。
举个例子,查看之前别名为lichee的容器,注意此时该容器已经处于运行状态:
1 | [root@envythink envythink]# docker top lichee |
stats命令查看统计信息
开发者也可以使用docker stats [OPTIONS] [CONTAINER...]
命令来查看统计信息,该命令会显示CPU、内存、存储、网络等使用情况的统计信息。这个OPTIONS可选参数有:
(1)-a, --all[=false]
,表示是否输出所有容器统计信息,默认是输出仅在运行中的容器信息;
(2)--format=""
,表示格式化输出信息;
(3)-f, --filter=
,表示以指定的条件来过滤容器;
(4)-no-stream
,表示不持续输出,默认会自动更新持续实时结果;
(5)--no-trunc[=false]
,表示不截断输出信息。
举个例子,查看当前所有处于运行状态容器的系统资源使用统计情况:
1 | [root@envythink envythink]# docker stats |
其他容器命令
前面学习的都是常用的容器命令,接下来学习一些不常使用,但是使用起来可以大幅度减轻工作负担的命令,如cp、diff、port和update命令。
cp命令复制文件
开发者可以使用docker cp
命令在容器和主机之间复制文件,其对应的格式为docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
或者docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
。这个OPTIONS可选参数有:
(1)-a, --archive[=false]
,打包模式,复制文件会带有原始的uid/gid信息;
(2)-L, --follow-link[=false]
,跟随软连接。当原路径为软连接时,默认只复制连接信息,如果使用该选项时则会复制连接的目标内容。
举个例子,将当前目录下的test.txt文件复制到别名为lichee的容器的/tmp
目录下,使用的命令为:
1 | [root@envythink envythink]# docker cp test.txt lichee:/tmp/ |
那么问题来了,我们怎么知道文件是否复制成功了呢?此时可以使用docker inspect lichee
来查看其对应的目录:
当然开发者也可以直接使用find / -name test.txt
命令来进行全文搜索:
1 | [root@envythink envythink]# find / -name test.txt |
可以看到这个文件被复制到了diff和merged目录下的tmp目录中,diff顾名思义就是容器内文件的变更,merged是合并的意思。
diff命令查看文件变更
开发者可以使用docker diff
命令来查看容器内文件系统的变更,其对应的格式为 docker diff CONTAINER
。
举个例子,开发者查看别名为lichee的容器内的数据修改情况,可以使用docker diff
命令:
1 | [root@envythink envythink]# docker diff lichee |
其中的C表示改变,A表示新增。
port命令查看端口映射
开发者可以使用docker port
命令来查看容器的端口映射情况,其对应的格式为docker port CONTAINER [PRIVATE_PORT[/PROTO]]
。
举个例子,开发者查看别名为lichee的容器的端口映射情况,可以使用docker port
命令:
1 | [root@envythink envythink]# docker container port lichee |
由于这里别名为lichee的容器没有设置端口,因此其端口映射为空,无任何信息输出。
update命令更新配置
开发者可以使用docker update
命令来更新容器的一些运行时配置,主要是资源限制份额,其对应的格式为docker update [OPTIONS] CONTAINER [CONTAINER...]
。这个OPTIONS可选参数有:
(1)--blkio-weight=0
,uint16类型,表示更新块IO限制,取值在10~1000之间,默认为0,代表无限制;
(2)--cpu-period=0
,int类型,表示限制CPU调度器CFS(Completely Fair Scheduler)的使用时间,单位为微秒,最小为1000;
(3)--cpu-quota=0
,int类型,表示限制CPU调度器CFS配额,单位为微秒,最小为1000;
(4)--cpu-rt-period=0
,int类型,表示限制CPU调度器CFS的实时周期,单位为微秒;
(5)--cpu-rt-runtime=0
,int类型,表示限制CPU调度器CFS的实时运行时,单位为微秒;
(6)--c, --cpu-shares=0
,int类型,表示限制CPU使用份额;
(7)--cpus=
,decimal类型,表示限制CPU个数;
(8)--cpu-period=0
,int类型,表示限制CPU;
(9)--cpuset-cpus=""
,string类型,表示允许使用的CPU核,如0-3, 0,1;
(10)--cpuset-mems=""
,string类型,表示允许使用的内存块,如0-3, 0,1;
(11)--kernel-memory=0
,bytes类型,表示限制使用的内核内存;
(12)-m, --memory=0
,bytes类型,表示限制使用的内存;
(13)--memory-reservation=0
,bytes类型,表示内存软限制;
(14)--memory-swap=0
,bytes类型,表示内存加上缓存区的限制,-1表示对缓冲区无限制;
(15)--restart=""
,string类型,表示容器退出后的重启策略。
这些参数较多,这个命令在涉及到程序优化的时候才会使用,一般使用的不多。
举个例子,限制总配额为1秒,别名为lichee的容器所占用时间为10%,此时的代码为:
1 | [root@envythink envythink]# docker update --cpu-quota 1000000 lichee |
小结
本篇主要介绍了docker容器相关的一些重要操作,如创建、启动、查看、删除、守护态运行、停止、导入、导出等,当然这些已经能满足大部分的工作场景,在必要的时候开发者可以使用docker container help
命令来查看docker支持的容器操作子命令。
在生产环境中,为了提高容器的高可用和安全,一般都会合理使用资源限制参数来管理容器的资源消耗,并通过指定合适的容器重启策略来自动重启退出的容器。当然也可以使用如HAProxy、F5等负载均衡软硬件来自动切换发生故障的应用容器,进而实现容器的高可用目的。