Docker 简明笔记

前置知识:SpringCloud

初识 Docker

项目部署的问题

微服务虽然具备各种各样的优势,但服务的拆分通用给部署带来了很大的麻烦

  • 分布式系统中,依赖的组件非常多,不同组件之间部署时往往会产生一些冲突
    • 依赖关系复杂,容易出现兼容性问题
  • 在数百上千台服务中重复部署,环境不一定一致,会遇到各种问题
    • 开发、测试、生产环境有差异

Docker 解决依赖兼容问题

Docker 如何解决依赖的兼容问题的呢,采用了两个手段:

  • 将应用的 Libs(函数库)、Deps(依赖)、配置与应用一起 打包
  • 将每个应用放到一个 隔离 容器去运行,避免互相干扰

01-应用放在隔离带容器中运行.png

这样打包好的应用包中,既包含应用本身,也保护应用所需要的 Libs、Deps,无需再操作系统上安装这些,自然就不存在不同应用之间的兼容问题了

Docker 解决操作系统环境差异

虽然解决了不同应用的兼容问题,但是开发、测试等环境会存在差异,操作系统版本也会有差异,怎么解决这些问题呢?

02-操作系统结构.png

  • 计算机硬件:例如 CPU、内存、磁盘等
  • 系统内核(内核与硬件交互,提供操作硬件指令):所有 Linux 发行版的内核都是 Linux,例如 CentOS、Ubuntu、Fedora 等。内核可以与计算机硬件交互,对外提供 内核指令,用于操作计算机硬件
  • 系统应用(系统应用封装内核指令为函数,便于程序员调用):操作系统本身提供的应用、函数库。这些函数库是对内核指令的封装,使用更加方便

Ubuntu 和 CentOS 都是基于 Linux 内核,无非是系统应用不同,提供的函数库有差异。此时,如果将一个 Ubuntu 版本的 MySQL 应用安装到 CentOS 系统,MySQL 在调用 Ubuntu 函数库时,会发现找不到或者不匹配,就会报错了,那 Docker 如何解决不同系统环境问题的呢

  • Docker 将用户程序与所需要调用的系统 (比如 Ubuntu) 函数库一起打包
  • Docker 运行到不同操作系统时,直接基于打包的函数库,借助于操作系统的 Linux 内核来运行

那么就可以认为 Docer 打包好的程序包可以应用在任何 Linux 内核的操作系统上

03-Docker打包好的程序包可以运行在任一Linux内核的系统上.png

总结

Docker 是一个快速交付应用、运行应用的技术,具备下列优势:

  • 可以将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任意 Linux 操作系统
  • 运行时利用沙箱机制形成隔离容器,各个应用互不干扰
  • 启动、移除都可以通过一行命令完成,方便快捷

Docker 架构

镜像和容器

  • 镜像(Image,硬盘中的文件):Docker 将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像
  • 容器(Container,相当于进程):镜像中的应用程序运行后形成的进程就是容器,只是 Docker 会给容器进程做隔离,对外不可见

镜像都是只读的,这样可以防止容器对镜像数据的写入,造成数据污染;如果容器需要写数据,可以从镜像中拷贝一份数据到自己的空间中,在本空间中进行读写操作

04-镜像和容器.png

DockerHub

DockerHub 是一个官方的 Docker 镜像的托管平台。这样的平台称为 Docker Registry,国内也有类似于 DockerHub 的公开服务,比如 网易云镜像服务阿里云镜像库

我们一方面可以将自己的镜像共享到 DockerHub,另一方面也可以从 DockerHub 拉取镜像
05-DockerHub.png

Docker 架构

Docker 是一个 CS 架构的程序,由两部分组成

  • 服务端 (server):Docker 守护进程,负责处理 Docker 指令,管理镜像、容器等
  • 客户端 (client):通过命令或 RestAPI 向 Docker 服务端发送指令。可以在本地或远程向服务端发送指令

06-Docker架构.png

安装 Docker

Docker 分为 CE 和 EE 两大版本。CE 即社区版(免费,支持周期 7 个月),EE 即企业版,强调安全,付费使用,支持周期 24 个月。这里主要介绍 CentOS 安装 Docker

1.1、卸载(可选)

