Docker
安装 Docker
配置好 CentOS 系统后,我们进入控制台后,先执行如下命令(如果系统中存在旧的Docker,则先卸载)
shell
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine \
docker-selinux
配置 Docker 的 yum 库
shell
yum install -y yum-utils
如果这一步执行错误,说 mirrorlist 识别不到之类的,看下面这篇文章
https://www.cnblogs.com/maowenqiang/articles/7728685.html
然后配置 Docker 的 yum 源为阿里云源的
shell
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce/repo
之后安装 docker
shell
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
安装完成后,我们可以通过以下命令查看是否完成安装

shell
# 查看Docker版本
docker -v
# 启动Docker
systemctl start docker
# 停止Docker
systemctl stop docker
# 重启
systemctl restart docker
# 设置开机自启
systemctl enable docker
# 执行docker ps命令,如果不报错,说明安装启动成功
docker ps
其中的 dokcer images 是 docker 的一个进程,后续会讲,只有它启动了才会有结果。
之后就是配置docker镜像加速,进入阿里云官网,选择容器镜像服务ACR

点击管理控制台

在里面选择镜像加速器,选择CentOS,按照提示进行配置

OK,结束
部署 Mysql
执行命令
shell
docker run -d
--name mysql
-p 3306:3306
-e TZ=Asia/Shanghai
-e MYSQL_ROOT_PASSWORD=mysql
mysql

第一句意思是本地没有 Mysql,然后就去下载了。
安装完成,注意那个密码根据自己的情况设置
可以在 CentOS 上输入 ip address 查看IP。
我们可以通过 Navicat 测试以下连接,结果成功了!

证明什么?它下载后就配置好并启动了!!!
当我们利用 Docker 安装应用时,Docker 会自动搜索并下载应用镜像(image) 。镜像不仅包含应用本身,还包含应用运行所需要的环境、配置、系统函数库。Docker 会在运行镜像时创建一个隔离环境,称为容器。
- 我们知道软件都是要依赖于操作系统的,比如哪些软件在windows系统上用,哪些在CentOS系统上用,用64位还是32位。而 Docker 在下载应用本身时相其依赖的环境和系统函数库一并下载,就可以实现跨系统运行了。
- Docker 运行时会创建一个隔离环境,也就是会和其他进程隔绝开来。因为一台好的服务器通常会运行多个项目,创建隔离环境把不同的项目隔绝开来。这里我们可以测试以下,再开启一个 mysql
改一下端口号和名字

使用命令docker ps查看开启的服务,两个 mysql,一个mysql,一个mysql2。
这个类似于什么呢?我们在电脑上登录两个QQ,但是它们之间的信息、聊天、会话都是相互之间不干扰的。
**镜像仓库:**存储和管理镜像的平台,Docker官方维护了一个公共仓库:Docker Hub

MySQL只要下载一次就可以了,前面提到的 mysql,mysql2 虽然创建了两次,但是只有第一次进行了下载,第二次并没有下载,而是直接拿来用的。
命令解读
shell
docker run -d -p 8080:8080 --name tongue-java-after tongue-java-after
shell
docker run -d
--name mysql
-p 3306:3306
-e TZ=Asia/Shanghai
-e MYSQL_ROOT_PASSWORD=mysql
mysql
-
docker run:创建并运行一个容器,
-
-d 是让容器在后台运行
-
--name mysql:给容器起个名字,必须唯一
-
-p 3306:3306:设置端口映射
-
-e KEY=VALUE:是设置环境变量
-
mysql :指定运行的镜像的名字
镜像名称一般分两部分组成:
[repository]:[tag]- repository 是镜像名
- tag 是镜像的版本
在没有指定 tag 时,默认是 latest,代表下载最新版本的镜像。如果想指定版本可以这么写
mysql:5.7
解释一下 -p 3306:3306,第一个参数就是宿主机的端口号,第二个是容器内端口,通常用该应用默认的端口号,mysql是3306,tomcat是8080,这些不用管,主要是修改第一个

