admin 管理员组

文章数量: 887016

应用容器引擎 - Docker

文档信息

笔者:LiiiYiAn

编写日期:2023.09.10

文章目录

  • 应用容器引擎 - Docker
    • 文档信息
    • 参考文献
    • 序论
      • 楔子
      • Docker 是何
        • 概述
        • Docker 名词概念
          • Desktop(客户端)
          • Daemon(守护进程)
          • Image(镜像)
          • Container(容器)
          • Registry(注册表)
          • Host(宿主机)
      • Docker 为何
      • Docker 如何
        • 安装 Docker
        • 使用 Docker 创建并运行容器
        • Docker 仓库配置
        • 更新 Docker Desktop
        • 使用 Docker 拉取镜像安装软件
        • 通过容器运行镜像
        • 容器运行报错端口冲突
        • Container 的停止、重启和回收
    • `Dockerfile`
      • 当前启动容器时的问题
      • 解决问题 1:命令太长
      • 解决问题 2:持久化数据
      • 拓展 - 执行默认数据库初始化 SQL 文件
      • 解决问题 3:过于复杂不好记
    • 镜像的导出和导入
    • Docker-Compose(容器编排)
      • 容器编排
      • Docker-Compose 是何
      • Docker-Compose 为何
      • Docker-Compose 如何
        • 安装
        • 使用 Compose 交付
          • 思路
          • 读取环境变量
          • 配置模块打包镜像
          • 拉取其他组件镜像
          • 数据库初始化脚本
          • 控制启动顺序
          • 编写 `docker-compose.yml`
          • 启动应用
          • 测试服务

参考文献

[9] 为什么要学习Docker?有什么用处呢? - 阿里云开发者社区 参考日期:2023.10.17

[8] 为什么要学习 Docker? (baidu) 参考日期:2023.10.17

[7] 程序员为什么要学Docker? - 知乎 (zhihu) 参考日期:2023.10.17

[6] 什么是 Docker? - 知乎 (zhihu) 参考日期:2023.10.17

[5] 【Docker】什么是Docker?一文带你了解_理解docker -CSDN 参考日期:2023.10.17

[4] docker是啥意思-Docker-PHP中文网 参考日期:2023.10.17

[3] Docker之compose介绍_docker-compose - CSDN 参考日期:2023.10.12

[2] Docker run命令汇总_docker run 命令 - CSDN 参考日期:2023.10.12

[1] 大学大三实训课程 - 微服务架构 “国服马超哥” 授 2023.10.12

序论

楔子

假定您在开发一个尚硅谷的谷粒商城,使用的是一台笔记本电脑,且您的开发环境具有特定的配置,其他开发人员身处的环境配置也各有不同

您正在开发的应用依赖于您当前的配置且还要依赖于某些配置文件

此外,您的企业还拥有标准化的测试和生产环境,且具有自身的配置和一系列支持文件。您希望尽可能多在本地模拟这些环境而不产生重新创建服务器环境的开销

请问?您要如何确保应用能够在这些环境中运行和通过质量检测?并且在部署过程中不出现令人头疼的版本、配置问题,也无需重新编写代码和进行故障修复?

答案:使用容器

Docker 之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案 —— 系统平滑移植,容器虚拟化技术

环境配置相当麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?

也就是说,安装的时候,把原始环境一模一样地复制过来。开发人员利用 Docker 可以消除协作编码时 “在我的机器上可正常工作” 的问题

之前在服务器配置一个应用的运行环境,要安装各种软件,就拿尚硅谷电商项目的环境来说,Java、RabbitMQ、MySQL、JDBC 驱动包等

安装和配置这些东西有多麻烦就不说了,它还不能跨平台。假如我们是在 Windows 上安装的这些环境,到了 Linux 又得重新装。况且就算不跨操作系统,换另一台同样操作系统的服务器,要移植应用也是非常麻烦的

传统认为,软件编码开发 / 测试结束后,成果应该是程序或是能够编译执行的二进制字节码等

而为了让这些程序可以顺利执行,开发团队也得准备完整的部署文件,让维运团队得以部署应用程式,开发需要清楚的告诉运维部署团队,用的全部配置文件 + 所有软件环境

不过,即便如此,仍然常常发生部署失败的状况

而 Docker 的出现得以打破过去「程序即应用」的观念。通过镜像,将应用核心除外,将应用运行所需要的系统环境,由下而上打包,达到应用跨平台间的无缝衔接

Docker 是何

概述

Docker 中文官网:https://docker.p2hp

Docker 学习文档:https://docs.docker

Docker 是一个 Go 语言开发的开源的应用容器引擎,也是一种 轻量级 虚拟化容器技术

使用容器来虚拟化应用程序和服务

理念是:

  • “Build,Ship and Run Any App,Anywhere”

也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的 APP 及其运行环境能够做到 “一次镜像,处处运行

可以是一个 WEB 应用或数据库应用等等

Linux 容器技术的出现就解决了这样一个问题,而 Docker 就是在它的基础上发展过来的

将应用打成镜像,通过镜像成为运行在 Docker 容器上面的实例,而 Docker 容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器

只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作


Docker 基于镜像,将应用程序和所需的依赖项打包到称为容器独立单元中,可以秒级启动各种容器,实现快速的在各个环境部署、启动和运行 应用

应用就是软件,代码打包好的程序,你的代码生成的 Jar 包

每一种容器都是一个完整的运行环境,容器之间互相隔离

使用 Docker,开发人员可以轻松地构建、发布和管理应用程序,而不受底层硬件或操作系统的限制


Docker 优点:

  • 轻量级的虚拟化技术:与传统的虚拟机相比,Docker 容器更加轻量级,启动更快,占用更少的资源。这使得 Docker 非常适合在云环境中部署和扩展应用程序
  • 可移植的部署方式:使用 Docker,开发人员可以将应用程序和所需的依赖项打包成一个容器,并在不同的环境中轻松地部署。这使得应用程序可以在开发、测试和生产环境中保持一致,减少了因环境差异造成的问题
  • 灵活的扩展方式:使用 Docker,开发人员可以轻松地在不同的主机上部署和扩展容器。他们可以根据应用程序的需求增加或减少容器的数量,以适应流量的变化
  • 简化和自动化的部署方式:使用 Docker,开发人员可以使用 Dockerfile 来定义和描述应用程序的环境和依赖项。然后,可以使用 Docker Compose 或 Docker Swarm 来部署和管理多个容器。这使得应用程序的部署过程更加简单和可重复