如果之前安装过旧版本的 Docker,可以使用下面命令卸载

1
2
3
4
5
6
7
8
9
10
11
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine \
docker-ce

\ 表示命令没有结束还需要继续往下读(换行),命令太长可以通过 \ 提高可读性

1.2、安装 yum-utils 工具

1
2
3
yum install -y yum-utils \
device-mapper-persistent-data \
lvm2 --skip-broken

1.3、设置下载的镜像源

1
2
3
4
5
6
7
# 设置docker镜像源
yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
yum makecache fast

1.4、安装 docker

1
yum install -y docker-ce

至此 docker 安装完毕

启动 docker

Docker 应用需要用到各种端口,逐一去修改防火墙设置。非常麻烦,因此建议大家直接关闭防火墙。启动 docker 前,一定要关闭防火墙!

1、关闭防火墙

1
2
3
4
5
6
# 关闭防火墙
systemctl stop firewalld
# 禁止开机启动防火墙
systemctl disable firewalld
# 查看防火墙状态
systemctl status firewalld

2、启动 Docker

1
2
3
4
5
systemctl start docker  # 启动docker服务

systemctl stop docker # 停止docker服务

systemctl restart docker # 重启docker服务

3、可通过查看 Docker 状态或 Docker 版本查看是否已经启动

1
2
3
systemctl status docker
//或者
docker -v

配置镜像

docker 官方镜像仓库网速较差,我们需要设置国内镜像服务,可参考阿里云的镜像加速文档:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

1
2
3
4
5
6
7
8
9
10
11
sudo mkdir -p /etc/docker

//将内容写入json文件中
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://5vycoa8o.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

Docker 基本操作

镜像操作

镜像名称

镜名称一般分两部分组成:[repository]:[tag],在没有指定 tag 时,默认是 latest,代表最新版本的镜像。如这里的 mysql 就是 repository,5.7 就是 tag,合一起就是镜像名称,代表 5.7 版本的 MySQL 镜像

07-镜像名称的组成.png

镜像命令

常见的镜像操作命令如图

08-常用镜像指令.png

  • 从镜像服务器拉去镜像:docker pull
  • 从本地文件构建镜像:docker build
  • 查看本地存在哪些镜像:docker images
  • 删除本地镜像:docker rmi
  • 推送镜像到镜像服务器:docker push
  • 将镜像打包成一个压缩包:docker save
  • 加载压缩包为镜像:docker load

查看 docker 帮助文档

1
2
3
4
//查看docker命令及简介
docker --help
//查看具体的某一命令,比如这里详细查看images命令的功能
docker images --help

案例:从 DockerHub 中拉取镜像

需求:从 DockerHub 中拉取一个 nginx 镜像并查看

1、首先去镜像仓库搜索 nginx 镜像,比如 Docker Hub Container Image Library | App Containerization

09-DockerHub拉取镜像.png

1
2
//这里官方示例中没有指定版本,那么默认就是最新版
docker pull nginx

10-DockerHub搜索镜像.png

2、查看镜像

1
docker images

可以查看到本地中已经有 Nginx 镜像了

1
2
3
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 12 months ago 141MB

案例:通过压缩包导出导入镜像

需求:利用 docker save 将 nginx 镜像导出磁盘,然后再通过 load 加载回来

1、利用 docker xx –help 命令查看 docker save 和 docker load 的语法

1
docker save --help

通过帮助文档可得知 save 的命令格式为:

1
docker save -o [保存的目标文件名称] [镜像名称]

2、使用 docker save 导出镜像到磁盘

1
docker save -o nginx.tar nginx:latest

-o 表示选项,注意此时镜像在本地还是有的

3、使用 docker load 加载镜像

3.1)先删除本地的 nginx 镜像

1
docker rmi nginx:latest

3.2)加载压缩包镜像

1
docker load -i nginx.tar

容器操作

容器相关命令

11-容器常用指令.png

容器有三个状态:

  • 运行:进程正常运行
  • 暂停:进程暂停,CPU 不再运行,并不释放内存
  • 停止:进程终止,回收进程占用的内存、CPU 等资源

