1. Docker 介绍

Docker 是一个开放源代码软件项目,让应用程序布署在软件容器下的工作可以自动化进行,借此在 Linux 操作系统上,提供一个额外的软件抽象层,以及操作系统层虚拟化的自动管理机制。
Docker 利用 Linux 核心中的资源分脱机制,例如 cgroups ,以及 Linux 核心名字空间(name space),来创建独立的软件容器(containers)。这可以在单一 Linux 实体下运作,避免启动一个虚拟机造成的额外负担。Linux 核心对名字空间的支持完全隔离了工作环境中应用程序的视野,包括进程树、网络、用户 ID 与挂载文件系统,而核心的 cgroup 提供资源隔离,包括 CPU 、存储器、block I/O 与网络。从 0.9 版本起,Dockers 在使用抽象虚拟是经由 libvirt 的 LXC 与 systemd - nspawn 提供界面的基础上,开始包括 libcontainer 库做为以自己的方式开始直接使用由 Linux 核心提供的虚拟化的设施。
依据行业分析公司“451研究”:“Dockers 是有能力打包应用程序及其虚拟容器,可以在任何 Linux 服务器上运行的依赖性工具,这有助于实现灵活性和便携性,应用程序在任何地方都可以运行,无论是公有云、私有云、单机等。”

  1. 简化环境管理

传统的软件开发与发布环境复杂,配置繁琐,经常有读者在微信上问:我的代码开发环境可以运行,一旦部署到服务器上就运行不了了。这个问题很常见,也确实很烦人,但是问题总要解决,开发环境、测试环境、生产环境,每个环节都有可能出现这样那样的问题,如果能够在各个环境中实现一键部署,就会方便很多,例如一键安装 linux 、一键安装 mysql、一键安装 nginx 等,docker 彻底解决了这个问题。

  1. 虚拟化更加轻量级

说到容器,说到虚拟化,很多人总会想到虚拟机,想到 VMware、VirtualBox 等工具,不同于这些虚拟技术,docker 虚拟化更加轻量级,传统的虚拟机都是先虚拟出一个操作系统,然后在操作系统上完成各种各样的配置,这样并不能充分的利用物理机的性能,docker 则是一种操作系统级别的虚拟技术,它运行在操作系统之上的用户空间,所有的容器都共用一个系统内核甚至公共库,容器引擎提供了进程级别的隔离,让每个容器都像运行在单独的系统之上,但是又能够共享很多底层资源。因此 docker 更为轻量、快速和易于管理。

  1. 程序可移植

有了前面介绍的两个特点,程序可移植就是顺理成章的事情了。

1. docker和虚拟机区别


docker 虚拟机
相同点 1. 都可在不同的主机之间迁移
2. 都具备 root 权限
3. 都可以远程控制
4. 都有备份、回滚操作

| | | 操作系统 | 在性能上有优势,可以轻易的运行多个操作系统 | 可以安装任何系统,但是性能不及容器 | | 原理 | 和宿主机共享内核,所有容器运行在容器引擎之上,容器并非一个完整的操作系统,所有容器共享操作系统,在进程级进行隔离 | 每一个虚拟机都建立在虚拟的硬件之上,提供指令级的虚拟,具备一个完整的操作系统 | | 优点 | 高效、集中。一个硬件节点可以运行数以百计的的容器,非常节省资源,QoS 会尽量满足,但不保证一定满足。内核由提供者升级,服务由服务提供者管理 | 对操作系统具有绝对权限,对系统版本和系统升级具有完全的管理权限。具有一整套的的资源:CPU、RAM 和磁盘。QoS 是有保证的,每一个虚拟机就像一个真实的物理机一样,可以实现不同的操作系统运行在同一物理节点上。 | | 资源管理 | 弹性资源分配:资源可以在没有关闭容器的情况下添加,数据卷也无需重新分配大小 | 虚拟机需要重启,虚拟机里边的操作系统需要处理新加入的资源,如磁盘等,都需要重新分区。 | | 远程管理 | 根据操作系统的不同,可以通过 shell 或者远程桌面进行 | 远程控制由虚拟化平台提供,可以在虚拟机启动之前连接 | | 缺点 | 对内核没有控制权限,只有容器的提供者具备升级权限。只有一个内核运行在物理节点上,几乎不能实现不同的操作系统混合。容器提供者一般仅提供少数的几个操作系统 | 每一台虚拟机都具有更大的负载,耗费更多的资源,用户需要全权维护和管理。一台物理机上能够运行的虚拟机非常有限 | | 配置 | 快速,基本上是一键配置 | 配置时间长 | | 启动时间 | 秒级 | 分钟级 | | 硬盘使用 | MB | GB | | 性能 | 接近原生态 | 弱于原生态 | | 系统支持数量 | 单机支持上千个 | 一般不多于几十个 |

2. docker与传统容器

不同与传统容器,docker 早起基于 LXC,后来基于自研的 libContainer,docker 对于传统容器做了许多优化,如下:

  1. 跨平台的可移植性
  2. 面向应用
  3. 版本控制
  4. 组件复用
  5. 共享性
  6. 工具生态系统

    3. docker 应用场景

  7. 加速本地开发

  8. 自动打包和部署应用
  9. 创建轻量、私有的PaaS环境
  10. 自动化测试和持续集成/部署
  11. 部署并扩展Web应用、数据库和后端服务器
  12. 创建安全沙盒
  13. 轻量级的桌面虚拟化

    4. docker 核心组件

    docker 中有三大核心组件:
  • 镜像

镜像是一个只读的静态模版,它保存了容器需要的环境和应用的执行代码,可以将镜像看成是容器的代码,当代码运行起来之后,就成了容器,镜像和容器的关系也类似于程序和进程的关系。

  • 容器

容器是一个运行时环境,是镜像的一个运行状态,它是镜像执行的动态表现。

