Docker教程
Docker 官网:https://www.docker.com
参考视频:黑马程序员Docker快速入门到项目部署,MySQL部署+Nginx部署+docker自定义镜像+DockerCompose项目实战一套搞定
1. 初识 Docker
1.1 什么是 Docker
Docker 是一个开源的应用容器引擎,它可以让开发者打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间没有任何接口(类似 iPhon e的 app),几乎没有性能开销,可以很容易地在机器和数据中心中运行。最重要的是,容器性能开销极低。
Docker 的主要特点包括:
- 轻量级与可移植性:Docker 容器是基于 Linux 内核的虚拟化技术,不依赖于特定的硬件架构或操作系统。因此,它可以轻松地从一个环境迁移到另一个环境,实现应用的快速部署和扩展。
- 版本控制:Docker 允许开发者像管理代码一样管理基础设施。这意味着可以使用版本控制工具(如 Git)来跟踪容器的变更历史,确保每次变更都是可追踪和可重复的。
- 隔离性:每个 Docker 容器都运行在自己的命名空间中,拥有独立的文件系统、网络栈和进程空间。这种隔离性确保了容器之间的安全性,防止了潜在的冲突和干扰。
- 自动化与编排:Docker 与各种自动化工具和编排平台(如 Kubernetes、Docker Swarm 等)集成,可以轻松地实现容器的自动化部署、扩展和管理。
- 丰富的生态系统:Docker 拥有庞大的用户群体和丰富的生态系统,包括大量的官方和第三方镜像、插件和工具。这使得开发者可以轻松地找到所需的资源,加速应用的开发和部署过程。
总的来说,Docker 提供了一种高效、可靠和灵活的方式来构建、部署和管理应用。它简化了应用的开发和运维过程,降低了成本,提高了效率。无论是个人开发者还是大型企业,都可以从 Docker 中受益。
1.2 Docker 和虚拟机的区别
Docker 可以让一个应用在任何操作系统中非常方便的运行。而以前我们接触的虚拟机,也能在一个操作系统中,运行另外一个操作系统,保护系统中的任何应用。两者有什么差异呢?
虚拟机(virtual machine)是在操作系统中模拟硬件设备,然后运行另一个操作系统,比如在 Windows 系统里面运行 Ubuntu 系统,这样就可以运行任意的Ubuntu应用了。
Docker仅仅是封装函数库,并没有模拟完整的操作系统,如图:
对比来看:
小结
Docker 和虚拟机的差异:
Docker 是一个系统进程;虚拟机是在操作系统中的操作系统
Docker 体积小、启动速度快、性能好;虚拟机体积大、启动速度慢、性能一般
1.3 Docker架构
1.3.1 镜像(Image)和容器(Container)
Docker 中有几个重要的概念:
镜像:Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。
容器:镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器进程做隔离,对外不可见。
一切应用最终都是代码组成,都是硬盘中的一个个的字节形成的文件。只有运行时,才会加载到内存,形成进程。
镜像就是把一个应用在硬盘上的文件、及其运行环境、部分系统函数库文件一起打包形成的文件包。这个文件包是只读的。
容器就是将这些文件中编写的程序、函数加载到内存中允许,形成进程,只不过要隔离起来。因此一个镜像可以启动多次,形成多个容器进程。
例如你下载了一个QQ,如果我们将QQ在磁盘上的运行文件及其运行的操作系统依赖打包,形成QQ镜像。然后你可以启动多次,双开、甚至三开QQ,跟多个妹子聊天。😎
1.3.2 DockerHub
开源应用程序非常多,打包这些应用往往是重复的劳动。为了避免这些重复劳动,人们就会将自己打包的应用镜像,例如 Redis、MySQL 镜像放到网络上,共享使用,就像 GitHub 的代码共享一样。
Docker 官方提供了一个专门管理、存储镜像的网站,并对外开放了镜像上传、下载的权利。Docker 官方提供了一些基础镜像,然后各大软件公司又在基础镜像基础上,制作了自家软件的镜像,全部都存放在这个网站。这个网站就成了 Docker 镜像交流的社区:https://hub.docker.com/
DockerHub:DockerHub 是一个官方的 Docker 镜像的托管平台。这样的平台称为 Docker Registry。
我们一方面可以将自己的镜像共享到 DockerHub,另一方面也可以从 DockerHub 拉取镜像:
1.3.3 Docker架构
Docker 是一个 CS 架构的程序,由两部分组成:
服务端(server):Docker 守护进程,负责处理 Docker 指令,管理镜像、容器等
客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令
2. 快速入门
2.1 安装 Docker
Docker 分为 CE 和 EE 两大版本。CE 即社区版(免费,支持周期 7 个月),EE 即企业版,强调安全,付费使用,支持周期 24 个月。
Docker CE 分为 stable
test
和 nightly
三个更新频道。
官方网站上有各种环境下的 安装指南,这里主要介绍 Docker CE 在 CentOS上的安装。
1、卸载旧版
首先如果系统中已经存在旧的 Docker,则先卸载:
1 | yum remove docker \ |
2、配置Docker的 yum 库
首先要安装一个 yum 工具
1 | yum install -y yum-utils |
安装成功后,执行命令,配置 Docker 的 yum 源:
1 | yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo |
PS:如果国内访问不到docker官方镜像,可以通过aliyun的源来完成:
1 | yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo |
3、安装 Docker
最后,执行命令,安装 Docker
1 | yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin |
4、启动和校验
1 | 启动Docker |
5、配置镜像加速
这里以阿里云镜像加速为例。
(1) 注册阿里云账号
首先访问阿里云网站注册一个账号。
(2) 开通镜像服务
在首页的产品中,找到阿里云的容器镜像服务:
点击后进入控制台:
(3) 配置镜像加速
找到镜像工具下的镜像加速器:
页面向下滚动,即可找到配置的文档说明:
具体命令如下:
1 | 创建目录 |
2.2 部署 MySQL
首先,我们利用 Docker 来安装一个 MySQL 软件,大家可以对比一下之前传统的安装方式,看看哪个效率更高一些。
如果是利用传统方式部署 MySQL,大概的步骤有:
- 搜索并下载 MySQL 安装包
- 上传至Linux环境
- 编译和配置环境
- 安装
而使用 Docker 安装,仅仅需要一步即可,在命令行输入下面的命令(建议采用 CV 大法):
1 | docker run -d \ |
运行效果如图:
MySQL 安装完毕!通过任意客户端工具即可连接到 MySQL。
大家可以发现,当我们执行命令后,Docker 做的第一件事情,是去自动搜索并下载了 MySQL,然后会自动运行 MySQL,我们完全不用插手,非常方便。而且,这种安装方式你完全不用考虑运行的操作系统环境。
PS:这里下载的不是安装包,而是镜像。😀 镜像中不仅包含了 MySQL 本身,还包含了其运行所需要的环境、配置、系统级函数库。因此它在运行时就有自己独立的环境,就可以跨系统运行,也不需要手动再次配置环境了。这套独立运行的隔离环境我们称为容器。
因此,Docker 安装软件的过程,就是自动搜索下载镜像,然后创建并运行容器的过程。😏😏Docker 会根据命令中的镜像名称去 DockerHub 自动搜索并下载镜像。
总结:
Docker 本身包含一个后台服务,我们可以利用 Docker 命令告诉 Docker 服务,帮助我们快速部署指定的应用。Docker 服务部署应用时,首先要去搜索并下载应用对应的镜像,然后根据镜像创建并允许容器,应用就部署完成了。
2.3 命令解读
利用 Docker 快速的安装了 MySQL,非常的方便,不过我们执行的命令到底是什么意思呢?
1 | docker run -d \ |
解读:
docker run -d
:创建并运行一个容器,-d
则是让容器以后台进程运行--name mysql
: 给容器起个名字叫mysql
,你可以叫别的-p 3306:3306
: 设置端口映射。- 容器是隔离环境,外界不可访问。但是可以将宿主机端口映射容器内到端口,当访问宿主机指定端口时,就是在访问容器内的端口了。
- 容器内端口往往是由容器内的进程决定,例如 MySQL 进程默认端口是 3306,因此容器内端口一定是 3306;而宿主机端口则可以任意指定,一般与容器内保持一致。
- 格式:
-p 宿主机端口:容器内端口
,示例中就是将宿主机的 3306 映射到容器内的3306端口
-e TZ=Asia/Shanghai
: 配置容器内进程运行时的一些参数- 格式:
-e KEY=VALUE
,KEY 和 VALUE 都由容器内进程决定 - 案例中,
TZ=Asia/Shanghai
是设置时区;MYSQL_ROOT_PASSWORD=123
是设置 MySQL 默认密码
- 格式:
mysql
: 设置镜像名称,Docker 会根据这个名字搜索并下载镜像- 格式:
REPOSITORY:TAG
,例如mysql:8.0
,其中REPOSITORY
可以理解为镜像名,TAG
是版本号 - 在未指定
TAG
的情况下,默认是最新版本,也就是mysql:latest
- 格式:
镜像的名称不是随意的,而是要到 DockerRegistry 中寻找,镜像运行时的配置也不是随意的,要参考镜像的帮助文档,这些在DockerHub 网站或者软件的官方网站中都能找到。
如果我们要安装其它软件,也可以到 DockerRegistry 中寻找对应的镜像名称和版本,阅读相关配置即可。
3. Docker 基础
Docker 使用的一些基础知识。具体用法可以参考 Docker 官方文档:https://docs.docker.com/
3.1 镜像操作
3.1.1 镜像名称
首先来看下镜像的名称组成:
- 镜名称一般分两部分组成:[repository]:[tag]
- 在没有指定 tag 时,默认是 latest,代表最新版本的镜像
3.1.2 镜像相关命令
常见的镜像操作命令:
命令 | 说明 | 文档地址 |
---|---|---|
docker pull | 拉取镜像 | docker pull |
docker push | 推送镜像到DockerRegistry | docker push |
docker images | 查看本地镜像 | docker images |
docker rmi | 删除本地镜像 | docker rmi |
docker save | 保存镜像到本地压缩文件 | docker save |
docker load | 加载本地压缩文件到镜像 | docker load |
用一副图来表示这些命令的关系:
演示
1、去 DockerHub 查看 nginx 镜像仓库及相关信息
2、拉取Nginx镜像
1 | docker pull nginx |
3、查看镜像
1 | docker images |
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
4、将 nginx 镜像导出磁盘
1 | 利用docker xx --help命令可以查看docker save和docker load的语法,例如,查看save命令用法,可以输入命令: |
1 | 命令格式 |
5、将 nginx 镜像导出磁盘
1 | docker save -o nginx.tar nginx:latest |
6、使用 load 加载镜像
1 | 先删除本地的nginx镜像 |
3.2 容器操作
3.2.1 容器相关命令
常见的容器操作命令:
命令 | 说明 | 文档地址 |
---|---|---|
docker run | 创建并运行容器(不能重复创建) | docker run |
docker stop | 停止指定容器 | docker stop |
docker start | 启动指定容器 | docker start |
docker restart | 重新启动容器 | docker restart |
docker rm | 删除指定容器 | docs.docker.com |
docker ps | 查看容器 | docker ps |
docker logs | 查看容器运行日志 | docker logs |
docker exec | 进入容器 | docker exec |
docker inspect | 查看容器详细信息 | docker inspect |
用一副图来表示这些命令的关系:
容器保护三个状态:
- 运行:进程正常运行
- 暂停:进程暂停,CPU 不再运行,并不释放内存
- 停止:进程终止,回收进程占用的内存、CPU 等资源
演示
1、创建并运行 nginx 容器的命令:
1 | #创建并运行Nginx容器 |
创建成功后会返回一串数字:
1 | f37101c8fff6fddcc5fa8207a53af1799a281a6b26fc2cc4139964b5f73fc05e |
命令解读:
docker run
:创建并运行一个容器-d
:后台运行容器--name
: 给容器起一个名字,比如叫做 nginx-p
:将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口nginx
:镜像名称,例如nginx
这里的 -p
参数,是将容器端口映射到宿主机端口。
默认情况下,容器是隔离环境,我们直接访问宿主机的 80 端口,肯定访问不到容器中的 nginx。
现在,将容器的 80 与宿主机的 80 关联起来,当我们访问宿主机的 80 端口时,就会被映射到容器的 80,这样就能访问到 nginx 了。
2、查看容器是否运行
1 | 查看运行中容器 |
格式化后查看到 nginx,说明已经运行:
1 | CONTAINER ID IMAGE PORTS STATUS NAMES |
3、停掉容器
1 | docker stop nginx |
4、停掉容器后再次执行 docker ps
就看不到 nginx 了,需要使用 -a
参数查看所有容器
1 | 查看所有容器 |
可以发现 nginx 的 STATUS
变为了 Exited (0)
,表示已经停掉
1 | CONTAINER ID IMAGE PORTS STATUS NAMES |
5、启动 nginx 容器,再次执行 docker ps
查看运行中容器就会发现 nginx 又运行起来了。
1 | 启动nginx容器 |
6、查看 nginx 容器日志
1 | docker logs nginx |
7、进入容器
(1) 进入我们刚刚创建的 nginx 容器:
1 | docker exec -it nginx bash |
命令解读:
docker exec
:进入容器内部,执行一个命令-it
: 给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互nginx
:要进入的容器的名称bash
:进入容器后执行的命令,bash是一个linux终端交互命令
进入 nginx 的 HTML 所在目录 /usr/share/nginx/html,容器内部会模拟一个独立的 Linux 文件系统,看起来如同一个 Linux 服务器一样。nginx 的环境、配置、运行文件全部都在这个文件系统中,包括我们要修改的 html 文件。
(2) 查看 DockerHub 网站中的 nginx 页面,可以知道 nginx 的 html 目录位置在 /usr/share/nginx/html
1 | 进入目录 |
(3) 修改 index.html 的内容
容器内没有 vi 命令,无法直接修改,我们用下面的命令来修改:
1 | sed -i -e 's#Welcome to nginx#木又枯了#g' -e 's#<head>#<head><meta charset="utf-8">#g' index.html |
在浏览器访问自己的虚拟机地址,例如我的是:http://192.168.88.132
,即可看到结果:
8、删除容器
1 | 删除容器 |
3.3 常见命令总结
Docker 中的常见命令,可以参考官方文档:https://docs.docker.com/engine/reference/commandline/cli/
3.3.1 命令介绍
其中,比较常见的命令有:
命令 | 说明 | 文档地址 |
---|---|---|
docker pull | 拉取镜像 | docker pull |
docker push | 推送镜像到DockerRegistry | docker push |
docker images | 查看本地镜像 | docker images |
docker rmi | 删除本地镜像 | docker rmi |
docker run | 创建并运行容器(不能重复创建) | docker run |
docker stop | 停止指定容器 | docker stop |
docker start | 启动指定容器 | docker start |
docker restart | 重新启动容器 | docker restart |
docker rm | 删除指定容器 | docs.docker.com |
docker ps | 查看容器 | docker ps |
docker logs | 查看容器运行日志 | docker logs |
docker exec | 进入容器 | docker exec |
docker save | 保存镜像到本地压缩文件 | docker save |
docker load | 加载本地压缩文件到镜像 | docker load |
docker inspect | 查看容器详细信息 | docker inspect |
用一副图来表示这些命令的关系:
PS:默认情况下,每次重启虚拟机我们都需要手动启动 Docker 和 Docker 中的容器。通过命令可以实现开机自启:
1 | Docker开机自启 |
3.3.2 命令别名
给常用 Docker 命令起别名,方便我们访问:
1 | 修改/root/.bashrc文件 |
内容如下:
1 | # .bashrc |
然后,执行命令使别名生效
1 | source /root/.bashrc |
接下来,试试看新的命令吧。
3.4 数据卷(volume)
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便。大家思考几个问题:
- 如果要升级 MySQL 版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
- MySQL、Nginx 容器运行后,如果我要修改其中的某些配置该怎么办?
- 我想要让 Nginx 代理我的静态资源怎么办?
因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦。
3.4.1 什么是数据卷
数据卷是一个虚拟目录,是容器内目录 宿主机目录之间映射的桥梁。
以 Nginx 为例,我们知道 Nginx 中有两个关键的目录:
html
:放置一些静态资源conf
:放置配置文件
如果我们要让 Nginx 代理我们的静态资源,最好是放到 html
目录;如果我们要修改 Nginx 的配置,最好是找到 conf
下的nginx.conf
文件。
但遗憾的是,容器运行的 Nginx 所有的文件都在容器内部。所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作。如图:
在上图中:
- 我们创建了两个数据卷:
conf
、html
- Nginx 容器内部的
conf
目录和html
目录分别与两个数据卷关联。 - 而数据卷 conf 和 html 分别指向了宿主机的
/var/lib/docker/volumes/conf/_data
目录和/var/lib/docker/volumes/html/_data
目录
这样以来,容器内的 conf
和html
目录就 与宿主机的 conf
和 html
目录关联起来,我们称为挂载。此时,我们操作宿主机的 /var/lib/docker/volumes/html/_data
就是在操作容器内的 /usr/share/nginx/html/_data
目录。只要我们将静态资源放入宿主机对应目录,就可以被 Nginx 代理了。
小提示😃
/var/lib/docker/volumes
这个目录就是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data
。
为什么不让容器目录直接指向宿主机目录呢?
因为直接指向宿主机目录就与宿主机强耦合了,如果切换了环境,宿主机目录就可能发生改变了。由于容器一旦创建,目录挂载就无法修改,这样容器就无法正常工作了。
但是容器指向数据卷,一个逻辑名称,而数据卷再指向宿主机目录,就不存在强耦合。如果宿主机目录发生改变,只要改变数据卷与宿主机目录之间的映射关系即可。
不过,我们通过由于数据卷目录比较深,不好寻找,通常我们也允许让容器直接与宿主机目录挂载而不使用数据卷。
3.4.2 数据卷命令
数据卷的相关命令有:
命令 | 说明 | 文档地址 |
---|---|---|
docker volume create | 创建数据卷 | docker volume create |
docker volume ls | 查看所有数据卷 | docs.docker.com |
docker volume rm | 删除指定数据卷 | docs.docker.com |
docker volume inspect | 查看某个数据卷的详情 | docs.docker.com |
docker volume prune | 清除数据卷 | docker volume prune |
在执行docker run命令时,使用 -v 数据卷:容器内目录
可以完成数据卷挂载。当创建容器时,如果挂载了数据卷且数据卷不存在,会自动创建数据卷。
PS:挂载的动作一定是在docker run(容器创建)的时候执行,容器一旦已经创建是没有办法再去挂载的!!😉
演示 Nginx 的 html 目录挂载
1、创建容器并指定数据卷,注意通过 -v
参数来指定数据卷
1 | docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx |
2、查看数据卷
1 | docker volume ls |
1 | DRIVER VOLUME NAME |
3、查看数据卷详情
1 | docker volume inspect html |
1 | [ |
4、查看 /var/lib/docker/volumes/html/_data 目录
1 | ll /var/lib/docker/volumes/html/_data |
可以看到与nginx的html目录内容一样,结果如下:
1 | total 8 |
5、进入该目录,并随意修改index.html内容,修改后打开页面,查看效果
1 | cd /var/lib/docker/volumes/html/_data |
6、进入容器内部,查看 /usr/share/nginx/html 目录内的文件是否变化
1 | docker exec -it nginx bash |
演示 MySQL 的匿名数据卷
1 | 查看MySQL容器详细信息 |
我们关注两部分内容,第一是.Config.Volumes
部分:
1 | { |
可以发现这个容器声明了一个本地目录,需要挂载数据卷,但是数据卷未定义。这就是匿名卷。
然后,我们再看结果中的.Mounts
部分:
1 | { |
可以发现,其中有几个关键属性:
- Name:数据卷名称。由于定义容器未设置容器名,这里的就是匿名卷自动生成的名字,一串hash值。
- Source:宿主机目录
- Destination : 容器内的目录
上述配置是将容器内的/var/lib/mysql
这个目录,与数据卷29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f
挂载。于是在宿主机中就有了/var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data
这个目录。这就是匿名数据卷对应的目录,其使用方式与普通数据卷没有差别。
接下来,可以查看该目录下的MySQL的data文件:
1 | ls -l /var/lib/docker/volumes/29524ff09715d3688eae3f99803a2796558dbd00ca584a25a4bbc193ca82459f/_data |
注意:每一个不同的镜像,将来创建容器后内部有哪些目录可以挂载,可以参考DockerHub对应的页面。
3.4.3 挂载本地目录或文件
可以发现,数据卷的目录结构较深,如果我们去操作数据卷目录会不太方便。在很多情况下,我们会直接将容器目录与宿主机指定目录挂载。挂载语法与数据卷类似:
1 | # 挂载本地目录 |
PS:本地目录或文件必须以 /
或 ./
开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。😄😄😄 例如:
1 | # 会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷 |
演示基于宿主机目录实现 MySQL 数据目录、配置文件、初始化脚本的挂载(查阅官方镜像文档):
- 挂载
/root/mysql/data
到容器内的/var/lib/mysql
目录 - 挂载
/root/mysql/init
到容器内的/docker-entrypoint-initdb.d
目录(初始化的SQL脚本目录) - 挂载
/root/mysql/conf
到容器内的/etc/mysql/conf.d
目录(这个是MySQL配置文件目录)
准备好了 mysql 的初始化 SQL 脚本和配置文件,在创建 mysql 容器后,上传到对应挂载的本地目录。
其中,hm.cnf 主要是配置了 MySQL 的默认编码,改为 utf8mb4;而 hmall.sql 则是黑马商城项目的初始化 SQL 脚本。
接下来,我们演示本地目录挂载:
1、删除原来的 MySQL 容器,进入 root 目录
1 | docker rm -f mysql |
2、创建并运行新 mysql 容器,挂载本地目录(注意挂载的目录权限,权限不足会导致挂载的目录文件不生效)
1 | docker run -d \ |
3、查看 root 目录,可以发现 ~/mysql/data 目录已经自动创建好了
1 | ls -l mysql |
1 | 总用量 4 |
4、查看MySQL容器内数据
1 | 进入MySQL |
(1) 查看编码表
1 | show variables like "%char%"; |
编码是 utf8mb4 则没有问题
1 | +--------------------------+--------------------------------+ |
(2) 查看数据库
1 | show databases; |
结果查看到 hmall 黑马商城数据库
1 | +--------------------+ |
(3) 切换到 hmall 数据库,查看表
1 | use hmall; |
1 | +-----------------+ |
(4) 查看 address 表数据
1 | +----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+ |
推荐挂载到一个固定的自己设置的本地目录。挂载到本地目录后,即使将 mysql 容器删掉,再次创建并运行新mysql容器,只要挂载的地方不变,会发现所有的数据依然会存在。也就是说只要挂载的目录还在,数据就不会丢失,实现了数据的持久保存。
3.5 镜像
前面我们一直在使用别人准备好的镜像,那如果我要部署一个 Java 项目,把它打包为一个镜像该怎么做呢?
3.5.1 镜像结构
镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
我们打包镜像也是分成这么几步:
- 准备Linux运行环境(Java项目并不需要完整的操作系统,仅仅是基础运行环境即可)
- 安装并配置JDK
- 拷贝jar包
- 配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一 id,称为 **Layer(层)**。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,第一步中需要的 Linux 运行环境,通用性就很强,所以 Docker 官方就制作了这样的只包含 Linux 运行环境的镜像。我们在制作Java 镜像时,就无需重复制作,直接使用 Docker 官方提供的 CentOS 或 Ubuntu 镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个 Java 项目的镜像结构如图所示:
3.5.2 Dockerfile
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以 Docker 就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给 Docker 去执行即可。
而这种记录镜像结构的文件就称为 Dockerfile,其对应的语法可以参考官方文档:https://docs.docker.com/engine/reference/builder/
其中的语法比较多,比较常用的有:
指令 | 说明 | 示例 |
---|---|---|
FROM | 指定基础镜像 | FROM centos:6 |
ENV | 设置环境变量,可在后面指令使用 | ENV key value |
COPY | 拷贝本地文件到镜像的指定目录 | COPY ./xx.jar /tmp/app.jar |
RUN | 执行Linux的shell命令,一般是安装过程的命令 | RUN yum install gcc |
EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的 | EXPOSE 8080 |
ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 | ENTRYPOINT java -jar xx.jar |
例如,要基于Ubuntu镜像来构建一个 Java 应用,其 Dockerfile 内容如下:
1 | # 指定基础镜像 |
我们思考一下:以后我们会有很多很多 Java 项目需要打包为镜像,他们都需要 Linux 系统环境、JDK 环境这两层,只有上面的3层不同(因为jar包不同)。如果每次制作 Java 镜像都重复制作前两层镜像,是不是很麻烦。
所以,就有人提供了基础的系统加 JDK 环境,我们在此基础上制作 Java 镜像,就可以省去 JDK 的配置了:
1 | # 基础镜像 |
是不是简单多了。
3.5.3 构建镜像
当 Dockerfile 文件写好以后,就可以利用命令来构建镜像了。
准备以下 Dockerfile 文件:
1 | # 基础镜像 |
将docker-demo.jar
包以及Dockerfile
拷贝到虚拟机的/root/demo
目录:
然后,执行命令,构建镜像:
1 | # 进入镜像目录 |
命令说明:
docker build
: 就是构建一个docker镜像-t docker-demo:1.0
:-t
参数是指定镜像的名称(repository
和tag
).
: 最后的点是指构建时 Dockerfile 所在路径,由于我们进入了 demo 目录,所以指定的是.
代表当前目录,也可以直接指定 Dockerfile 目录:1
2# 直接指定Dockerfile目录
docker build -t docker-demo:1.0 /root/demo结果:
查看镜像列表:
1 | # 查看镜像列表: |
然后尝试运行该镜像:
1、创建并运行容器
1 | docker run -d --name dd -p 8080:8080 docker-demo:1.0 |
2、查看容器
1 | docker ps |
1 | CONTAINER ID IMAGE PORTS STATUS NAMES |
3、访问
1 | curl localhost:8080/hello/count |
1 | <h5>欢迎访问黑马商城, 这是您第1次访问<h5> |
3.6 网络
上面我们创建了一个 Java 项目的容器,而 Java 项目往往需要访问其它各种中间件,例如 MySQL、Redis 等。现在,我们的容器之间能否互相访问呢?我们来测试一下
首先,我们查看下 MySQL 容器的详细信息,重点关注其中的网络IP地址:
1、用基本命令,寻找Networks.bridge.IPAddress属性
1 | docker inspect mysql |
1 | # 得到的 IP 地址 |
2、然后通过命令进入dd容器
1 | docker exec -it dd bash |
3、在容器内,通过 ping 命令测试网络
1 | ping 172.17.0.3 |
1 | PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data. |
发现可以互联,没有问题。
但是,容器的网络IP其实是一个虚拟的 IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个 IP,而在部署时很可能 MySQL容器的 IP 会发生变化,连接会失败。
所以,我们必须借助于docker的网络功能来解决这个问题,官方文档:https://docs.docker.com/engine/reference/commandline/network/
常见命令有:
命令 | 说明 | 文档地址 |
---|---|---|
docker network create | 创建一个网络 | docker network create |
docker network ls | 查看所有网络 | docs.docker.com |
docker network rm | 删除指定网络 | docs.docker.com |
docker network prune | 清除未使用的网络 | docs.docker.com |
docker network connect | 使指定容器连接加入某网络 | docs.docker.com |
docker network disconnect | 使指定容器连接离开某网络 | docker network disconnect |
docker network inspect | 查看网络详细信息 | docker network inspect |
演示自定义网络
1、首先通过命令创建一个网络
1 | docker network create hmall |
2、然后查看网络
1 | docker network ls |
1 | NETWORK ID NAME DRIVER SCOPE |
其中,除了hmall以外,其它都是默认的网络
3、让dd和mysql都加入该网络,注意,在加入网络时可以通过–alias给容器起别名,这样该网络内的其它容器可以用别名互相访问!
(1) mysql容器,指定别名为db,另外每一个容器都有一个别名是容器名
1 | docker network connect hmall mysql --alias db |
(2) db容器,也就是我们的Java项目
1 | docker network connect hmall dd |
4、进入 dd 容器,尝试利用别名访问 db
(1) 进入容器
1 | docker exec -it dd bash |
(2) 用 db 别名访问
1 | ping db |
1 | PING db (172.19.0.2) 56(84) bytes of data. |
(3) 用容器名访问
1 | ping mysql |
1 | PING mysql (172.19.0.2) 56(84) bytes of data. |
OK,现在无需记住 IP 地址也可以实现容器互联了。
总结:
- 在自定义网络中,可以给容器起多个别名,默认的别名是容器名本身
- 在同一个自定义网络中的容器,可以通过别名互相访问
4. 项目部署
项目说明:
- hmall:商城的后端代码
- hmall-portal:商城用户端的前端代码
- hmall-admin:商城管理端的前端代码
部署的容器及端口说明:
项目 | 容器名 | 端口 | 备注 |
---|---|---|---|
hmall | hmall | 8080 | 黑马商城后端API入口 |
hmall-portal | nginx | 18080 | 黑马商城用户端入口 |
hmall-admin | nginx | 18081 | 黑马商城管理端入口 |
mysql | mysql | 3306 | 数据库 |
在正式部署前,我们先删除之前的 nginx、dd 两个容器:
1 | docker rm -f nginx dd |
mysql容器中已经准备好了商城的数据,所以就不再删除了。
4.1 部署Java项目
hmall
项目是一个maven聚合项目,使用IDEA打开hmall
项目,查看项目结构如图:
我们要部署的就是其中的hm-service
,其中的配置文件采用了多环境的方式:
其中的application-dev.yaml
是部署到开发环境的配置,application-local.yaml
是本地运行时的配置。
查看 application.yaml
,你会发现其中的 JDBC 地址并未写死,而是读取变量:
这两个变量在application-dev.yaml
和application-local.yaml
中并不相同:
在 dev 开发环境(也就是 Docker 部署时)采用了 mysql 作为地址,刚好是我们的 mysql 容器名,只要两者在一个网络,就一定能互相访问。
我们将项目打包:
结果:
将hm-service
目录下的Dockerfile
和hm-service/target
目录下的hm-service.jar
一起上传到虚拟机的root
目录:
部署项目:
1、构建项目镜像,不指定tag,则默认为latest
1 | docker build -t hmall . |
2、查看镜像
1 | docker images |
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
3、创建并运行容器,并通过 --network
将其加入 hmall
网络,这样才能通过容器名访问 mysql
1 | docker run -d --name hmall --network hmall -p 8080:8080 hmall |
测试,通过浏览器访问:http://YourIp:8080/search/list,查看到对应信息就成功部署。
4.2 部署前端
hmall-portal
和hmall-admin
是前端代码,需要基于nginx部署。nginx的部署目录:
其中:
html
是静态资源目录,我们需要把hmall-portal
以及hmall-admin
都复制进去nginx.conf
是nginx的配置文件,主要是完成对html
下的两个静态资源目录做代理
我们现在要做的就是把整个 nginx 目录上传到虚拟机的 /root
目录下:
然后创建 nginx 容器并完成两个挂载:
- 把
/root/nginx/nginx.conf
挂载到/etc/nginx/nginx.conf
- 把
/root/nginx/html
挂载到/usr/share/nginx/html
由于需要让 nginx 同时代理 hmall-portal 和 hmall-admin 两套前端资源,因此我们需要暴露两个端口:
- 18080:对应hmall-portal
- 18081:对应hmall-admin
命令如下:
1 | docker run -d \ |
测试,通过浏览器访问:http://YourIp:18080
4.3 DockerCompose
Docker Compose就可以帮助我们实现多个相互关联的Docker容器的快速部署。它允许用户通过一个单独的 docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器。
4.3.1 基本语法
docker-compose.yml文件的基本语法可以参考官方文档:https://docs.docker.com/compose/compose-file/compose-file-v3/
docker-compose文件中可以定义多个相互关联的应用容器,每一个应用容器被称为一个服务(service)。由于 service 就是在定义某个应用的运行时参数,因此与 docker run
参数非常相似。
举例来说,用docker run部署MySQL的命令如下:
1 | docker run -d \ |
如果用docker-compose.yml
文件来定义,就是这样:
1 | version: "3.8" |
对比如下:
docker run 参数 | docker compose 指令 | 说明 |
---|---|---|
–name | container_name | 容器名称 |
-p | ports | 端口映射 |
-e | environment | 环境变量 |
-v | volumes | 数据卷配置 |
–network | networks | 网络 |
黑马商城部署文件:
1 | version: "3.8" |
4.3.2 基础命令
编写好docker-compose.yml文件,就可以部署项目了。常见的命令:https://docs.docker.com/compose/reference/
基本语法如下:
1 | docker compose [OPTIONS] [COMMAND] |
其中,OPTIONS 和 COMMAND 都是可选参数,比较常见的有:
类型 | 参数或指令 | 说明 |
---|---|---|
Options | -f | 指定compose文件的路径和名称 |
Options | -p | 指定project名称。project就是当前compose文件中设置的多个service的集合,是逻辑概念 |
Commands | up | 创建并启动所有service容器 |
Commands | down | 停止并移除所有容器、网络 |
Commands | ps | 列出所有启动的容器 |
Commands | logs | 查看指定容器的日志 |
Commands | stop | 停止容器 |
Commands | start | 启动容器 |
Commands | restart | 重启容器 |
Commands | top | 查看运行的进程 |
演示
1、进入 root 目录
1 | cd /root |
2、删除旧容器
1 | docker rm -f $(docker ps -qa) |
3、删除hmall镜像
1 | docker rmi hmall |
4、清空MySQL数据
1 | rm -rf mysql/data |
5、启动所有,-d
参数是后台启动
1 | docker compose up -d |
1 | [+] Building 15.5s (8/8) FINISHED |
6、查看镜像
1 | docker compose images |
1 | CONTAINER REPOSITORY TAG IMAGE ID SIZE |
7、查看容器
1 | docker compose ps |
1 | NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS |
打开浏览器,访问:http://YourIp:8080