容器操作的命令:

  • docker run:创建并运行一个容器,处于运行状态
  • docker pause:让一个运行的容器暂停
  • docker unpause:让一个容器从暂停状态恢复运行
  • docker stop:停止一个运行的容器
  • docker start:让一个停止的容器再次运行
  • docker rm:删除一个容器
  • docker ps:查看所有运行的容器及状态
  • docker logs:查看容器运行日志
  • docker exec:进入容器执行命令

案例:创建运行一个容器

需求:创建并运行 nginx 容器的命令

可以去官网搜索 Nginx,并查看其文档:nginx - Official Image | Docker Hub,比如官网中给了如下运行命令示例:

1
docker run --name some-nginx -d -p 8080:80 some-content-nginx

这里以如下命令进行命令解读:

1
docker run --name containerName -p 80:80 -d nginx
  • docker run :创建并运行一个容器
  • –name : 给容器起一个名字,比如叫做 mn
  • -p :将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口;此处宿主机端口不做要求,但容器端口基本上取决于容器本身(软件监听的端口可能就是某一个端口)
  • -d:后台运行容器
  • nginx:镜像名称,例如 nginx,没有写标签 tag 说明是最新版 latest

![[Pasted image 20230110202450.png]]

因为容器是隔离的,所以用户无法直接通过 80 端口来访问到容器,需要将容器的端口与宿主机端口映射。端口映射就相当于将原本隔离的容器暴露出一个小窗口,通过这个小窗口来对容器进行访问

容器创建完成后,会生成一个唯一 ID

1
2
[root@localhost ~]# docker run --name mn -p 80:80 -d nginx
b8ae9bcbdde97a1ef9b055e44470427cd937571c4f2fdb5cb7a710c3d9a828e7

通过访问宿主机 80 端口,就可访问 docker 中的 Nginx 服务了

1
192.168.119.128:80

通过 logs 命令可以查看容器日志

1
2
3
4
5
6
7
8
//命令格式
docker logs [OPTIONS] CONTAINER

//docker logs 容器名
docker logs mn

//持续跟踪日志,通过Ctrl+C可以停止跟踪
docker logs -f mn

案例:操作容器

需求:进入 Nginx 容器,修改 HTML 文件内容,添加 “coffeelize 欢迎您”

1、进入容器(容器是运行的)

1
docker exec -it mn bash

命令解读:

  • docker exec :进入容器内部,执行一个命令
  • -it : 给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
  • mn :要进入的容器的名称
  • bash:进入容器后执行的命令,bash 是一个 linux 终端交互命令

注意:exec 命令可以进入容器修改文件,但是在容器内修改文件是不推荐的,修改了是没有记录(日志的),之后都不知道进行了哪些修改操作

2、进入 nginx 的 HTML 所在目录 /usr/share/nginx/html

容器内部会模拟一个独立的 Linux 文件系统,看起来如同一个 linux 服务器一样

1
2
3
root@b8ae9bcbdde9:/# ls
bin dev docker-entrypoint.sh home lib64 mnt proc run srv tmp var
boot docker-entrypoint.d etc lib media opt root sbin sys usr

我们进入 Nginx 的目录(至于如何找到这个目录的可能需要在 DockerHub 查看 Nginx 的文档了),可以发现目录下包含 index.html

1
cd /usr/share/nginx/html

3、修改 index.html 的内容

容器内没有 vi 命令,无法直接修改,我们用下面的命令来修改

1
sed -i -e 's#Welcome to nginx#coffeelize欢迎您#g' -e 's#<head>#<head><meta charset="utf-8">#g' index.html

4、验证

访问虚拟机 80 端口,输出页面如下,说明修改成功

12-修改成功.png

5、退出容器

1
exit

6、停止容器

1
2
3
4
5
6
docker stop mn

//查看运行中的docker
docker ps
//查看所有容器(包括停止的)
docker ps -a

7、启动容器

1
docker start mn

8、删除容器

1
2
//停掉容器之后删除容器 或者 强制删除运行中的程序
docker rm -f mn

数据卷

在之前的 nginx 案例中,修改 nginx 的 html 页面时,需要进入 nginx 内部。并且因为没有编辑器,修改文件也很麻烦,这就是因为容器与数据(容器内文件)耦合带来的后果。要解决这个问题,必须将 数据与容器解耦,这就要用到数据卷了

13-容器与数据耦合度高.png

