1-Docker - 容器、镜像、网络、数据卷
before
本篇主要介绍docker的镜像、容器、网络、数据卷的常用操作。
环境:centos7
docker守护进程与客户端
docker的C/S 模式
docker是以客户端和守护进程的方式运行。
如上图所示,docker的守护进程运行在宿主机上,守护进程在启动后负责提供各种服务;而docker的使用者不会直接与守护进程打交道,而是通过docker的客户端(即命令行接口)与docker的守护进程进行通信和执行客户端的命令,然后将命令再返回给docker的客户端。
docker的命令行接口是与守护进程打交道的最重要的方式,这也是我们需要重点掌握的;除此之外,还有别的方式可以与守护进程进行通信,那就是Remote API
。
Remote API
提供了RESTful
风格API
与守护进程进行通信。也就是说我们可以通过编写程序来与docker守护进程进行通信,如下图。
docker remote api
官方网站参考文档:https://docs.docker.com/engine/api/
如果你是Python开发者,你可以直接使用pip下载,如下面的简单的示例:
# pip install docker
import docker
c = docker.APIClient(base_url='tcp://192.168.85.129:2375')
print(c.info())
print(c.images())
当然,默认docker是不支持远程访问的,需要单独配置,我们后续会讲,这里先来研究客户端和守护进程是如何通信的。
docker客户端与守护进程的连接方式
docker通过socket实现客户端与守护进程进行通信,有下面三种方式实现:
unix:///var/run/docker.sock
,unit sock是docker默认的客户端和守护进程进行通信。tcp://host:port
fd://socketfd
来看完整的客户端与server端通信的原理。
如上图,用户通过命令行或者自定义的程序或者app与docker client通信,然后docker client通过socket与docker server端进行通信。也因此,我们可以在本地和远程与docker server端进行通信和执行命令。
如下面通过命令行来查看docker的版本信息:
[root@C ~]# docker version
Client:
Version: 17.12.1-ce
API version: 1.35
Go version: go1.9.4
Git commit: 7390fc6
Built: Tue Feb 27 22:15:20 2018
OS/Arch: linux/amd64
Server:
Engine:
Version: 17.12.1-ce
API version: 1.35 (minimum version 1.12)
Go version: go1.9.4
Git commit: 7390fc6
Built: Tue Feb 27 22:17:54 2018
OS/Arch: linux/amd64
Experimental: false
docker守护进程的配置和操作
查看守护进程
ps -ef|grep docker
启动/停止/重启docker服务
systemctl status docker
systemctl start docker
systemctl restart docker
容器管理
容器部分,主要掌握:
- 容器的基本操作
- 守护式容器
- 在容器中部署一个静态网站
容器的基本操作
启动容器
在docker中,使用run
命令启动容器
docker run IMAGE [COMMAND] [ARG...]
# 如
docker run centos echo "hello docker"
docker run hello-world
docker run --name myjenkins --restart=always -d -p 8231:8080 jenkins:latest
相关参数(不完全):
--name
为容器指定一个别名。-d
后台运行容器。-p
端口映射,将宿主机的8231
端口与Jenkins的8080
端口建立映射关系。-e
向容器内传递环境变量,比如启动MySQL容器的时候,传递密码。--restart=always
,该参数用来设置容器伴随docker服务重启而自动启动;默认的, 当docker服务器重启后,容器不会自动重启。
注意,如上面的示例docker run centos echo "hello docker"
这个容器在执行输出完hello docker
后,就停止了,就像起了一个进程,然后执行了命令,然后进程结束;如果你执行的是能让容器永久运行的命令,如最后一个命令启动Jenkins
,这个容器就会一直运行。
进入交互式容器环境
# 进入容器交互式环境
docker run -i -t IMAGE /bin/bash
docker run -i -t IMAGE bash
docker exec -it 容器名 bash
docker exec -it -u root 容器名 bash
# 退出交互式环境
exit
-it
参数是直接启动一个tty,也就是启动一个虚拟机的shell窗口。-u root
,使用root用户进入交互式环境,避免权限问题。
当你进入到容器中的交互式环境中时,就可以执行常用的命令了,就像你使用虚拟机一样。
注意docker exec
是进入容器中执行shell命令,而不是专门用来进入容器交互式环境的专有命令,如下面这条命令。
docker exec -it myjenkins ping www.baidu.com
更新容器的配置
有的时候,我们再启run容器的时候,可能忘了带某些参数,那想要添加上这些参数该怎么办呢?一个是把容器删了重新run,另一种方法就是使用下面命令更新:
docker container update [OPTINOS] 容器名
[root@r /]# docker container update --restart=always zentao-server
zentao-server
列出容器的详细(元)信息
docker inspect IMAGE/CONTAINER ID
# inspect 后可以跟镜像名称或者容器的id,也可以是我们自定义的容器名
查看docker的信息
docker info
copy命令
# 将容器中的指定目录下的文件拷贝到宿主机的指定目录中
docker cp myjenkins:/var/jenkins.home/text.txt /home
# 将宿主机中的文件拷贝到容器中指定目录下
docker cp /home/text.txt myjenkins:/var/jenkins.home/text.txt
(重要)挂载数据卷
docker run --name myjenkins -d -p 8099:8080 -v /home/data/jenkins_data:/var/jenkins_home jenkins/jenkins
-v
将宿主机的/home/data/jenkins_data
挂载到Jenkins容器的/var/jenkins_home
目录,该目录也是Jenkins的工作目录。目标是将容器产生的数据持久化到本地(宿主机)。
docker ps
# docker ps不加别的参数时,返回的是正在运行中的容器
docker ps
docker ps -s
docker ps -a
# 过滤
docker ps|grep mysql
各参数:
-s
增加容器大小列。-a
列出所有的容器,不加-a
参数,只列出Up
容器。Up
正在运行的容器。Exited
已退出的容器。
重新启动已经停止的容器
docker start [-i] CONTAINER
docker stop CONTAINER
删除容器
删除容器分为两种情况,容器是否正在运行和已经停止的容器。对于已经停止的容器,可以直接使用下面命令删除:
docker rm CONTAINER
那么如何删除正在运行的容器呢?
# 强制删除
docker rm -f 容器名称
# 一次删除多个容器,以空格分割
docker rm -f Container1 Container2
docker守护式容器
在之前的示例中,执行下面的容器,在echo命令执行完毕后,容器就停止了。
docker run centos echo "hello world"
但是在大多数情况下,我们需要容器来长期运行。这就用到了docker的守护式容器了:
- 能够长期运行容器
- 没有交互式会话
- 适合长期运行应用和服务
那如何让容器以守护的形式运行呢?
以CTRL + P
和CTRL + Q
的方式运行守护式容器
先来看最简单的方式:
docker run -it IMAGE /bash
然后以CTRL + P
和CTRL + Q
退出容器,这样容器就会在后台运行。
来看示例:
[root@C ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@C ~]# docker run -it centos bash
[root@73f7b9eb8b00 /]# [root@C ~]#
[root@C ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
73f7b9eb8b00 centos "bash" 11 seconds ago Up 10 seconds happy_thompson
上面的示例中,首先我们使用dpcker ps
命令查看,发现并没有正在运行的容器,然后使用docker run -it centos bash
启动容器,然后以CTRL + P
和CTRL + Q
退出容器,在使用docker ps
命令查看,发现刚才的容器正在运行了。
此时,如进入正在运行的容器中,也就是进入交互式容器环境:
docker attach IMAGE/CONTAINER ID
使用attach
命令来进入启动中的容器交互式环境。注意,此时想要退出交互式环境,有两种方式:
exit
退出后,容器也停止了。- 以
CTRL + P
和CTRL + Q
退出容器,容器还在执行。
以-d
的方式运行守护式容器
这种方式是使用最多的方式,也就是在启动的时候,添加-d
参数,让容器在后台运行。
docker run -d IMAGE [COMMAND][ARG...]
# 示例
[root@C ~]# docker run -d --name mycentos centos /bin/sh -c "while true;do echo hello world;sleep 1;done"
19f7595c6e017b1357e66c7d3be2474c3cfd15481341922498e823c6fac4d65b
[root@C ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
19f7595c6e01 centos "/bin/sh -c 'while t…" 12 seconds ago Up 12 seconds mycentos
上面示例中,使用--name
参数为容器启一个别名,然后使用-c
命令写个shell脚本, 一直死循环每隔一秒输出hello world
,使用-d
参数让容器在后台运行。至于返回的一串随机字符串,则是docker的守护进程为该容器分配的唯一id;完事就可以使用ps
命令来查看刚才运行的容器了。
docker logs
那么我们如何查看shell脚本中输出内容呢?这里我们借助logs
命令来查看容器内部的执行情况:
docker logs [-f][-t][--tail] CONTAINER
# --tail 10 表示只显示最新的10条
[root@C ~]# docker logs -f -t --tail 10 mycentos
# --tail 0 表示只显示最新的日志
[root@C ~]# docker logs -f -t --tail 0 mycentos
各参数:
-f --follows=true|false 默认为false
,一直跟踪logs日志的变化并返回结果。-t --timestamps=true 默认为false
,在返回的结果中加上时间戳。--tail="all"
,控制返回结果条数,如果不指定,logs泛会所的有日志
除了使用logs命令来感知容器的执行情况,也可以使用top命令来查看容器的进程情况:
docker top CONTAINER
docker top mycentos
此时,我们也可以使用exec命令来进入运行中的容器的交互式环境(你可以执行其他的操作),然后使用ctrl+P ctrl+Q
的方式退出,此时,使用top命令查看该容器,就会发现,该容器多了一个新的进程:
[root@C ~]# docker exec -it mycentos bash
[root@19f7595c6e01 /]# read escape sequence
[root@C ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
19f7595c6e01 centos "/bin/sh -c 'while t…" 19 minutes ago Up 19 minutes mycentos
[root@C ~]# docker top mycentos
UID PID PPID C STIME TTY TIME CMD
root 1945 1931 0 11:07 ? 00:00:00 /bin/sh -c while true;do echo hello world;sleep 1;done
root 3412 1931 0 11:26 pts/0 00:00:00 bash
root 3454 1945 0 11:26 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1
停止守护式容器
docker stop CONTAINER
docker kill CONTAINER
stop发送一个停止信号给容器,然后等待容器的停止,而kill命令直接杀死容器的进程。
docker man
可以使用docker man
来查看docker各个命令的帮助文档:
man docker-run
man docker-logs
man docker-top
man docker-top
容器的端口映射
如果,在容器中部署一个web服务,那我们从远程该如何访问呢?
这就需要我们了解容器的端口映射规则。
docker run [-P][-p]
-P,--publish-all=true|false 默认为false
为容器暴露的所有端口进行映射。
-p
指定映射哪些容器的端口。
容器端口的映射有如下四种形式:
containerPort
,只指定容器的端口,这种情况下宿主机的端口是随机映射的docker run -p 80 -it centos bash
hostPort:containerPort
,同时指定宿主机和容器端口docker run -p 8080 -it centos bash
ip::containerPort
,指定宿主机ip和容器的端口docker run -p 0.0.0.0:80 -it centos bash
ip:hostPort
,指定宿主机ip和端口及容器的端口docker run -p 0.0.0.0:8080:80 -it centos bash
怎么玩呢?这里我们在容器中搭建一个静态网站作为练习。
在容器中部署nginx静态网站
流程如下:
- 创建映射80端口的交互式容器
[root@C ~]# docker run -p 80 --name web -it centos bash
- 安装Nginx、vim
[root@5a3855185834 /]# yum install -y nginx vim
- 创建一个静态页面
创建一个目录,用来存放网站的目录
mkdir -p /var/html
使用vim /var/html/index.html
命令编辑一些内容:
<html>
<head>
<title>docker web</title>
</head>
<body>
<h1>
docker index page
</h1>
</body>
</html>
- 修改Nginx配置文件
使用where
命令来查看nginx的安装目录;然后编辑nginx的配置文件,完事之后重新加载nginx。
[root@5a3855185834 /]# whereis nginx
nginx: /usr/sbin/nginx /usr/lib64/nginx /etc/nginx /usr/share/nginx /usr/share/man/man3/nginx.3pm.gz /usr/share/man/man8/nginx.8.gz
[root@5a3855185834 /]# ls /etc/nginx/
conf.d fastcgi.conf fastcgi_params koi-utf mime.types nginx.conf scgi_params uwsgi_params win-utf
default.d fastcgi.conf.default fastcgi_params.default koi-win mime.types.default nginx.conf.default scgi_params.default uwsgi_params.default
[root@5a3855185834 /]# vim /etc/nginx/nginx.conf
[root@5a3855185834 /]# nginx -s reload
- 运行Nginx
[root@5a3855185834 /]# nginx
[root@5a3855185834 /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 04:03 pts/0 00:00:00 bash
root 128 1 0 04:16 ? 00:00:00 nginx: master process nginx
root 132 0 0 04:20 pts/1 00:00:00 bash
nginx 162 128 0 07:10 ? 00:00:00 nginx: worker process
nginx 163 128 0 07:10 ? 00:00:00 nginx: worker process
root 166 132 0 07:14 pts/1 00:00:00 ps -ef
可以看到nginx已经启动了。
- 访问该网站
我们之前说如果使用docker run -p 80 --name web -it centos bash
这种方式运行容器的话,该容器的端口是80,而映射的宿主机的端口是随机映射的,如何查看呢?首先CTRL+P CTRL+Q
退出交互式环境,然后执行下面的命令就可以查看宿主机的映射端口了。
docker ps
docker port web
[root@C ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5a3855185834 centos "bash" 3 hours ago Up 3 hours 0.0.0.0:32768->80/tcp web
[root@C ~]# docker port web
80/tcp -> 0.0.0.0:32768
通过上面两种方式都可以发现,宿主机的映射端口是32768
,然后我们就就可以浏览器访问宿主机的ip和32768端口了。
OK了。
镜像管理
镜像基本操作
拉取镜像
下面命令是从远程服务器拉取镜像。
docker pull [OPTIONS] NAME[:TAG]
docker pull mysql
# 默认的,docker会下载mysql:latest镜像,也就是最新的版本,你也可以自己指定
docker pull mysql:5.7
# 使用国内源
docker pull hub.c.163.com/library/ubuntu:latest
如果镜像仓库已经有了该镜像,该命令不会重新下载镜像。
还有另一种情况是没有网络的情况下,如何拉取镜像?
docker save nginx > mynginx.tar
# 注意重定向符号的方向
使用save命令和重定向输出符将nginx
镜像打包成tar包,然后将这个tar包传到没有网络的服务器上,然后再load
回来:
docker load < mynginx.tar
# 注意重定向符号的方向
PS:save命令执行较慢。
查看镜像
docker images [OPTIONS] [REPOSITORY]
docker images --no-trunc
docker images -a
docker images -q
# 列出所有的镜像
docker images
# 过滤镜像
docker images|grep mysql
可选的参数:
-a,--all=false
,显式所有镜像,默认不显示中间层镜像。-f,--filter=[]
,显式镜像的过滤条件。--no-trunc=false
,指定不截断镜像的唯一id,因为镜像的id较长,一般显式都是截断后的镜像。-q,--quiet-false
,只显示镜像的唯一id
在返回的结果中:
[root@C ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 470671670cac 2 months ago 237MB
hello-world latest fce289e99eb9 15 months ago 1.84kB
各参数:
REPOSITORY
镜像在镜像仓库中的名称,其实它是路径加镜像名称,我们一般简称它为镜像名称,该名称在本机是唯一的。TAG
镜像的标签名,也可以理解为镜像的版本,如果镜像的标签名为none,那就是中间层镜像。IMAGE ID
镜像的ID,这个ID是全球唯一的ID,这里只是截取ID的一部分作为展示。CREATED
镜像被制作的时间,注意,它不是我们使用pull
命令拉取得时间,而是这个镜像被作者制作出来的时间。SIZE
镜像大小。
repository和registry的区别
Repository:本身是一个仓库,这个仓库里面可以放具体的镜像,是指具体的某个镜像的仓库,比如Tomcat下面有很多个版本的镜像,它们共同组成了Tomcat的Repository。
Registry:镜像的仓库,比如官方的是Docker Hub,它是开源的,也可以自己部署一个,Registry上有很多的Repository,Redis、Tomcat、MySQL等等Repository组成了Registry。
按照Docker的logo来看,Repository是集装箱,Registry是鲸鱼。
查看指定镜像信息
docker inspect 仓库名/镜像id
[root@C ~]# docker inspect centos:latest
[root@C ~]# docker inspect 470671670cac
给镜像打标签
# 原镜像名称 新标签镜像名称
docker tag mysql:5.6 msyql:5.6.1
# 完事你可以过滤查看一下
docker images|grep mysql
注意,这个命令只是为镜像打了一个TAG
,而它们的image id
还是一致的。
删除镜像
docker rmi 镜像名称:TAG
# 这个命令也行,适用于镜像的TAG为none的情况
docker rmi 镜像ID
# 如果镜像仓库中,有多个centos镜像,下面命令则是一次将它们都删除
docker rmi $(docker images -q centos)
# 如下面
docker rmi jenkins:latest
docker rmi mysql:5.6
需要注意的是,如果你的删除命令是这样的docker rmi mysql
,docker会自动给你补全docker rmi mysql:latest
也就是加上TAG
名称。
构建和上传镜像
这里我们主要掌握本地的镜像构建,将本地镜像推送到远程仓库,然后如何从远程仓库拉取镜像等等这些交互性的操作。
查找镜像
查找镜像有两种方式:
- docker hub官网查找,这个需要你注册个账号(人机验证和邮箱验证就行了)。
- 使用
docker search
查找镜像
官网查找
官网查找就是在搜索框中,搜索你的要查找的镜像名称,然后标记有OFFICIAL IMAGE
标志的是官方发布的版本。一般选择这个就没问题的。
点进去之后,你可以查看该镜像的说明和tag,然后就可以使用docker pull
拉取镜像了。
使用docker search命令搜索
使用docker search
搜索其实跟在官网下载差不多:
docker search [OPTIONS] IMGAGE
# -s 过滤stars星级在100及以上的镜像
[root@C ~]# docker search --filter=stars=100 centos
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
centos The official build of CentOS. 5906 [OK]
ansible/centos7-ansible Ansible on Centos7 128 [OK]
jdeathe/centos-ssh OpenSSH / Supervisor / EPEL/IUS/SCL Repos - … 114 [OK]
consol/centos-xfce-vnc Centos container with "headless" VNC session… 112 [OK]
默认最多显式25条结果,其中可选参数:
--automated=false
只显示自动化构建的镜像结果--no-trunc=false
,不截断镜像id显式-s,--stars=0
过滤结果的最低星级。
现在查询到了镜像,使用docker pull
拉取即可。
如果本地仓库已存在镜像,则不会重复下载。如果下载过慢或者超时,修改配置文件下载国内镜像源即可。
步骤是,编辑vim /etc/docker/daemon.json
文件,查看有没有daemon.json
。这是docker默认的配置文件。如果没有新建,如果有,则修改。
{
"registry-mirrors": ["https://registry.docker-cn.com","http://hub-mirror.c.163.com"]
}
完事保存退出,重启docker服务即可。
构建镜像
构建镜像能够保存对容器的修改,方便再次使用;提供了自定义镜像的能力;然后以软件的形式打包并分发服务机器运行环境。
构建镜像有两种方式:
docker commit
通过容器构建镜像,不推荐。docker build
通过Dockerfile文件构建镜像,推荐。
来看怎么玩的吧。
docker commit
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
[root@C ~]# docker commit centos web1
sha256:0f92132965724df14834f5b96da8b2d36dd157aec0bc9c2735ef5878ee1f6443
各参数:
-a,--author=""
,指定作者,一般填写作者和邮箱。-m,--message=""
,记录构建镜像的信息。-p
,指示commit命令不暂停容器来进行镜像的构建。
来个示例,构建一个容器的简单步骤:
- 根据一个正在运行的容器container1,然后该容器中启动nginx服务,然后以守护进程的方式退出。
- 使用commit命令根据容器container1来构建一个新镜像web。
- 然后根据新镜像web启动一个新的容器container2。
[root@C ~]# docker run --name container1 -p 5533:80 -it centos bash
[root@fd8a81408a9f /]# yum install -y nginx
[root@fd8a81408a9f /]# nginx
[root@fd8a81408a9f /]# [root@C ~]#
[root@C ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fd8a81408a9f centos "bash" About a minute ago Up About a minute 0.0.0.0:5533->80/tcp container1
[root@C ~]# docker commit container1 web
[root@C ~]# docker run -d --name container2 -p 5544:80 web nginx -g "daemon off;"
[root@C ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3096d395785 web "nginx -g 'daemon of…" 7 minutes ago Up 7 minutes 0.0.0.0:5544->80/tcp container2
fd8a81408a9f centos "bash" 12 minutes ago Up 12 minutes 0.0.0.0:5533->80/tcp container1
nginx -g "daemon off;"
让nginx保持前台运行,也就是让容器不能停止。完事使用ps命令可以看到我们运行了两个容器。
这种方式构建的镜像是有缺点的,因为如果该镜像交给了别人使用,那么别人就不知道你到底对镜像都做了哪些操作。时间长了,你自己都可能不知道你做了哪些操作,所以我们不推荐你使用docker commint
这种方式来制作镜像。
Dockerfile
快速构建一个镜像
官方推荐制作镜像的方式,通过类似编程方式来构建镜像,这样,你对该镜像的所有操作都记录在一个文件中。非常易于维护,这个文件就是Dockerfile
文件,该文件包含了一系列指令,如下示例文件:
FROM centos:latest
MAINTAINER zhangkai "1206180814@qq.com"
RUN yum install -y nginx
# RUN yum install -y vim
EXPOSE 80
如上示例中,包含该镜像依赖的基础镜像、维护人、执行的一系列指令和暴露的端口。
来个示例:
- 创建一个目录,用于存放Dockerfile文件,并且cd 到该目录下,注意,这里建议,构建该镜像的依赖文件等其他的文件都存放在该目录下,这样一个文件夹就是一个镜像,构建起来比较方便。
[root@C /]# mkdir -p dockerfile_set/first_dockerfile
[root@C /]# cd dockerfile_set/first_dockerfile/
[root@C first_dockerfile]#
- 编辑dockerfile文件,并编写构建指令。
[root@C first_dockerfile]# vim Dockerfile
- 使用build命令构建镜像,注意,生成的镜像在docker的镜像仓库,而不是在当前目录下。
docker build [OPTIONS] PATH/URL
# 最后的空格点的意思是docker服务会自动的在当前目录下寻找Dockerfile
[root@C first_dockerfile]# docker build -t first_dfi:0.0.1 .
可选参数:
-t
参数后跟新构建的镜像的名字,PATH/URL
是新镜像保存的路径。-q
指定该参数的话,将不会显示构建过程。
- 现在,就可以使用
docker images
命令来查看刚才构建的镜像了。
[root@C first_dockerfile]# docker images |grep first
first_dfi 0.0.1 524ef96da98f 2 minutes ago 318MB
- 我们根据该镜像来启动一个容器,然后浏览器就可以正常的访问了。
[root@C first_dockerfile]# docker run -d --name container3 -p 5536:80 first_dfi:0.0.1 nginx -g "daemon off;"
bbee2fa489a1419812ad91bb311f147620aa40b1a7bb86a51aae1f2dc6bafe1c
[root@C first_dockerfile]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bbee2fa489a1 first_dfi:0.0.1 "nginx -g 'daemon of…" 3 seconds ago Up 3 seconds 0.0.0.0:5536->80/tcp container3
这么着,一个镜像就构建完毕了。使用也是正常的使用就可以了。
dockerfile指令解析
虽然我们已经使用Dockerfile
构建出了一个镜像,但是还有些内容需要交代,还是刚才的那个示例。
FROM centos:latest
MAINTAINER zhangkai "1206180814@qq.com"
RUN yum install -y nginx
# RUN yum install -y vim
EXPOSE 80
上面的示例中,主要包含了几大部分:
-
FROM
,form指令包含了两种指令参数,FROM <image>
和FROM <image>:<tag>
,通过from指令指定的镜像名有以下要求:- 必须是一个已经存在的镜像,因为后续的镜像包括中间镜像都会基于该镜像来执行,所以,我们也称该镜像为基础镜像。
- 必须是
Dockerfile
文件中第一条非注释的指令。
-
MAINTAINER
,指定镜像的作者信息,包含镜像的所有者和联系信息。 -
RUN
,指定当前镜像中运行的指令- 注意,每一条run指令都会新创建一层镜像来执行指令,也就是说,如果一个
dockerfile
文件中run指令行数越多就会产生越多的中间层镜像,这也就意味着我们尽可能的将多行run指令合并成一条指令,如RUN yum install -y nginx && yum instlal -y vim
- run指令有两种执行模式
- shell模式,
RUN <command>
,以/bin/sh -c command
的形式,如RUN echo hello
- exec模式,
RUN ["executable", "param1", "param2"]
的形式,如RUN ["/bin/bash", "-c", "echo hello"]
- shell模式,
- 注意,每一条run指令都会新创建一层镜像来执行指令,也就是说,如果一个
-
EXPOSE
,指定运行该镜像的容器使用的端口;可以指定一个或多个端口,也可以在一个dockerfile
文件中使用多个expose
指令。当然,处于安全角度考虑,在运行该镜像的容器时,docker服务并不会自动的打开指定端口,还是需要我们手动的使用-p
参数来指定端口映射。 -
#
,不用多说,注释用的。 -
CMD
, 用来提供容器执行时的默认命令,与RUN
指令不同,RUN
指令是在镜像构建时执行的命令,而CMD
指令则是在容器执行时执行的命令;并且,如果在docker run
时指定了cmd
指令,就会覆盖掉CMD
的指令,即CMD
指定的是容器运行时的默认行为;其次CMD
指令也有exec
和shell
两种指令模式。
FROM centos:latest
MAINTAINER zhangkai "1206180814@qq.com"
RUN yum install -y nginx && yum install -y vim
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
在上述dockerfile
的最后一行,指定了CMD
命令,告诉容器启动时以前台的模式启动nginx
;然后依次构建的镜像启动容器就不用手动的手动在命令行写启动nginx
的指令了。
# second_dfi是 根据上面的dockerfile构建的新镜像
[root@C second_dockerfile]# docker build -t second_dfi:0.0.1 .
[root@C second_dockerfile]#docker run -d --name container4 -p 5537:80 second_dfi:0.0.1
ENTRYPOINT
,该指令与CMD
指令非常相似,唯一区别就是,该指令不会被docker run
中的指令覆盖,如果要覆盖就要手动的添加docker run --entrypoint
选项。ADD
和COPY
指令,这两个指令都是将文件或者目录复制到dockerfile
构建的镜像中ADD <src> <dest>
,src
是来源地址,dest
是目标地址,以空格分割;来源地址可以是本地地址和远程URL,但docker并不推荐使用远程的url
地址,更推荐使用curl
和wget
来获取件,如果是本地地址必须是构建目录中的相对地址;而目标路径需要指定镜像中的绝对路径。ADD ["src", "dest"]
适用于文件路径中有空格的情况COPY <src> <dest>
,src
是来源地址,dest
是目标地址,以空格分割COPY ["src", "dest"]
用于文件路径中有空格的情况ADD
和COPY
的区别是,ADD
指令包含了类似tar
的解压功能,在使用有tar包的软件包时,ADD
指令是你的首选;而如果单纯的复制文件,docker推荐使用COPY
指令。
FROM centos:latest
MAINTAINER zhangkai "1206180814@qq.com"
RUN yum install -y nginx && yum install -y vim
# index.html跟Dockerfile文件同级,/usr/share/nginx/html 是nginx默认的静态文件路径
COPY index.html /usr/share/nginx/html
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
VOLUME ["/data"]
,用来向基于镜像的容器添加卷,一个卷可以是存在于一个或多个容器的特定目录,这个目录可以绕过联合文件系统,并提供如共享数据或者数据持久化的功能。WORKDIR /path/to/workdir
,该指令用来,从镜像创建容器时,为容器设置工作目录,ENTRYPOINT
和CMD
指定的命令在该目录下执行,也可以使用该指令在构建镜像时,为后续的构建指令指定工作目录。需要注意的是WORKDIR
使用的是绝对路径,如果使用相对路径,工作路径会一直传递下去,如下面这种情况:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# 结果就是
/a/b/c
ENV <key> <valyue>/<key>=<value>
,用来设置容器的环境变量,作用于构建或者容器运行时。USER
指令用来指定镜像会以什么样的用户去运行,如USER nginx
,基于该镜像启动的容器,就会以nginx
用户的身份去运行;我们也可以在USER
命令中,使用uid、用户组、gid或者这几种身份的组合去运行:
USER user USER uid
USER user:group USER uid:gid
USER user:gid USER uid:group
如果不使用USER
指令来指定用户,默认会以root
用户的身份运行容器。
ONBUILD [INSTRUCTION]
指令能够为镜像添加触发器,当镜像被当做其他镜像的基础镜像时,该触发器会被执行;当子镜像在构建过程中会插入触发器中的指令。
# image: father
FROM centos:latest
MAINTAINER zhangkai "1206180814@qq.com"
RUN yum install -y nginx
# 为镜像添加触发器,触发 COPY 指令
ONBUILD COPY index.html /usr/share/nginx/html
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
如果你根据上面的Dockerfile
去构建镜像,那么ONBUILD
指令不会执行。只有该镜像被当做其他镜像的基础镜像时,才会被触发,比如下面的Dockerfile
文件。
# image: son, 该镜像在构建时 FROM 的是 father 镜像
FROM father
MAINTAINER zhangkai "1206180814@qq.com"
# 由于 father 镜像已经下载了 nginx,这里的子镜像无需再下载
# RUN yum install -y nginx
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
你可以build
出这两个镜像,然后分别启动两个容器访问测试一下,哦,别忘了在各自Dockerfile
文件的统计目录创建index.html
文件。
docker run -d --name container5 -p 5538:80 son
docker run -d --name container6 -p 5539:80 father
构建缓存与构建过程
构建过程
是时候来研究一下docker build
的个构建过程了。
[root@C father_dockerfile]# docker build -t father .
Sending build context to Docker daemon 3.072kB
Step 1/6 : FROM centos:latest
---> 470671670cac
Step 2/6 : MAINTAINER zhangkai "1206180814@qq.com"
---> Using cache
---> f5d119c76e4a
Step 3/6 : RUN yum install -y nginx
---> Using cache
---> 7fcaf658815e
Step 4/6 : ONBUILD COPY index.html /usr/share/nginx/html
---> Running in 6dcf4c6aa6e8
Removing intermediate container 6dcf4c6aa6e8
---> b57d7f7d81dc
Step 5/6 : EXPOSE 80
---> Running in 4070bfcd71ef
Removing intermediate container 4070bfcd71ef
---> cdf845d17ebc
Step 6/6 : CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
---> Running in 884529f24ad8
Removing intermediate container 884529f24ad8
---> 3c35363e436b
Successfully built 3c35363e436b
Successfully tagged father:latest
简单来说,docker build
的原理和流程如下:
- 从基础镜像运行一个容器。
- 执行
Dockerfile
中的一条指令,指令结果作用于刚才创建的容器。 - 完事执行类似
docker commit
的操作(根据容器提交的镜像),提交一个新的镜像,即中间层镜像;此时,就有了该中间层镜像和容器,谁留下,谁离开这是个问题?很明显,留中间层镜像,删除容器,所以,docker也是这么做的。 - 再基于刚提交的镜像运行一个新容器。
- 执行上述
2~4
的操作,直至所有指令执行完毕。
--no-cache
如果你仔细的观察上面的构建过程,你会在在某些步骤中发现Using cache
字样, 这是什么?
默认的,使用Dockerfile
构建镜像可以利用它的缓存功能:只有在命令已更改的情况下,才会重建已构建的步骤。如下图的构建过程中的Using cache
。
缓存非常有用并且省时间,使构建过程变得高效,不过有时候docker缓存的行为不都能达到你的期望。
假设你更改了代码并push到Git仓库。新代码不会check out下来,因为git clone命令没有更改。在Docker看来git clone的步骤一样,所以使用了缓存。
在这种情况下,你可能不想开启docker的缓存了那该如何禁止使用缓存呢?那就是在docker build
的过程中使用 --no-cache
选项来完成。
**docker history [IMAGE] **
最后,我们来研究一下如何查看一个给定的镜像,它的构建过程呢?这就用到了docker history
命令了。
[root@C son_dockerfile]# docker history son
IMAGE CREATED CREATED BY SIZE COMMENT
4a55923ec3f8 About an hour ago /bin/sh -c #(nop) CMD ["/usr/sbin/nginx" "-… 0B
8874ce4f6e2c About an hour ago /bin/sh -c #(nop) EXPOSE 80 0B
b8389d16c965 About an hour ago /bin/sh -c #(nop) MAINTAINER zhangkai "1206… 0B
7e5698c499bf About an hour ago /bin/sh -c #(nop) COPY file:fe64a14da61eafb3… 57B
3c35363e436b About an hour ago /bin/sh -c #(nop) CMD ["/usr/sbin/nginx" "-… 0B
cdf845d17ebc About an hour ago /bin/sh -c #(nop) EXPOSE 80 0B
b57d7f7d81dc About an hour ago /bin/sh -c #(nop) ONBUILD COPY index.html /… 0B
7fcaf658815e 4 hours ago /bin/sh -c yum install -y nginx 81MB
f5d119c76e4a 4 hours ago /bin/sh -c #(nop) MAINTAINER zhangkai "1206… 0B
470671670cac 2 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 2 months ago /bin/sh -c #(nop) ADD file:aa54047c80ba30064… 237MB
推送镜像到远程仓库
这里需要你提前注册好docker hub账号和创建好仓库。
现在,我们手动的构建一个镜像,当运行的时候,只是输出hello world
,用来演示如何本地构建镜像和将该镜像上传到docker hub上的镜像仓库中。
准备镜像
- 创建存放
Dockerfile
文件的目录。
[root@C dockerfile_set]# mkdir my-hello-world
[root@C dockerfile_set]# cd my-hello-world/
vim Dockerfile编辑
Dockerfile`文件。
[root@C my-hello-world]# vim Dockerfile
Dockerfile
文件内容。
FROM centos:latest
MAINTAINER zhangkai 1206180814@qq.com
CMD echo "hello world, by zhangkai"
- 编译和运行创建的镜像。
[root@C my-hello-world]# docker build -t my_hello_world .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM centos:latest
---> 470671670cac
Step 2/3 : MAINTAINER zhangkai 1206180814@qq.com
---> Running in cfcfc884e051
Removing intermediate container cfcfc884e051
---> 27aed015c71b
Step 3/3 : CMD echo "hello world, by zhangkai"
---> Running in fef8914658a9
Removing intermediate container fef8914658a9
---> 60a0ecedd08f
Successfully built 60a0ecedd08f
Successfully tagged my_hello_world:latest
[root@C my-hello-world]# docker run my_hello_world
hello world, by zhangkai
可以看到成功的输出了hello world, by zhangkai
这个CMD
命令。
- 重点来了,将镜像打个
TAG
。
docker tag 镜像名称/镜像ID dockerhub的用户名/镜像名称:TAG
[root@C my-hello-world]# docker tag my_hello_world wangzhangkai/my_hello_world:1.0
[root@C my-hello-world]# docker tag my_hello_world wangzhangkai/my_hello_world:1.1
- 查看一下打好
TAG
的镜像。
[root@C my-hello-world]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my_hello_world latest bf5bd58ffa34 About an hour ago 237MB
wangzhangkai/my_hello_world 1.0 bf5bd58ffa34 About an hour ago 237MB
wangzhangkai/my_hello_world 1.1 bf5bd58ffa34 About an hour ago 237MB
father latest 033978ca3dad 20 hours ago 318MB
son latest 4a55923ec3f8 20 hours ago 318MB
second_dfi 0.0.1 05a99ef223db 21 hours ago 345MB
first_dfi 0.0.1 524ef96da98f 23 hours ago 318MB
web latest df977c15f19b 43 hours ago 318MB
打好TAG
后的完整镜像是wangzhangkai/my_hello_world:1.0
和wangzhangkai/my_hello_world:1.0
,只需要将这两个镜像推送到远程仓库即可。
现在就可以上传到了远程的镜像仓库了。
上传本地镜像到远程仓库
- 首先要登录。
[root@C my-hello-world]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: wangzhangkai
Password:
Login Succeeded
- 使用
docker push
命令将镜像推送到远程仓库。
[root@C my-hello-world]# docker push wangzhangkai/my_hello_world:1.0
The push refers to repository [docker.io/wangzhangkai/my_hello_world]
0683de282177: Mounted from wangzhangkai/my_hello_word
1.0: digest: sha256:14e637e742b7fb7c8cd639e564d3f5ca232ffbf6581ad6a924506146e6eb4981 size: 529
[root@C my-hello-world]# docker push wangzhangkai/my_hello_world:1.1
The push refers to repository [docker.io/wangzhangkai/my_hello_world]
0683de282177: Layer already exists
1.1: digest: sha256:14e637e742b7fb7c8cd639e564d3f5ca232ffbf6581ad6a924506146e6eb4981 size: 529
这里需要注意的是,这里的my_hello_world
是镜像也是一个镜像的集合,因为它通过不同TAG
来标记,但是都属于my_hello_world
镜像,如上的推送中,我们分别推送了1.0
和1.1
两个TAG
,那么这两个TAG
都会被保存在docker hub中的my_hello_world
仓库中。如下面两幅图片所示:
还需要注意的是,在两次推送不同的TAG
时,docker只会推送被修改的部分,很明显,我这里除了TAG
号,别的并没有修改什么,所以,第一次提交1.0
时,是Pushed
,而第二次则是Layer already exists
。
0683de282177: Pushed
0683de282177: Layer already exists
由于docker hub的镜像仓库是收费的, 这里就不演示充钱等操作了。
that's all