Docker 笔记(基础)

容器是一个打包了应用和服务的环境。它是一个轻量级的虚拟机,每一个容器都由一组特定的应用和必要的依赖组成。

# 容器的管理操作

# 创建容器

对于容器常见的命令包括:创建、查看、启动、终止(停止)、删除。

创建容器可以使用 docker create 和 docker run,它们的区别是,docker create 创建容器完成后,容器处于停止状态,而 docker run 创建容器后同时启动容器(相当于执行了:docker create -> docker start)。

docker create 镜像名称

# 比如:docker create ubuntu 不带 “:版本号” 时将使用官方最新的镜像。

# 使用 docker ps 可以查看当前正在运行的容器,使用 docker ps -a 可以查看所有容器,包括运行中和未运行的容器。
1
2
3
4
5

docker run 可以创建交互型容器和后台型容器。

docker run -i -t --name=inspect_shell ubuntu /bin/bash # 将创建交互型容器

docker run -d --name=daemon_while ubuntu /bin/bash -c
"while true;do echo hello world;sleep 1;done" # 将创建后台型容器

# 上面 --name 参数的名称必须是唯一的不能重复。
1
2
3
4
5
6

创建容器时首先会搜索本地是否存在该镜像,如果不存在就从公有仓库下载,否则直接使用本地镜像创建容器,容器的文件系统是在只读的镜像文件上增加一层可读写的文件层,并通过网桥的方式将该网络接口桥接到宿主主机上去,然后该虚拟网络接口分配一个 IP 地址。

# 查看容器

使用 docker ps 命令即可,可以跟不同的参数,具体使用查看帮助即可:docker ps --help

docker ps  # 只查看当前运行中的容器

docker ps -l  # 列出最后创建的容器

docker ps -n=x  # 查看最后创建的 x 个容器

# 结果中的状态是 Exited 时,括号时的数字为 0 时正常退出,其它值都为异常退出。
1
2
3
4
5
6
7

# 启动容器

通过 docker run 创建的容器直接进行运行状态,而使用 docker create 创建的容器需要使用 docker start 来启动它。

docker start 容器 ID 或容器名称

docker run --restart=always  --name docker_restart -d ubuntu /bin/sh -c
"while rue;do echo hello world;sleep 1;done"

docker run ubuntu echo "Hello docker" # 此容器输出后马上就停止了
docker ps -a # 可以查看到容器的 ID 和名称,要想再次启动此容器而不是创建一个新的容器可以使用下面的方式:
docker start -i 容器 ID # 多了个 -i 参数,表明输出到终端,如果不带 -i 参数是看不到 Hello docker 输出的。
1
2
3
4
5
6
7
8

restart 为 always 是不管容器返回码是什么都会尝试重启容器,另外可以设置成 --restart=on-failure:5 来设置非 0 返回码时重启尝试的次数。

# 终止容器

正常退出容器使用 docker stop 容器名称或容器 ID,另外可以使用 docker kill 强制退出容器。

docker stop 容器名称或容器 ID
docker kill 容器名称或容器 ID
1
2

# 删除容器

docker rm 容器 ID 或容器名称 # 只能删除未运行的容器
docker rm -f 容器 ID 或容器名称 # 强制删除,运行中的容器可将删除
docker rm `docker ps -a -q` # 批量删除所有容器,-q 参数只列出容器的 ID
1
2
3

# 容器内信息获取和命令执行

# 依附容器

依附容器命令只能用在交互型容器中,就是以 -i -t 参数运行的容器,可以依附若干的终端,而且多个终端操作是同步的,就是你在其中一个终端输入了什么,在其它的终端也将显示什么,退中一个终端使用 exit 退出,所有依附的终端都将退出。

docker run -i -t ubuntu /bin/sh

#然后再开另一个终端使用

docker attach ubuntu
#即可以进入交互界面,这样就会有两个终端,而且是同步的。此命令目前输入完后必须按两次回车才正常出现交互界面。

#后台型容器是无法使用此命令依赖终端的。
1
2
3
4
5
6
7
8

# 查看容器日志

交互型容器可台通过依附命令直接进入查看日志,而后台型容器可以通过 docker logs 命令来查看容器日志。

docker run -d --name =daemon_logs ubuntu /bin/bash -c "for((i=0;1;i++));do echo $i;sleep 1;done;"
# 注意这里要使用双引号

docker logs -f deamon_logs #-f 实时查看容器日志

docker logs -f --tail=x daemon_logs # 只实时查看最后 x 行日志

docker logs -f --tail=5 -t daemon_logs # -t 查看日志产生的时间
1
2
3
4
5
6
7
8

# 查看容器进程