数据卷(volume)是一个虚拟目录,指向宿主机文件系统中的某个目录

14-数据卷示意图.png

一旦完成数据卷挂载,对容器的一切操作都会作用在数据卷对应的宿主机目录了,这样,我们操作宿主机的 /var/lib/docker/volumes/html 目录,就等于操作容器内的 /usr/share/nginx/html 目录了;多个容器可以挂在同一个卷,就可以 “共享” 修改操作了;如果哪一天将容器删除了,没关系,数据卷还在,将新容器在挂载到这个数据卷上就可以了访问之前的数据了

数据卷操作命令

数据卷操作的基本语法如下

1
docker volume [COMMAND]
  • create:创建一个 volume
  • inspect:显示一个或多个 volume 的信息
  • ls:列出所有的 volume
  • prune:删除未使用的 volume
  • rm:删除一个或多个指定的 volume

案例:创建和查看数据卷

需求:创建一个数据卷,并查看数据卷在宿主机的目录位置

1、创建数据卷

1
docker volume create html

2、查看所有数据卷

1
docker volume ls

3、查看数据卷详细信息卷

1
docker volume inspect html

返回信息如下,其中重点关注 Mountpoint 挂载点

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# docker volume inspect html
[
{
"CreatedAt": "2023-01-10T21:32:34+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/html/_data",
"Name": "html",
"Options": {},
"Scope": "local"
}
]

4、小结

数据卷的作用:将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全

挂载数据卷

我们在创建容器时,可以通过 -v 参数来挂载一个数据卷到某个容器内目录,命令格式如下:

1
2
3
4
5
docker run \
--name mn \
-v html:/root/html \
-p 8080:80
nginx \

这里的 -v 就是挂载数据卷的命令:

  • docker run:创建并运行容器
  • --name mn:给容器起个名字叫 mn
  • -v html:/root/html :把 html 数据卷挂载到容器内的 /root/html 这个目录中
  • -p 8080:80:吧宿主机的 8080 端口映射到容器内的 80 端口
  • nginx:镜像名称

案例:给 nginx 挂载数据卷

需求:创建一个 nginx 容器,修改容器内的 html 目录内的 index.html 内容
分析:上个案例中,我们进入 nginx 容器内部,已经知道 nginx 的 html 目录所在位置 /usr/share/nginx/html ,我们需要把这个目录挂载到 html 这个数据卷上,方便操作其中的内容

0、查看容器是否在运行,并且已经提前创建好了 html 数据卷

1
2
# 检查容器是否在运行
docker ps -a

1、创建容器并挂载数据卷到容器内的 HTML 目录

1
docker run --name mn -v html:/usr/share/nginx/html -p 80:80 -d nginx

2、进入 html 数据卷所在位置,并修改 HTML 内容

1
2
3
4
5
6
7
8
# 查看html数据卷的位置
docker volume inspect html
# 通过查看挂载点可知如下目录,进入该目录
cd /var/lib/docker/volumes/html/_data
# 查看目录下有哪些文件
ls
# 修改文件,此处可通过FinalShell使用本地的高级编辑工具来打开编辑
vi index.html

3、在做数据卷挂在时,如果要创建数据卷不存在,docker 会为我们自动创建数据卷

比如在我们使用如下命令前,docker 中是没有 html 数据卷的,一样可以正常使用如下命令,因为 docker 会为我们自动创建 html 数据卷

1
docker run --name mn -v html:/usr/share/nginx/html -p 80:80 -d nginx

案例:给 MySQL 挂载本地目录

容器不仅仅可以挂载数据卷,也可以直接挂载到宿主机目录上。关联关系如下

  • 带数据卷模式:宿主机目录 –> 数据卷 –> 容器内目录
  • 直接挂载模式:宿主机目录 –> 容器内目录

15-容器直接挂在到宿主机.png

目录挂载与数据卷挂载的语法是类似的:

  • -v [宿主机目录]:[容器内目录]
  • -v [宿主机文件]:[容器内文件]

案例需求:创建并运行一个 MySQL 容器,将宿主机目录直接挂载到容器

1、将课前资料中的 mysql.tar 文件上传到虚拟机的 tmp 目录,通过 load 命令加载为镜像

1
2
3
4
5
6
7
8
cd /
cd tmp/

