目录
- 1.Docker介绍
- 2.Docker安装
- 3.Docker快速入门
-
- [3.1 部署MySQL](#3.1 部署MySQL)
- [3.2 命令解读](#3.2 命令解读)
- 4.Docker基础
-
- [4.1 常见命令](#4.1 常见命令)
-
- [4.1.1 命令介绍](#4.1.1 命令介绍)
- [4.1.2 演示](#4.1.2 演示)
- [4.1.3 命令起别名](#4.1.3 命令起别名)
- [4.2 数据卷](#4.2 数据卷)
-
- [4.2.1 什么是数据卷](#4.2.1 什么是数据卷)
- [4.2.2 数据卷命令](#4.2.2 数据卷命令)
- [4.2.3 案例演示](#4.2.3 案例演示)
- [4.2.4 匿名数据卷](#4.2.4 匿名数据卷)
- [4.2.5 挂载本地目录或文件](#4.2.5 挂载本地目录或文件)
- [4.2.6 挂载本地目录或文件演示](#4.2.6 挂载本地目录或文件演示)
- [4.3 镜像](#4.3 镜像)
-
- [4.3.1 镜像结构](#4.3.1 镜像结构)
- [4.3.2 Dockerfile](#4.3.2 Dockerfile)
- [4.3.3 准备java项目的jar包](#4.3.3 准备java项目的jar包)
- [4.3.4 构建镜像](#4.3.4 构建镜像)
- [4.4 网络](#4.4 网络)
-
- [4.4.1 介绍](#4.4.1 介绍)
- [4.4.2 案例演示](#4.4.2 案例演示)
- [4.5 DockerCompose](#4.5 DockerCompose)
-
- [4.5.1 介绍](#4.5.1 介绍)
- [4.5.2 基本语法](#4.5.2 基本语法)
- [4.5.3 基础命令](#4.5.3 基础命令)
- [4.5.4 案例演示](#4.5.4 案例演示)
1.Docker介绍
Docker 是一个开源的容器化平台,用于打包、发布和运行应用程序 。它可以把应用程序及其运行所依赖的环境(如操作系统库、运行时、配置文件等)统一打包成一个镜像(Image),并以**容器(Container)**的形式运行。
Docker 解决了这样一个经典问题:
"在我电脑上能跑,为什么到你那就不行了?"
简单来说,Docker 会通过操作系统级别的隔离机制创建一个轻量级的运行环境(容器) 。容器可以被理解为类似迷你版的 Linux 服务器 ,容器并不是完整的虚拟机,而是共享宿主机内核,但在进程、网络、文件系统等方面相互隔离。程序运行在容器中,就像运行在一台独立的服务器上一样,拥有自己的运行环境而不会影响其他应用。同时,Docker 可以将应用及其依赖的运行环境打包成镜像,从而实现"一次构建,到处运行"。无论在本地、测试环境还是生产环境,只要有 Docker,就可以直接运行该应用。
2.Docker安装
1.确定你是CentOS7及以上版本
plain
cat /etc/redhat-release
.
2.卸载旧版本
plain
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
3.yum安装gcc相关
首先确保CentOS能上外网
plain
yum -y install gcc
yum -y install gcc-c++
4.下载Docker依赖的组件
plain
yum -y install yum-utils device-mapper-persistent-data lvm2
5.设置下载Docker服务的镜像源,设置为阿里云
plain
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
6.安装Docker服务
plain
yum -y install docker-ce docker-ce-cli containerd.io
plain
sudo systemctl daemon-reload
sudo systemctl restart docker
7.安装成功后,需要启动docker服务
plain
# 启动docker服务
systemctl start docker
8.测试安装结果
plain
docker version
9.配置镜像加速器
plain
sudo tee /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker-registry.nmqu.com",
"https://dockercf.jsdelivr.fyi",
"https://docker.jsdelivr.fyi",
"https://dockertest.jsdelivr.fyi",
"https://mirror.iscas.ac.cn",
"https://docker.m.daocloud.io"
],
"dns": ["114.114.114.114", "8.8.8.8"]
}
EOF
plain
sudo systemctl daemon-reload
sudo systemctl restart docker
拉取hello-world
plain
docker run hello-world

显示如上表示Docker配置成功了
9.如果想卸载docker依次执行如下的步骤
plain
systemctl stop docker
yum remove docker-ce docker-ce-cli containerd.io
rm -rf /var/lib/docker
rm -rf /var/lib/containerd
3.Docker快速入门
3.1 部署MySQL
首先,我们利用Docker来安装一个MySQL软件,大家可以对比一下之前传统的安装方式,看看哪个效率更高一些。
如果是利用传统方式部署MySQL,大概的步骤有:
- 搜索并下载MySQL安装包
- 上传至Linux环境
- 编译和配置环境
- 安装
而使用Docker安装,仅仅需要一步即可,在命令行输入下面的命令(建议采用CV大法):
plain
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql
运行效果如图:

MySQL安装完毕!通过任意客户端工具即可连接到MySQL.
大家可以发现,当我们执行命令后,Docker做的第一件事情,是去自动搜索并下载了MySQL,然后会自动运行MySQL,我们完全不用插手,是不是非常方便。
而且,这种安装方式你完全不用考虑运行的操作系统环境,它不仅仅在CentOS系统是这样,在Ubuntu系统、macOS系统、甚至是装了WSL的Windows下,都可以使用这条命令来安装MySQL。
要知道,不同操作系统下其安装包、运行环境是都不相同的 !如果是手动安装,必须手动解决安装包不同、环境不同的、配置不同的问题!
而使用Docker,这些完全不用考虑。就是因为Docker会自动搜索并下载MySQL。注意:这里下载的不是安装包,而是镜像。镜像中不仅包含了MySQL本身,还包含了其运行所需要的环境、配置、系统级函数库。因此它在运行时就有自己独立的环境,就可以跨系统运行,也不需要手动再次配置环境了。这套独立运行的隔离环境我们称为容器。
说明:
- 镜像:英文是image
- 容器:英文是container
因此,Docker安装软件的过程,就是自动搜索下载镜像,然后创建并运行容器的过程。
Docker会根据命令中的镜像名称自动搜索并下载镜像,那么问题来了,它是去哪里搜索和下载镜像的呢?这些镜像又是谁制作的呢?
Docker官方提供了一个专门管理、存储镜像的网站,并对外开放了镜像上传、下载的权利。Docker官方提供了一些基础镜像,然后各大软件公司又在基础镜像基础上,制作了自家软件的镜像,全部都存放在这个网站。这个网站就成了Docker镜像交流的社区(可能需要翻墙):
plain
https://hub.docker.com/

基本上我们常用的各种软件都能在这个网站上找到,我们甚至可以自己制作镜像上传上去。
像这种提供存储、管理Docker镜像的服务器,被称为DockerRegistry,可以翻译为镜像仓库。DockerHub网站是官方仓库,阿里云、华为云会提供一些第三方仓库,我们也可以自己搭建私有的镜像仓库。
官方仓库在国外,下载速度较慢,一般我们都会使用第三方仓库提供的镜像加速功能,提高下载速度。而企业内部的机密项目,往往会采用私有镜像仓库。
总之,镜像的来源有两种:
- 基于官方基础镜像自己制作
- 直接去DockerRegistry下载.
Docker本身包含一个后台服务,我们可以利用Docker命令告诉Docker服务,帮助我们快速部署指定的应用。Docker服务部署应用时,首先要去搜索并下载应用对应的镜像,然后根据镜像创建并允许容器,应用就部署完成了。
用一幅图标示如下:

3.2 命令解读
利用Docker快速的安装了MySQL,非常的方便,不过我们执行的命令到底是什么意思呢?
plain
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql
解读:
docker run -d:创建并运行一个容器,-d则是让容器以后台进程运行- --name 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网站或者软件的官方网站中都能找到。
比如在DockerHub网站中搜索MySQL,然后就能看到默认的一些环境变量的

如果我们要安装其它软件,也可以到DockerRegistry中寻找对应的镜像名称和版本,阅读相关配置即可。
4.Docker基础
Docker官方文档:
plain
https://docs.docker.com/get-started/introduction/
4.1 常见命令
Docker的命令有很多,想要了解命令的细节直接去Docker官网查看
plain
https://docs.docker.com/reference/cli/docker/

4.1.1 命令介绍
| 命令 | 说明 | 常用参数 |
|---|---|---|
| docker build | 基于dockerfile文件构建容器 | |
| docker pull | 拉取镜像 | |
| docker push | 推送镜像到DockerRegistry | |
| docker images | 查看本地镜像 | |
| docker rmi | 删除本地镜像 | -f:强制删除镜像(即使有容器在使用) |
| docker run | 创建并运行容器 | -d:后台运行容器 -it:交互式运行(就是能进入容器内部) -e:设置环境变量 |
| docker stop | 停止指定容器 | |
| docker start | 启动指定容器 | |
| docker restart | 重新启动容器 | |
| docker rm | 删除指定容器 | -f:强制删除正在运行的容器(相当于先 stop 再 rm)。 |
| docker ps | 查看容器 | -a: 查看所有容器 |
| docker logs | 查看容器运行日志 | -f 表示"持续跟踪"日志 |
| docker exec | 进入容器 | |
| docker save | 保存镜像到本地压缩文件 | -o: 指定输出的 tar 文件路径(推荐使用) 例如:docker save -o myapp.tar nginx:latest |
| docker load | 加载本地压缩文件到镜像 | -i: 指定输入的 tar 文件路径 例如:docker load -i myapp.tar |
| docker inspect | 查看容器详细信息 |
用一副图来表示这些命令的关系:

补充:
默认情况下,每次重启虚拟机我们都需要手动启动Docker和Docker中的容器。通过命令可以实现开机自启:
plain
# Docker开机自启
systemctl enable docker
# Docker容器开机自启
docker update --restart=always [容器名/容器id]
4.1.2 演示
下面以安装Nginx为例子将上边的命令演示一遍
- 去DockerHub查看nginx镜像仓库及相关信息

- 拉取Nginx镜像

- 查看镜像
plain
docker images

- 创建并允许Nginx容器
plain
docker run -d --name nginx -p 80:80 nginx
- 查看运行中容器
plain
docker ps

也可以加格式化方式访问,格式会更加清爽
plain
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
docker ps返回命令的结果讲解:
- CONTAINER ID 容器的唯一标识符
- IMAGE 该容器所基于的镜像名称和标签
- COMMAND 容器启动时执行的主命令 通常会被截断显示,完整内容可通过
docker inspect查看。 - CREATED 容器创建的时间
- STATUS 容器的当前状态
- PORTS 容器端口与主机端口的映射关系
- NAMES 容器的名称
- 访问网页,地址:http://虚拟机地址

- 停止容器
java
docker stop nginx
- 查看所有容器
plain
docker ps -a

- 再次启动容器
plain
docker start nginx
- 再次查看容器

- 查看容器详细信息
docker inspect是 Docker 中一个非常强大的命令,用于获取容器、镜像、网络或卷等 Docker 对象的详细底层信息(元数据)
plain
docker inspect nginx

- 进入容器,查看容器内目录
指的是进入nginx容器后执行bash命令,启动 bash shell,从而获得一个交互式命令行界面
plain
docker exec -it nginx bash

- 删除容器
plain
docker rm nginx

发现无法删除,因为容器运行中,强制删除容器
plain
docker rm -f nginx
4.1.3 命令起别名
给常用Docker命令起别名,方便我们访问:
修改/root/.bashrc文件
plain
vi /root/.bashrc
添加如下内容并保存文件
plain
alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
alias dis='docker images'

然后,执行命令使别名生效
plain
source /root/.bashrc

4.2 数据卷
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便。大家思考几个问题:
- 如果要升级MySQL版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
- MySQL、Nginx容器运行后,如果我要修改其中的某些配置该怎么办?
- 我想要让Nginx代理我的静态资源怎么办?
因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦
4.2.1 什么是数据卷
数据卷(volume)是一个虚拟目录,是容器内目录 与宿主机****目录之间映射的桥梁。
以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/目录。只要我们将静态资源放入宿主机对应目录,就可以被Nginx代理了。
/var/lib/docker/volumes这个目录就是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data。为什么不让容器目录直接指向宿主机目录呢?
- 因为直接指向宿主机目录就与宿主机强耦合了,如果切换了环境,宿主机目录就可能发生改变了。由于容器一旦创建,目录挂载就无法修改,这样容器就无法正常工作了。
- 但是容器指向数据卷,一个逻辑名称,而数据卷再指向宿主机目录,就不存在强耦合。如果宿主机目录发生改变,只要改变数据卷与宿主机目录之间的映射关系即可。
4.2.2 数据卷命令
数据卷的相关命令有:
| 命令 | 说明 |
|---|---|
| docker volume create | 创建数据卷 |
| docker volume ls | 查看所有数据卷 |
| docker volume rm | 删除指定数据卷 |
| docker volume inspect | 查看某个数据卷的详情 |
| docker volume prune | 清除数据卷 |
注意:Docker 容器的数据卷挂载通常需要在创建容器时通过 -v 或 --mount 参数指定。对于已经创建的容器,无法直接新增挂载点。
4.2.3 案例演示
- 首先创建容器并指定数据卷,注意通过 -v 参数来指定数据卷
bash
docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
- 然后再查看容器卷

- 查看数据卷详情
plain
docker volume inspect html
上图中可以看到新创建的html数据卷再宿主机中的位置为*/var/lib/docker/volumes/html/_data*
- 查看/var/lib/docker/volumes/html/_data目录可以看到与nginx的html目录内容一样,结果如下:

- 进入该目录,并随意修改index.html内容
plain
cd /var/lib/docker/volumes/html/_data
vi index.html

- 清空浏览器缓存访问nginx首页
说明修改的内容已经同步到了容器内中 - 进入容器内部,查看/usr/share/nginx/html目录内的文件是否变化
plain
docker exec -it nginx bash

4.2.4 匿名数据卷
匿名卷 是 Docker 自动创建的一种没有用户指定名称的数据卷 。它的名字由 Docker 自动生成,通常是一串随机的哈希字符串,认在宿主机的 /var/lib/docker/volumes/ 目录下
匿名卷会在以下情况下生成:
- Dockerfile 中使用了 VOLUME 指令,但运行容器时未显式挂载
dockerfile
# 示例 Dockerfile
FROM ubuntu
VOLUME /app/data
- docker run 时使用 -v 但只写了容器路径
bash
docker run -d -v /data nginx
上面在部署MySQL镜像的时候执行的是如下的命令:
bash
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql
其中的mysql镜像是我们从远程仓库中拉取的,这个镜像底层就中定义了 VOLUME
下面来看下我们运行的mysql容器的内部匿名卷的信息
bash
docker inspect mysql
先看.Config.Volumes部分

可以发现这个容器声明了一个本地目录,需要挂载数据卷,但是数据卷未定义。这就是匿名卷。
然后,我们再看结果中的.Mounts部分:

上述配置是将容器内的/var/lib/mysql这个目录,与数据卷e0fd7cb9ea0fd338b84857788ca2d7571caca424112ca51a7434e444547cb357挂载。于是在宿主机中就有了/var/lib/docker/volumes/e0fd7cb9ea0fd338b84857788ca2d7571caca424112ca51a7434e444547cb357/_data这个目录。这就是匿名数据卷对应的目录,其使用方式与普通数据卷没有差别。
接下来,可以查看该目录下的MySQL的data文件:
bash
ls /var/lib/docker/volumes/e0fd7cb9ea0fd338b84857788ca2d7571caca424112ca51a7434e444547cb357/_data

可以看到这个目录下就是存放着MySQL的数据binglog日志
注意:每一个不同的镜像,将来创建容器后内部有哪些目录可以挂载,可以参考DockerHub对应的页面
4.2.5 挂载本地目录或文件
可以发现,数据卷的目录结构较深,如果我们去操作数据卷目录会不太方便。在很多情况下,我们会直接将容器目录与宿主机指定目录挂载。挂载语法与数据卷类似:
bash
# 挂载本地目录
-v 本地目录:容器内目录
# 挂载本地文件
-v 本地文件:容器内文件
注意 :本地目录或文件必须以 / 或 ./开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。
例如:
bash
-v mysql:/var/lib/mysql # 会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷
-v ./mysql:/var/lib/mysql # 会被识别为当前目录下的mysql目录,运行时如果不存在会创建目录
下面以mysql为例子演示,删除并重新创建mysql容器,并完成本地目录挂载:
- 挂载
/root/mysql/data到容器内的/var/lib/mysql目录 - 挂载
/root/mysql/init到容器内的/docker-entrypoint-initdb.d目录(这个容器下的SQL脚本会在MySQL容器运行时自动的初始化) - 挂载
/root/mysql/conf到容器内的/etc/mysql/conf.d目录(这个是MySQL配置文件目录)
将如下SQL脚本和配置文件导入到对应的目录中
init.sql
sql
-- 1. 创建数据库(可选)
CREATE DATABASE IF NOT EXISTS myapp;
USE myapp;
-- 2. 删除已存在的 User 表(避免重复创建)
DROP TABLE IF EXISTS `User`;
-- 3. 创建 User 表
CREATE TABLE `User` (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID,主键',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名,唯一',
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱,唯一',
password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希值(实际应用中不应存明文)',
full_name VARCHAR(100) COMMENT '真实姓名',
age INT CHECK (age >= 0 AND age <= 150) COMMENT '年龄',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 4. 插入示例数据
INSERT INTO `User` (username, email, password_hash, full_name, age) VALUES
('alice', 'alice@example.com', '$2b$12$examplehash1234567890123456789012345678901234567890', 'Alice Johnson', 28),
('bob', 'bob@example.com', '$2b$12$examplehash1234567890123456789012345678901234567890', 'Bob Smith', 34),
('charlie', 'charlie@example.com', '$2b$12$examplehash1234567890123456789012345678901234567890', 'Charlie Brown', 22),
('diana', 'diana@example.com', '$2b$12$examplehash1234567890123456789012345678901234567890', 'Diana Prince', 31);
my.cnf
plain
[client]
default_character_set=utf8mb4
[mysql]
default_character_set=utf8mb4
[mysqld]
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci
init_connect='SET NAMES utf8mb4'
其中,hm.cnf主要是配置了MySQL的默认编码,改为utf8mb4;而init.sql主要是创建对应的sql表
将inti.sql放置到/root/mysql/init目录下

将my.cnf放置到/root/mysql/conf目录下

接下来,我们演示本地目录挂载:
4.2.6 挂载本地目录或文件演示
- 删除原来的MySQL容器
plain
docker rm -f mysql
- 进入root目录
plain
cd
- 创建并运行新mysql容器,挂载本地目录
bash
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
-v ./mysql/data:/var/lib/mysql \
-v ./mysql/conf:/etc/mysql/conf.d \
-v ./mysql/init:/docker-entrypoint-initdb.d \
mysql
- 查看root目录,可以发现~/mysql/data目录已经自动创建好了

- 进入data目录下查看,会发现里面有大量数据库数据,说明数据库完成了初始化

- 看MySQL容器内数据,进入MySQL
bash
docker exec -it mysql mysql -uroot -p123
查看编码表结果,发现编码是utf8mb4没有问题

- 查看数据查看数据库
plain
show databases;
脚本中创建的数据库存在切换到myapp数据库,查看user表数据
plain
user myapp;
select * from User;

演示成功
4.3 镜像
前面我们一直在使用别人准备好的镜像,那如果我要部署一个Java项目,把它打包为一个镜像该怎么做呢?
4.3.1 镜像结构
要想自己构建镜像,必须先了解镜像的结构。
之前我们说过,镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。
因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,我们要从0部署一个Java应用,大概流程是这样:
- 准备一个linux服务(CentOS或者Ubuntu均可)
- 安装并配置JDK
- 上传Jar包
- 运行jar包
那因此,我们打包镜像也是分成这么几步:
- 准备Linux运行环境(java项目并不需要完整的操作系统,仅仅是基础运行环境即可)
- 安装并配置JDK
- 拷贝jar包
- 配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer (层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,第一步中需要的Linux运行环境,通用性就很强,所以Docker官方就制作了这样的只包含Linux运行环境的镜像。我们在制作java镜像时,就无需重复制作,直接使用Docker官方提供的CentOS或Ubuntu镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个Java项目的镜像结构如图所示:

下面我们拉取以下redis:
plain
docker pull redis

从 Redis 镜像可以看出,Docker 镜像是由多个只读层(layers)组成的,每一层都有唯一的 ID。这些层叠加在一起形成完整的镜像。Redis 镜像本身也可以作为基础镜像被其他镜像复用。因此,Docker 镜像并非一个单一的大文件,而是像积木一样,由多个可复用、分层的文件系统层组合而成。
4.3.2 Dockerfile
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。
而这种记录镜像结构的文件就称为Dockerfile,其对应的语法可以参考官方文档:
plain
https://docs.docker.com/engine/reference/builder/
其中的语法比较多,比较常用的有:
| 指令 | 说明 |
|---|---|
| FROM | 指定基础镜像 |
| ENV | 设置环境变量,可在后面指令使用 |
| COPY | 拷贝本地文件到镜像的指定目录 |
| RUN | 执行Linux的shell命令,一般是安装过程的命令 |
| EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的 |
| ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 |
例如,要基于Ubuntu镜像来构建一个Java应用,其Dockerfile内容如下:
plain
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 设定时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安装JDK
RUN cd $JAVA_DIR \
&& tar -xf ./jdk8.tar.gz \
&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 指定项目监听的端口
EXPOSE 8080
# 入口,java项目的启动命令
ENTRYPOINT ["java", "-jar", "/app.jar"]
同学们思考一下:以后我们会有很多很多java项目需要打包为镜像,他们都需要Linux系统环境、JDK环境这两层,只有上面的3层不同(因为jar包不同)。如果每次制作java镜像都重复制作前两层镜像,是不是很麻烦。
所以,就有人提供了基础的系统加JDK环境,我们在此基础上制作java镜像,就可以省去JDK的配置了:
plain
# 基础镜像
FROM openjdk:8u342-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
是不是简单多了。
4.3.3 准备java项目的jar包
创建一个springboot项目
引入依赖
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zhp</groupId>
<artifactId>docker-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
HelloController
java
@RestController
public class HelloController {
@RequestMapping("hello")
public String hello() {
return "Hello World!";
}
}
启动项目访问localhost:8080/hello
<!-- 这是一张图片,ocr 内容为: -->

执行package打成jar包


将jar包上传到服务器中/root/demo目录下

4.3.4 构建镜像
编写Dockerfile文件
plain
# 基础镜像
FROM openjdk:8u342-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]


然后,执行命令,构建镜像:
plain
docker build -t docker-demo:1.0 .
命令说明:
docker build: 就是构建一个docker镜像-t docker-demo:1.0:-t参数是指定镜像的名称(repository和tag).: 最后的点是指构建时Dockerfile所在路径,由于我们进入了demo目录,所以指定的是.代表当前目录,也可以直接指定Dockerfile目录:
plain
# 直接指定Dockerfile目录
docker build -t docker-demo:1.0 /root/demo
结果:

查看镜像列表:
bash
docker images

然后尝试运行该镜像:
plain
docker run -d --name dd -p 8080:8080 docker-demo:1.0
查看运行状态

访问

4.4 网络
4.4.1 介绍
Docker 启动时会自动创建一个名为 bridge 的默认网桥,所有未指定网络的容器默认都连接到该网桥下。
默认情况下,所有容器都是以bridge方式连接到Docker的一个虚拟网桥上:

上节课我们创建了一个Java项目的容器,而Java项目往往需要访问其它各种中间件,例如MySQL、Redis等。现在,我们的容器之间能否互相访问呢?我们来测试一下
首先,我们查看下MySQL容器的详细信息,重点关注其中的网络IP地址:
用基本命令,寻找Networks.bridge.IPAddress属性
plain
docker inspect mysql

可以看到mysql容器在默认网桥bridge下,容器内部ip地址为172.17.0.3
再来看我们创建的dd容器的内部ip地址
plain
docker inspect dd

可以看到它们都在一个网段下,下边我们进入dd容器内部去ping一下mysql的ip地址
plain
docker exec -it dd bash
<!-- 这是一张图片,ocr 内容为: -->

发现可以互联,没有问题
但是,容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。
所以,我们必须借助于docker的网络功能来解决这个问题,官方文档:
plain
https://docs.docker.com/engine/reference/commandline/network/
常见命令有:
| 命令 | 说明 |
|---|---|
| docker network create | 创建一个网络 |
| docker network ls | 查看所有网络 |
| docker network rm | 删除指定网络 |
| docker network prune | 清除未使用的网络 |
| docker network connect | 使指定容器连接加入某网络 |
| docker network disconnect | 使指定容器连接离开某网络 |
| docker network inspect | 查看网络详细信息 |
4.4.2 案例演示
- 首先通过命令创建一个网络
plain
docker network create newnet
查看网络的详细信息
plain
docker network inspect newnet
可以看到新网桥是在默认的网桥的IP地址递增之前是17.17现在是17.18
- 然后查看网络
plain
docker network ls
除了newnet之外都是默认网络
- 让dd和mysql都加入该网络注意,在加入网络时可以通过--alias给容器起别名,这样就可以通过别名和容器名互相访问,这样就避免了ip地址变化连接失败的问题,当然需要注意只有我们自己创建的网络通过别名互相访问
plain
docker network connect newnet mysql --alias db
plain
docker network connect newnet dd
- 进入dd容器,尝试利用别名访问db
plain
docker exec -it dd bash
用容器名访问
OK,现在无需记住IP地址也可以实现容器互联了。
总结:
- 在自定义网络中,可以给容器起多个别名,默认的别名是容器名本身
- 在同一个自定义网络中的容器,可以通过别名互相访问
4.5 DockerCompose
4.5.1 介绍
大家可以看到,我们部署一个简单的java项目,其中包含3个容器:
- MySQL
- Nginx
- Java项目
而稍微复杂的项目,其中还会有各种各样的其它中间件,需要部署的东西远不止3个。如果还像之前那样手动的逐一部署,就太麻烦了。
而Docker Compose就可以帮助我们实现多个相互关联的Docker容器的快速部署。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器。
4.5.2 基本语法
docker-compose.yml文件的基本语法可以参考官方文档:
plain
https://docs.docker.com/reference/compose-file/legacy-versions/
docker-compose文件中可以定义多个相互关联的应用容器,每一个应用容器被称为一个服务(service)。由于service就是在定义某个应用的运行时参数,因此与docker run参数非常相似。
举例来说,用docker run部署MySQL的命令如下:
bash
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
-v ./mysql/data:/var/lib/mysql \
-v ./mysql/conf:/etc/mysql/conf.d \
-v ./mysql/init:/docker-entrypoint-initdb.d \
--network hmall
mysql
如果用docker-compose.yml文件来定义,就是这样:
bash
version: "3.8"
services:
mysql:
image: mysql
container_name: mysql
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123
volumes:
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
networks:
- new
networks:
new:
name: hmall
对比如下:
| docker run 参数 | docker compose 指令 |
|---|---|
| --name | container_name |
| -p | ports |
| -e | environment |
| -v | volumes |
| --network | networks |
将上边的部署过的MySQL、nginx、java项目通过DockerCompose部署,部署文件如下:
yaml
services:
mysql:
image: mysql
container_name: mysql
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123
volumes:
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
- "./mysql/init:/docker-entrypoint-initdb.d"
networks:
- docker-newnet
dd:
build:
context: .
dockerfile: Dockerfile
container_name: dd
ports:
- "8080:8080"
networks:
- docker-newnet
depends_on:
- mysql
nginx:
image: nginx
container_name: nginx
ports:
- "80:80"
depends_on:
- dd
networks:
- docker-newnet
networks:
docker-newnet:
name: dnewnet
docker-newnet是 Compose 文件内部使用的网络标识符(逻辑名称) ,仅在该docker-compose.yml文件中引用网络时使用。dnewnet是 实际创建的 Docker 网络的名称(真实名称) ,也就是你在运行docker network ls时看到的名字。
4.5.3 基础命令
需要将上边的部署文件编写到docker-compose.yml文件

然后就可以部署项目了。常见的命令:
plain
https://docs.docker.com/reference/cli/docker/compose/
基本语法如下:
plain
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 | 查看运行的进程 |
| Commands | exec | 在指定的运行中容器中执行命令 |
4.5.4 案例演示
- 进入root目录
plain
cd /root
- 删除旧容器
plain
docker rm -f $(docker ps -qa)
- 删除镜像
plain
docker rmi docker-demo:1.0
- 清空MySQL数据
plain
rm -rf mysql/data
- 进入demo目录
plain
cd demo
注意Dockerfile和jar包也要放在下边
- 启动所有, -d 参数是后台启动
plain
docker compose up -d

- 查看镜像
plain
docker compose images

-
查看容器

-
验证打开浏览器访问后端接口
访问nginx

登录mysql
plain
docker exec -it mysql mysql -uroot -p123

DockerCompose案列演示成功