Docker 运行容器前需要本地存在对应的镜像。本文包括:
从 Docker 镜像仓库获取镜像的命令是 docker pull 。其命令格式为:
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号] 。默认地址是 Docker
Hub。
仓库名: <软件名> 。对于 DockerHub,如果不给出用户名,则默认为 library ,也就是官方镜像,。如果不给出标签,将以 latest 作为默认标签。
比如:
docker pull ubuntu:16.04
运行这部分内容在容器中详细介绍
$ docker run -it --rm \
ubuntu:16.04 \
bash
docker image ls
$ docker image rm [选项] <镜像1> [<镜像2> ...]
用 ID、镜像名、摘要删除镜像,可以用镜像的完整 ID,来删除镜像。 也可以使用短ID(3个字符以上)来删除镜像。
$ docker image rm 7aa
Untagged 和 Deleted:
到删除行为分为两类,一类是Untagged ,另一类是 Deleted 。镜像的唯一标识是其 ID 和摘要,而一个
镜像可以有多个标签。
当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。
简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。启动容器有两种方式,一种是基于镜像新建一个容器,另外一个是将在终止状态( stopped )的容器重新启动。定制镜像应该使用 Dockerfile 来完成,这部分内容我们在后面介绍
新建并启动
用镜像Ubunut16.04创建容器并启动,然后终止容器。
docker run ubuntu:16.04 /bin/echo 'Hello world!'
还可以则启动一个 bash 终端,允许用户进行交互。
docker run -t -i ubuntu:16.04 /bin/bash
-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i则让容器的标准输入保持打开。
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
后台运行容器
docker run -d ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
这条命令是无限的输出hello word,此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用docker logs 查看)。
查看容器
docker container ls
-a 参数可以查看停止的容器
查看docker日志: docker container logs [container ID or NAMES]
docker logs jovial_blackwell
进入容器
在使用 -d 参数时,容器启动后会进入后台。某些时候需要进入容器进行操作:
docker attach 是 Docker 自带的命令。下面示例如何使用该命令。
Ctrl+c会导致容器的停止。使用docker container ls
就不会看到刚才进入的容器了。这就引出了exec命令
docker exec 后边可以跟多个参数,这里主要说明 -i -t 参数。
当 -i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。如果从这个 stdin 中 exit,不会导致容器的停止。
导出/入容器
如果要导出本地某个容器,可以使用 docker export 命令。
docker export
可以使用 docker import 从容器快照文件中再导入为镜像,例如
可以通过指定 URL 或者某个目录来导入,例如
docker import http://example.com/exampleimage.tgz example/imagerepo
删除容器
用 docker container ls -a 命令可以查看所有已经创建的包括终止状态的容器。可以使用 docker container rm 来删除一个处于终止状态的容器。清理掉所有处于终止状态的容器:docker container prune
镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。举个例子:
docker run --name webserver -d -p 80:80 nginx
这条命令会用 nginx 镜像启动一个容器,命名为 webserver ,并且映射了 80 端口,可以用浏览器去访问这个nginx服务器
如果需要修改页面中的文字,该怎么办?可以使用docker exec 命令进入容器,修改其内容。
docker exec -it webserver bash
echo 'Hello, Docker!
' > /usr/share/nginx/tml/index.html
exit
最终变成了:
为什么能?使用的命令就是将hello docker 写入了一个文件中,但是请注意这是容器的文件,而不是linux中的文件,也就是说修改了容器的文件,也就是改动了容器的存储层。
Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
比如:
docker commit --author four --message '修改主页' webserver nginx:v1
可以docker image ls 中看到这个新定制的镜像。当然这个镜像也是可以使用的,和普通的没什么区别。
commit的缺点:
在上面的commit一节中了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,这个脚本就是 Dockerfile。Dockerfile 是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。至于Dockerfile的优点我们后面再说。
举个例子:
mkdir mynginx
cd mynginx
touch Dockerfile
内容为:
FROM nginx
RUN echo 'Hello, Docker!' > /usr/share/nginx/html/index.html
构建镜像:
其中-t指定了最终镜像的名称。返回的结果描述了镜像构建的过程,在 Step2 中,RUN 指令启动了一个容器。RUN 指令启动了一个容器 22829ea4af92 ,执行了所要求的命令,并最后提交了这一层 e133641a2a62 ,随后删除了所用到的这个容器 22829ea4af92 。
Dockerfile 中每一个指令都会建立一层。每一个 RUN 的行为,就和刚才我们commit建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。因此构建环境等命令尽量使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。
**注意:**每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。比如下面例子:
RUN cd /app
RUN echo "hello" > world.txt
第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任
何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。当然可以使用WORKDIR 指定工作目录WORKDIR <工作目录路径>
docker build 的工作原理:
docker build 命令进行镜像构建。其格式为:
docker build [选项] <上下文路径/URL/->
命令最后有一个’.’ ,’.’ 表示当前目录,这是在指定上下文路径。docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
实际上 Dockerfile 的文件名并不要求必须为 Dockerfile ,而且并不要求必须位于上下文目录中,比如可以用 -f …/Dockerfile.php 参数指定某个文件作为Dockerfile 。
Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 DockerRemote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。
和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
<源路径> 可以是多个,甚至可以是通配符,<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。
ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。比如 <源路径> 可以是一个 URL
CMD 指令的格式和 RUN 相似,也是两种格式:
CMD <命令>
CMD ["可执行文件", "参数1", "参数2"...]
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。 CMD 指令就是用于指定默认的容器主进程的启动命令(参数)。
ENTRYPOINT 的目的和 CMD一样,都是在指定容器启动程序及参数。 ENTRYPOINT 在运行时也可以替代,不过比 shell 要略显繁琐。
练习
复习一下curl命令:
如上图所示,curl -si url 命令打印出了响应的内容。
编辑如下Dockerfile,构建docker build -t myip .
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD curl -s https://ip.cn
然后执行:
docker run myip
当前 IP:1.180.209.251 来自:内蒙古自治区呼和浩特市 电信
那么如果我们希望显示 HTTP头信息,就需要加上 -i 参数。但是直接加载上面的命令后面会报错,跟在镜像名后面的是 command ,运行时会替换 shell 的默认值。因此这里的 -i 替换了原来的shell ,而不是添加在原来的 curl -s http://ip.cn 后面。而 -i 根本不是命令。
ENTRYPOINT的应用,编辑Dokerfile文件:
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "https://ip.cn" ]
然后使用docker run myip -i
这是因为当存在 ENTRYPOINT 后, 我们的命令参数将会作为参数传给ENTRYPOINT ,而这里 -i 参数传给 curl ,从而达到了我们预期的效果。
设置环境变量格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
如:
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是, ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。
格式为:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
比如VOLUME /data命令中, /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:
docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。 WORKDIR 是改变工作目录, USER 则是改变之后层的执行 RUN , shell 以及 ENTRYPOINT 这类命令的身份。
HEALTHCHECK 健康检查
HEALTHCHECK [选项] shell <命令> :设置检查容器健康状况的命令
HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting ,在HEALTHCHECK 指令检查成功后变为 healthy ,如果连续一定次数失败,则会变为unhealthy。和 shell , ENTRYPOINT 一样, HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
shell curl -fs http://localhost/ || exit 1
–interval=<间隔> :两次健康检查的间隔为5秒,–timeout=<时长> :健康检查命令运行超时时间为3s。并且使用 curl -fs http://localhost/ || exit1 作为健康检查命令。。命令的返回值决定了该次健康检查的成功与否: 0 :成功; 1 :失败; 2 :保留,不要使用这个值。
ONBUILD
ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
ONBUILD [INSTRUCTION]