//rz或FinalShell上传mysql.tar

docker load -i mysql.tar
//查看镜像是否导入,mysql的版本为5.7.25
docker images

2、创建目录 /tmp/mysql/data

1
2
//-p表示多级目录创建
mkdir -p /tmp/mysql/data

3、创建目录 /tmp/mysql/conf,将课前资料提供的 hmy.cnf 文件上传到 /tmp/mysql/conf

1
2
3
4
//-p表示多级目录创建
mkdir -p /tmp/mysql/conf

//rz或FinalShell上传hmy.cnf

hmy.cnf 的文件内容为

1
2
3
4
5
[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
server-id=1000

4、去 DockerHub 查阅资料 mysql | Docker Hub,创建并运行 MySQL 容器,要求:

1)挂载 /tmp/mysql/data 到 mysql 容器内数据存储目录
2)挂载 /tmp/mysql/conf/hmy.cnf 到 mysql 容器的配置文件
3)设置 MySQL 密码

官网上给定的运行示例如下

1
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

其中 -e 表示运行环境,后面可以直接设置 mysql 密码;-d 表示后台运行;tag 为版本号,其中还缺少了端口号的设置,我们对这个命令进行修改

1
2
3
4
5
6
7
8
docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=123 \
-p 3306:3306 \
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/data:/var/lib/mysql \
-d \
mysql:5.7.25

但是此时有个报错 (bind: address already in use),因为之前我们已经在虚拟机中运行 MySQL 了,也就是已经占用了宿主机的 3306 端口,这里我们改成 3305 试一下

1
2
3
4
5
6
7
8
9
10
11
12
//删除刚才创建的mysql容器
docker rm mysql

//再次执行如下命令,注意端口改为了3305
docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=123 \
-p 3305:3306 \
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/data:/var/lib/mysql \
-d \
mysql:5.7.25

注意:此处的 /etc/mysql/conf.d 目录,可以合并添加我们创建的 hmy.cnf 配置,而不是将 MySQL 默认的配置文件完全覆盖掉(因为我们创建的配置文件只包含了默认配置的少数配置信息,替换掉默认配置的话配置就不全了)

5、测试 MySQL 连接

通过 Navicat 测试可正常连接
16-Navicat连接成功.png

6、小节

数据卷挂载与目录直接挂载的比较:

  • 数据卷挂载耦合度低,由 docker 来管理目录,但是目录较深,不好找
  • 目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看

Dockerfile 自定义镜像

镜像结构

常见的镜像在 DockerHub 就能找到,但是我们自己写的项目就必须自己构建镜像了,而要自定义镜像,就必须先了解镜像的结构才行

镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。我们以 MySQL 为例,来看看镜像的组成结构

17-镜像结构.png

  • 基础镜像(BaseImage):应用依赖的系统函数库、环境、配置、文件等
  • 入口(Entrypoint):镜像运行入口,一般是程序启动的脚本和参数
  • 层(Layer):在 BaseImage 基础上添加安装包、依赖、配置等,每次操作都形成新的层

Dockerfile

Dockerfile 是一个文本文件,其中包含一个个的 ** 指令 (Instruction)**,用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层 Layer。更新详细语法说明,可参考官网文档: https://docs.docker.com/engine/reference/builder

18-Dockerfile指令.png

构建 Java 项目

基于 Ubuntu 构建 Java 项目

需求:基于 Ubuntu 镜像构建一个新镜像,运行一个 java 项目

1、新建一个空文件夹 docker-demo

1
2
cd /tmp/
mkdir docker-demo

2、拷贝课前资料中的 docker-demo.jar 文件到 docker-demo 这个目录
3、拷贝课前资料中的 jdk8.tar.gz 文件到 docker-demo 这个目录
4、拷贝课前资料提供的 Dockerfile 到 docker-demo 这个目录

Dockerfile 中的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar

# 安装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 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

5、进入 docker-demo

1
cd docker-demo

6、运行命令

1
docker build -t javaweb:1.0 .

-t 表示 tag;javaweb 为镜像名称;注意命令后面还有个 .,表示 dockerfile 所在的目录(构建时告知 dockerfile 在哪)