docker top 可以查看容器内正在运行的进程

docker top 容器 ID 或容器名称
1

# 查看容器信息

docker inspect 用于查看容器的配置信息,包括容器名称、环境变量、运行命令、主机配置、网络配置和数据卷配置等。

docker inspect 容器 ID 或容器名称
1

详细使用可以查看帮助,docker inspect --help

# 容器内执行命令

在容器启动的时候,通常需要指定需要执行的程序,然而有时候我们需要在容器运行之后中途启动另一个程序 。从 Docker 1.3 开始,我们可以使用 docker exec 命令在容器中运行新的任务。它可以创建两种任务:后台型任务和交互型任务。

docker exec -d daemon_dave touch /etc/new_config_file # 后台型任务

docker exec -i -t daemon_dave /bin/bash # 交互型任务,这个和创建交互型容器一样。

#注意:运行此命令容器都必须是运行中
1
2
3
4
5

# 容器的导入和导出

用户不仅可以把容器提交到公共服务器上,也可以将容器导出到本地文件系统中,再需要的时候可以重新导入到 Docker 运行环境中。

# 容器导出

docker run -i -t --name=inspect_import ubuntu /bin/bash
1

然后对容器按需要修改,安装需要的软件等,完成后执行下面的命令导出到本地系统中。

docker export inspect_import > my_container.tar
1

# 容器导入

cat my_container.tar |docker import - imported:container
#imported 为镜像名称,container 为镜像标签 (tag)
1
2

还可以从 url 导入网络上的容器:

docker import url res:tag
1

导入成功后,使用 docker images 命令可以查看导入的镜像。

# 镜像的概念

镜像是一个包含程序运行必要依赖环境和代码的只读文件,它采用分层的文件系统,将每一次改变以读写层的形式增加到原来的只读文件上。

# 镜像与容器

如果将容器理解为一套程序运行的虚拟环境,那么镜像就是用来构建这个环境的模板。通过同一镜像,我们可以构造出很多相互独立但运行环境一样的容器。

镜像的最底层必须是一个称为启动文件 (bootfs) 的镜像,用户不会与这一层直接打交道。bootfs 的上层镜像叫作根镜像 (rootfs),它在通常情况下是一个操作系统,如 Ubuntu、Debina 和 CentOS 等。

(素材来源与网络)

# 本地镜像的管理

# 查看

docker images
docker images ub* #使用通配符
1
2

查看结果类似下表

REPOSITORY TAG IMAGE_ID CREATED VIRTUAL SIZE
centos laster 10009deww3 45 hours ago 199MB

REPOSITORY 是仓库名称,一般用来存放同一类型的镜像,其名称由创建者指定。

# 命令规则

1、[namespace\ubuntu]:由命名空间和实际的仓库名组成,中间用”\“隔开,如果要提交到 DOcker Hub 非官方的仓库名必须要遵守此规则。

2、[ubuntu]:只有仓库名,它属于顶级命名空间,该空间只能用于官方的镜像。本地也可以这种命名,但是无法分发到 DOcker Hub 上进行分享。

3、[dl.dockerpool.com:5000\ubuntu:12.04]:指定 URL 路径的方式。一般是用于自己搭建的 Hub 或者第三方 Hub 上,dl.dockerpool.com:5000 是第三方 Hub 的主机名及端口。

TAG 用于区分同一仓库中的不同镜像。如果未指定,默认为 laster

IMAGE_ID 每个镜像都有一个字符串类型、长为 64 位的 HashID,同容器 ID 一样,用来唯一标识一个镜像。一般取前面一部分只要在本机唯一标识一个镜像即可,和 Git 的 CommitID 规则类似。

CREATED 镜像创建时间。

VIRTUAL SIZE 镜像所占用的虚拟大小,该大小包含了所有共享文件的大小。

# 下载

docker run 命令运行一个镜像时,首先会在本地查找镜像,如果本地不存在会继续去 Docker Hub 上面搜索符合条件的镜像并将其下载下来运行。

docker pull ubuntu:13.10
13.10: Pulling from library/ubuntu #本地已经有了
6599cadaf950: Pull complete
23eda618d451: Pull complete
f0be3084efe9: Pull complete
52de432f084b: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:15b79a6654811c8d992ebacdfbd5152fcf3d165e374e264076aa435214a947a3
Status: Downloaded newer image for ubuntu:13.10
1
2
3
4
5
6
7
8
9
docker search wordpress #Docker Hub 搜索符合条件的镜像

#搜索结果会显示
#镜像的名称 (NAME)、描述 (DESCRIPTION)、
#镜像评分 (STARS)(越高表明质量越好)、
#是否为官方镜像 (OFFICIAL)、是否使用了自动构建 (AUTOMATED)
1
2
3
4
5
6