Docker 架构由三个主要组件组成:

  • Docker Desktop:Docker 客户端

  • Docker Daemon:Docker 守护进程,也称为 dockerd

  • Docker Image:Docker 镜像

  • Docker Container:Docker 容器

  • Docker Registry:Docker 注册表

Daemon /ˈdiːmən/ n.程序,守护线程

Registry /ˈredʒɪstri/ n.注册表

官方高级 Docker 架构图:

Docker 名词概念
Desktop(客户端)

Docker Desktop,也就是 Docker 客户端,用户与 Docker 交互的命令行工具或图形界面

它可以通过 Docker API 与 Docker 守护进程通信,以执行各种操作,如创建、启动、停止和删除容器

Daemon(守护进程)

Docker 具有客户端-服务器架构。Docker Daemon ( dockerd) 或服务器负责与容器相关的所有操作

运行在主机上的后台进程,负责管理和监控 Docker 的运行。接收来自 Docker 客户端的命令,并根据命令来创建、启动、停止和删除容器。守护进程还负责管理容器的网络和存储,以及其他与容器相关的任务

Image(镜像)

使用 Docker 时,就像是使用一台电脑,而这台电脑在使用前需要安装操作系统,那么存放这个操作系统的光盘或 U 盘,就是 Image

Image 是 Docker 的基本组成部分,一个只读的模板,通过 Image 来运行 Docker 容器。Image 包含操作系统库、依赖项和运行应用程序的工具,可以快速创建和启动容器,并在不同的环境中共享和重现

Image 可以由 Dockerfile 构建,Dockerfile 是一个文本文件,包含了构建 Image 所需的步骤和指令

Docker Image 采用分层方式组织。Dockerfile 上的每条指令都会在 Image 中添加一个图层,最顶层的可写层是容器

每个 Image 都是从一个基础 Image 创建起

例如,如果我们使用 Ubuntu 的基本 Image,并在其中创建另一个包含 Nginx 应用程序的 Image。基础镜像可以是父镜像,也可以是从父镜像构建的子镜像

那么这个基础镜像(父镜像)又来自哪里呢?

有 Docker 实用程序可以创建初始父基本镜像。基本上它需要所需的操作系统库并将其创建到基本镜像中。不必这样做,因为可以免费获取所有 Linux 发行版的官方基础映像

这在后文 使用 Docker 拉取镜像安装软件 会讲述

Container(容器)

container /kənˈteɪnə®/ n.容器

Docker 容器是 Docker 的执行环境,从 Image 创建,属于 Image 最顶层的可写层

当你准备好 Image,也就是操作系统的光盘,并将其安装在电脑里面,电脑就可以正常启动了,而 Container,就是这台电脑

Container 与虚拟机类似,但二者在原理上不同。Container 是将操作系统层虚拟化,虚拟机则是虚拟化硬件,因此 Container 更具有便携性、高效地利用服务器

Container 更多的用于表示软件的一个标准化单元。由于 Container 的标准化,因此它可以无视基础设施的差异,部署到任何一个地方。Docker 也为 Container 提供更强的隔离兼容

Image 是静态的,而 Container 是动态的,可以用一个 Image 启动多个 Container

Container 可以启动、停止、提交和终止。如果在未提交的情况下终止 Container,所有的容器更改都将丢失。理想情况下,Container 被视为不可变的对象,不建议对正在运行的 Container 进行更改

Registry(注册表)

Docker 注册表是 Docker 镜像的存储库。使用 Docker 注册表,我们可以共享镜像。Docker 注册表充当了 Docker 镜像的中央存储库

注册中心可以是公共的,也可以是私有的。Docker Inc 提供了一项称为 Docker Hub 的托管注册表服务。它允许用户从一个中心位置上传和下载镜像

默认情况下,当安装 Docker 时,除非在 Docker 设置中指定自定义注册表,否则它会从公共的 Docker Hub 中查找镜像

Docker hub 的作用类似于 Git,我们可以在本地构建镜像,然后镜像提交推送到 Docker Hub 上

在企业网络 / 项目中使用 Docker 时,请设置自己的 Docker 注册表,而不是使用公共的 Docker Hub。所有云提供商都有自己的容器注册服务

Host(宿主机)

当然,Container 并不能像真正的电脑一样,它仅仅包含了运行应用所需要的组件(轻量化),并且它只是一个软件,需要运行在硬件上,需要网卡,CPU,内存等等,而它运行的硬件,就被称为 Host,也就是宿主机

当 Container 运行在你的电脑上时,Host 就是你的电脑,当它运行在远程的服务器上时,Host 就是服务器

Docker 为何

为什么程序员应该学习 Docker?

环境部署一致性

在容器技术诞生之前,所有的应用都是直接部署在操作系统上面的,彼此之间共享底层的操作系统资源,比如内存、CPU、磁盘等

例如,我们要将应用 A 和应用 B 部署到同一台机器上,那么这两个应用需要的环境信息,我们都需要满足。如果应用 A 和 B 的环境依赖之间存在冲突,或者说不兼容,管理起来就会非常的困难

而 Docker 容器提供了在不同环境中实现一致性的方法,从而减少了开发、测试和生产环境之间的问题。这有助于避免 “在我的机器上可以工作” 的问题

快速部署

使用 Docker,可以将应用程序和它们的依赖项打包到容器中,然后快速部署到任何支持 Docker 的主机上,大大缩短了部署时间

资源(应用)隔离

很容易理解,比如不同的应用依赖了冲突的基础软件包,如果都直接部署在操作系统环境下,必然会引起冲突。这个时候就需要应用之间互相隔离

容器则提供了资源隔离,确保不同容器之间的应用程序不会相互干扰,这有助于提高安全性和稳定性