这个 -e KEY=VALUE 从官网查就可以,hub.docker.com,搜索 mysql。注意哦,这里面的第一个蓝色链接也是

常见命令
Docker 最常见的命令就是操作镜像、容器的命令,详见官方文档:https://docs.docker.com

镜像仓库和本地镜像就相当于maven仓库和本地maven仓库
-
docker pull:远端镜像拉取到本地 -
docker images:查看本地所有镜像,images 单词意思为镜像 -
docker rmi:删除镜像 -
docker build:编写 dockerfile 文件,通过docker build命令自定义镜像 -
docker save:镜像打包,保存到一个压缩文件中 -
docker load:压缩文件加载到本地镜像 -
docker push:docker save + docker load 是镜像文件进行分享的一种方式。还有一种就是push + pull,类似于 git 的,比较方便。 -
docker run:创建并运行容器,每次都会创建一个新的容器 -
docker stop:停止容器的运行,但是容器还在 -
docker start:启动容器 -
docker ps:查看当前容器的运行状态 -
docker rm:删除容器 -
docker logs:查看容器运行的日志 -
docker exec:进入容器做一些操作
docker save + docker load 或者 dokcer push + docker pull 实现镜像传递
还有很多命令,需要的时候直接去官网查就可以

案例演示
查看 DockerHub,拉去 Nginx 镜像,创建并运行 Nginx 容器
-
在DockerHub中搜索Nginx镜像,查看镜像的名称

在 dockerhub 中搜索 nginx,认准绿色的字样,代表nginx官方提供的,然后使用右边的命令进行拉取
-
拉去Nginx镜像

使用命令拉取镜像,不指定版本时默认使用最新的
-
查看本地镜像列表

可以看到镜像的名字,版本,镜像ID,大小等信息。
下面可以测验一下镜像的保存和加载


docker load -i nginx.tar后会输出加载过程,加上 -q 会不让它输出 -
创建并运行Nginx容器

返回的一个长字符串即容器的ID
-
查看容器

通过
docker ps -a可以查看所有的容器,使用命令docker ps只能查看正在运行的容器。这里使用docker ps -a发现 mysql 停了(因为我重启了虚拟机),所以我又重新把虚拟机打开了。这里显示比较杂乱,我们可以通过一个命令来格式化一下输出,让输出更简洁
shelldocker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
-
停止容器

停止后,可以看到不再运行了。通过
-a参数可以查看到 nginx 停止了
-
再次启动日志
docker start nginx。注意不能用 docker run。 -
查看日志

可以看到
nginx容器的日志输出当你使用
docker logs -f nginx查看日志时,意思是持续查看,当有nginx日志记录更新时会直接输出,只有 Ctrl+C 才会退出。
-
进入Nginx容器
进入容器内部与 nginx 以命令行的方式进行交互


exit 退出 mysql,再 exit 退出容器
-
删除容器

这里举例删除 mysql2。注意,运行中的容器要删除,必须先 stop,再 rm。
也可以直接使用
docker rm mysql2 -f强制删除

部署完成后,我们输入IP地址就可以访问到 nginx 了。
数据加载卷
🐯 需求
- 创建 Nginx 容器,修改 nginx 容器内的 html 目录下的 index.html 文件内容
- 将静态资源部署到 nginx 的 html 目录
nginx 的静态资源目录在哪里呢,我们查看官方文档可以看到

位于 /usr/share/nginx/html 目录下,我们尝试进去并修改 index.html

无法修改!!!这是因为容器只包含它运行所需要的系统函数库和依赖,也就是该容器的大小是最小的,此处 vi 命令显然不需要。那我们想修改的时候怎么办?使用数据卷
数据集
数据集是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。每个容器都有一个自己的文件目录