库是一个特定的用户存储镜像的目录,一个用户可以建立多个库来保存自己的镜像。

5. docker相关技术

  1. 隔离性
  2. 可度量性
  3. 移植性
  4. 安全性

    6. docker 安装

    相对而言,Linux 上安装 Docker 是最容易的,其次是 Mac ,最后是 Windows ,Windows 因此要装的东西比较多,官方也提供了两个不同的安装包,支持不同的 Windows 的不同版本,一个是针对 Win10 的安装引导程序,还有一个是兼容性较好的 Toolbox ,但是在 Windows 上运行 Docker ,后期在虚拟目录等方面还会遇到各种问题,所以这里松哥是非常不建议大家在 Windows 中安装 Docker ,有 Mac 的上 Mac (Mac 上安装 Docker 就像安装普通软件一样),没有 Mac 的装 Linux 虚拟机,再装 Docker 即可,这里我就先以 CentOS 上安装 Docker 为例,来说说 Docker 安装。 ```shell

    首先安装 Docker

    yum -y install docker

然后启动 Docker 服务

service docker start

测试安装是否成功

docker -v

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/8391423/1640669175854-0a913ee0-c3be-49bb-884e-01e3e4f01cf6.png#clientId=u606d2344-0a6d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=44&id=u6bf1a83e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=44&originWidth=367&originalType=binary&ratio=1&rotation=0&showTitle=false&size=3570&status=done&style=none&taskId=u356164c0-0896-4cbf-8447-68429371d33&title=&width=367)
  2. <a name="DSG8X"></a>
  3. # 2. docker 基本操作
  4. <a name="MuG6D"></a>
  5. ## 1. 创建容器
  6. ```shell
  7. # 查看容器
  8. docker ps

2. 查看所有容器

  1. # 查看所有容器
  2. docker ps -a

3. 查看最新创建的容器

  1. # 查看最新创建的容器
  2. docker ps -l
  3. # 查看最新创建的n个容器
  4. docker ps -n=xx

查看容器时,涉及到几个查看参数,含义分别如下:

  • CONTAINER ID:CONTAINER ID是指容器的id,是一个唯一标识符,这是一个64位的十六进制整数,在不会混淆的情况下可以只采用id的前几位进行标识一个容器。
  • IMAGE:IMAGE表示创建容器时使用的镜像。
  • COMMAND:COMMAND表示容器最后运行的命令。
  • CREATED:创建容器的时间。
  • STATUS:容器的状态,这里可能显示一个容器启动时间,也能显示容器关闭时间。具体显示哪个要看容器当前的状态。
  • PORTS:容器对外开放的端口。
  • NAMES:容器的名字,如果不设置,会有一个默认的名字。

    4. 创建容器

    ```shell

    创建容器; 此时创建的容器并未运行,处于停止状态,容器的 name 是随机生成的,开发者也可以在创建容器时指定 name

    docker create nginx

创建容器并指定名称

docker create —name=nginx nginx

  1. <a name="Ecm2P"></a>
  2. ## 5. 创建容器并启动
  3. 如果开发者需要既创建又启动容器,则可以使用 docker run 命令。 docker run 命令又可以启动两种不同模式的容器:后台型容器和交互型容器,顾名思义,后台型容器就是一个在后台运行的容器,默默的在后台执行计算就行了,不需要和开发者进行交互,而交互型容器则需要接收开发者的输入进行处理给出反馈。对于开发者而言,大部分情况下创建的都是后台型容器,不过在很多时候,即使是后台型容器也不可避免的需要进行交互,下面分别来看。
  4. <a name="USATA"></a>
  5. ### 后台型容器
  6. ```shell
  7. docker run --name nginx -d -p 8080:80 nginx

—name 含义和上文一样,表示创建的容器的名字,-d 表示容器在后台运行,-p 表示将容器的 80 端口映射到宿主机的 8080 端口,创建
image.png
首先依然会去本地检查,本地没有相应的容器,则会去 Docker Hub 上查找,查找到了下载并运行,并且生成了一个容器 id。运行成功后,在浏览器中输入 http://localhost:8080 就能看到 Nginx 的默认页面了,如下:
image.png

这是一个后台型容器的基本创建方式。

交互型容器

也可以创建交互型容器,例如创建一个 ubuntu 容器,开发者可能需要在 ubuntu 上面输入命令执行相关操作,交互型容器创建方式如下:

  1. docker run --name ubuntu -it ubuntu /bin/bash

参数含义都和上文一致,除了 -it,-it 参数,i 表示开发容器的标准输入(STDIN),t 则表示告诉 docker,为容器创建一个命令行终端。执行结果如下:
image.png
该命令执行完后,会打开一个输入终端,读者就可以在这个终端里愉快的操作 ubuntu 了。想要退出该终端,只需要输入 exit 命令即可。

6. 容器启动

如果开发者使用了 docker run 命令创建了容器,则创建完成后容器就已经启动了,如果使用了 docker create 命令创建了容器,则需要再执行 docker start 命令来启动容器,使用 docker start 命令结合容器 id 或者容器 name 可以启动一个容器,如下:
image.png
docker start 启动的是一个已经存在的容器,要使用该命令启动一个容器,必须要先知道容器的 id 或者 name ,开发者可以通过这两个属性启动一个容器(案例中,nginx 是通过 name 启动,而 ubuntu 则是通过 id 启动)。一般来说,第一次可以使用 docker run 启动一个容器,以后直接使用 docker start 即可

7. 重启