弹性伸缩

Docker 容器可以根据负载需求进行快速伸缩,从而提高应用程序的性能和可用性

开发人员生产力

Docker 容器可以使开发人员更轻松地创建开发环境,快速测试新功能,并提供了一种便捷的方式来与团队共享开发环境

跨平台兼容性

Docker 容器可以在不同操作系统和云平台上运行,使应用程序更具可移植性

Docker 如何

安装 Docker

访问官网 https://docker.p2hp 下载客户端,官网会自动判断你的操作系统类型,为你提供对应的安装包,下载前先确认是否和你的操作系统匹配

为方便以后使用,可以注册一个帐号

下载后运行安装包程序,根据不同的 Windows 版本,选项可能不同,默认即可:

等待安装完成

安装成功

安装成功后,桌面上就会岀现 Docker Desktop 的图标:

双击启动,第一次运行,需要接受使用协议

Docker 和 Docker Desktop 是两个东西,由于我们使用的是桌面版的系统,所以有 Docker 的桌面程序,实际上在实际生产环境中,为了节省资源,并不会安装 Docker Desktop,而是直接通过命令行的方式调用

使用 Docker 创建并运行容器

进入Docker Desktop:

首页提示:

Try running a container:Copy and paste this command into your terminal and then come back

尝试运行容器:将此命令复制并粘贴到您的终端,然后返回

按照该提示,打开终端,输入以下命令,使用 Docker 创建并运行容器:

$ docker run -d -p 80:80 docker/getting-started

关于 Docker 的 run 命令在 通过容器运行镜像 会介绍

因为默认是从国外源拉取 Image,如果下载很慢,可以将 Docker 拉取 Image 路径其替换为国内的

先在终端中 Ctrl + C 终止 Docker 命令,然后参考后文 Docker 仓库配置 替换拉取源

Docker 仓库配置完成后,再去指定运行命令:

$ docker run -d -p 80:80 docker/getting-started

下载就会很快了

等待运行完成以后,输入 docker ps 命令,就可以看到所有已启动的 Docker 容器信息

可以发现已经启动了一个 Docker 容器

通过 Docker Desktop 查看 Containers 也可以看到所有已启动的 Docker 容器:

在 Docker Desktop 点击该容器的 Port(s),或者在浏览器中打开 http://localhost,都可以查看这个容器的运行效果:

因此,通过 Docker 启动一个别人写好的网站程序,是十分简单的

Docker 仓库配置

当你需要购买一张操作系统的光盘时,需要去商店购买,那么 Docker 的 Image 又是从哪里获取呢?

Docker 的大多数应用的 Image 都是从中央仓库获取,但是这个仓库在国外,在国内连接速度不稳定或很慢,因此我们需要将 Docker 拉取 Image 的地址替换为国内

进入 Docker Desktop,点击设置

点击左侧菜单 【Docker Engine】,将其仓库地址配置为国内源:

直接将下面代码替换文本框中即可:

{
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "enabled": true
    }
  },
  "experimental": false,
  "registry-mirrors": [
    "http://hub-mirror.c.163",
    "https://docker.mirrors.ustc.edu",
    "https://registry.docker-cn"
  ]
}

然后点击 【Apply & restart】应用并重启

更新 Docker Desktop

打开 Docker Desktop 设置,点击 【Software updates】:

Docker Desktop 4.24.1 (123237) is currently the newest version available.

Docker Desktop 4.24.1(123237)是目前可用的最新版本。

这里会显示当前 Docker Desktop 版本以及是否为最新版本

如果不是最新,可以点击 【Ckeck for updates】进行更新

更新完成后重启 Docker Desktop 即可

使用 Docker 拉取镜像安装软件

现在,我们可以使用 Docker 通过拉取 Image 来安装绝大多数我们可能使用到的软件,例如 Mysql 5.7

那么如何精确的找到我们我们需要安装的 Image 的名称呢?

进入 https://hub.docker/

因为是国外源,如果无法进入,可以访问镜像站点 https://hub.axlinux.top

进入后,在上方搜索框中输入 mysql:

可以看到所有关于 Mysql 的镜像,我们使用的是原版的 Mysql,因此点第一个:

进入后点击 Tags 标签,就可以看到所有可以安装的 mysql image 版本

向下找我们需要的版本,然后点击对应版本右边的复制按钮就会复制这条命令:

$ docker pull mysql:5.7

然后在终端中粘贴并运行,Docker 就会将 mysql 5.7 的 Image 拉取到我们的电脑上:

需要注意:官方打包镜像时,一般仅会打包 X86 架构的镜像,因此如果你的电脑是 AMD 架构的,那么可能需要寻找对应架构的镜像

拉取完成后,就可以在本地的镜像列表中查看了

在终端中输入 docker images 命令,列出当前 Host 上的所有镜像,可以看到已经有了 MySql 5.7:

也可以通过 Docker Desktop 查看 Images:

这样,我们就学会了 Docker 的两个命令:

  • docker pull 镜像名称:版本号:它会帮我们从镜像仓库中拉取需要的镜像
  • docker images:列出当前 Host 上的所有镜像

注意:当你 pull 镜像时没有输入版本号,Docker 会默认拉取 latest,这通常是最新的镜像,但是你无法控制具体的版本,例如在拉取 mysql:5.7 时,如果没有输入 5.7,那么拉取的就会是 MySql 最新镜像,如 Mysql 8.1.0-1.el8

通过容器运行镜像

镜像拉取完成以后,我们就可以创建并运行容器来运行这个镜像

终端输入以下命令,创建并运行容器 mysql 来运行镜像 mysql:5.7:

$ docker run -d -p 3306:3306 --privileged=true  -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7
命令含义
docker run通过一个 Image,生成 Container 并运行
-d后台运行,生成守护进程,当前终端命令关闭后或命令被打断,也不停止 Container
-p 3306:3306指定端口映射,格式为:主机(Host)端口:容器端口,将Container 的 3306 端口,映射到 Host 的 3306 端口
-e MYSQL_ROOT_PASSWORD=123456在容器中设置一个环境变量 MYSQL_ROOT_PASSWORD 为 123456,目的是设置容器中 MySql 的密码为 123456
–name mysql设置容器名称为 mysql,在一个 Host 上必须唯一
mysql:5.7镜像名称,根据镜像 mysql:5.7 运行