可以看到 dockerfile 共有 9 个指令,也就分为了 9 个 step,每个指令执行都会创建出一个层

7、通过命令查看构建好的镜像

1
2
3
4
5
6
7
[root@localhost docker-demo]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
javaweb 1.0 c94aff541e94 42 seconds ago 722MB
nginx latest 605c77e624dd 12 months ago 141MB
redis latest 7614ae9453d1 12 months ago 113MB
ubuntu 16.04 b6f507652425 16 months ago 135MB
mysql 5.7.25 98455b9624a9 3 years ago 372MB

可以看到我们基于 ubuntu 构建的(配置好 java 环境的)javaweb 项目的镜像已经构建好了

可以通过命令来运行镜像(8090 端口在 dockerfile 中已经声明暴露了端口)

1
docker run --name web -p 8090:8090 -d javaweb:1.0

浏览器访问如下地址,可以发现我们的项目(之前我们的 docker-demo.jar 项目)正常跑起来了

1
http://192.168.119.128:8090/hello/count

19-项目运行成功.png

虚拟机中运行 docker –> docker 中运行 ubuntu –> ubuntu 中运行 docker-demo java 项目😂,虚拟机内存开始吃紧了

20-虚拟机内存吃紧.png

小结

分析:其实我们的 java 项目真正只用到了如下一行

1
COPY ./docker-demo.jar /tmp/app.jar

dockerfile 文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar

# 安装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 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

那么我们之后在构建 Java 项目镜像时,可以先构建如下不会改变的层做一个镜像,然后在基于这个镜像来构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝jdk
COPY ./jdk8.tar.gz $JAVA_DIR/

# 安装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

而实际上,有人也已经构建好了这个镜像了,我们直接拿来用就行,镜像名为 java:8-alpine

1
2
3
4
5
6
7
8
# 指定基础镜像
FROM java:8-alpine
# 拷贝java项目的包
COPY ./app.jar /tmp/app.jar
# 暴露端口
EXPOSE 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

DockerCompose

Docker Compose 可以基于 Compose 文件帮我们快速部署分布式应用,而无需手动一个个创建和运行容器

初识 DockerCompose

Compose 文件是一个文本文件,通过指令定义集群中的每个容器如何运行。格式如下(相当于把 docker run 中的所有指令转换为了 Compose 指令了):

1
2
3
4
5
6
7
8
9
10
11
12
13
version: "3.8"
services:
  mysql:
    image: mysql:5.7.25
environment:
MYSQL_ROOT_PASSWORD: 123
    volumes:
     - "/tmp/mysql/data:/var/lib/mysql"
     - "/tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf"
  web:
    build: .
    ports:
     - "8090:8090"

上面的 Compose 文件就描述一个项目,其中包含两个容器

  • mysql:一个基于 mysql:5.7.25 镜像构建的容器,并且挂载了两个目录
    • 为什么没有定义端口呢:因为 MySQL 运行在微服务当中,供内部使用无需对外开放
    • 无需定义后台运行,默认就是后台运行
  • web:一个基于 docker build 临时构建的镜像容器,映射端口时 8090
    • 为什么没有指定镜像:因为通过 build 就可以构建镜像

安装 DockerCompose

1、下载

1
2
# 安装
curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

或者通过本地文件准备好的文件直接上传,上传至 /usr/local/bin/ 目录

2、修改文件权限

1
2
# 修改权限
chmod +x /usr/local/bin/docker-compose

3、Base 自动补全命令

之后使用 Docker Compose 时就会有补全提示

1
2
# 补全命令
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose

此时可能会报:拒绝连接的错误,需要执行如下命令修改 hosts 文件

1
echo "199.232.68.133 raw.githubusercontent.com" >> /etc/hosts

案例:利用 DockerCompose 部署

需求:将之前学习的 cloud-demo 微服务集群利用 DockerCompose 部署

1、查看课前资料提供的 cloud-demo 文件夹,里面已经编写好了 docker-compose 文件

课前资料提供的 cloud-demo 文件夹,里面已经编写好了 docker-compose 文件,而且每个微服务都准备了一个独立的目录。对于每一个微服务目录,其中都包含一个 Dockerfile 文件和对于的微服务 jar 包。最外层包含 docker-compose.yml 配置文件,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: "3.2"