容器在运行过程中,会不可避免的出问题,出了问题时,需要能够自动重启,在容器启动时使用 –restart 参数可以实现这一需求。根据 docker 官网的解释,docker 的重启策略可以分为 4 种,如下:
image.png
四种的含义分别如下:

  1. no表示不自动重启容器,默认即此。
  2. on:failure:[max-retries]表示在退出状态为非0时才会重启(非正常退出),有一个可选择参数:最大重启次数,可以设置最大重启次数,重启次数达到上限后就会放弃重启。
  3. always表示始终重启容器,当docker守护进程启动时,也会无论容器当时的状态为何,都会尝试重启容器。
  4. unless-stopped表示始终重启容器,但是当docker守护进程启动时,如果容器已经停止运行,则不会去重启它。

    8. 容器停止

    通过 docker stop 命令可以终止一个容器,如下:
    image.png

    可以通过 name 或者 id 终止一个容器。

9. 容器删除

1. 单个删除

容器停止后还依然存在,如果需要,还可以通过 docker start 命令再次重启一个容器,如果不需要一个容器,则可以通过 docker rm 命令删除一个容器。删除容器时,只能删除已经停止运行的容器,不能删除正在运行的容器。如下:
image.png

可以通过 name 或者 id 删除一个容器。如果非要删除一个正在运行的容器,可以通过 -f 参数实现,如下:

image.png

2. 批量删除

容器也可以批量删除,命令如下:

  1. docker rm $(docker ps -a -q)

docker ps -a -q 会列出所有容器的 id ,供 rm 命令删除。
如下命令也支持删除已退出的孤立的容器:

  1. docker container prune

3. Docker 容器高级操作

1. 依附容器

docker attach
依附容器这个主要是针对交互型容器而言的,该命令有一定的局限性,可以作为了解即可,真正工作中使用较少。
要是用 docker attach 命令,首先要确保容器已经启动,然后使用该命令才能进入到容器中。具体操作步骤如下:

  • 创建一个容器,然后启动:

image.png

  • 不要关闭当前窗口,再打开一个新的终端,执行 docker attach ubuntu :

image.png
此时就能进入到容器的命令行进行操作了。
如果容器已经关闭或者容器是一个后台容器,则该命令就无用武之地了。
由上面的操作大家可以看到,这个命令的局限性很大,使用场景也不多,因此大家作为一个了解即可。

2. 容器内执行命令

如果容器在后台启动,则可以使用 docker exec 在容器内执行命令。不同于 docker attach ,使用 docker exec 即使用户从终端退出,容器也不会停止运行,而使用 docker attach 时,如果用户从终端退出,则容器会停止运行。如下图:
image.png
基于这样的特性, 我们以后在操作容器内部时,基本上都是通过 docker exec 命令来实现。

3. 查看容器信息

容器创建成功后,用户可以通过 docker inspect 命令查看容器的详细信息,这些详细信息包括容器的 id 、容器名、环境变量、运行命令、主机配置、网络配置以及数据卷配置等信息。执行部分结果如下图
image.png
使用 format 参数可以只查看用户关心的数据,例如:

  • 查看容器运行状态

image.png

  • 查看容器ip地址

image.png

  • 查看容器名、容器id

image.png

  • 查看容器主机信息

image.png
容器的详细信息,在我们后边配置容器共享目录、容器网络时候非常有用,这个我们到后面再来详细介绍。

4. 查看容器进程

使用 docker top 命令可以查看容器中正在运行的进程,首先确保容器已经启动,然后执行 docker top 命令,如下:

image.png
5. 查看容器日志

交互型容器查看日志很方便,因为日志就直接在控制台打印出来了,但是对于后台型容器,如果要查看日志,则可以使用docker提供的 docker logs 命令来查看,如下:
image.png
如下图,首先启动一个不停打日志的容器,然后利用 docker logs 命令查看日志,但是默认情况下只能查看到历史日志,无法查看实时日志,使用 -f 参数后,就可以查看实时日志了。
使用 —tail 参数可以精确控制日志的输出行数, -t 参数则可以显示日志的输出时间。
image.png
该命令在执行的过程中,首先输出最近的三行日志,同时由于添加了 -f 参数,因此,还会有其他日志持续输出。同时,因为添加了 -t 参数,时间随同日志一起打印出来了。docker 的一大优势就是可移植性,容器因此 docker 容器可以随意的进行导入导出操作。

6. 容器导出

既然是容器,我们当然希望 Docker 也能够像 VMWare 那样方便的在不同系统之间拷贝,不过 Docker 并不像 VMWare
导出容器那样方便(事实上,VMWare 中不存在容器导出操作,直接拷贝安装目录即可),在 Docker 中,使用 export 命令可以导出容器,具体操作如下:

  • 创建一个容器,进行基本的配置操作

本案例中我首先创建一个 nginx 容器,然后启动,启动成功后,将本地一个 index.html 文件上传到容器中,修改 nginx 首页的显示内容。具体操作步骤如下:

  1. docker run -itd --name nginx -p 80:80 nginx
  2. vi ./blog/docker/index.html
  3. docker cp ./blog/docker/index.html nginx:/usr/share/nginx/html/

首先运行一个名为 nginx 的容器,然后在宿主机中编辑一个 index.html 文件,编辑完成后,将该文件上传到容器中。然后在浏览器中输入 http://localhost:80 可以看到如下结果:
image.png

容器已经修改成功了。接下来通过 export 命令将容器导出,如下:
image.png
该命令将容器导入到 docker 目录下。导出成功之后,我们就可以随意传播这个导出文件了,可以发送给其他小伙伴去使用了,相对于 VMWare 中庞大的文件,这个导出文件非常小。一般可能只有几百兆,当然也看具体情况。

7. 容器导入

其他小伙伴拿到这个文件,通过执行如下命令可以导入容器(如果自己重新导入,需要记得将 docker 中和 nginx 相关的容器和镜像删除):
image.png
容器导入成功后,就可以使用 docker run 命令运行了。运行成功之后,我们发现自己定制的 index.html 页面依然存在,说明这是我们自己的那个容器。

4. Docker 镜像基本操作