如果已经创建过了,可以在 Docker Desktop 的【Containers】中直接启动或停止:

容器运行报错端口冲突

输入容器运行命令后,提示错误:

cb1775698cf5da45fd0494a57df3394818378650a1aa26f272c93dd36ee90e63
docker: Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:3306 -> 0.0.0.0:0: listen tcp 0.0.0.0:3306: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.

来自守护进程的错误响应:端口不可用:暴露端口TCP 0.0.0.0:3306->0.0.0.0:0:侦听TCP 0.0.0.0:306:绑定:通常只允许使用每个套接字地址(协议/网络地址/端口)一次

这时虽然创建好了容器 mysql,但无法运行,因为镜像运行出错,提示端口映射错误,这是因为在宿主机,也就是本地电脑中端口 3306 已被占用(本地 MySql 占用了)

Container 运行可能会报错,可以通过 docker ps 命令查看正在运行的 Container,确认该容器是否启动:

可以看到的确没有容器 mysql,也就说明容器 mysql 没有启动

如果没有启动,这时就需要输入 docker ps -a 命令,它会显示 所有状态 的 Container信息

所有状态就是包括已启动的和已创建等状态的所有容器

可以看到容器 mysql 的 STATUS 是 “Created”,说明该容器已经创建但没有运行

status /ˈsteɪtəs/ 状态

容器 jovial_curie 的 STATUS 是 “Up About an hour”,说明该容器已经创建并运行了将近 1 小时

这时可以通过 docker restart 容器ID或名称 重启该容器试试能否解决错误

$ docker restart mysql
mysql

这时再查看正在运行的 Container:

可以发现容器 mysql 正常启动了,也可以在 Docker Desktop 中查看 Container:

再查看所有状态的容器信息:

可以发现:容器 mysql 的 STATUS 是 “Up 6 seconds”,说明该容器已经创建并运行了 6 秒

也可以将容器映射宿主机端口设置为 3307,避免与宿主机的 MySql 冲突:

$ docker run -d -p 3307:3306 --privileged=true  -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7

这时可以去 Navicat 中测试是否能够连接到 Docker 部署的 MySql 5.7 容器:

将容器映射宿主机端口设置为了 3307,因为本地 MySql 占用了 3306 端口

连接成功


如果想查看为什么运行容器失败了,那么就需要查看容器的日志

输入 docker logs -f 容器ID或容器名称,就可以查看对应容器的日志

容器名称和容器 ID,在当前的 Host 上唯一:

比如说查看容器 mysql 的日志:

$ docker logs -f mysql

查看日志时无法操作终端,需要在终端输入 Ctrl + C 退出查看,返回操作终端

Container 的停止、重启和回收

当我们的电脑重启后,Docker 也会重启,这时如果我们的 Container 没有设置自动重启,那么在使用时,就需要手动启动它了:

$ docker start 容器ID或容器名称

重启指定容器:

例如上一节创建并运行容器 mysql 报错

$ docker restart 容器ID或容器名称

停止指定容器:

$ docker stop 容器ID或容器名称

回收(删除)指定容器:

$ docker rm 容器ID或容器名称

回收(删除)指定镜像:

$ docker rmi 容器ID或容器名称

Dockerfile

当前启动容器时的问题

目前我们已经使用了这样一条命令来生成一个 Mysql 的 Container:

$ docker run -d -p 3306:3306 --privileged=true -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7

但是在实际使用时,会遇到以下几个问题:

  1. 命令太长,可能会输入错误,在团队之间传递容易发生混乱
  2. 如果这个容器回收,那么里面的数据也将消失
  3. 过于复杂不好记忆

解决问题 1:命令太长

启动的命令是由多个部分组成的,那么能否像写文章一样分段、分含义描述?

解决方案:使用 Dockerfile 文件来编写命令,构建一个自定义的 Image。Dockerfile 是一种文本文件,它包含一系列指令和配置,用于自动化构建 Docker 容器镜像,定义了如何从一个基础镜像开始,逐步添加文件、命令和设置,以构建最终的镜像

在本机任意路径下新建文件 Dockerfile ,里面的内容为原本的运行命令,改为按行描述:

注意文件没有后缀名

# 使用官方MySql5.7镜像
FROM mysql:5.7

# 设置MySql的root用户密码
ENV MYSQL_ROOT_PASSWORD 123456

# 设置MySql容器端口
EXPOSE 3306

# 可以在这里添加其他自定义操作,如创建数据库、用户等

# 这里不需要CMD指令,因为基础镜像中已经包含了启动MySQL服务的命令

构建一个新镜像 my-mysql:v1

通过存放 Dockerfile 的路径打开终端,然后运行命令:

$ docker build -t my-mysql:v1 .

注意最后还有个 .

docker build 即构建镜像,-t 参数为镜像的名称,. 表示使用当前目录下的 Dockerfile 文件

使用 docker images 命令,查看当前生成的镜像:

现在,我们就可以通过容器直接启动这个镜像了,而不再需要指定其他额外的参数,启动命令变的十分简洁,例如初始密码 123456,它已经在镜像中内置了,无需额外说明:

$ docker run -d -p 3306:3306 --name my-mysql my-mysql:v1

解决问题 2:持久化数据

容器启动的 Mysql 数据的数据,一般来说存放在它的 /var/lib/mysql 目录下,如果想持久化数据,那么只需要把这个数据放在 Host 上即可,即使容器回收,Host 上的文件也是不会消失的

这个原理,和网盘的同步目录类似

Tips:Mysql 的数据文件是如何存储的?

数据文件:这包括数据库表的实际数据,通常以 .ibd 和 .frm 文件的形式存储

表结构和索引:这包括 MySQL 表的结构和索引信息,通常以 .frm 文件存储表结构,而索引和其他元数据则存储在这个目录中