services:
nacos:
image: nacos/nacos-server
environment:
MODE: standalone
ports:
- "8848:8848"
mysql:
image: mysql:5.7.25
environment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- "$PWD/mysql/data:/var/lib/mysql"
- "$PWD/mysql/conf:/etc/mysql/conf.d/"
userservice:
build: ./user-service
orderservice:
build: ./order-service
gateway:
build: ./gateway
ports:
- "10010:10010"

这几个微服务(mysql、userservice、orderservice 以及 gateway)中,只有网关暴露了端口,因为网关是外部访问微服务的入口。其他微服务都需要注册到 Nacos 服务中

MySQL 微服务中需要的表和数据课程资料也已经为我们准备好了

2、修改自己的 cloud-demo 项目,将数据库、nacos 地址都命名为 docker-compose 中的服务名

因为微服务将来要部署为 docker 容器,而容器之间互联不是通过 IP 地址,而是通过容器名。这里我们将 order-service、user-service、gateway 服务的 mysql、nacos 地址都修改为基于容器名的访问

比如 user-service 中的 bootstrap 配置文件

1
2
3
4
5
6
7
8
9
10
11
spring:  
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
- server-addr: localhost:8848 # Nacos地址
+ server-addr: nacos:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名

application.yml 配置文件中

1
2
- url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
+ url: jdbc:mysql://mysql:3306/cloud_user?useSSL=false

同理,order-service 和 gateway 微服务的配置文件也这样修改

3、使用 maven 打包工具,将项目中的每个微服务都打包为 app.jar

为什么都打包成 app.jar 呢 –> 因为我们在微服务目录下的 Dockerfile 文件里是这样配置的,我们配置的名称都是 app.jar

1
2
3
FROM java:8-alpine
COPY ./app.jar /tmp/app.jar
ENTRYPOINT java -jar /tmp/app.jar

那么,既然各个微服务打包完成都需要叫这个 app.jar 名字,我们是否可以修改配置文件实现项目打包自动叫这个名字呢 –> 可以的,在各个微服务的 pom 文件中添加如下配置

1
2
3
4
5
6
7
8
9
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

通过 Maven 的 Package 来打包

4、将打包好的 app.jar 拷贝到 cloud-demo 中的每一个对应的微服务子目录中

21-项目部署前准备.gif

5、将 cloud-demo 上传至虚拟机 (tmp 目录),利用 docker-compose up -d 来部署

1
2
3
4
5
6
7
cd /
cd /tmp/

//上传cloud-demo文件夹

cd cloud-demo/
docker-compose up -d
  • up:表示创建并执行容器
  • down:停止并删除容器
  • 其他命令可以通过 help 命令查看

6、查看打包好的镜像和运行的容器

1
2
docker images
docker ps
1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost cloud-demo]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cloud-demo_gateway latest 3ce691e26939 About a minute ago 185MB
cloud-demo_orderservice latest b81195944331 About a minute ago 187MB
cloud-demo_userservice latest 2dc6d8c88bdc About a minute ago 184MB
javaweb 1.0 c94aff541e94 3 hours ago 722MB
nginx latest 605c77e624dd 12 months ago 141MB
redis latest 7614ae9453d1 12 months ago 113MB
ubuntu 16.04 b6f507652425 16 months ago 135MB
nacos/nacos-server latest bdf60dc2ada3 17 months ago 1.05GB
mysql 5.7.25 98455b9624a9 3 years ago 372MB
java 8-alpine 3fd9dd82815c 5 years ago 145MB

虚拟机 2G 内存快要炸了😳,开始借用交换内存了

7、通过查看日志可发现 order-service 有报错

1
2
3
4
5
6
7
docker-compose logs -f

//退出日志
Ctrl+C

//重启微服务(微服务启动前Nacos已经启动完成)
docker-compose restart gateway userservice orderservice

原因是因为 Nacos 微服务启动晚于 order-service,导致报错。关键是报错之后没有进行重新启动 –> 因此,我们最好是先启动 Nacos 微服务,之后再启动 order-service 等系列微服务

浏览器访问如下,均可正常接收到数据

1
2
http://192.168.119.128:10010/user/2?authorization=admin
http://192.168.119.128:10010/order/102?authorization=admin