镜像也是 docker 的核心组件之一,镜像时容器运行的基础,容器是镜像运行后的形态。前面我们介绍了容器的用法,今天来和大家聊聊镜像的问题。总体来说,镜像是一个包含程序运行必要以来环境和代码的只读文件,它采用分层的文件系统,将每一层的改变以读写层的形式增加到原来的只读文件上。这有点像洋葱,一层一层的,当我们后面学习了 Dockerfile ,相信大家对于这样的架构理解将更为准确.

1. 镜像与容器的关系

前文已经向读者介绍过容器的使用了,细心的读者可能已经发现,容器在启动或者创建时,必须指定一个镜像的名称或者 id ,其实,这时镜像所扮演的角色就是容器的模版,不同的镜像可以构造出不同的容器,同一个镜像,我们也可以通过配置不同参数来构造出不通的容器。如下命令:

  1. docker run -itd --name nginx nginx

命令中的最后一个 nginx 即表示创建该容器所需要的镜像(模版),当然这里还省略了一些信息,例如版本号等,这些我们后文会详细介绍。

2. 镜像的体系结构

镜像的最底层是一个启动文件系统(bootfs)镜像,bootfs 的上层镜像叫做根镜像,一般来说,根镜像是一个操作系统,例如 Ubuntu、CentOS 等,用户的镜像必须构建于根镜像之上,在根镜像之上,用户可以构建出各种各样的其他镜像。
从上面的介绍读者可以看出,镜像的本质其实就是一系列文件的集合,一层套一层的结构有点类似于 Git ,也有点类似于生活中的洋葱。
image.png

3. 镜像的写时复制机制

通过 docker run 命令指定一个容器创建镜像时,实际上是在该镜像之上创建一个空的可读写的文件系统层级,可以将这个文件系统层级当成一个临时的镜像来对待,而命令中所指的模版镜像则可以称之为父镜像。父镜像的内容都是以只读的方式挂载进来的,容器会读取共享父镜像的内容,用户所做的所有修改都是在文件系统中,不会对父镜像造成任何影响。当然用户可以通过其他一些手段使修改持久化到父镜像中,这个我们后面会详细介绍到。
简而言之,镜像就是一个固定的不会变化的模版文件,容器是根据这个模版创建出来的,容器会在模版的基础上做一些修改,这些修改本身并不会影响到模版,我们还可以根据模版(镜像)创建出来更多的容器。如果有必要,我们是可以修改模版(镜像)的。

4. 镜像查看

用户可以通过 docker images 命令查看本地所有镜像,如下:
image.png
这里一共有五个参数,含义分别如下:

  • TAG: TAG用于区分同一仓库中的不同镜像,默认为latest。
  • IMAGE ID: IMAGE ID是镜像的一个唯一标识符。
  • CREATED: CREATED表示镜像的创建时间。
  • SIZE: SIZE表示镜像的大小。
  • REPOSITORY:仓库名称,仓库一般用来存放同一类型的镜像。仓库的名称由其创建者指定。如果没有指定则为 。一般来说,仓库名称有如下几种不同的形式:
  1. [namespace\ubuntu]:这种仓库名称由命名空间和实际的仓库名组成,中间通过 \ 隔开。当开发者在 Docker Hub 上创建一个用户时,用户名就是默认的命名空间,这个命令空间是用来区分 Docker Hub 上注册的不同用户或者组织(类似于 GitHub 上用户名的作用),如果读者想将自己的镜像上传到 Docker Hub 上供别人使用,则必须指定命名空间,否则上传会失败。
  2. [ubuntu]:这种只有仓库名,对于这种没有命名空间的仓库名,可以认为其属于顶级命名空间,该空间的仓库只用于官方的镜像,由 Docker 官方进行管理,但一般会授权给第三方进行开发维护。当然用户自己创建的镜像也可以使用这种命名方式,但是将无法上传到 Docker Hub 上共享。
  3. [hub.c.163.com/library/nginx]:这种指定 url 路径的方式,一般用于非 Docker Hub 上的镜像命名,例如一个第三方服务商提供的镜像或者开发者自己搭建的镜像中心,都可以使用这种命名方式命名。

使用 docker images 命令可以查看本地所有的镜像,如果镜像过多,可以通过通配符进行匹配,如下:
image.png
如果需要查看镜像的详细信息,也可以通过上文提到的 docker inspect 命令来查看。

5. 镜像下载

当用户执行 docker run 命令时,就会自动去 Docker Hub 上下载相关的镜像,这个就不再重复演示,开发者也可以通过 search 命令去 Docker Hub 上搜索符合要求的镜像,如下:
image.png
其中:

  • NAME:表示镜像的名称。
  • DESCRIPTION:表示镜像的简要描述。
  • STARS:表示用户对镜像的评分,评分越高越可以放心使用。
  • OFFICIAL:是否为官方镜像。
  • AUTOMATED:是否使用了自动构建。

在执行 docker run 命令时再去下载,速度会有点慢,如果希望该命令能够快速执行,可以在执行之前,先利用 docker pull 命令将镜像先下载下来,然后再运行。
image.png
运行命令如下

image.png
6. 镜像删除

镜像可以通过 docker rmi 命令进行删除,参数为镜像的id或者镜像名,参数可以有多个,多个参数之间用空格隔开。如下:
image.png
有的时候,无法删除一个镜像,大部分原因是因为该镜像被一个容器所依赖,此时需要先删除容器,然后就可以删除镜像了,删除容器的命令可以参考本系列前面的文章。
通过前面文章的阅读,读者已经了解到所谓的容器实际上是在父镜像的基础上创建了一个可读写的文件层级,所有的修改操作都在这个文件层级上进行,而父镜像并未受影响,如果读者需要根据这种修改创建一个新的本地镜像,有两种不同的方式,先来看第一种方式:commit。

7. 创建容器

首先,根据本地镜像运行一个容器,如下:

image.png
命令解释:

  1. 首先执行 docker images 命令,查看本地镜像。
  2. 根据本地镜像中的 nginx 镜像,创建一个名为 nginx 的容器,并启动。
  3. 将宿主机中一个名为 index.html 的文件拷贝到容器中。
  4. 访问容器,发现改变已经生效。
  5. 接下来再重新创建一个容器,名为 nginx2.
  6. 访问 nginx2 ,发现 nginx2 中默认的页面还是 nginx 的默认页面,并未发生改变。

    8. commint 创建本地镜像

    接下来,根据刚刚创建的第一个容器,创建一个本地镜像,如下:
    image.png
    命令解释:

  7. 参数 -m 是对创建的该镜像的一个简单描述。

  8. –author 表示该镜像的作者。
  9. ce1fe32739402 表示创建镜像所依据的容器的 id。
  10. sang/nginx 则表示仓库名,sang 是名称空间,nginx 是镜像名。
  11. v1 表示仓库的 tag。
  12. 创建完成后,通过 docker images 命令就可以查看到刚刚创建的镜像。
  13. 通过刚刚创建的镜像运行一个容器,访问该容器,发现 nginx 默认的首页已经发生改变。

这是我们通过 commint 方式创建本地镜像的方式,但是 commit 方式存在一些问题,比如不够透明化,无法重复,体积较大,为了解决这些问题,可以考虑使用 Dockerfile ,实际上,主流方案也是 Dockerfile。

9. Dockerfile

Dockerfile 就是一个普通的文本文件,其内包含了一条条的指令,每一条指令都会构建一层。先来看一个简单的例子。
首先在一个空白目录下创建一个名为 Dockerfile 的文件,内容如下
image.png
命令解释:

  1. FROM nginx 表示该镜像的构建,以已有的 nginx 镜像为基础,在该镜像的基础上构建。
  2. MAINTAINER 指令用来声明创建镜像的作者信息以及邮箱信息,这个命令不是必须的。
  3. RUN 指令用来修改镜像,算是使用比较频繁的一个指令了,该指令可以用来安装程序、安装库以及配置应用程序等,一个 RUN 指令执行会在当前镜像的基础上创建一个新的镜像层,接下来的指令将在这个新的镜像层上执行,RUN 语句有两种不同的形式:shell 格式和 exec 格式。本案例采用的 shell 格式,shell 格式就像 linux 命令一样,exec 格式则是一个 JSON 数组,将命令放到数组中即可。在使用 RUN 命令时,适当的时候可以将多个 RUN 命令合并成一个,这样可以避免在创建镜像时创建过多的层。
  4. COPY 语句则是将镜像上下文中的 hello.html 文件拷贝到镜像中。

文件创建完成后,执行如下命令进行构建:
image.png命令解释:

  1. -t 参数用来指定镜像的命名空间,仓库名以及 TAG 等信息。
  2. 最后面的 . 是指镜像构建上下文。

注意
Docker 采用了 C/S 架构,分为 Docker 客户端(Docker 可执行程序)与 Docker 守护进程,Docker 客户端通过命令行和 API 的形式与 Docker 守护进程进行通信,Docker 守护进程则提供 Docker 服务。因此,我们操作的各种 docker 命令实际上都是由 docker 客户端发送到 docker 守护进程上去执行。我们在构建一个镜像时,不可避免的需要将一些本地文件拷贝到镜像中,例如上文提到的 COPY 命令,用户在构建镜像时,需要指定构建镜像的上下文路径(即前文的 . ), docker build 在获得这个路径之后,会将路径下的所有内容打包,然后上传给 Docker 引擎。
镜像构建成功后,可以通过 docker images 命令查看,如下:

image.png
然后创建容器并启动,就可以看到之前的内容都生效了。

5. DockerHub 与容器网络

DockerHub

DockerHub 类似于 GitHub 提供的代码托管服务,Docker Hub 提供了镜像托管服务,Docker Hub 地址为 https://hub.docker.com/
利用 Docker Hub 读者可以搜索、创建、分享和管理镜像。Docker Hub 上的镜像分为两大类,一类是官方镜像,例如我们之前用到的 nginx、mysql 等,还有一类是普通的用户镜像,普通用户镜像由用户自己上传。对于国内用户,如果觉得 Docker Hub 访问速度过慢,可以使用国内一些公司提供的镜像,例如网易:

本文使用官方的 Docker Hub 来演示,读者有兴趣可以尝试网易的镜像站。首先读者打开 Docker Hub ,注册一个账号,这个比较简单,我就不赘述了。账号注册成功之后,在客户端命令行可以登录我们刚刚注册的账号,如下:
image.png
看到 Login Succeeded 表示登录成功!
登录成功之后,接下来就可以使用 push 命令上传我们自制的镜像了。注意,自制的镜像要能够上传,命名必须满足规范,即 namespace/name 格式,其中 namespace 必须是用户名,以前文我们创建的 Dockerfile 为例,这里重新构建一个本地镜像并上传到 Docker Hub ,如下:
image.png
首先调用 docker build 命令重新构建一个本地镜像,构建成功后,通过 docker images 命令可以看到本地已经有一个名为 wongsung/nginx 的镜像,接下来通过 docker push 命令将该镜像上传至服务端。上传成功后,用户登录Docker Hub ,就可以看到刚刚的镜像已经上传成功了,如下
image.png
看到这个表示镜像已经上传成功了,接下来,别人就可以通过如下命令下载我刚刚上传的镜像:

  1. docker pull wongsung/nginx

pull下来之后,就可以直接根据该镜像创建容器了。具体的创建过程读者可以参考我们本系列前面的文章。

自动化构建

自动化构建,就是使用 Docker Hub 连接一个包含 Dockerfile 文件的 GitHub 仓库或者 BitBucket 仓库, Docker Hub 则会自动构建镜像,通过这种方式构建出来的镜像会被标记为 Automated Build ,也称之为受信构建 (Trusted Build) ,这种构建方式构建出来的镜像,其他人在使用时可以自由的查看 Dockerfile 内容,知道该镜像是怎么来的,同时,由于构建过程是自动的,所以能够确保仓库中的镜像都是最新的。具体构建步骤如下:

  • 添加仓库