修改 Dockerfile,增加挂载目录的参数 VOLUME,将 Mysql 的数据文件挂载到 Host 中想用于存放数据的目录,自定义即可:

# 使用官方MySql5.7镜像
FROM mysql:5.7

# 设置MySql的root用户密码
ENV MYSQL_ROOT_PASSWORD 123456

# 设置MySql容器端口
EXPOSE 3306

# 将宿主机的数据存放目录挂载到容器中
VOLUME /teachDockerfile:/var/lib/mysql

# 可以在这里添加其他自定义操作,如创建数据库、用户等

# 这里不需要CMD指令,因为基础镜像中已经包含了启动MySQL服务的命令

重新生成镜像 my-mysql:v2:

$ docker build -t my-mysql:v2 .

. 表示 Dockerfile 位于当前目录

运行 my-mysql:v2:

$ docker run -d -p 3306:3306 --name my-mysqlV2 my-mysql:v2

此时前面创建的镜像和容器已经没用了,可以回收(删除)前面创建的镜像和容器节省空间

先查看所有容器:

除了容器 my-mysqlV2,其他都可以回收:

也可以通过 Docker Desktop 操作,前文已说明方法

$ docker rm my-mysql
my-mysql
$ docker rm mysql
mysql
$ docker rm jovial_curie
jovial_curie

再来看看所有容器:

其他容器都已回收成功

再查看所有镜像:

除了镜像 my-mysql:v2,其他都可以回收:

$ docker rmi my-mysql:v1
Untagged: my-mysql:v1
Deleted: sha256:d813246ea321fe4cfcac908a9336eff8e50b4b5587a6dea1cc15cf3b04fb8d71
$ docker rmi mysql:5.7
Untagged: mysql:5.7
Untagged: mysql@sha256:a06310bb26d02a6118ae7fa825c172a0bf594e178c72230fc31674f348033270
Deleted: sha256:a5b7ceed4074932a04ea553af3124bb03b249affe14899e2cd746d1a63e12ecc
$ docker rmi latest
docker rmi docker/getting-started
Untagged: docker/getting-started:latest
Untagged: docker/getting-started@sha256:d79336f4812b6547a53e735480dde67f8f8f7071b414fbd9297609ffb989abc1
Deleted: sha256:3e4394f6b72fccefa2217067a7f7ff84d5d828afa9623867d68fce4f9d862b6c
Deleted: sha256:cdc6440a971be2985ce94c7e2e0c2df763b58a2ced4ecdb944fcd9b13e7a2aa4
Deleted: sha256:041ac26cd02fa81c8fd73cc616bdeee180de3fd68a649ed1c0339a84cdf7a7c3
Deleted: sha256:376baf7ada4b52ef4c110a023fe7185c4c2c090fa24a5cbd746066333ce3bc46
Deleted: sha256:d254c9b1e23bad05f5cde233b5e91153a0540fa9a797a580d8a360ad12bf63a9
Deleted: sha256:dd5c79fa9b6829fd08ff9943fc1d66bebba3e04246ba394d57c28827fed95af0
Deleted: sha256:8d812a075abf60a83013c37f49058c220c9cdf390266952126e7e60041b305dc
Deleted: sha256:ff1787ee3dcae843dc7dd1933c49350fc84451cf19ed74f4ea72426e17ee7cd1
Deleted: sha256:85ebd294be1553b207ba9120676f4fd140842348ddf1bb5f7602c7a8401f0a13
Deleted: sha256:ded7a220bb058e28ee3254fbba04ca90b679070424424761a53a043b93b612bf

再次查看所有镜像:

其他镜像都已回收成功

拓展 - 执行默认数据库初始化 SQL 文件

当 Mysql 容器启动时,我们想执行一下默认的数据库初始化 SQL 文件

例如当数据库启动时,执行 user_service.sqledu_service.sqlblog_service.sql,自动帮我们在一个新的数据库中创建 user_service、edu_service 和 blog_service 三个库,并将对应的表也创建岀来

想实现这一点,需要 步

(1)sql 文件存放到指定位置

user_service.sqledu_service.sqlblog_service.sql 存放到 Dockerfile 文件所在目录下

(2)在 Dockerfile 中声明 sql 拷贝路径

打开 Dockerfile,修改为以下:

# 使用官方MySql5.7镜像
FROM mysql:5.7

# 设置MySql的root用户密码
ENV MYSQL_ROOT_PASSWORD 123456

# 设置MySql容器端口
EXPOSE 3306

# 将当前路径的指定文件拷贝到 mysql 容器的 /docker-entrypoint-initdb.d/ 目录下
# 这个目录是 MySql 镜像的特定目录,MySql 容器将自动执行该目录中的 SQL 文件
COPY user_service.sql /docker-entrypoint-initdb.d/
COPY edu_service.sql /docker-entrypoint-initdb.d/
COPY blog_service.sql /docker-entrypoint-initdb.d/

# 将宿主机的数据存放目录挂载到容器中
VOLUME /teachDockerfile:/var/lib/mysql

# 可以在这里添加其他自定义操作,如创建数据库、用户等

# 这里不需要CMD指令,因为基础镜像中已经包含了启动MySQL服务的命令

然后再通过 Dockerfile 存放路径进入终端

运行以下命令构建一个新镜像 my-mysql:v3,根据这个镜像创建并运行容器 my-mysqlV3:

构建新就像 my-mysql:v3 比较久,大概需要 1 min,因为需要执行 3 个 SQL 文件

$ docker build -t my-mysql:v3 .
...
$ docker run -d -p 3306:3306 --name my-mysqlV3 my-mysql:v3

此时报错:3306 端口已被占用,因此只是创建了该 Container 但没有运行

这是因为之前创建的 my-mysqlV2 也是映射宿主机端口 3306,所以我们要把它删除:

也可以修改映射宿主机端口为 3307,防止与本地 MySql 服务冲突

$ docker rm my-mysqlV2
Error response from daemon: You cannot remove a running container 3917ef5320ad72c0a0dafa7376a8e20e3b6b62a4477667c3c573e8af1f4e1922. Stop the container before attempting removal or force remove

