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 + PCTRL + Q的方式运行守护式容器

先来看最简单的方式:

docker run -it IMAGE /bash

然后以CTRL + PCTRL + 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 + PCTRL + Q退出容器,在使用docker ps命令查看,发现刚才的容器正在运行了。

此时,如进入正在运行的容器中,也就是进入交互式容器环境:

docker attach IMAGE/CONTAINER ID

使用attach命令来进入启动中的容器交互式环境。注意,此时想要退出交互式环境,有两种方式:

  • exit退出后,容器也停止了。
  • CTRL + PCTRL + 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

如上示例中,包含该镜像依赖的基础镜像、维护人、执行的一系列指令和暴露的端口。

来个示例:

  1. 创建一个目录,用于存放Dockerfile文件,并且cd 到该目录下,注意,这里建议,构建该镜像的依赖文件等其他的文件都存放在该目录下,这样一个文件夹就是一个镜像,构建起来比较方便。
[root@C /]# mkdir -p dockerfile_set/first_dockerfile
[root@C /]# cd dockerfile_set/first_dockerfile/
[root@C first_dockerfile]# 
  1. 编辑dockerfile文件,并编写构建指令。
[root@C first_dockerfile]# vim Dockerfile

  1. 使用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指定该参数的话,将不会显示构建过程。
  1. 现在,就可以使用docker images命令来查看刚才构建的镜像了。
[root@C first_dockerfile]# docker images |grep first
first_dfi                      0.0.1               524ef96da98f        2 minutes ago       318MB
  1. 我们根据该镜像来启动一个容器,然后浏览器就可以正常的访问了。
[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"]
  • EXPOSE,指定运行该镜像的容器使用的端口;可以指定一个或多个端口,也可以在一个dockerfile文件中使用多个expose指令。当然,处于安全角度考虑,在运行该镜像的容器时,docker服务并不会自动的打开指定端口,还是需要我们手动的使用-p参数来指定端口映射。

  • #,不用多说,注释用的。

  • CMD, 用来提供容器执行时的默认命令,与RUN指令不同,RUN指令是在镜像构建时执行的命令,而CMD指令则是在容器执行时执行的命令;并且,如果在docker run时指定了cmd指令,就会覆盖掉CMD的指令,即CMD指定的是容器运行时的默认行为;其次CMD指令也有execshell两种指令模式。

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 选项。
  • ADDCOPY指令,这两个指令都是将文件或者目录复制到dockerfile构建的镜像中
    • ADD <src> <dest>src是来源地址,dest是目标地址,以空格分割;来源地址可以是本地地址和远程URL,但docker并不推荐使用远程的url地址,更推荐使用curlwget来获取件,如果是本地地址必须是构建目录中的相对地址;而目标路径需要指定镜像中的绝对路径。
    • ADD ["src", "dest"]适用于文件路径中有空格的情况
    • COPY <src> <dest> src是来源地址,dest是目标地址,以空格分割
    • COPY ["src", "dest"]用于文件路径中有空格的情况
    • ADDCOPY的区别是,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,该指令用来,从镜像创建容器时,为容器设置工作目录,ENTRYPOINTCMD指定的命令在该目录下执行,也可以使用该指令在构建镜像时,为后续的构建指令指定工作目录。需要注意的是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的原理和流程如下:

  1. 从基础镜像运行一个容器。
  2. 执行Dockerfile中的一条指令,指令结果作用于刚才创建的容器。
  3. 完事执行类似docker commit的操作(根据容器提交的镜像),提交一个新的镜像,即中间层镜像;此时,就有了该中间层镜像和容器,谁留下,谁离开这是个问题?很明显,留中间层镜像,删除容器,所以,docker也是这么做的。
  4. 再基于刚提交的镜像运行一个新容器。
  5. 执行上述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上的镜像仓库中。

准备镜像

  1. 创建存放Dockerfile文件的目录。
[root@C dockerfile_set]# mkdir my-hello-world
[root@C dockerfile_set]# cd my-hello-world/

  1. 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"
  1. 编译和运行创建的镜像。
[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命令。

  1. 重点来了,将镜像打个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
  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.0wangzhangkai/my_hello_world:1.0,只需要将这两个镜像推送到远程仓库即可。

现在就可以上传到了远程的镜像仓库了。

上传本地镜像到远程仓库

  1. 首先要登录。
[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
  1. 使用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.01.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
posted @ 2020-04-02 15:14  听雨危楼  阅读(721)  评论(0编辑  收藏  举报