首先登录到 Docker Hub,点击右上角的 Create,然后选择 Create Automated Build ,如下图:
image.png
则新进入到的页面,选择 Link Account 按钮,然后,选择连接 GitHub ,在连接方式选择页面,我们选择第一种连接方式,如下:
image.png
选择完成后,按照引导登录 GitHub ,完成授权操作,授权完成后的页面如下:
image.png

  • 构建镜像

授权完成后,再次点击右上角的 Create 按钮,选择 Create Automated Build ,在打开的页面中选择 GitHub ,如下两张图:
image.png
image.png
这里展示了刚刚关联的 GitHub 上的仓库,只有一个 docker ,然后点击进去,如下:
image.png
填入镜像的名字以及描述,然后点击 Create 按钮,创建结果如下:image.png
如此之后,我们的镜像就算构建成功了,一旦 GitHub 仓库中的 Dockerfile 文件有更新, Docker Hub 上的镜像构建就会被自动触发,不用人工干预,从而保证镜像始终都是最新的。接下来,用户可以通过如下命令获取镜像:

  1. docker pull wongsung/nginx2

获取到镜像之后,再运行即可。
有没有觉得很神奇!镜像更新只要更新自己的 GitHub 即可。镜像就会自动更新!事实上,我们使用的大部分 镜像都是这样生成的。

构建自己的 DockerHub

前面我们使用的 Docker Hub 是由 Docker 官方提供的,我们也可以搭建自己的 Docker Hub ,搭建方式也很容器,因为 Docker 官方已经将 Docker 注册服务器做成镜像了,我们直接 pull 下来运行即可,没有没很酷!。具体步骤如下:

  • 拉取镜像

运行如下命令拉取registry官方镜像:

  1. docker pull registry
  • 运行

接下来运行如下命令将registry运行起来,如下:

  1. docker run -itd --name registry -p 5000:5000 2e2f252f3c88

运行成功后,我们就可以将自己的镜像提交到registry上了,如下:image.png
这里需要注意的是,本地镜像的命名按照 registryHost:registryPort/imageName:tag 的格式命名。
容器运行在宿主机上,如果外网能够访问容器,才能够使用它提供的服务。本文就来了解下容器中的网络知识。

暴露网络端口

在前面的文章中,我们已经有用过暴露网络端口相关的命令了,即 -p 参数,实际上,Docker 中涉及暴露网络端口的参数有两个,分别是 -p 和 -P 。下面分别来介绍。

  • -P