删除报错,提示容器 my-mysqlV2 正在运行,因此我们需要先把它停止,然后再删除:

$ docker stop my-mysqlV2
my-mysqlV2
$ docker rm my-mysqlV2
my-mysqlV2

删除成功,再重启容器 my-mysqlV3:

$ docker restart my-mysqlV3
my-mysqlV3

重启成功,查看当前已运行容器:

删除了容器 my-mysqlV2,还要删除镜像 my-mysql:v2:

$ docker rmi my-mysql:v2
Untagged: my-mysql:v2
Deleted: sha256:e9fc18b03c57e46bebf2ef6e82c75d6149fe2514d9d33af087dd1c433ed18416

删除成功

此时去 Navicat 中查看 Docker 部署的 MySql 5.7 容器,可以发现里面多了指定执行的 SQL 数据库:

修改映射宿主机端口为 3307

解决问题 3:过于复杂不好记

分析以下 docker run 命令,尝试将其转换为 Dockerfile

$ docker run -it --name userService -d -e spring.cloud.bootstrap.enabled=true -p 8081:8081 -v /root/UserService-0.0.1-SNAPSHOT.jar:/app/app.jar kdvolder/jdk8 /bin/bash -c "java -jar /app/app.jar"

Dockerfile

# 使用一个基础的JDK8镜像
FROM openjdk:8

# 设置工作目录
WORKDIR /app

# 复制应用程序JAR文件到容器中
COPY UserService-0.0.1-SNAPSHOT.jar /app/app.jar

# 暴露应用程序的端口
EXPOSE 8081

# 运行Spring Boot应用程序
CMD ["java", "-jar", "app.jar"]

镜像的导出和导入

当我们在工作中,构建了一个镜像后,需要将其分发给团队的其他成员使用,有两种实现方式:

  • 将镜像提交到镜像仓库中,通常是一个团队的私有仓库

  • 使用 docker save -odocker load -i 命令手动在 Host 上导出导入

镜像导出

docker save 命令语法:

$ docker save -o <output_file_name>.tar <image_name>
  • <output_file_name>:导出的镜像保存到本地的文件名
  • <image_name>:要导出的镜像的名称

例如,想将 my-mysql:v3 镜像导出并给其他成员使用,那么可以执行命令:

$ docker save -o our-mysql.tar my-mysql:v3

$ dir
 驱动器 D 中的卷是 Data
 卷的序列号是 8A87-7E80

 D:\codeSet\Text 的目录

2023/10/13  09:32    <DIR>          .
2023/10/13  08:22    <DIR>          ..
2023/10/12  13:39            35,692 blog_service.sql
2023/10/13  09:11               785 Dockerfile
2023/10/12  13:39             7,048 edu_service.sql
2023/10/13  09:32       600,899,584 our-mysql.tar
2023/10/13  08:46    <DIR>          teachDockerfile
2023/10/12  13:39            27,627 user_service.sql
               5 个文件    600,970,736 字节
               3 个目录 60,167,131,136 可用字节

运行后,将在终端当前路径生成一个名叫 our-mysql.tar 的文件

镜像导入

docker load 命令语法:

$ docker load -i <input_file_name>.tar
  • <input_file_name> 是包含导出镜像的 .tar 文件的名称

例如,我们想将刚刚导出的 our-mysql.tar 导入,那么就可以执行:

$ docker load -i our-mysql.tar

在一台 our-mysql 镜像(即 my-mysql:v2) 的 Host 上导入后,就可以在它上面运行了

Docker-Compose(容器编排)

容器编排

目前使用 Docker 的困境

  1. 部署的都是单容器应用,在实际生产中并没有太大的价值

  2. 在大型应用中,使用单个容器无法解决部署问题

    例如微服务架构的第一次大型作业,就很难通过单容器部署。因为至少需要 7 个容器:4 个微服务 + MySql + Redis + Nacos

  3. 横向扩展较困难,无法增大并发能力和鲁棒性。

    例如 UserService 占用端口 8081,无法启动两个 UserService

  4. 运维困难,除了开发者,无法知道整个应用的部署架构

使用一个 Dockerfile 模板文件可以定义一个单独的应用容器,如果需要定义多个容器就需要服务编排

Docker-Compose 是何

compose /kəmˈpəʊz/ v.组成

Docker-Compose 项目是 Docker 官方的开源项目, 基于 Docker,负责实现对 Docker 容器集群的快速编排,通过配置文件以及简单的命令,来帮我们管理多容器的复杂应用程序

后文简称 Compose

Dockerfile 可以让用户管理一个单独的应用容器,而 Compose 则允许用户在一个模板(yaml 格式)中定义一组相关联的 应用容器

被称为一个 Project,即项目

Compose 将所管理的容器分为三层, 分别是工程、服务、容器

Project、Service、Container

Compose 运行目录下的所有文件组成一个工程,如无特殊指定,工程名即为当前目录名

docker-compose.yml、extends 文件或环境变量等

一个工程当中,可以包含多个服务,每个服务中定义了容器运行的镜像、参数、依赖

一个服务中可以包括多个容器实例,Compose 并没有解决负载均衡的问题。因此需要借助其他工具实现服务发现及负载均衡,比如 Consul

Compose 的工程配置文件默认为 docker-compose.yml。可以通过环境变量 COMPOSE_FILE -f 参数自定义配置文件,其自定义多个有依赖关系的服务及每个人服务运行的容器

与 Docker 当初从一众的虚拟化技术中脱颖而出不同,Docker-Compose 目前面临着极大的技术竞争压力,不同的公司使用的 Docker 编排工具也不尽相同:

Docker-Compose 为何

使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独应用容器。但在工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况,例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等

Compose 允许用户通过一个单独的 docker-compose.yml 模板文件来定义一组相关联的应用容器为一个项目

Docker-Compose 项目由 Pypthon 编写,调用 Docker 服务提供的 API 来管理镜像,因此, 只要所操作的平台支持 Docker-API,就可以在其上利用 Compose 来进行编排管理

简单来说:就是来管理多个容器的,定义启动顺序的,合理编排,方便管理

Docker-Compose 如何

安装