有时运行的镜像本地没有,会从 Docker Hub 上获取,可以通过 docker pull 命令预先下载到本地,再使用 docker run 命令。

docker pull ubuntu
1

# 删除

删除的镜像如果被容器所依赖,即便容器已停止了,也仍然需要依赖镜像,这里不能直接删除,如果一定要强制删除可以使用 -f 参数。但是为了安全起见建议先停止容器,删除容器后再删除镜像。

docker rmi 镜像 ID 或名称 #比删除容器命令多了一个 i
docker rmi -f  镜像 ID 或名称 #强制删除镜像
1
2

# 创建本地镜像

# 创建本地镜像有三种方式

1、通过镜像导出成 tar 文件的方式,然后导入镜像创建一个新镜像。(这种方式在 Docker 开发实践笔记一 容器的导入和导出已说明)

2、使用 commit 命令保存对基础镜像的修改创建出新的镜像

运行镜像容器后,对容器进行修改,然后使用 commit 提交创建。

docker run -i -t ubuntu
#修改容器,比如安装软件、修改配置等。

docker commit -m="Message" --author="LIU" 运行的容器 ID myDOcker/redis:v1

#如果需要对上面的镜像再修改,进入容器内更新后再使用上面的命令提交一个新版本镜像即可。

docker commit -m="Message" --author="LIU" 运行的容器 ID myDOcker/redis:v2
1
2
3
4
5
6
7
8

3、使用 Dockerfile 文件创建镜像(推荐方式)

Dockerfile 用来创建一个自定义的 image, 包含了用户指定的软件依赖等。

下面是一个 Dockerfile 文件实例,本例只是介绍语法无任何实际意义。

#version: 1.00

#指定待扩展的父级镜像。除注释外文件开头必须是一个 FROM 指令
FROM ubuntu:latest

#作者信息
MAINtAINER jiuwu "[email protected]"

#设置 root 用户为后续命令的执行者
USET root

#执行操作
RUN apt-age update
RUN apt-get install -y nginx

#使用 && 拼接命令
RUN touch test.txt && echo "abc" >> abc.txt

#对外暴露端口
EXPOSE 80 8080 1038

#添加文件 第一个是源文件,第二个是要添到的容器中的目标路径
ADD abc.txt /opt/

#添加文件件
ADD /webapp /opt/webapp

#添加网络文件
ADD https://www.baidu.com/logo.jpg /opt/

#设置环境变量
ENV WEBAPP_PORT=9000

#设置工作目录
WORKDIR /opt/

#设置启动命令,只能出现一次,后面的会覆盖前面的
ENTRYPOINT ["ls"]

#设置启动参数,只能出现一次,后面的会覆盖前面的
CMD ["-a","-l"]

#设置卷
VOLUME ["/data","/var/www"]

#设置子镜像的触发操作(当有镜像以此镜像为父镜像时,会执行下面的操作)
ONBUILD ADD ./app/src
ONBUILD RUN echo "on build excuted " >> onbuild.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 下面是对上述指令的详细说明

指令不区分大小写。但是,命名约定为全部大写。 所有 Dockerfile 都必须以 FROM 命令开始。 FROM 命令会指定镜像基于哪个基础镜像创建,接下来的命令也会基于这个基础镜像(译者注:CentOS 和 Ubuntu 有些命令可是不一样的)。FROM 命令可以多次使用,表示会创建多个镜像。具体语法如下:

FROM <image name>
1

1、MAINTAINER:设置该镜像的作者。语法如下:

MAINTAINER <author name>
1

2、RUN:在 shell 或者 exec 的环境下执行的命令。RUN 指令会在新创建的镜像上添加新的层面,接下来提交的结果用在 Dockerfile 的下一条指令中。语法如下:

RUN <command>
RUN apt-get update #这种形式使用 /bin/sh 环境中执行的命令
RUN ["apt-get","update"] #直接使用系统调用 exec 来执行

#多个命令可以用 && 连接执行
RUN apt-get update && apt-get install nginx
1
2
3
4
5
6

这里需要注意一下,因为一次 RUN 会增加一个新层,所有最佳实践是减少 RUN 次数,尽量将多个操作合并到 RUN 中一次执行。

3、ADD:复制文件指令。它有两个参数 <source><destination>。destination 是容器内的路径。source 可以是 URL 或者是启动配置上下文中的一个文件。语法如下:

ADD <src> <destination>
1

如果源文件是主机上的 zip 或 tar 形式的压缩文件,Docker 会先解压缩,然后将文件添加到镜像指定的位置,如果是 URL 指定的网络压缩文件则不解压。