至此,DockerCompose 部署微服务完成

8、删除掉这些容器吧,虚拟机要炸了

1
2
//删除通过docker-compose部署的容器,同时删除镜像
docker-compose down --rmi all
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost cloud-demo]# docker-compose down --rmi all
Stopping cloud-demo_nacos_1 ... done
Stopping cloud-demo_userservice_1 ... done
Stopping cloud-demo_gateway_1 ... done
Stopping cloud-demo_mysql_1 ... done
Stopping cloud-demo_orderservice_1 ... done
Removing cloud-demo_nacos_1 ... done
Removing cloud-demo_userservice_1 ... done
Removing cloud-demo_gateway_1 ... done
Removing cloud-demo_mysql_1 ... done
Removing cloud-demo_orderservice_1 ... done
Removing network cloud-demo_default
Removing image nacos/nacos-server
Removing image mysql:5.7.25
Removing image cloud-demo_userservice
Removing image cloud-demo_orderservice
Removing image cloud-demo_gateway

Docker 镜像仓库

简化版镜像仓库

Docker 官方的 Docker Registry 是一个基础版本的 Docker 镜像仓库,具备仓库管理的完整功能,但是没有图形化界面

1
2
3
4
5
6
docker run -d \
--restart=always \
--name registry \
-p 5000:5000 \
-v registry-data:/var/lib/registry \
registry

命令中挂载了一个数据卷 registry-data 到容器内的 /var/lib/registry 目录,这是私有镜像库存放数据的目录,访问如下链接可以查看当前私有镜像服务中包含的镜像

1
http://yourip:5000/v2/_catalog

带有图形化界面版本

操作此步骤前,需要先完成 Docker 信任地址配置

使用 DockerCompose 部署带有图象界面的 DockerRegistry,命令如下

1
2
3
4
5
6
7
8
9
cd /
cd /tmp/
mkdir registry-ui
cd registry-ui
touch docker-compose.yml
//修改这个yml文件,内容如下代码块

//修改yml文件完成后执行docker-compose
docker-compose up -d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.0'
services:
registry:
image: registry
volumes:
- ./registry-data:/var/lib/registry
ui:
image: joxit/docker-registry-ui:static
ports:
- 8080:80
environment:
- REGISTRY_TITLE=coffeelize私有仓库
- REGISTRY_URL=http://registry:5000
depends_on:
- registry

通过访问如下地址,即可访问我们创建的带有图形界面的 Docker 镜像仓库了

1
192.168.119.128:8080

22-建立私有仓库.png

配置 Docker 信任地址

我们的私服采用的是 http 协议,默认不被 Docker 信任,所以需要做一个配置

1
2
3
4
5
6
7
8
# 打开要修改的文件
vi /etc/docker/daemon.json
# 添加内容:
"insecure-registries":["http://192.168.119.128:8080"]
# 重加载
systemctl daemon-reload
# 重启docker
systemctl restart docker

在添加内容时,注意多个配置之间别把逗号忘加了

1
2
3
4
{
"registry-mirrors": ["https://5vycoa8o.mirror.aliyuncs.com"],
+ "insecure-registries":["http://192.168.119.128:8080"]
}

在私有镜像仓库中推送或拉去镜像

推送镜像到私有镜像服务必须先 tag,步骤如下:

1、重新 tag 本地镜像(重命名镜像,并且以镜像仓库地址为前缀),名称前缀为私有仓库的地址:192.168.119.128:8080/

1
docker tag nginx:latest 192.168.119.128:8080/nginx:1.0 

利用 tag 命令,可以将一个镜像重命名,这里我们对之前下载的最新版 Nginx 镜像进行操作

23-推送镜像前重命名镜像.png

此时,查看本地的镜像,就可以找到我们打包并且重命名后的镜像了,可以发现这两个镜像的 ID(605c77e624dd)其实是一样的

1
2
192.168.119.128:8080/nginx   1.0        605c77e624dd   12 months ago   141MB
nginx latest 605c77e624dd 12 months ago 141MB

2、推送镜像

1
docker push 192.168.119.128:8080/nginx:1.0 

24-推送镜像到私有仓库.png

3、拉取镜像

1
docker pull 192.168.150.101:8080/nginx:1.0