如果是 Windows,那么安装完成 Docker Desktop 后,就已经包含了 Docker-Compose 工具了

在终端输入以下命令查看 Docker-Compose 版本:

$ docker-compose version
Docker Compose version v2.22.0-desktop.2

如果是在 Linux 服务器上,使用 apt-get install docker-compose 来安装

假设我们已经开发完成一个项目,需要交付给客户使用,如何交付?

以微服务架构的第一次团队作业为例

使用 Compose 交付
思路

首先,确定部署架构,来规划网络和容器资源

经过对部署架构的分析,我们总共需要 8~10 个容器来部署我们的应用

部署代码需要的容器为:

网关 1 个,用户服务 2 个(提高性能),教务服务 1 个,论坛服务 1 个

部署工具需要的容器为:

Nacos 1 个,Mysql 1~3 个,Redis 1 个

网络方面的需求为:

  1. Gateway 需要提供对外访问,其余都不需要提供对外访问
  2. 所有服务都需要内部网络互通
  3. Nacos、Mysql、Redis 需要提供运维网络

整体思路:

因为 Mysql、Redis 和 Nacos 都会使用 Docker 动态部署,因此先修改代码配置文件中的连接地址,从外部注入,然后使用 Maven 将需要交付的源代码打包成 Jar 包,并制作成镜像,再使用 Compose 将代码加环境整体运行起来

读取环境变量

修改代码中需要连接 Mysql,Redis 和 Nacos 的地方,使用读取环境变量的方式,而非硬编码:

${环境变量名称:默认值}

修改 UserService 的 application.yml

修改 UserService 的 bootstrap.yml

格式:

${XXX_HOST:127.0.0.1}
${XXX_PORT:XXXX}

EduService、BlogService 也是按照这样修改

全部修改完成后,重新打包整个项目:

配置模块打包镜像

打包成功后,去每个模块下的 target 中,将 Maven 帮我们打包好的 Jar 包拷贝

创建一个文件夹 deployment,并创建 4 个子文件夹:

  • gateway、user-service、edu-service 和 blog-service

然后分别将对应的 Jar 包放于文件夹下:

然后在每个文件夹下添加 Dockerfile

  • 拉取官方的 JDK 8 镜像
  • 将同级目录下的 Jar 包通过 CP 命令拷贝进去,重命名为 app.jar
  • 设置启动命令 java -jar app.jar

根据如上条件,UserService 的打包镜像 Dockerfile

# 使用JDK8作为基础镜像
FROM openjdk:8-jre

# 指定该自定义镜像的作者信息
LABEL maintainer="LiiiYiAn"

# 将.jar文件复制到容器中
COPY UserService-0.0.1-SNAPSHOT.jar /app.jar

# 设置启动命令
ENTRYPOINT [ "java", "-jar", "/app.jar" ]

其余三个模块的 Dockerfile 也是同理,例如 EduService 的打包镜像 Dockerfile

# 使用JDK8作为基础镜像
FROM openjdk:8-jre

# 指定该自定义镜像的作者信息
LABEL maintainer="LiiiYiAn"

# 将.jar文件复制到容器中
COPY EduService-0.0.1-SNAPSHOT.jar /app.jar

# 设置启动命令
ENTRYPOINT [ "java", "-jar", "/app.jar" ]

四个模块的 Dockerfile 完成后,开始生成对应镜像

启动 Docker Desktop 以启动 Docker,分别在四个模块对应的文件目录下,生成 Docker 的 Image,将版本命名为 v1

例如,在 user-service 目录下进入终端,执行命令:

$ docker build -t user-service:v1 .

如果是第一次执行该命令,会去拉取 openjdk:8-jre 镜像,过程会长一些,但是等拉取完成后,它就存在于你电脑的镜像库中了,后续其他镜像打包就会更快

如果提示错误:“failed to solve: failed to read expected number of bytes: unexpected EOF”

这是因为下载 openjdk:8-jre 镜像失败了,具体原因不知道,但可以先单独拉取 openjdk:8-jre 镜像:

$ docker pull openjdk:8-jre

拉取成功

然后再执行命令:

$ docker build -t user-service:v1 .

这时就能正常执行了:

然后再分别通过 edu-service、blog-service、gateway 目录进入终端,执行对应命令

例如,通过 edu-service 目录进入终端,执行命令:

$ docker build -t edu-service:v1 .

在终端中,可以通过上箭头快速生成上一条命令

因为拉取好了 openjdk:8-jre,所以现在的构建镜像速度非常快

在四个模块的代码镜像打包完成后,使用以下命令查看宿主机 Docker 当前所有镜像:

$ docker images

镜像 my-mysql:v3 无关

拉取其他组件镜像

除了代码外,部署还需要 MySql 8.0.28、Redis 5.0.3 和 Nacos 2.2.3,在 Docker 中拉取:

$ docker pull mysql:8.0.28
...
$ docker pull redis:5.0.3
...
$ docker pull nacos/nacos-server:v2.2.3
...

最后,所有部署需要的镜像如下:

  • gateway:v1

  • user-service:v1

  • edu-service:v1

  • blog-service:v1

  • nacos/nacos-server:v2.2.3

  • mysql:8.0.28

  • redis:5.0.3

数据库初始化脚本

除此之外,由于应用启动时,需要有对应的数据库(user-service、edu-service 和 blog-service)及数据库表,所以我们要将数据库 .sql 文件也放在 deployment 目录下,在创建 MySql 时自动执行

也可以将创建一个自定义的 mysql 镜像,将这些库和表预置进去

控制启动顺序

由于我们的应用在启动前,需要等待 Nacos 启动完成,因此需要一个可以让程序代码延迟启动的工具 wait-for-it,可以从 GitHub 上下载:https://github/vishnubob/wait-for-it

下载后解压,将其中的 wait-for-it.sh 拷贝到 deployment 目录下:

编写 docker-compose.yml

所有部署的软件准备好以后,开始编写 docker-compose.yml,使用它来组织和启动应用所需的所有容器

在 deployment 目录下新建 docker-compose.yml