4、CMD:提供了容器启动时默认执行的命令。 Dockerfile 只允许使用一次 CMD 指令。 使用多个 CMD 会抵消之前所有的指令,只有最后一个指令生效。 CMD 有三种形式:

CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2
1
2
3

5、EXPOSE:指定容器在运行时监听的端口。语法如下:

EXPOSE <port> # 多个端口使用空格隔开
EXPOSE 80 8080 9000
1
2

运行容器时通过参数 -P(大写) 即可将 EXPOSE 里所指定的端口映射到主机上另外的随机端口,其它容器或主机就可以通过映射后的端口与此容器通信。同时,我们也可以通过 -p(小写) 参数将 DOckerfile 中 EXPOSE 中没有列出的端口设置成公开的。

6、ENTRYPOINT:配置给容器一个可执行的命令,这意味着在每次使用镜像创建容器时一个特定的应用程序可以被设置为默认程序。同时也意味着该镜像每次被调用时仅能运行指定的应用。类似于 CMD,Docker 只允许一个 ENTRYPOINT,多个 ENTRYPOINT 会抵消之前所有的指令,只执行最后的 ENTRYPOINT 指令。语法如下:

ENTRYPOINT ["executable", "param1","param2"]
ENTRYPOINT command param1 param2
1
2

7、WORKDIR:指定 RUN、CMD 与 ENTRYPOINT 命令的工作目录。语法如下:

WORKDIR /path/to/workdir
1

8、ENV:设置环境变量。它们使用键值对,增加运行程序的灵活性。语法如下:

ENV <key> <value>
1

在运行容器的时候可以通过 -e 参数来设置或修改环境变量:

docker run -e WEBAPP_PORT=8000 -e WEBAPP_HOST=www.test.com ....
1

9、USER:镜像正在运行时设置一个 UID。语法如下:

USER <uid> # 比如:USER root
1

10、VOLUME:授权访问从容器内到主机上的目录。语法如下:

VOLUME ["/data"]
1

此挂载一般是在运行容器的时候指定,因为要关联到宿主机上,这样分享 Dockerfile 就不方便了。多个容器可以通过同一个挂载点共享数据,即便其中一个容器已经停止,挂载点也仍然可以访问,只有当挂载点的容器引用全部消失时,挂载点才会自动删除。

11、ONBUILD:指定的命令在构建镜像时并不执行,而是在它的子镜像中执行。

ONBUILD 中的命令会在当前镜像的子镜像构建时执行。可以把 ONBUILD 命令当成父镜像的 Dockerfile 传递给子镜像的 Dockerfile 的指令。

在子镜像的构建过程中,Docker 会在执行 Dockerfile 中的任何指令之前,先执行父镜像通过 ONBUILD 传递的指令。

当从给定镜像构建新镜像时,ONBUILD 指令很有用。例如,你可能会在一个语言栈镜像中使用 ONBUILD,语言栈镜像用于在 Dockerfile 中构建用户使用相应语言编写的任意软件,正如 Ruby 的 ONBUILD 变体

使用 ONBUILD 构建的镜像应用一个单独的标签,例如:ruby:1.9-onbuild 或 ruby:2.0-onbuild。

在 ONBUILD 中使用 ADD 或 COPY 时要格外小心。如果新的构建上下文中缺少对应的资源,“onbuild”镜像会灾难性地失败。添加一个单独的标签,允许 Dockerfile 的作者做出选择,将有助于缓解这种情况。

最后再重点说明一下 CMD 和 ENTRYPOINT 命令:

两个命令都是用来指定容器启动时默认运行的命令。

假如,我们指定的 ENTRYPOINT 是这样的:

ENTRYPOINT ["ls","-l"]
1

并构建出名为 jiuwu/newImage 的镜像,那么 docker run jiuwu/newImage 的运行结果与 docker run ubuntu ls -l 是一样,而 docker run jiuwu/newImage -a 的运行结果与 docker run ubuntu ls -l -a 一样。

不然发现,ENTRYPOINT 与 CMD 的区别在于运行容器时添加在镜像名之后的参数,对 ENTRYPOINT 是拼接,而对 CMD 则是覆盖。我们可以在运行容器的时候使用 --entrypoint 来覆盖 Dockerfile 中的指定:

docker run --entrypoint echo jiuwu/newImage "hello docker"
1

输出为:hello docker

通常情况下风们会将 ENTRYPOINT 和 CMD 搭配起来使用。ENTRYPOINT 用于指定需要运行的命令,CMD 用于指定运行命令所需要的参数(这是个知识点,记住)。示例如下:

ENTRYPOINT  ["ls"]
CMD ["-a","-l"]
1
2