使用 -P,Docker 会在宿主机上随机为应用分配一个未被使用的端口,并将其映射到容器开放的端口,以 Nginx 为例,如下:
image.png
可以看到,Docker 为应用分配了一个随机端口 32768 ,使用该端口即可访问容器中的 nginx(http://lcalhost:32768)。

  • -p

-p 参数则有几种不同的用法:

  • hostPort:containerPort

这种用法是将宿主机端口和容器端口绑定起来,如下用法:
image.png
如上命令表示将宿主机的80端口映射到容器的80上,第一个 80 是宿主机的 80 ,第二个 80 是容器的 80 。

  • ip:hostPort:containerPort

这种是将指定的 ip 地址的端口和容器的端口进行映射。如下:
将 192.168.0.195 地址的80端口映射到容器的80端口上。

  • ip::containerPort

这种是将指定 ip 地址的随机端口映射到容器的开放端口上,如下:image.png

6. Docker 数据卷操作

1. 数据卷入门

在前面的案例中,如果我们需要将数据从宿主机拷贝到容器中,一般都是使用 Docker 的拷贝命令,这样性能还是稍微有点差,没有办法能够达到让这种拷贝达到本地磁盘 I/O 性能呢?有!
数据卷可以绕过拷贝系统,在多个容器之间、容器和宿主机之间共享目录或者文件,数据卷绕过了拷贝系统,可以达到本地磁盘 I/O 性能。
本文先通过一个简单的案例向读者展示数据卷的基本用法。
以前面使用的 nginx 镜像为例,在运行该容器时,可以指定一个数据卷,命令如下:

  1. docker run -itd --name nginx -v /usr/share/nginx/html/ -p 80:80 bc26f1ed35cf

运行效果如下:
image.png
此时,我们创建了一个数据卷并且挂载到容器的 /usr/share/nginx/html/ 目录下,小伙伴们知道,该目录实际上是 nginx 保存 html 目录,在这里挂载数据卷,一会我们只需要修改本地的映射位置,就能实现页面的修改了。
接下来使用 docker inspect 命令查看刚刚创建的容器的具体情况,找到数据卷映射目录,如下:image.png
可以看到,Docker默认将宿主机的 /var/lib/docker/volumes/0746bdcfc045b237a6fe2288a3af9d7b80136cacb3e965db65a212627e217d75/_data 目录作为source目录,接下来,进入到该目录中,如下:
image.png
此时发现该目录下的文件内容与容器中 /usr/share/nginx/html/ 目录下的文件内容一致,这是因为挂载一个空的数据卷到容器中的一个非空目录中,那么这个目录下的文件会被复制到数据卷中(如果挂载一个非空的数据卷到容器中的一个目录中,那么容器中的目录中会显示数据卷中的数据。如果原来容器中的目录中有数据,那么这些原始数据会被隐藏掉)。
小贴士:

由于 Mac 中的 Docker 有点特殊,上文提到的 /var/lib/xxxx 目录,如果是在 linux 环境下,则直接进入即可,如果是在 mac 中,需要首先执行如下命令,在新进入的命令行中进入到 /var/lib/xxx 目录下:
screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty

接下来修改改文件中的index.html文件内容,如下:

  1. echo "hello volumes">index.html

修改完成后,再回到浏览器中,输入 http://localhost查看nginx中index.html 页面中的数据,发现已经发生改变。说明宿主机中的文件共享到容器中去了。

结合宿主机目录

上文中对于数据卷的用法还不是最佳方案,一般来说,我们可能需要明确指定将宿主机中的一个目录挂载到容器中,这种指定方式如下:

  1. docker run -itd --name nginx -v /Users/sang/blog/docker/docker/:/usr/share/nginx/html/ -p 80:80 bc26f1ed35cf

这样便是将宿主机中的 /Users/sang/blog/docker/docker/ 目录挂载到容器的 /usr/share/nginx/html/ 目录下。接下来读者只需要在 /Users/sang/blog/docker/docker/ 目录下添加 html 文件,或者修改 html 文件,都能在 nginx 访问中立马看到效果。
这种用法对于开发测试非常方便,不用重新部署,重启容器等。
注意:宿主机地址是一个绝对路径

更多操作

Dockerfile中的数据卷
如果开发者使用了 Dockerfile 去构建镜像,也可以在构建镜像时声明数据卷,例如下面这样:

  1. FROM nginx
  2. ADD https://www.baidu.com/img/bd_logo1.png /usr/share/nginx/html/
  3. RUN echo "hello docker volume!">/usr/share/nginx/html/index.html
  4. VOLUME /usr/share/nginx/html/

查看所有数据卷

使用如下命令可以查看所有数据卷:

  1. docker volume ls

image.png

查看数据卷详情

根据 volume name 可以查看数据详情,如下:

  1. docker volume inspect

执行结果如下图:
image.png

删除数据卷

可以使用 docker volume rm 命令删除一个数据卷,也可以使用 docker volume prune 批量删除数据卷,如下:
image.png
image.png
批量删除时,未能删除掉所有的数据卷,还剩一个,这是因为该数据卷还在使用中,将相关的容器停止并移除,再次删除数据卷就可以成功删除了,如图:
image.png

数据卷容器

数据卷容器是一个专门用来挂载数据卷的容器,该容器主要是供其他容器引用和使用。所谓的数据卷容器,实际上就是一个普通的容器,举例如下:

  • 创建数据卷容器

使用如下方式创建数据卷容器:

  1. docker run -itd -v /usr/share/nginx/html/ --name mydata ubuntu

命令执行效果如下图:
image.png

  • 引用容器

使用如下命令引用数据卷容器

  1. docker run -itd --volumes-from mydata -p 80:80 --name nginx1 nginx
  2. docker run -itd --volumes-from mydata -p 81:80 --name nginx2 nginx

此时, nginx1 和 nginx2 都挂载了同一个数据卷到 /usr/share/nginx/html/ 目录下,三个容器中,任意一个修改了该目录下的文件,其他两个都能看到变化。
此时,使用 docker inspect 命令查看容器的详情,发现三个容器关于数据卷的描述都是一致的,如下图:
image.png

7. Docker 容器连接

数据卷容器以及和大家聊过了,本文我们再来看看使用数据卷容器实现数据的备份与恢复,然后再来看看容器的连接操作。
利用数据卷容器可以实现实现数据的备份和恢复。

1. 数据备份与恢复

备份

数据的备份操作很容易,执行如下命令:

  1. docker run --volumes-from mydata --name backupcontainer -v $(pwd):/backup/ ubuntu tar cvf /backup/backup.tar /usr/share/nginx/html/

命令解释:

  1. 首先使用 —volumes-from 连接待备份容器。
  2. -v 参数用来将当前目录挂载到容器的 /backup 目录下。
  3. 接下来,将容器中 /usr/share/nginx/html 目录下的内容备份到 /backup 目录下的 backup.tar 文件中,由于已经设置将当前目录映射到容器的 /backup 目录,因为备份在容器 /backup 目录下的压缩文件在当前目录下可以立马看到。

执行结果如下:
image.png
备份完成后,在当前目录下就可以看到 /backup 文件,打开压缩文件,发现就是 /usr/share/nginx/html 目录及内容。

恢复

数据的恢复则稍微麻烦一些,操作步骤如下:

创建容器

首先创建一个容器,这个容器就是要使用恢复的数据的容器,我这里创建一个 nginx 容器,如下:

  1. docker run -itd -p 80:80 -v /usr/share/nginx/html/ --name nginx3 nginx

创建一个名为 nginx3 的容器,并且挂载一个数据卷。

恢复

数据恢复需要一个临时容器,如下:

  1. docker run --volumes-from nginx3 -v $(pwd):/backup nginx tar xvf /backup/backup.tar

命令解释:

  1. 首先还是使用 —volumes-from 参数连接上备份容器,即第一步创建出来的 nginx3 。
  2. 然后将当前目录映射到容器的 /backup 目录下。
  3. 然后执行解压操作,将 backup.tar 文件解压。解压文件位置描述是一个容器内的地址,但是该地址已经映射到宿主机中的当前目录了,因此这里要解压缩的文件实际上就是宿主机当前目录下的文件。

    容器连接

    一般来说,容器启动后,我们都是通过端口映射来使用容器提供的服务,实际上,端口映射只是使用容器服务的一种方式,除了这种方式外,还可以使用容器连接的方式来使用容器服务。
    例如,有两个容器,一个容器运行一个 SpringBoot 项目,另一个容器运行着 mysql 数据库,可以通过容器连接使 SpringBoot 直接访问到 Mysql 数据库,而不必通过端口映射来访问 mysql 服务。
    为了案例简单,我这里举另外一个例子:

    有两个容器,一个 nginx 容器,另一个 ubuntu ,我启动 nginx 容器,但是并不分配端口映射,然后再启动 ubuntu ,通过容器连接,在 ubuntu 中访问 nginx

具体操作步骤如下:

  • 首先启动一个 nginx 容器,但是不分配端口,命令如下

    1. docker run -d --name nginx1 nginx

    命令执行结果如下
    image.png
    容器启动成功后,在宿主机中是无法访问的。

  • 启动ubuntu

接下来,启动一个 ubuntu ,并且和 nginx 建立连接,如下:

  1. docker run -dit --name ubuntu --link nginx1:mylink ubuntu bash

这里使用 –link 建立连接,nginx1 是要建立连接的容器,后面的 mylink 则是连接的别名。
运行成功后,进入到 ubuntu 命令行:

  1. docker exec -it ubuntu bash

然后,有两种方式查看 nginx 的信息:
第一种
在 ubuntu 控制台直接输入 env ,查看环境变量信息:image.png
可以看到 docker 为 nginx 创建了一系列环境变量。每个前缀变量是 MYLINK ,这就是刚刚给连接取得别名。开发者可以使用这些环境变量来配置应用程序连接到 nginx 。该连接是安全、私有的。 访问结果如下
image.png
第二种
另一种方式则是查看 ubuntu 的 hosts 文件,如下:image.png
可以看到,在 ubuntu 的 hosts 文件中已经给 nginx1 取了几个别名,可以直接使用这些别名来访问 nginx1 。
小贴士:

默认情况下,ubuntu 容器中没有安装 curl 命令,需要手动安装下,安装命令如下:
apt-get update
apt-get install curl

8. Docker 容器编排入门

在实际的开发环境或者生产环境,容器往往都不是独立运行的,经常需要多个容器一起运行,此时,如果继续使用 run 命令启动容器,就会非常不便,在这种情况下,docker-compose 是一个不错的选择,使用 docker-compose 可以实现简单的容器编排,本文就来看看 docker-compose 的使用。本文以 jpress 这样一个开源网站的部署为例,向读者介绍 docker-compose 的使用。jpress 是 Java 版的 wordPress ,不过我们不必关注 jpress 的实现,在这里我们只需要将之当作一个普通的应用即可,完成该项目的部署工作。

准备工作

这里我们一共需要两个容器:

  • Tomcat
  • MySQL

然后需要 jpress 的 war 包,war 包地址:jpress
当然,这里的 jpress 并不是必须的,读者也可以结合自身情况,选择其他的 Java 项目或者自己写一个简单的 Java 项目部署都行。

编写 Dockerfile

Tomcat 容器中,要下载相关的 war 等,因此我这里编写一个 Dockerfile 来做这个事。在一个空的文件夹下创建 Dockerfile ,内容如下:

  1. FROM tomcat
  2. ADD https://github.com/JpressProjects/jpress/raw/alpha/wars/jpress-web-newest.war /usr/local/tomcat/webapps/
  3. RUN cd /usr/local/tomcat/webapps/ \
  4. && mv jpress-web-newest.war jpress.war

解释:

  1. 容器基于 Tomcat 创建。
  2. 下载 jpress 项目的 war 包到 tomcat 的 webapps 目录下。
  3. 给 jpress 项目重命名。

    编写 docker-compose.yml

    在相同的目录下编写 docker-compose.yml ,内容如下(关于 yml 的基础知识,这里不做介绍,读者可以自行查找了解)

    1. version: "3.1"
    2. services:
    3. web:
    4. build: .
    5. container_name: jpress
    6. ports:
    7. - "8080:8080"
    8. volumes:
    9. - /usr/local/tomcat/
    10. depends_on:
    11. - db
    12. db:
    13. image: mysql
    14. container_name: mysql
    15. command: --default-authentication-plugin=mysql_native_password
    16. restart: always
    17. ports:
    18. - "3306:3306"
    19. environment:
    20. MYSQL_ROOT_PASSWORD: 123
    21. MYSQL_DATABASE: jpress

    解释:

  4. 首先声明了 web 容器,然后声明db容器。

  5. build . 表示 web 容器项目构建上下文为 . ,即,将在当前目录下查找 Dockerfile 构建 web 容器。
  6. container_name 表示容器的名字。
  7. ports 是指容器的端口映射。
  8. volumes 表示配置容器的数据卷。
  9. depends_on 表示该容器依赖于 db 容器,在启动时,db 容器将先启动,web 容器后启动,这只是启动时机的先后问题,并不是说 web 容器会等 db 容器完全启动了才会启动。
  10. 对于 db 容器,则使用 image 来构建,没有使用 Dockerfile 。
  11. restart 描述了容器的重启策略。
  12. environment 则是启动容器时的环境变量,这里配置了数据库 root 用户的密码以及在启动时创建一个名为 jpress 的库,environment 的配置可以使用字典和数组两种形式。

OK,经过如上步骤,docker-compose.yml 就算配置成功了

运行

运行的方式有好几种,但是建议使用 up 这个终极命令,up 命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。对于大部分应用都可以直接通过该命令来启动。默认情况下, docker-compose up 启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试,通过 Ctrl-C 停止命令时,所有容器将会停止,而如果使用 docker-compose up -d 命令,则将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。
因此,这里进入到 docker-compose.yml 所在目录下,执行如下命令:

  1. docker-compose up -d

执行结果如下:image.png
执行后,通过 docker-compose ps 命令可以看到容器已经启动了。

初始化配置

接下来,浏览器中输入 http://localhost:8080/jpress ,就可以看到 jpress 的配置页面,如下:
image.png
根据引导页面配置数据库的连接信息以及网站的基本信息:image.pngimage.png
注意:由于 mysql 和 web 都运行在容器中,因此在配置数据库地址时,不能写回环地址,否则就去 web 所在的容器里找数据库了
配置完成后,运行如下命令,重启 web 容器:

  1. docker restart jpress

测试

浏览器中分别查看博客首页以及后台管理页,如下图:image.png
image.png

其他

如果想要停止容器的运行,可以执行如下命令:

  1. docker-compose down