从Docker镜像仓库获取镜像的命令是docker pull。其命令格式为:$ docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签],具体的选项可以通过docker pull --help命令看到。
Docker镜像仓库地址:地址的格式一般是<域名/IP>[:端口号],默认地址是Docker Hub(docker.io)。
仓库名:这里的仓库名是两段式名称,即<用户名>/<软件名>。对于Docker Hub,如果不给出用户名,则默认为library,也就是官方镜像。
上面的命令没有给出Docker镜像仓库地址,因此将会从Docker Hub(docker.io)获取镜像。镜像名称是ubuntu:18.04,因此将会获取官方镜像library/ubuntu仓库中标签为18.04的镜像。docker pull命令的输出结果最后一行给出了镜像的完整名称,即: docker.io/library/ubuntu:18.04。
从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的ID的前12位。并且下载结束后,给出该镜像完整的sha256的摘要,以确保下载一致性。
在使用上面命令的时候,可能会发现你所看到的层ID以及sha256的摘要和这里的不一样。这是因为官方镜像是一直在维护的,有任何新的bug或者版本更新,都会进行修复再以原来的标签发布,这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。
要想列出已经下载下来的镜像,可以使用docker image ls命令。列表包含了仓库名、标签、镜像ID、创建时间以及所占用的空间。其中仓库名、标签在之前的基础概念章节已经介绍过了。镜像ID则是镜像的唯一标识,一个镜像可以对应多个标签。
如果要删除本地的镜像,可以使用docker image rm命令,其格式为:$ docker image rm [选项] <镜像1> [<镜像2> …]
其中,<镜像> 可以是镜像短ID、镜像长ID、镜像名或者镜像摘要。
比如我们有这么一些镜像:
我们可以用镜像的完整ID,也称为长ID来删除镜像。使用脚本的时候可能会用长ID,但是人工输入太累了,所以更多的时候是用短ID来删除镜像。docker image ls 默认列出的就已经是短ID了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。
比如这里,如果我们要删除 redis:alpine 镜像,可以执行:
我们也可以用镜像名,也就是<仓库名>:<标签>,来删除镜像:
当然,更精确的是使用镜像摘要来删除镜像:
观察上面这几个命令的运行输出信息的话,会注意到删除行为分为两类,一类是Untagged,另一类是Deleted。镜像的唯一标识是其ID和摘要,而一个镜像可以有多个标签 ,因此使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的Untagged的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么Delete行为就不会发生。所以并非所有的docker image rm都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。
当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的原因。
除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的,因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。
像其它可以承接多个实体的命令一样,可以使用docker image ls -q来配合使用docker image rm,这样可以成批的删除希望删除的镜像。
比如,我们需要删除所有仓库名为redis的镜像:
$ docker image rm $(docker image ls -q redis)
或者删除所有在 mongo:3.2 之前的镜像:
$ docker image rm $(docker image ls -q -f before=mongo:3.2)
Dockerfile是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容就是描述该层应当如何构建。
使用Dockerfile来定制nginx镜像,在一个空白目录中,建立一个文本文件,并命名为Dockerfile:
其内容为:
这个Dockerfile很简单,一共就两行。涉及到了两条指令,FROM和RUN。
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个nginx镜像的容器,再进行修改一样,基础镜像是必须指定的。而FROM就是指定基础镜像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。
在 Docker Hub 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如nginx、redis、mongo、mysql、httpd、php、tomcat等;也有一些方便开发、构建、运行各种语言应用的镜像,如node、openjdk、python、ruby、golang等;官方镜像中还提供了一些更为基础的操作系统镜像,如ubuntu、debian、centos、fedora、alpine等,这些操作系统的软件库为我们提供了更广阔的扩展空间。
除了选择现有镜像为基础镜像外,Docker还存在一个特殊的镜像,名为scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。如果以scratch为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,对于Linux下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接FROM scratch会让镜像体积更加小巧。使用Go语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为Go是特别适合容器微服务架构的语言的原因之一。
RUN指令是用来执行命令行命令的。由于命令行的强大能力,RUN指令在定制镜像时是最常用的指令之一。其格式有两种:
shell格式:RUN<命令>,就像直接在命令行中输入的命令一样。刚才写的Dockerfile中的RUN指令就是这种格式。
exec格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。
既然RUN就像Shell脚本一样可以执行命令,那么我们是否就可以像Shell脚本一样把每个命令对应一个RUN呢?比如这样:
Dockerfile中每一个指令都会建立一层,RUN也不例外。每一个RUN的行为就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit这一层的修改,构成新的镜像。而上面的这种写法,创建了7层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
上面的Dockerfile正确的写法应该是这样:
首先,之前所有的命令只有一个目的,就是编译、安装redis可执行文件。因此没有必要建立很多层,这只是一层的事情。这里没有使用很多个RUN一一对应不同的命令,而是仅仅使用一个RUN指令,并使用&&将各个所需命令串联起来,将之前的7层简化为了1层。在撰写Dockerfile的时候,要经常提醒自己,这并不是在写Shell脚本,而是在定义每一层该如何构建。
并且,这里为了格式化还进行了换行。Dockerfile支持Shell类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。
此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了apt缓存文件。镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像,因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。很多人初学Docker制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。
定制的nginx镜像的Dockerfile,在Dockerfile文件所在目录执行:
从命令的输出结果中,可以清晰的看到镜像的构建过程。在Step 2中,RUN指令启动了一个容器9cdc27646c7b,执行了所要求的命令,并最后提交了这一层44aa4490ce2c,随后删除了所用到的这个容器9cdc27646c7b。
使用了docker build命令进行镜像构建。其格式为:docker build [选项] <上下文路径/URL/->,在这里指定了最终镜像的名称-t nginx:v3。
docker build命令最后有一个 . 表示当前目录,而Dockerfile就在当前目录,因此不少初学者以为这个路径是在指定Dockerfile所在路径,这么理解其实是不准确的,其实这是在指定上下文路径。那么什么是上下文呢?
首先我们要理解docker build的工作原理。Docker在运行时分为Docker引擎(也就是服务端守护进程)和客户端工具。Docker的引擎提供了一组REST API,被称为Docker Remote API,而如docker命令这样的客户端工具,则是通过这组API与Docker引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker引擎)完成。也因为这种C/S设计,让我们操作远程服务器的Docker引擎变得轻而易举。
当我们进行镜像构建的时候,并非所有定制都会通过RUN指令完成,经常会需要将一些本地文件复制进镜像,比如通过COPY指令、ADD指令等。而docker build命令构建镜像,其实并非在本地构建而是在服务端,也就是Docker引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build命令得知这个路径后,会将路径下的所有内容打包,然后上传给Docker引擎。这样 Docker引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在Dockerfile中这么写:COPY ./package.json /app/,这并不是要复制执行docker build命令所在目录下的package.json,也不是复制Dockerfile所在目录下的package.json,而是复制上下文(context)目录下的package.json。
因此,COPY这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么COPY …/package.json /app或者COPY /opt/xxxx /app无法工作的原因,因为这些路径已经超出了上下文的范围,Docker引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
现在就可以理解刚才的命令docker build -t nginx:v3 . 中的这个 . ,实际上是在指定上下文的目录,docker build命令会将该目录下的内容打包交给Docker引擎以帮助构建镜像。
理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现COPY /opt/xxxx /app不工作后,于是干脆将Dockerfile放到了硬盘根目录去构建,结果发现docker build执行后,在发送一个几十GB的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让docker build打包整个硬盘,这显然是使用错误。
一般来说,应该会将Dockerfile置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给Docker引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给Docker引擎的。
那么为什么会有人误以为 . 是指定 Dockerfile 所在目录呢?这是因为在默认情况下,如果不额外指定Dockerfile的话,会将上下文目录下的名为Dockerfile的文件作为Dockerfile。这只是默认行为,实际上Dockerfile的文件名并不要求必须为Dockerfile,而且并不要求必须位于上下文目录中,比如可以用-f …/Dockerfile.php参数指定某个文件作为Dockerfile。当然,一般大家习惯性的会使用默认的文件名Dockerfile,以及会将其置于镜像构建上下文目录中。
在日常的工作中,我们常常有需求将一个程序运行在不同架构CPU的设备上,尤其在嵌入式领域,我们常常接触的各种开发板、路由器往往都是使用ARM架构的芯片,而我们日常开发的设备都是在x86平台。我们在x86平台写的程序需要运行在使用ARM芯片的开发板上,这时候就需要跨CPU构建程序。总的来说跨平台构建程序有以下几种方式。
直接在目标硬件编译:这是最直接的方法,如果我们有目标CPU架构的机器,且机器上装有我们构建和运行程序必备的各种工具,那我们可以直接把代码放到目标设备上构建、运行。这种方法虽然直接,但太过局限,如果我们没有有目标CPU架构的机器怎么办?或者即便有机器,但是构建像文件系统这样的项目,非常耗时且低效,而往往在工作中,大多都是这样庞大的项目,所以需要另一种方式。
使用交叉编译器:交叉编译器是专门为在给定的系统平台上运行而设计的编译器,作用是可以在一种CPU架构上编译出另一个CPU架构的可执行文件。最普遍的例子,开发人员开发安卓应用的时候几乎都在X86的平台上开发构建,但安卓应用很明显是ARM架构的,这其中就是交叉编译器在起作用,x86的机器上通过交叉编译器将程序编译成ARM或ARM64的程序。但交叉编译不具有通用性,它的复杂度取决于程序使用的语言,如果使用Golang的话,那就超级容易了。
模拟目标硬件:模拟目标硬件最常见的开源模拟器是QEMU,QEMU是纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备,我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机,虚拟机认为自己和硬件打交道,但其实是和QEMU模拟出来的硬件打交道,QEMU将这些指令转译给真正的硬件。QEMU支持许多常见的CPU架构,包括ARM、Power-PC和RISC-V等,然而QEMU的性能非常低,因为QEMU完全使用CPU来模拟硬件,所以的指令都要经过QEMU。但其实我们执行应用程序并不需要QEMU模拟整个硬件,还可以再精简。
通过binfmt_misc模拟目标硬件的用户空间:QEMU除了可以模拟完整的操作系统之外,还有另外一种模式叫用户态模式(User mod)。该模式下QEMU将通过binfmt_misc在Linux 内核中注册一个二进制转换处理程序,并在程序运行时动态翻译二进制文件,根据需要将系统调用从目标CPU架构转换为当前系统的CPU架构。最终的效果看起来就像在本地运行目标CPU架构的二进制文件。通过QEMU的用户态模式,我们可以创建轻量级的虚拟机(chroot 或容器),然后在虚拟机系统中编译程序,和本地编译一样简单轻松。
直接使用QEMU的用户态模式(User mod)来构建跨平台镜像,在该模式下QEMU通过binfmt_misc在Linux内核中注册一个二进制转换处理程序,在程序运行的时候实时动态的翻译二进制文件,根据实际需要将系统调用从目标CPU架构转换为当前系统的CPU架构,这样就能实现在本地运行不同于本地机器架构的二进制程序。这对于容器技术来说是最合适的模式。
首先确认内核开启了binfmt_misc,手动添加
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
echo’rm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:’ > /proc/sys/fs/binfmt_misc/register
为了方便,可以使用qemu-user-static提供的基于docker的一键解决方案:
docker run –rm –privileged multiarch/qemu-user-static:register
之后就可以构建aarch64的镜像了。构建Docker镜像的方法是使用Dockerfile,Dockerfile是一个文本文件,其中包含很多条指令,每一条指令构建一层,多层构成一个完整的Docker镜像。
代码如下:
FROM arm64v8/ubuntu:16.04
MAINTAINER qiaoshi [email protected]
COPY qemu-aarch64-static /usr/bin/qemu-aarch64-static
#执行命令
ADD riemann_cam_rom.tar.gz /usr/local2/
RUN cp -nr /usr/local2/usr / ;
cp -nr /usr/local2/bin /bin ;
cp -nr /usr/local2/lib /lib ;
cp -nr /usr/local2/sbin /sbin ;
cp -nr /usr/local2/realsee / ;
rm -rf /usr/local2/
&& sed -i ‘s/ports.ubuntu.com/mirrors.ustc.edu.cn/g’ /etc/apt/sources.list
&& sed -i ‘s/ports.ubuntu.com/mirrors.ustc.edu.cn/g’ /etc/apt/sources.list
&& apt update && apt --no-install-recommends install git gcc automake make g++ autoconf dbus curl file pkg-config shellcheck splint cppcheck libjpeg8 libtiff5 cmake libudev-dev
libtiff5-dev libpng-dev lua-zlib lua-zlib-dev doxygen openssl libssl-dev valgrind -y
&& rm -rf /var/lib/apt/lists/*
#RUN echo “export PATH=/realsee/bin:/realsee/sbin:KaTeX parse error: Expected 'EOF', got '#' at position 23: …> /etc/profile #̲RUN echo "expor…PATH
ENV LD_LIBRARY_PATH=/realsee/lib
ENV PKG_CONFIG=/usr/bin/pkg-config
ENV PKG_CONFIG_PATH=/realsee/lib/pkgconfig/:/usr/lib/aarch64-linux-gnu/pkgconfig
RUN echo “. /etc/init.d/rc.environment " >> /etc/profile && echo “export DESTDIR=/packdir” >> /root/.bashrc && echo “export DESTDIR=/packdir” >> /etc/profile
&& echo “/realsee/lib” > /etc/ld.so.conf.d/opencv.conf && ldconfig /etc/ld.so.conf.d >> /root/.bashrc
RUN mkdir -p /var/run/dbus ; rm -rf /var/run/dbus/pid
#RUN apt install jq -y
CMD [“sh”,”-c”,“source /etc/profile”]
#对外端口
EXPOSE 80
定制镜像,首先要选择一个基础镜像,我们使用Ubuntu官方提供的aarch64的ubuntu镜像进行构建。首先需要通过ADD指令将qemu-aarch64-static二进制文件复制到镜像中,因为本机架构和docker镜像架构不同,如果不使用qemu-aarch64-static动态翻译二进制文件,后面的构建层会遇到指令无法执行的问题。之后在镜像内安装一些测试需要的文件,设置一些环境变量,完成Dockerfile的编写。 然后使用如下命令构建镜像:
docker build -t realsee-camera-riemann:0.test.528 .
将镜像的名字命名为realsee-camera-riemann,版本为0.test.528。
在操作系统中(Linux),默认情况下Docker容器的存放位置在 /var/lib/docker目录下面,可以通过命令docker info | grep “Docker Root Dir” 查看。
使用docker pull下载的镜像,都会存在这个目录下,当下载的镜像过多,或容器运行过程中产生大量数据导致存储容量不足时,可以修改镜像储存的位置,有以下几种方式修改docker默认储存位置。
首先停止docker进程,然后进行链接:
#stop
$ sudo systemctl stop docker
#move
$ mv /var/lib/docker /data/docker
#ln
$ ln -sf /data/docker /var/lib/docker
然后移动整个 /var/lib/docker目录到空间比较大的目的路径。这时候启动Docker时发现存储目录依旧是 /var/lib/docker目录,但是实际上是存储在数据盘 /data/docker上了。
在配置文件中指定容器启动的参数 --graph=/var/lib/docker来指定镜像和容器存放路径。Docker的配置文件可以设置大部分的后台进程参数,在各个操作系统中的存放位置不一致。在Ubuntu中的位置是 /etc/default/docker文件,在CentOS中的位置是 /etc/sysconfig/docker文件。
#Cent 7
#更改储存位置
$ vi /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --graph /new-path/docker
sudo systemctl restart docker
如果Docker的版本是1.12或以上的,可以修改或新建daemon.json文件。修改后会立即生效,不需重启Docker服务。
#修改配置文件
$ vim /etc/docker/daemon.json
{
“registry-mirrors”:
[“http://7e61f7f9.m.daocloud.io”],
“graph”: “/new-path/docker”
}
在 /etc/systemd/system/docker.service.d目录下创建一个Drop-In文件docker.conf,默认docker.service.d文件夹不存在,必须先创建它。创建Drop-In文件的原因是希望Docker服务使用docker.conf文件中提到的特定参数,将默认服务所使用的位于 /lib/systemd/system/docker.service 文件中的参数进行覆盖。
#定义新的存储位置
$ sudo vi /etc/systemd/system/docker.service.d/docker.conf
[Service]
ExecStart=/usr/bin/dockerd --graph="/data/docker" --storage- driver=devicemapper
#重启
$ sudo systemctl start docker
/data/docker就是新的存储位置,而devicemapper是当前Docker所使用的存储驱动。如果你的存储驱动有所不同,请输入之前第一步查看并记下的值。现在,你可以重新加载服务守护程序,并启动Docker服务了,这将改变新的镜像和容器的存储位置。为了确认一切顺利,运行docker info命令检查Docker的根目录。
命令主要为docker run。
例如,输出一个“Hello World”,之后终止容器。
$ docker run ubuntu:18.04 /bin/echo ‘Hello world’
Hello world
这跟在本地直接执行 /bin/echo ‘hello world’ 几乎感觉不出任何区别。下面的命令则启动一个bash终端,允许用户进行交互。
$ docker run -t -i ubuntu:18.04 /bin/bash
其中,-t选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上,-i则让容器的标准输入保持打开。
在交互模式下,用户可以通过所创建的终端来输入命令,例如
当利用docker run来创建容器时,Docker在后台运行的标准操作包括:
(1)检查本地是否存在指定的镜像,不存在就从registry下载
(2)利用镜像创建并启动一个容器
(3)分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
(4)从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
(5)从地址池配置一个 ip 地址给容器
(6)执行用户指定的应用程序
(7)执行完毕后容器被终止
利用docker container start命令,直接将一个已经终止的容器启动运行。容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的,除此之外,并没有其它的资源。可以在伪终端中利用docker ps或docker top来查看进程信息。
可见,容器中仅运行了指定的bash应用。这种特点使得Docker对资源的利用率极高,是货真价实的轻量级虚拟化。
docker stop可以停止运行的容器。容器在docker host中实际上是一个进程,docker stop命令本质上是向该进程发送一个SIGTERM信号。如果想要快速停止容器,可使用docker kill命令,其作用是向容器进程发送SIGKILL信号。
备注:docker ps列出容器,默认列出只在运行的容器;加-a可以显示所有的容器,包括未运行的,例如异常退出状态的容器。
对于已经处于停止状态的容器,可以通过docker start重新启动。
docker start会保留容器的第一次启动时的所有参数。docker restart可以重启容器,其作用就是依次执行docker stop和docker start。容器可能因某种错误而停止运行,对于服务类容器,通常希望它能够自动重启。启动容器时设置–restart就可以达到效果。–restart=always意味着无论容器因何种原因退出(包括正常退出),都立即重启。
更多的时候,需要让Docker在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d参数来实现。下面举两个例子来说明一下。
如果不使用 -d参数运行容器,容器会把输出的结果打印到宿主机上面:
如果使用了 -d参数运行容器,此时容器会在后台运行并不会把输出的结果打印到宿主机上面(输出结果可以用docker logs查看):
注:容器是否会长久运行,是和docker run指定的命令有关,和 -d参数无关,只要命令不结束,容器也就不会退出。上述命令中,while语句不会让bash退出,因此该容器就不会退出。
使用 -d参数启动后会返回一个唯一的Id,也可以通过docker container ls命令来查看容器信息。
使用 -d启动容器后,会回到host终端。此时如果想要获取容器的输出信息,可以通过 docker container logs命令。
在使用 -d参数时,容器启动后会进入后台,启动完容器之后会停在host端。某些时候需要进入容器进行操作,包括使用docker attach命令或docker exec命令,推荐使用docker exec命令。
下面示例如何使用docker attach命令:
注意:如果从这个stdin中exit回到host端,会导致容器的停止。
docker exec后边可以跟多个参数,这里主要说明 -i -t参数。
只用 -i参数时,由于没有分配伪终端,界面没有我们熟悉的Linux命令提示符,但命令执行结果仍然可以返回。当 -i -t参数一起使用时,则可以看到我们熟悉的Linux命令提示符。
如果从这个stdin中exit回到host端,不会导致容器的停止,这就是为什么推荐大使用docker exec的原因。
(1)attach直接进入容器启动命令的终端,不会启动新的进程;
(2)exec则是在容器中打开新的终端,并且可以启动新的进程;
(3)如果想直接在终端中查看命令的输出,用attach,其他情况使用exec;
有时只是希望让容器暂停工作一段时间,比如要对容器的文件系统打个快照,或者docker host需要使用CPU,可以执行:docker pause CONTAINER [CONTAINER…]
如:bash $ docker pause bdf593fda8be bdf593fda8be
可以使用docker container rm来删除一个处于终止状态的容器。如果要删除一个运行中的容器,可以添加 -f 参数。Docker会发送SIGKILL信号给容器。
清理所有处于终止状态的容器:用docker container ls -a命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用$ docker container prune命令可以清理掉所有处于终止状态的容器。
批量删除所有已经退出的容器:$ docker rm -v $(docker ps -aq -f status=exited)。
使用docker export命令导出本地某个容器,这样将导出容器快照到本地文件。
可以使用docker import从容器快照文件中再导入为镜像。
此外,也可以通过指定URL或者某个目录来导入,例如:
$ docker import http://example.com/exampleimage.tgz example/imagerepo
注:用户既可以使用docker load来导入镜像存储文件到本地镜像库,也可以使用docker import来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。