使用docker命令创建数据卷,数据卷会在宿主机创建真实文件,/var/lib/docker/volumes/*/_data 这个路径是固定的。然后让容器的 /html 目录和 html 卷进行挂载,容器的 /conf 目录和 conf 卷挂载。挂载成功后,那么容器的 /conf 目录和宿主机的 /conf/_data 目录以及容器的 /html 和宿主机的 /html/_data 就进行了双向绑定。注意是双向绑定(和Vue的双向绑定一样)。这样想修改容器中的内容直接修改宿主机的就可以了。
常用命令
| 命令 | 说明 |
|---|---|
| docker volume create | 创建数据卷 |
| docker volume ls | 查看所有数据卷 |
| docker volume rm | 删除指定数据卷 |
| docker volume inspect | 查看某个数据卷的详情 |
| docker volume prune | 清除数据卷 |
我们可以直接通过 docker volume --help 命令查看

实例
那么如何完成我们开头的需求,来修改nginx容器内的文件内容呢?
🍈 在执行 docker run 命令时,使用 -v 数据卷:容器内目录 可以完成数据卷挂载,如果容器创建好了,那就没办法挂载了(所以此处需要删除nginx容器重新创建)。此外,当创建容器时,如果挂载了数据卷且数据卷不存在,会自动创建数据卷(也就是说 docker volume create 命令没必要了)

如此映射关系就有了
| 宿主机目录 | 数据卷 | 容器目录 |
|---|---|---|
/var/lib/docker/volumes/html/_data |
html |
/usr/share/nginx/html |
我们来到这个宿主机目录下查看

确实就是 nginx 的两个 html 文件。我们直接用 Xftp 连接,修改文件

保存,刷新网页,查看结果

成功!我们再测试一下(用 Xshell 吧,比在虚拟机上敲命令看的更清晰)

网页上是可以直接访问到的

自定义镜像
镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建镜像的过程其实就是把上述文件打包的过程。

准备 Linux 运行环境是为了让JVM虚拟机在任何环境下都能运行,所以直接把运行环境准备一份

镜像分层的好处是可以把镜像需要的东西更加细化,便于共享重复使用。举例如下,我们原先下载过 nginx 和 mysql 的镜像,我们再下载 redis 试试,发现有一部分是不用下载的,已经有的

制作镜像的时候别忘了定义入口
Dockerfile
Dockerfile 就是一个文本文件,其中包含一个个的指令,用指令来说明要执行什么操作来构建镜像。将来Docker可以根据Dockerfile帮我们构建镜像。常见指令如下:
| 指令 | 说明 | 示例 |
|---|---|---|
| FROM | 指定基础镜像 | FROM centos:6 |
| ENV | 设置环境变量,可在后面指令使用 | ENV key value |
| COPY | 拷贝本地文件到镜像的指定目录 | COPY ./jrell.tar.gz /tmp |
| RUN | 执行Linux的shell命令,一般是安装过程的命令 | RUN tar -zxvf /tmp/jrell.tar.gz && EXPORTS path=/tmp/jrell:$path |
| EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的(最终还是 run 的时候通过 -p 指定才算,这里只是用来作提示作用) | EXPOSE 8080 |
| ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 | ENTRYPOINT java -jar xx.jar |
详细语法参考 https://docs.docker.com/engine/reference/builder

当编写好了 Dockerfile,可以利用下面命令来构建镜像
shell
docker build -t myImage:1.0 .
-
-t:给镜像起名,格式依然是repository:tag的格式,不指定 tag 时,默认为 latest -
.:最后的一个小点,是指定 dockerfile 所在目录,如果就在当前目录,则指定为.。此外还要注意,我们 dockerfile 中的一些操作是和目录有关的,比如shellCOPY docker.deom.jar /app.jar是相对路径,所以你在执行的时候要把这个
docker-demo.jar也放在这个目录下。
实例
我们在打包前需要修改配置文件

注意这里的 mysql8,是后面自定义网络中定义的容器名
然后编写Dockerfile文件,注意这里就是文件,不加后缀名
sql
# 基础镜像
FROM openjdk:17
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝 jar 包
COPY tongue-after.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
然后我们在根下创建文件夹 tongue,进行构建

然后启动
shell
docker run -d -p 8080:8080 --name tongue-java-after tongue-java-after
启动后将 mysql8 和 tongue-java-after 加到同一个自定义网络中,这样这两个容器间就可以相互访问了,根据容器名就可以
shell
docker network create tongue-network
docker network connect tongue-network mysql8
docker network connect tongue-network tongue-java-after
注意前面 Java 中配置文件里写的 mysql8 就是因为这里定义的名字为 mysql8
yaml
spring:
datasource:
druid:
url: jdbc:mysql://mysql8:3306/tongue
-- 这里可能要开放端口8080和3306,不知道需不需要哈。
然后就可以访问了
容器网络互连
容器是有自己独立的环境的,每个容器都有自己虚拟的IP地址,各个容器之间都是在一个网段中,有相同的网关。也就是说可以相互访问

我们需要自定义一个网络,让某些容器之间可以相互访问。
| 命令 | 说明 |
|---|---|
| docker network create | 创建一个网络 |
| docker network ls | 查看所有网络 |
| docker network rm | 删除指定网络 |
| docker network prune | 清除未使用的网络 |
| docker network connect | 使指定容器连接加入了某网络 |
| docker network disconnect | 使指定容器连接离开某网络 |
| docker network inspect | 查看网络详细信息 |
比如前面,我们想让 SpringBoot 程序的容器访问 mysql 容器,就必须如此
shell
docker network create tongue-network
docker network connect tongue-network mysql8
docker network connect tongue-network tongue-java-after # 这样,mysql8和tongue-java-after 可以互相访问了
并且注意:加入自定义网络的容器才可以通过容器名互相访问,所以前面我们访问 mysql IP地址的时候直接使用的 mysql8,也就是容器的名字

Java应用部署
前面 1.5.2 已经说过怎么部署了,但是这里再提一个小技巧。就是开发环境与生产环境的变量配置,我们直接新建两个配置文件
application-dev.yaml
yaml
tongue:
db:
host: mysql8 # docker中的容器名
password: mysql
application-local.yaml
yaml
tongue:
db:
host: 127.0.0.1
password: mysql
这样我们在 application.yaml 中的配置就为
yaml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 使用 druid 连接池
druid:
username: root
password: ${tongue.db.password}
url: jdbc:mysql://${tongue.db.host}:3306/tongue
driver-class-name: com.mysql.cj.jdbc.Driver
这样我们就不需要来回修改 application.yaml 文件了,当我们在本地继续编写代码时就读取的 application-local.yaml 文件的 host 和 password。当我们把项目部署上去,那就是读取的 application-dev 中的,非常方便。
部署前端
这里改一下配置之类的东西,首先是前端的
ts
const request = axios.create({
baseURL: '/tongue',
timeout: 5000
}); // baseURL 注意
然后是 nginx.conf 的编写
js
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/json;
sendfile on;
keepalive_timeout 65;
server {
listen 8081; // 前端页面在这个端口启动
# 指定前端项目所在的位置
location / {
root /usr/share/nginx/html/tongue-dist; // 在容器中的位置
}
location /tongue {
rewrite /tongue/(.*) /$1 break; # 发起的所有 /tongue 在后端都将去掉
proxy_pass http://tongue-java-after:8080; // 请求发向后端
}
}
}
比如发送 /tongue/drink/data 映射到后端就是 /drink/data
所以Java后端的 yaml 配置
yaml
# 去掉
#server:
# servlet:
# context-path: /tongue
以及之前配置的跨域的内容也去掉。
然后将前端的项目部署到某个目录下,这里放在了 /root/nginx 目录下
包括一个 html 文件夹和一个 nginx.conf 文件

html 文件夹放了我打包的前端项目,也就是打包后生成的 dist 文件,这里我重命名为 tongue-dist

然后新建一个 nginx 容器
shell
docker run -d
--name tongue-nginx
-p 8081:8081
-v /root/nginx/html:/usr/share/nginx/html
-v /root/nginx/nginx.conf:/etc/nginx/nginx.conf # 映射关系的定义
--network tongue-network # 加入到原来的 mysql 与 springboot 所在的网络
nginx
DockerCompose
Docker Compose 通过一个单独的 docker-compose.yml 模板文件来定义一组相关联的应用容器(前端、后端、redis、中间件、nginx、网络互联等),帮助我们实现多个相互关联的 Docker 容器的快速部署。

最终都是为了运行,所以进行对比,基本上 docker-compose 中的内容和 docker run 差不多,只是语法不同
我们的 docker-compose.yml 文件可以这样写
yaml
version: "3.8"
services:
mysql8: # compose 中的服务名
image: mysql:8.0
container_name: mysql8 # 真正创建出来的容器名
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: mysql
volumes:
- ./mysql/conf:/etc/mysql/conf.d # 前面写我们宿主机上真实的地址
- ./mysql/data:/var/lib/mysql
- ./mysql/init:/docker-entrypoint-initdb.d
networks:
- tongue-network
restart: always
tongue-java-after:
build:
context: . # 读取当前目录下的 dockerfile
dockerfile: Dockerfile # 还是用 dockerfile 构建 Java 应用
container_name: tongue-java-after
ports:
- "8080:8080"
depends_on:
- mysql8 # 依赖于 mysql8,会先创建 mysql8
networks:
- tongue-network
restart: always
tongue-nginx:
image: nginx:latest
container_name: tongue-nginx
ports:
- "8081:8081"
volumes:
- /root/nginx/html:/usr/share/nginx/html
- /root/nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- tongue-java-after
networks:
- tongue-network
restart: always
networks:
tongue-network: # compose 文件中用这个名字来引用
name: tongue-network # 真正创建网络时的名字
driver: bridge # 默认值,可省略
注意,无论是在 Java 程序中用数据库还是 Nginx 反向代理,都用这个里面的服务名而不是容器名!
docker compose 的命令格式如下
bash
docker compose [OPTIONS] [COMMAND]
| 类型 | 参数或指令 | 说明 |
|---|---|---|
| Options | -f |
指定 compose 文件的路径和名称,不指定则默认当前目录 |
| Options | -p |
指定 project 名称 |
| Commands | up |
创建并启动所有 service 容器 |
| Commands | down |
停止并移除所有容器、网络 |
| Commands | ps |
列出所有启动的容器 |
| Commands | logs |
查看指定容器的日志 |
| Commands | stop |
停止某个容器 |
| Commands | start |
启动某个容器 |
| Commands | restart |
重启某个容器 |
| Commands | top |
查看运行的进程 |
| Commands | exec |
在指定的运行中容器中执行命令 |
下面做一下,我们就可以把之前创建的所有 images, container 全部删除。
执行命令
bash
docker compose up -d # -d 后台运行
之后输入 docker compose ps 大概输出如下
bash
NAME IMAGE COMMAND SERVICE STATUS PORTS
mysql8 mysql:8.0 "docker-entrypoint.s..." mysql8 Up 0.0.0.0:3306->3306/tcp
tongue-java-after xxx-tongue-java-after "java -jar app.jar" tongue-java-after Up 0.0.0.0:8080->8080/tcp
tongue-nginx nginx:latest "/docker-entrypoint...." tongue-nginx Up 0.0.0.0:8081->8081/tcp
Compose 会用 project 名称来区分不同项目创建出来的容器、网络、卷等资源。
-p 的作用就是来指定你这个项目名,这样在执行 docker compose ps、docker compose down 的时候知道是要操作哪个项目
如果我们没指定 container_name,那 Compose 会自动生成容器名,一般就是
bash
项目名-服务名-序号
默认会用 compose 文件所在的目录名作为 project 名,也可以用 -p 手动指定,所以也可以理解为啥在 compose 下,容器之间互相访问是用 service 名而不是容器名了,因为容器名可能会变动。
还可以做集群部署