第一部分:先部署 NACOS、MYSQL、REDIS,并开放运维端口,创建数据持久化目录:

# 第一行定义docker-coompose的版本为 3
version: '3'

# 定义要启动的容器,用services标签,一个service就是一个服务,代表一个容器集群
services: 
  # 先部署Nacos
  nacos:
    # 使用的镜像是拉取到本地的镜像,如果没有,则会动态拉取
    image: nacos/nacos-server:v2.2.3
    # 运维需要开放的端口,nacos开放8848,在docker-compose中非必需
    ports: 
      - "8848:8848"
    # 单独启动
    environment:
      - MODE=standalone

  # 部署MySql
  mysql: 
    image: mysql:8.0.28
    environment: 
      - MYSQL_ROOT_PASSWORD=123456
    # 运维需要开放端口,非必需
    ports: 
      - "3307:3306"
    # 挂载初始化脚本
    volumes: 
      - mysql_data:/var/lib/mysql
      - ./blog_service.sql:/docker-entrypoint-initdb.d/blog_service.sql
      - ./edu_service.sql:/docker-entrypoint-initdb.d/edu_service.sql
      - ./user_service.sql:/docker-entrypoint-initdb.d/user_service.sql

  # 部署Redis
  redis: 
    image: redis:5.0.3
    # 运维需要开放端口,非必需
    ports: 
      - "6379:6379"
    # 数据持久化,容器重新创建后数据也存在
    volumes: 
      - redis_data:/data

第二部分 - 1:部署代码 gateway、user-service:

在这里通过环境变量的方式设置 MySql 地址、Nacos 地址等信息

# 第一部分代码,注意下面缩进,属于services标签里的

  # 部署代码
  # 部署gateway
  gateway: 
    image: gateway:v1
    # 开放对外服务端口
    ports: 
      - "80:80"
    # gateway启动需要依赖于Nacos
    depends_on: 
      - nacos
    # 设置Nacos的发现地址,在配置文件中使用${NACOS_HOST}来代替的
    environment: 
      # docker-compose中,名称就相当于ip
      - NACOS_HOST=nacos
      - SPRING_CLOUD_BOOTSTRAP_ENABLED=true
    # 启动命令
    command: ["/bin/sh", "-c", "/app/wait-for-it.sh nacos:8848 -- java -jar /app.jar"]

    volumes: 
      - ./wait-for-it.sh:/app/wait-for-it.sh
    
  # 部署user-service
  user_service: 
    image: user-service:v1
    # 依赖于MySql、Redis和Nacos
    depends_on: 
      - mysql
      - redis
      - nacos
    # 设置环境变量,包括MYSQL地址、REDIS地址、NACOS地址等
    environment: 
      - MYSQL_HOST=mysql
      - MYSQL_PORT=3307
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - NACOS_HOST=nacos
      - SPRING_CLOUD_BOOTSTRAP_ENABLED=true
    # 启动命令
    command: ["/bin/sh", "-c", "/app/wait-for-it.sh nacos:8848 -- java -jar /app.jar"]
    volumes: 
      - ./wait-for-it.sh:/app/wait-for-it.sh

第二部分 - 2,部署代码 edu-service、blog-service:

# 第二部分-1代码,,注意下面缩进,属于services标签里的

  # 部署edu-service
  edu_service: 
    image: edu-service:v1
    depends_on: 
      - mysql
      - nacos
    environment: 
      - MYSQL_HOST=mysql
      - MYSQL_PORT=3307
      - NACOS_HOST=nacos
      - SPRING_CLOUD_BOOTSTRAP_ENABLED=true
    # 启动命令
    command: ["/bin/sh", "-c", "/app/wait-for-it.sh nacos:8848 -- java -jar /app.jar"]
    volumes: 
      - ./wait-for-it.sh:/app/wait-for-it.sh

  # 部署blog-service
  blog_service: 
    image: blog-service:v1
    depends_on: 
      - mysql
      - nacos
    environment: 
      - MYSQL_HOST=mysql
      - MYSQL_PORT=3307
      - NACOS_HOST=nacos
      - SPRING_CLOUD_BOOTSTRAP_ENABLED=true
    # 启动命令
    command: ["/bin/sh", "-c", "/app/wait-for-it.sh nacos:8848 -- java -jar /app.jar"]
    volumes: 
      - ./wait-for-it.sh:/app/wait-for-it.sh

第三部分,定义挂载卷用来存放持久化数据:

# 第二部分-2代码,注意代码缩进,与services标签同级

# 挂载卷,用于持久化
volumes: 
  mysql_data: 
  redis_data: 
启动应用

在 deployment 目录下进入终端,运行以下命令启动:

$ docker-compose up -d

该命令也适用于做了更新后重启应用

如果想提高 user-service 的并发能力,可以在启动时加入参数:--scale user_service=2

这样就会启动两个 user-service

启动成功,通过 docker ps 就可以看到已经启动的容器:

在 Docker Desktop 中查看启动容器,可以看到 7 个容器已编排为容器集群 deployment:

可以像正常的测试接口一样,测试服务是否正常


如果启动失败,可以使用 docker-compose logs 来查看日志,分析错误

如果只有部分服务没有启动,那么可以通过 docker logs -f 容器名称 / 容器 ID 来查看具体那个启动失败的容器日志

因为没有指定容器名称,所以只能使用容器 ID 来匹配容器

排错完成后,执行以下两条命令:

$ docker-compose down  # 终止Docker-Compose服务
$ docker-compose up -d  # 启动Docker-Compose服务

如果需要将所有持久化的数据也删除,使用 docker-compose down -v 命令

测试服务

由于我们部署在自己的电脑上,因此可以通过 localhost 来访问接口、Nacos、Redis 和 Mysql

此时已经启动完成,可以在浏览器中打开 Nacos 管理页,根据上面的 docker-compose.yml 中配置的地址,是 localhost:8848/nacos/index.html:

点击【服务管理】→【服务列表】,查看服务是否已经全部注册:

服务全部注册成功

查看 MySql 8.0.28 是否连接正常,以及数据库初始化脚本是否成功:

数据库正常

本文标签: 容器 引擎 docker