前言
我们设想这样一个场景,现在 leader 给了你一个任务,让你把开发完毕的应用程序进行打包、分发、部署,要求多平台通用,程序的前后端是单独的工程、后端依赖了 mysql、redis、RabbitMQ 等。按常规的运维思路就是,先将配置好的前后端程序分别打包 --> 将打包文件上传到服务器 --> 后端安装 mysql、redis、RabbitMQ --> 配置 nginx 服务器实现反向代理,虽然看上去步骤很简单,顺利部署成功,也不算太糟心,但是保不齐部署的时候,就会出现各种环境上的不兼容问题。此外,当一个程序依赖了五个、六个或者更多的环境时,按照这种传统的部署方式显然很繁琐,一旦遇到环境上的冲突、不兼容等问题,不禁让人陷入苦恼之中。假如有这样一种工具,能够将程序依赖的环境根据层级关系进行一个叠加 ,比如一个 Java 应用程序最底层肯定依赖的是 JDK,接着是一些数据库、中间件等其他环境,最后就是我们运行这个程序的一些入口,将这些环境根据层级关系叠加在一起,我们暂且将其称为安装包吧! 而且这些安装包已经有人已经制作完毕并发布到公共仓库里边,当我们在部署我们的应用时,只需要按照这种工具规定的方式拉取安装包,部署岂不是方便很多了, docker
就应运而生了!
一、什么是 Docker?
docker 是一款可以为应用程序进行打包、分发、部署的工具,也可以理解为一个轻量的虚拟机,只需要虚拟机软件运行的环境,多余的一点都不需要。而 docker 可以帮助我们下载应用镜像,创建并运行镜像的容器,从而快速部署应用。对于上边提到的打包、分发、部署、镜像、容器这些概念,屏幕前的你是否会有些疑惑?
-
打包:就是将软件所需要的依赖,第三方库,软件包打包到一起,变成一个安装包。
-
分发:你可以把你打包好的 " 安装包 " 上传到一个镜像仓库,其他人可以非常方便的获取和安装。
-
部署:拿着 " 安装包 ",就可以一行命令运行起来你的应用,在不同的平台上自动模拟出一摸一样的运行环境。
-
镜像 (image): 将应用所需的函数库、依赖、配置等与应用一起打包得到的就是镜像 。更加官方的说法就是:镜像是一个包含程序运行必要依赖环境和代码的只读文件,它采用分层的文件系统,将每一层的改变以读写层的形式增加到原来的只读文件上。哈哈,官方说法看来很晦涩难懂呀, 我们只需要把它认为是一个安装包就行,随着使用的增加和深入,慢慢会理解的。
-
容器(container):为每个镜像的应用创建的隔离运行环境就是容器,说白了就是一个进程。
-
镜像仓库(repository):存储和管理镜像的服务就是镜像仓库,目前,DockerHub 是最大的镜像仓库,其中包含各种常见的应用镜像。
对于镜像和容器之间的关系我们需要有一定的了解,是非常重要的概念,是我们学习 docker 的基础 ,当然,我也不可能通过上边两个简单的概念就可以讲明白这两者之间的概念。打个比方吧, 镜像(image)好比面向对象编程语言里边的类,而容器 (container)好比面向编程语言里类实例化后的对象。一个类可以有多个对象,同理,一个镜像可以有多一个容器。容器是由镜像实例话而来,简单来说,镜像就是文件,容器就是进程。说到这里,你应该对两者的概念和关系有初步的理解了吧,镜像就是容器运行的基础,两者是辩证统一的关系。谈到这里,不得不提一下 docker 的生命周期了,它主要由三部分组成:镜像 + 容器 + 仓库。
二、docker 的安装
桌面版和服务器版的区别就是桌面版有 docker-compose 这个工具,而服务器版本没有,建议安装桌面版。
对于 mac 用户,可以使用 homebrew 安装,但是前提你得有 homebrew 安装工具,关于这个工具的安装,自行搜索网上的教程安装。当你电脑有 homebrew 时,直接用下面的命令行安装:
1 | brew install --cask docker |
三、docker 常用命令
当我们的电脑上安装了 docker 之后,接着我们便可以使用 docker 来安装软件,在此处,我们以安装 mysql 为例来认识 docker 的基本命令。
在终端输入上述命令之后,就会安装好 mysql。关于这条命令的解读如下:
-
docker run :创建并运行一个容器,-d 是让容器在后台运行
-
–name mysql :给容器起个名字,必须唯一
-
-p 3306:3306 :设置端口映射
-
-e KEY=VALUE :是设置环境变量
-
mysql 指定映射镜像运行的名称。镜像命名一般格式为:镜像名称: 镜像版本 ==>
[repository]: [tag]
,在没有指定版本的时候,默认是 latest 最新版本。通过上边的小例子我们算是对 docker 的命令有了一定的了解,下边这张图是对 docker 命令的一个概述:
docker 的其他命令,查看请展开
命令 | 具体作用 |
---|---|
docker pull | 将远程镜像仓库的镜像拉取到本地镜像中,类似于 git 的推送,注意在拉取镜像到本地后,镜像已经开始运行了 |
docker push | 将本地仓库的镜像推送到远程镜像仓库中,类似于 git 的推送 |
docker images | 查看本地运行的镜像 |
docker rmi | 删除本地的镜像,具体命令为 docker rmi -f 镜像名称 |
docker build | 构建本地的镜像并运行 |
docker load | 加载本地的镜像并运行 |
docker run | 运行容器 |
docker logs | 产看容器运行的日志 |
docker exec | 进入容器内部 |
docker stop | 停止容器运行 |
docker start | 启动容器 |
docker ps | 查看正在运行的容器 |
docker rm | 删除容器 |
这里有一个技巧,命令的具体细节我们可以不记忆,可以通过 --help 指令来查看它的用法,比如 docker rm 删除容器,我们可以通过 docker rm --help
来查看他的具体用法:
从终端的命令我们可看到 docker rm -f 容器名称
就是删除掉一个运行中的容器。通过这种方法我们便可以不用在记忆这么多繁琐的命令,只知道具体的命令关键字即可。通过上边的学习,我们便可以拉取、删除、运行、查看镜像了。
四、容器目录挂载的方式
通过上边的学习我们可以通过 docker 拉取远程镜像来部署我们的应用,现在有这样一个需求,用 nginx 部署一个 vue 项目,我们首先做的是拉取 nginx 镜像,接着配置 nginx. conf
文件,然后将 vue 打包的应用上传到 nginx 上。那么,问题来了,我们上边提到容器是镜像的实例化对象,而镜像本身就是一个虚拟机,如何去修改配置文件和上传 vue 打包的文件。对于修改配置文件 nginx. conf
,我们不妨使用命令 docker exec nginx
进入容器内部, 但是当我们使用 vi 编辑器的时候,发现容器内部不支持,我们上边提到过:docker 是一款可以为应用程序进行打包、分发、部署的工具,也可以理解为一个轻量的虚拟机,只需要虚拟机软件运行的环境,多余的一点都不需要 ,所以便不会存在 vi 了。假如我们本地存在这样一个目录,可以将修改的配置文件和 vue 打包文件同步到容器内部,那么就方便多了。因此,我们需要了解一个新的概念–数据卷, 它是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。
关于数据卷的详细命令,直接通过帮助指令在终端自行查看,这里只提供常用指令。
1 | docker volume --help // 查看其他数据卷指令 |
下边我将介绍三种目录挂载的方式:
1、volume 方式
由容器创建和管理,创建在宿主机上,所以删除容器不会丢失,官方推荐,更高效,Linux 文件系统,适合存储数据库数据,可以挂载多个容器。
当我们在创建并运行镜像的时候,可通过语法 -v 数据卷名称(自定义): 容器内目录
来挂载目录,容器创建的时候,如果发现挂载的数据卷不存在时,就会自动创建数据卷。
1 | docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx |
我们在终端查看发现目录 html 挂载成功了。
其实,这个 html 目录位于你本地 docker 安装目录下的 volumes 目录内,比如 mac 上是这样的,orbstack 是 mac 电脑上一种内含 dockerde 轻量化软件,不必深究这个目录,只需要找到你的 docker 安装目录即可。
2、bind Mount 方式
直接把宿主机的物理目录映射到容器内,适合挂载代码目录和配置文件。而且是一种双向绑定,宿主机和容器内的内容都是同步的,也可以挂载到多个容器上。现在有这样一个需求:
查看 mysql 容器,判断是否有数据卷挂载,基于宿主机目录实现 MySQL 数据目录、配置文件、初始化脚本的挂载(查阅官方镜像文档)
①挂载 /root/mysql/data 到容器内的 /var/lib/mysql 目录
②挂载 /root/mysql/init 到容器内的 /docker-entrypoint-initdb.d 目录
③挂载 /root/mysql/conf 到容器内的 /etc/mysql/conf. d 目录
对于以上需求,我们可以使用 volume 方式挂载,但是由于涉及的配置文件过多,我们难免在各个过程都需要修改,需要保持本地配置文件和容器内的配置一致,尤为重要的一点是当我们用 volume 的挂载删除容器时,虽然挂载的目录还在,但是当再次运行容器时,挂载的目录名称变了,上次的数据就会丢失,数据便没有迁移过来 ,因此,我们便选用 bind mount 进行绑定。具体语法如下:
1 | -v 本地目录 : 容器内目录 |
在运行 docker run
命令的时候,便可以将这条命令加入到后边。具体执行如下:
1 | docker run -d \ --name mysql \ |
由于命令太长,这里使用了 \
将命令进行分割。在终端运行以上命令,如下图:
我们使用 docker inspect mysql
命令来查看容器的详细信息,这里只截取了挂载目录的信息,如下图:
从上图发现已经主机目录已经和容器目录映射成功了!
3、tmpfs Mount
适合存储临时文件,存储在宿主机内存中,不可多容器共享。
以上就是我们常见的三种目录挂载方式,核心掌握前两种方法。
五、自定义镜像
通过以上的学习,我们可以通过别人制作好的镜像去部署一个应用了,但是我们如果要制作属于自己的镜像该怎么做?下边我们将通过一个例子来讲述如何制作一个镜像。镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建镜像的过程其实就是把上述文件打包的过程。
每一个镜像结构都是根据其依赖关系的层级关系或者说是依赖范围大小分层的,分别有基础镜像,中间的每一层,入口层。
关于这些层之间的结构 docker 提供了一个描述文件 Dockfile ,它会说明每一层具体要执行哪些操作,帮我们自动构建镜像,常见指令如下:
具体指令的详细描述如下 (可以略过):
1 | // FROM 指令 |
通过 FROM 指定的镜像名称必须是一个已经存在的镜像,这个镜像称之为基础镜像,必须位于 第一条非注释指令。
1 | // MAINTAINER 指令 |
指定镜像的作者信息,包含镜像的所有者和联系人信息
1 | // RUN 指令 |
多条 RUN 指令可以合并为一条:
1 | RUN yun install httpd && yun install ftp |
这样在构建的时候会减少产生中间层镜像
1 | // EXPOSE 指令 |
使用这个指令的目的是告诉应用程序容器内应用程序会使用的端口,在运行时还需要使用 -p 参数指定映射端口。这是 docker 处于安全的目的,不会自动打开端口。
1 | docker run -p 80 -d dockertest/dockerfile_build nginx -g "daemon off" |
1 | // CMD 指令 |
1 | // ADD 和 COPY |
-
build 为镜像(安装包)和运行
1 | docker build -t repository(package name):tag . |
我们可以基于 Ubuntu 基础镜像,利用 Dockerfile 描述镜像结构,
但是这样有点麻烦,我们可以基于 Ubuntu 基础镜像,利用 Dockerfile 描述镜像结构,直接基于 JDK 为基础镜像,省略前面的步骤。
当我们便写好了 Dockefile 文件后,可以直接使用一下命令来构建镜像:
1 | docker build -t 镜像名称 . |
-t
:是给镜像起名,格式依然是 repository: tag 的格式,不指定 tag 时,默认为 latest
.
:是指定 Dockerfile 所在目录,如果就在当前目录, 则指定为 .
。
六、网络(多容器通信)
对于前后端分离的应用,我们单独部署的时候需要配置跨域,而且每个应用都是一个单独的容器,默认情况下,所有容器都是以 bridge 方式连接到 Docker 的一个虚拟网桥上:
但是如果重新启动容器后,IP 地址就会发生变化,我们又得在配置文件中修改配置,如果能把变化的 IP 地址用一个变量代替了,这样即使重启应用后,我们在也不需要修改 IP 地址了,而这个变量就是容器名称,但是默认情况下是不能通过容器名互相访问的,这时候我们需要通过自定义网络,让它们加入同一个网络,便可以使用容器名互相访问了!
Docker 网络操作命令如下:
也可以在容器运行的时候创建网络,具体示例操作如下:
1 | docker run -d --name redis --netowrk test-net --network-alias redis redis:latest |
七、docker-compose
通过上边的讲述,我们可以将一个项目中涉及的环境进行部署,但是这样的方式只能一个容器一个容器的单独部署运行,如果项目中涉及的容器比较多,效率就会很低,因此 docker 提供了一个工具 docker- compose。Docker Compose 通过一个单独的 docker-compose. yml
模板文件(YAML 格式)来定义一组相关联的应用容器,帮助我们实现多个相互关联的 Docker 容器的快速部署。
事实上,Dockerfile 描述文件中的指令和 docker-compose. yml
配置文件的内容是对应的。
当我们配置好了 docker-compose. yml
文件后,直接运行 docker compose [OPTIONS] [COMMAND]
命令部署并运行所有容器。
总结
上述内容可以帮助我们处理日常开发过程中碰到的部署问题,对于不是专业的运维工作人员而言,已经足够了。有了 docker 后,我们在部署应用,分享自己的软件的时候,再也不会因为计算机系统、环境冲突而引起的问题苦恼了,这么好的工具我们有什么理由不学习使用呢?