一、安装Docker
以CentOS 7安装Docker18.06.3-ce为例。
卸载旧版本Docker
[root@master ~]# yum -y remove docker docker-common docker-selinux docker-engine
更新yum包
[root@master ~]# yum -y upgrade
添加docker-ce源
[root@master ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安装Docker
[root@master ~]# yum list docker-ce --showduplicates # 列出所有可用的软件包
[root@master ~]# yum -y install docker-ce-18.06.3.ce-3.el7 # 安装指定版本
启动Docker
[root@master ~]# systemctl enable docker.service
[root@master ~]# systemctl start docker.service
验证
[root@master ~]# docker version
[root@master ~]# docker ps
异常处理:
安装完Docker启动时遇到了以下错误:
Error starting daemon:Error initializing network controller: list bridge addresses failed: no available network
解决办法:
[root@master ~]# ip link add name docker0 type bridge
[root@master ~]# ip addr add dev docker0 172.17.42.1/16
配置镜像加速器
这里使用了163的容器镜像加速器,使用加速器可以提升获取Docker官方镜像的速度。
[root@master ~]# tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["http://hub-mirror.c.163.com"]
}
EOF
[root@master ~]# systemctl daemon-reload
[root@master ~]# systemctl restart docker.service
二、基础知识
简单概括图中的内容,就是容器运行着由镜像定义的系统。镜像由一个或多个层加上一些Docker元数据组成。
一个Docker镜像是由文件和元数据组成的。镜像文件占用了大部分空间。因为每个容器提供的隔离性,它们必须拥有自己所需工具的副本,包括语言环境和库。元数据包含了环境变量、端口映射、卷等。同一个镜像可以运行多个容器。
容器是从镜像中创建的,继承了它们的文件系统,并使用它们的元数据来确定其启动配置。容器是相互隔离的,但可以配置进行彼此通信。容器在启动时会运行一个进程。在这个进程完成时,容器将停止。这个启动进程可以派生其他进程。文件的变更通过写时复制(copy-on-write)机制存储在容器中。基础镜像不会受容器影响。
层是文件变更的集合。每当一个运行中的容器需要写入一个文件时,它会通过将该项目复制到磁盘的一个新区域来记录这一修改。在执行Docker提交时,这块磁盘新区域将被冻结并记录为具有自身标识符的一个层。每个层都可以被多个运行中的容器共享,就像一个共享库可以在内存中被多个运行中的进程共享一样。
写时复制:现有一个父进程P1,在其虚拟地址空间上有:正文段、数据段、堆、栈。相应的,内核要为这四部分分配各自的物理块,即:正文段块、数据段块、堆块、栈块。用fork()函数为该进程创建一个子进程P2。在写时复制技术下,内核只为新生成的子进程创建虚拟空间结构,它们来复制父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。
三、构建一个Docker应用程序
以构建一个to-do应用程序为例。
3.1、编写dockerfile
dockerfile是包含一系列特定命令的文本文件:
# cat dockerfile
FROM node
MAINTAINER admin@example.com
RUN git clone -q https://github.com/docker-in-practice/todo.git
WORKDIR todo
RUN npm install > /dev/null
EXPOSE 8000
CMD ["npm","start"]
- 使用
FROM
命令定义基础镜像; MAINTAINER
命令声明维护人员。这一行不是必需的,但最好有;- 使用
RUN
命令运行git命令克隆todoapp代码。Git是在基础node镜像中已经安装好的; - 使用
WORKDIR
移动到git命令克隆的新目录中。这不仅会改变构建环境中的目录,最后一条WORKDIR
命令还决定了从所构建的镜像启动容器时用户所处的默认目录; - 使用
RUN
命令运行node包管理器的安装命令(npm),这将为应用程序设置依赖,并且将安装过程中输出的内容重定向到/dev/null中; - 使用
EXPOSE
指定从所构建的镜像启动容器时应该监听8000端口。 CMD
命令告诉Docker在容器启动时将运行哪条命令。
3.2、构建Docker镜像
# docker build . -t todoapp:v1 # “.”指dockerfile所在的路径,这里用“.”表示当前目录
#-t为镜像指定名称
Sending build context to Docker daemon 53.25kB
Step 1/7 : FROM node
latest: Pulling from library/node
……
Status: Downloaded newer image for node:latest
---> bd4dba13afd5 #Dockerfile中的每个命令会创建一个新镜像,此处为新镜像的ID
Step 2/7 : MAINTAINER admin@example.com
---> Running in a397b75b6cad
Removing intermediate container a397b75b6cad
---> 6bbab057a572
Step 3/7 : RUN git clone -q https://github.com/docker-in-practice/todo.git
---> Runni‘“”’ng in fbd818e76730
Removing intermediate container fbd818e76730
---> edbd90598f11
Step 4/7 : WORKDIR todo
---> Running in 7792550b37fc
Removing intermediate container 7792550b37fc
---> b65105cc694d
Step 5/7 : RUN npm install > /dev/null
---> Running in 26210c1cc161
……
Removing intermediate container 26210c1cc161
---> a5819820aaae
Step 6/7 : EXPOSE 8000
---> Running in fc1f16b72459
Removing intermediate container fc1f16b72459
---> e711a9ae6159
Step 7/7 : CMD ["npm","start"]
---> Running in 067aa33b3b82
Removing intermediate container 067aa33b3b82
---> ebcb87c16086
Successfully built ebcb87c16086 #此次构建的最终镜像ID
Successfully tagged todoapp:v1 #像“ebcb87c16086”这样的ID引用起来会很麻烦,所以在命令中通过“-t”选项为镜像指定了标签:“todoapp:v1”。
3.3、运行Docker容器
使用刚刚构建好的镜像运行一个容器:
# docker run -d -p 8000:8000 --name example1 todoapp:v1
-d
选项:以后台程序的形式运行容器;-p
选项:将宿主机的8000端口映射到容器的8000端口上,前一个8000端口是宿主机上的端口;--name
:为容器指定唯一的名称;- “todoapp:v1”:使用的镜像。
如果容器正常运行,就可以通过http://localhost:8000
来访问应用程序了。
3.4、查看容器的状态
使用docker ps
命令查看正在运行的容器的状态:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e1013207cff7 todoapp:v1 "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 0.0.0.0:8000->8000/tcp example1
使用docker stop
命令停止容器:
# docker stop example1
使用docker ps -a
查看所有容器,包含已经停止运行的容器的状态:
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e1013207cff7 todoapp:v1 "docker-entrypoint.s…" 7 minutes ago Exited (1) 9 seconds ago example1
使用docker start
运行已经停止的容器:
# docker start example1
使用docker diff
命令查看由镜像运行容器后的文件变化:
# docker diff example1
- A:容器运行后增加的文件;
- D:容器运行后删除的文件;
- C:容器运行后有变更的文件。
四、管理容器
4.1、以交互方式运行一个容器
# docker run -t -i busybox /bin/sh
- -t:分配一个终端;
- -i:进入交互模式;
- busybox:基础镜像的名称,是一个能保证系统运行的最精简的镜像;
- /bin/sh:运行容器后启动一个bash。
4.2、以守护进程方式运行容器
在docker run
命令中使用-d
选项,可以在后台运行一个Docker容器:
# docker run -d --name daemon busybox /bin/sh -c "while true;do echo 'hello' && sleep 1;done"
运行docker logs
命令查看容器的输出:
# docker logs daemon
hello
……
强制删除正在运行的容器:
# docker rm -f daemon #对于已经退出的容器,执行`docker rm`即可
daemon
4.3、退出容器时不停掉它
如果想在退出一个容器的交互会话时不停掉容器,可以先按下Ctrl+P
然后再按Ctrl+Q
:
# docker run -t -i busybox /bin/sh
/ #
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3d16f36f319d busybox "/bin/sh" 35 seconds ago Up 35 seconds trusting_goldstine
4.4、退出容器后自动删除
运行容器时加上--rm
参数,会在容器退出后自动删除该容器:
# docker run -t -i --rm --name example2 busybox /bin/sh
/ # exit
# docker ps -a | grep example2
4.5、进入正在运行的容器
首先在后台运行一个容器:
# docker run -d --name sleeper busybox sleep infinity
使用docker exec
命令以交互方式进入到该容器:
# docker exec -t -i sleeper /bin/sh
4.6、容器重启策略
docker run
命令的--restart
选项允许用户应用一组容器终止时需要遵循的规则(即所谓“重启策略”):
- no:容器退出时不重启,是默认策略;
- always:容器退出时总是自动重启;
- on-failure[:max-retry]:只在失败时重启,也可以指定尝试重启的次数。
如下的例子,将在容器退出时自动重启,并且在重启三次后退出:
# docker run -d --name example3 --restart=on-failure:3 busybox /bin/sh -c "sleep 30;exit 1"
4.7、为容器开放端口
通过-p
选项将宿主机的12345端口映射到容器的12345端口上,前一个8000端口是宿主机上的端口:
# docker run -d -p 12345:12345 --name example4 busybox nc -l -p 12345
4.8、链接容器
假如host1容器运行着MySQL,其他容器可以通过--link
连接MySQL。
--link
选项用于链接容器,同时会设置容器的hosts文件
# docker run -d -p 3306:3306 --name host1 busybox nc -l -p 3306
# docker run --link host1:host1 --name host2 busybox ping host1
PING host1 (172.17.0.5): 56 data bytes
64 bytes from 172.17.0.5: seq=0 ttl=64 time=0.170 ms
4.9、挂载数据卷
通过-v
选项可以将宿主机中的文件挂载到容器中:
# mkdir test
# touch test/1.txt
# docker run --rm -v /root/test/:/test/ busybox /bin/sh -c "ls /test"
1.txt`
-v:是–volume的缩写,表示为容器指定一个外部的卷,告诉Docker将外部的/root/test/目录映射到容器的/test目录。如果外部目录和内部容器不存在均会被自动创建
。
4.10、数据容器
为了防止数据意外丢失,可以启动一个数据容器,让其它容器都来挂载该数据容器。
首先,启动一个数据容器:
# docker run -v /shared_data --name dc busybox touch /shared_data/somefile
其它容器通过--volumes-from
挂载该数据容器:
# docker run --rm --volumes-from dc busybox /bin/sh -c "ls /shared_data"
somefile
注意:数据容器并不需要运行,它只需要存在,在宿主机上运行过并且没有被删除。
4.11、获取容器详细信息
使用docker inspect
查看和过滤容器的元数据:
# docker inspect example1
另外,通过--format
选项可以获取指定信息。例如IP地址(正在运行的容器才能获取到IP地址):
# docker inspect --format={{.NetworkSettings.IPAddress}} sleeper
172.17.0.3
获取正在运行的容器的IP然后逐个ping:
# docker ps -q | xargs docker inspect --format={{.NetworkSettings.IPAddress}} | xargs -l1 ping -c1
docker ps -q
:获取所有正在运行的容器的ID;docker inspect
:针对容器ID获取它们的IP;xargs -l1
:ping命令每次只能接受一个IP,所以给xargs传入一个额外的参数,告诉它对每个单独的行执行该命令。
4.12、干净地杀掉容器
命令行kill程序的默认工作方式是向指定的进程发送TERM信号(即信号值为15)。这个信号表示程序应该终止,但是不要强迫程序终止。当这个信号被处理时,大部分程序将会执行某种清理工作,但是该程序也可以执行其他操作,包括忽略该信号;
docker kill
使用的是kill信号(即信号值是9),会强迫指定的程序终止。这使得程序没办法处理终止过程,意味着包括运行进程ID之类的文件可能会残留在文件系统中;
docker stop
命令则像kill命令那样工作,发送的是一个TERM信号。
4.13、找出异常退出的容器
通常,正常退出的容器,退出状态码为0。其他的可以被认为是异常退出的容器:
# comm -3 <(docker ps -a -q --filter status=exited | sort) <(docker ps -a -q --filter exited=0 | sort)
- bash里的"
<(command)
"语法称为进程替换。允许把一个命令的输出结果作为一个文件,传递给其他命令,在无法使用管道的时候很有用; docker ps -a -q --filter status=exited | sort
:找出所有已经退出的容器ID,并排序;docker ps -a -q --filter exited=0 | sort
:找出所有正常退出的容器,并排序;comm -3
:列出只在第一个文件而不在第二个文件中的行。
关于comm
命令:
comm -12 file1 file2: Print only lines present in both file1 and file2;
comm -3 file1 file2:Print lines in file1 not in file2, and vice versa。
4.14、批量删除已经退出的容器
# docker ps -a -q --filter status=exited | xargs --no-run-if-empty docker rm
--filter
根据条件过滤容器。命令根据退出状态;xargs --no-run-if-empty
:避免在前面的命令完全没有输出的情况下执行该命令
也可以根据4.13中的comm
命令,找出异常退出的容器并删除。
五、创建Docker镜像的方式
5.1、使用Docker命令创建镜像
用已有的基础镜像运行一个容器,并在容器中做一些改动,构建自定义的镜像。
首先使用基础镜像运行一个容器,例如使用busybox镜像运行名称为“temp”的容器,并在其中创建/data/hello文件:
# docker run -ti --name temp busybox /bin/sh
/ # mkdir /data
/ # touch /data/hello
/ # exit
使用docker commit
命令基于该容器构建名称为“mybusybox:v1”的镜像:
# docker commit temp mybusybox:v1
# docker images | grep mybusybox
使用自定义的镜像运行容器:
# docker run --rm mybusybox:v1 /bin/sh -c "ls /data/"
hello
5.2、使用dockerfile构建镜像
构建步骤参考步骤三中的内容。
除了3.1中用到的dockerfile命令,还有以下常见的命令:
- ADD:将宿主机中的目录或文件添加到镜像中,并且会自动解压文件。如:bzip2、gzip、tar、xz等;
- COPY:作用同
ADD
命令,但是不会自动解压缩文件; - ENTRYPOINT:入口点。总是在容器启动之后运行,如果用户在运行容器时传入一条命令,将会作为参数被传给入口点,然后取代在
CMD
部分定义的默认值。只能通过--entrypoint
选项给docker run
命令来重载入口点。 - VOLUME:挂载数据卷。
5.3、关于构建镜像时的缓存
使用Dockerfile进行构建时,可以利用一个很有用的缓存功能:已经运行过的构建步骤只有在命令内容发生变化时才会被重新执行。
如果在重新构建时不希望使用缓存,可以使用--no-cache
参数:
# docker build --no-cache .
5.4、导入、导出镜像
通过save
导出镜像,load
导入镜像:
# docker save todoapp:v1 -o todoapp.tar #将镜像导出为文件
# docker tag todoapp:v1 todoapp:v2 #重命名已有的镜像,避免同名镜像
# docker load -i todoapp.tar #将导出的文件导入
通过export
导出容器,import
将容器作为镜像导入。也是构建镜像的一种方法:
# docker export host1 -o host1.tar #将运行中的容器导出
# docker import host1.tar host1:v1 #将容器导入为镜像
或者简化为:
# docker export host1 | docker import - host1:v2
5.5、镜像构建历史
可以通过docker history
查看镜像的构建历史:
# docker history todoapp:v1
这样的,其他用户可以指定任何一个构建过程中的分层运行一个容器,如果该分层里敏感信息,则将暴露给其他用户。
为了避免这种情况,可以通过docker export | docker import
对镜像做扁平化处理:
# docker history host1:v1
IMAGE CREATED CREATED BY SIZE COMMENT
c8cda2e05870 14 seconds ago 1.23MB Imported from -