学习 Docker - 从头开始,第三部分 数据库和链接
在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris
长话短说:这篇文章有点长,但我保证,中间肯定有精彩内容。中间会有一些痛苦和煎熬,但我们最终会胜利的 :)
本文是系列文章的一部分:
- Docker — 从第一部分开始,涵盖了为什么使用 Docker、基本概念以及管理图像和容器等所需的命令。
- Docker — 从第二部分开始,这是关于卷的,以及如何使用卷来保存数据,以及如何将我们的开发环境转变为卷,并使我们的开发体验大大改善
- Docker — 从头开始,第三部分,我们在这里
- Docker — 从头开始,第 IV 部分,介绍如何使用 Docker Compose 管理多个服务(这是 Docker Compose 的一半)
- Docker - 从头开始,第五部分,本部分是 Docker Compose 的第二部分,也是最后一部分,其中我们介绍了卷、环境变量以及如何使用数据库和网络
这是我们系列的第三部分。在本部分中,我们将重点学习如何将数据库和 Docker 协同工作。我们还将介绍链接的概念,因为它与在容器化环境中使用数据库紧密相关。
在本文中,我们将介绍以下内容:
- 介绍一些使用 MySql 的基础知识,介绍一些管理数据库的基础知识总是好的,尤其是 MySql,是本文选择的数据库类型
- 了解为什么我们需要使用 MySql Docker 镜像,我们还将介绍如何从该镜像创建容器以及需要设置哪些环境变量才能使其工作
- 学习如何使用链接从我们的应用程序容器连接到我们的 MySql 容器,这是关于如何在两个容器之间进行基本链接,以及如何在定义数据库启动配置时利用这一点
- 扩展我们对链接的知识,通过介绍链接容器的新方法,有两种链接方式,其中一种方式比另一种更受欢迎,而另一种方式已被弃用,因此我们将介绍如何完成新的链接方式
- 描述一些管理数据库的好方法,例如为其提供初始结构并为其提供种子
资源
使用 Docker 和容器化就是将单体应用拆分成微服务。在本系列文章中,我们将学习掌握 Docker 及其所有命令。您迟早会想把容器部署到生产环境中。这个环境通常是云端。当您觉得自己拥有足够的 Docker 经验后,可以查看以下链接,了解如何在云端使用 Docker:
- 云中的容器精彩的概述页面,展示了有关云中容器的其他信息
- 在云中部署容器教程展示了如何轻松利用您现有的 Docker 技能并在云中运行您的服务
- 创建容器注册表您的 Docker 镜像可以存储在 Docker Hub 中,也可以存储在云端的容器注册表中。将镜像存储在某个地方,然后只需几分钟就能从该注册表创建服务,岂不是很棒?
数据库操作,特别是 MySql
对于数据库,我们通常希望能够执行以下操作:
- 读取,我们希望能够使用不同类型的查询来读取数据
- 修改/创建/删除,我们需要能够更改数据
- 添加结构,我们需要一种方法来创建像表或集合这样的结构,以便我们的数据以特定的格式保存
- 添加种子/初始数据。在简单的数据库中,这可能完全不需要,但在更复杂的场景中,您需要一些表格来预先填充一些基本数据。在开发阶段,拥有种子数据也非常有用,因为它可以轻松渲染某些视图或测试不同的场景(如果已经存在预先存在的数据或数据处于特定状态,例如购物车中有商品,而您想测试结账页面)。
我们希望对数据库做更多的事情,比如添加索引、添加具有不同访问权限的用户等等,但让我们重点关注上述四点,作为我们在 Docker 的帮助下能够支持的内容的参考。
安装并连接到 MySql
安装 MySql 的方法有很多,但并非所有方法都很快 :/。在 Linux 系统上,一种更简单的方法是输入以下命令:
sudo apt-get 安装 mysql 服务器
在 Mac 上,您可以使用 brew,然后输入:
brew 安装 mysql
在其他一些情况下,您可以下载安装程序包并按照向导进行操作。
一旦完成 MySql 的安装,您将获得一些类似这样的信息,当然,每个安装包的信息都会有所不同:
上面的信息告诉我们,我们还没有 root 密码,哎呀。不过,我们可以通过运行 mysql-secure-installation 来解决这个问题。现在,我们先运行建议的 mysql -uroot 来连接到数据库。
不,发生了什么?实际上,我们从上面更大的图片中得到了这些信息,我们需要启动 MySql,要么运行 brew services start mysql ,这会将其作为后台服务运行,要么使用 mysql-server start ,这更像是一次性操作。好的,让我们输入 brew services start :
最后显示的内容是“Successfully started mysql”:

让我们看看马修是否正确,我们可以联系起来吗?
我们得到了上面的提示mysql>
,我们进入了:D
好的,我们设法使用无密码进行连接,我们应该修复它,我们没有创建任何数据库,我们也应该修复它:)
嗯,这并不完全正确,我们确实有一些数据库,不仅仅是任何由您自己创建的内容的数据库,而是我们不应该触及的支持性数据库:
接下来就是创建并选择新创建的数据库,以便我们可以从中查询:
好的,太棒了,但是等等,我们还没有表?没错,我们需要以某种方式创建它们。我们可以在终端中创建它们,但那样会很麻烦,需要写很多很多的多行语句。所以,我们看看能不能给 MySql 提供一个文件,其中包含数据库和我们想要的所有表。首先,我们定义一个可以不断添加表的文件:
// database.sql
// creates a table `tasks`
CREATE TABLE IF NOT EXISTS tasks (
task_id INT AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
start_date DATE,
due_date DATE,
status TINYINT NOT NULL,
priority TINYINT NOT NULL,
description TEXT,
PRIMARY KEY (task_id)
);
// add more tables below and indeces etc as our solution grows
好的,我们有一个具有数据库结构的文件,现在为了获取其中的内容,我们可以使用源命令,如下所示(屏蔽用户名):
如果您想要文件所在位置的完整路径,只需在文件所在位置输入 PWD,然后输入 source [sql 文件路径]/database.sql。如上所示,我们需要在运行 SQL 文件之前选择一个数据库,以便它能够定位到特定的数据库,然后使用 SHOW TABLES 验证表是否已创建;我们也可以使用相同的命令为数据库添加数据,只需给它一个不同的文件进行处理,该文件包含 INSERT 语句,而不是 CREATE TABLE……
好的。我想现在关于 MySql 的讨论就到此为止了,接下来我们来谈谈 Docker 环境下的 MySql。
为什么我们需要 MySql Docker 镜像以及如何将其作为容器启动并运行
好的,现在假设我们需要一个容器来容纳我们的应用程序。此外,假设我们的解决方案需要一个数据库。在运行应用程序的主机上安装 MySql 意义不大。我的意思是,容器的一大原则就是我们不必关心容器运行的主机系统。好的,我们需要在容器中安装 MySql,但是应该在哪个容器中呢?应用程序自己的容器还是单独的容器?这是一个好问题,具体取决于您的具体情况。您可以随应用程序一起安装 MySql,也可以在单独的容器中运行它。对此的一些解释是:
- 滚动更新,您的数据库可以保持在线,同时您的应用程序节点进行单独重启,您不会遇到停机。
- 可扩展性,您可以以松散耦合的方式添加节点进行扩展
有很多支持和反对的论点,只有你清楚什么最适合你——所以你就去做吧:)
MySql 作为独立图像
我们来讨论一下拉取 MySql 镜像的场景。好的,我们将一步一步地进行,首先尝试运行它,正如我们在之前的文章中了解到的那样,如果我们没有镜像,它会自动拉取下来,命令如下:
docker run --name=mysql-db mysql
好的,那么输出是什么?
拉下图像,很好,然后出现错误。
我们做了很多错事 :(。
数据库未初始化,密码选项未指定等等。我们来看看能否修复这些问题:
docker run --name mysql-db -e MYSQL_ROOT_PASSWORD=complexpassword -d -p 8001:3306 mysql
获胜者是:
啊,我们之前启动的容器虽然报了错误,但仍然正常运行。好了,让我们关闭这个容器:
docker rm mysql-db
让我们尝试使用如上设置的数据库再次运行它:
好的,我们没有在终端上看到一大堆日志,因为我们在守护进程模式下运行,所以让我们运行 docker ps 来检查:
现在我们想从外部连接到它。我们的端口转发意味着我们需要在 0.0.0.0:8001 上连接它:
mysql -uroot -pcomplexpassword -h 0.0.0.0 -P 8001
好的,上面只是一条注释,我们可以指定密码,或者直接写 -p,下一行会提示我们输入密码。让我们看看结果:
好的,我们成功连接到我们的数据库,可以访问,太棒了:)。
但是等等,我们能从另一个容器访问容器内部的数据库吗?嗯,这里面有点棘手。我们需要将两个容器连接起来。
从 Node.js 连接到数据库
好的,我们先在应用中添加一些尝试连接数据库的代码。首先,我们需要安装 mysql 的 NPM 包:
npm 安装 mysql
然后我们将以下代码添加到 app.js 文件的顶部:
// app.js
const mysql = require('mysql');
const con = mysql.createConnection({
host: "localhost",
port: 8001,
user: "root",
password: "complexpassword",
database: 'Customers'
});
con.connect(function (err) {
if (err) throw err;
console.log("Connected!");
});
那么让我们在终端中尝试一下:
痛苦和悲惨 :(
那么为什么它不起作用呢?
这是因为 MySQL 8.0 中引入了 caching_sha2_password,但 Node.js 版本尚未实现。
好吧,那我们试试 Postgres 或其他数据库吗?其实我们可以通过连接到容器来解决这个问题,如下所示:
mysql -uroot -pcomplexpassword -h 0.0.0.0 -P 8001
一旦我们进入 mysql 提示符,我们就可以输入以下内容:
mysql> ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY 'complexpassword';
mysql> FLUSH PRIVILEGES;
让我们尝试再次运行 node app.js 命令,这次我们得到以下结果:
最后!
好的,调用一下这个 mysql_native_password 似乎就能修复所有问题。让我们深入挖掘一下这个“兔子洞”,它到底是什么?
MySQL 包含一个mysql_native_password插件,可实现本机身份验证;也就是说,基于在引入可插入身份验证之前使用的密码哈希方法进行身份验证
好的,这意味着 MySql 8 已经切换到一些新的可插入式身份验证,而我们的 Node.js mysql 库尚未跟上。这意味着我们可以下载旧版本的 MySql,或者恢复到原生身份验证,一切由您决定 :)
链接
链接的理念是,容器无需知道数据库(在本例中是运行在哪个 IP 或端口上)的任何细节。它只需假设应用程序容器和数据库容器可以相互访问即可。典型的语法如下:
docker run -d -p 5000:5000 --name product-service --link mypostgres:postgres chrisnoring/node
让我们稍微分解一下上述内容:
- 应用容器,我们正在创建一个名为 product-service 的应用容器
- --link 我们调用此命令将我们的产品服务容器与现有的 mypostgres 容器链接起来
- 链接别名,语句 --link mypostgres:postgres 表示我们指定要与 mypostgres 链接的容器,并为其指定一个 别名。当我们尝试连接到数据库时,
postgres
我们将在容器内部使用此别名。product-service
好的,我们认为我们已经掌握了链接的基础知识,让我们看看如何将其应用于我们现有的 my-container 以及如何将其链接到我们的数据库容器mysql-db
。因为我们启动 my-container 时没有链接它,所以我们需要将其拆除并使用 --link 命令作为附加参数重新启动它,如下所示:
docker kill 我的容器 && docker rm 我的容器
这样就关闭了容器。不过,在启动它之前,我们实际上需要修改一些代码,也就是与连接数据库有关的部分。我们将使用--link mysql-db:mysql
参数将其链接到数据库容器,这意味着我们不再需要 IP 或 PORT 引用,因此我们的连接代码现在看起来像这样:
// part of app.js, the rest omitted for brevity
const con = mysql.createConnection({
**host: "mysql",**
user: "root",
password: "complexpassword",
database: 'Customers'
});
// and the rest of the code below
区别在于,现在我们的主机不再是 localhost,端口也没有明确说明,8001
因为链接时会计算出这一点,我们需要关注的只是在链接时给数据库容器赋予了什么别名,例如mysql
。因为我们都修改了代码,并且添加了另一个库 myql ,所以我们需要重建镜像,如下所示:
docker build-t chrisnoring/node。
现在让我们重新开始,这次使用链接:
docker run -d -p 8000:3000 --name my-container --link mysql-db:mysql chrisnoring/node
让我们看一下输出:
我们使用 docker logs my-container 查看来自应用程序容器的日志,最后一行显示 Connected!,它来自回调函数,告诉我们已成功连接到 MySql。
你知道现在几点吗?轰!

扩展我们对链接的了解——还有另一种方法
好的,一切都很好。我们设法将应用程序放在一个容器中,将数据库放在另一个容器中。但是……我们刚才看到的这种链接被认为是遗留的,这意味着我们需要学习另一种链接方式。等等……它并没有你想象的那么糟糕,实际上从语法上看起来更好。
这种新方法称为创建网络或自定义桥接网络。实际上,这只是我们可以创建的一种网络类型。关键在于,它们是专用组,您可以指定您的容器应该属于这些组。如果一个容器对多个组具有跨切关注点,则可以存在于多个网络中。听起来很棒,请给我演示一下语法,好吗?
我们要做的第一件事是创建我们希望应用容器和数据库容器所属的网络。我们使用以下命令创建该网络:
docker network create --driver bridge isolated_network
现在我们有了网络,只需将新创建的网络作为附加参数创建每个容器即可。让我们从应用程序容器开始:
docker run -d -p 8000:3000 --net isolated_network --name my-container chrisnoring-node
上面我们使用 --net 语法创建了一个名为 isolated_network 的网络,其余部分只是我们创建和运行容器时使用的常规语法。很简单 :)
那么数据库容器怎么样?
docker run -p 8001:3306 --net isolated_network --name mysql-db -e MYSQL_ROOT_PASSWORD=complexpassword -d mysql
正如您上面看到的,我们只是创建并运行我们的数据库容器,但添加了--net isolated_network。
回顾这种方式,我们不再需要明确地说一个容器需要主动链接到另一个容器,我们只需将容器放置在特定的网络中,并且该网络中的容器就知道如何相互通信。
关于网络还有很多东西需要学习,包括不同的类型和大量的命令。我认为我们已经掌握了要点,例如它与传统链接的区别。点击此链接,了解更多关于网络的文档。
容器设置中的常规数据库管理
好的,我们在本节开头讨论了如何将数据库结构创建为文件,如何将种子数据创建为文件,以及如何在连接到 MySql 提示符后运行它们。事情是这样的。如何获取这些结构和种子数据取决于你,但我会尝试传达一些通用的指导原则和想法:
- 结构,您希望在某个时刻运行此文件。要访问数据库,您可以使用
mysql
客户端连接并引用主机外部的文件。或者,您可以将此文件作为应用程序仓库的一部分,然后让 Dockerfile 将其复制到容器中并运行它。另一种方法是查看支持迁移的库(例如 Knex 或 Sequelize),并在正在运行的迁移中指定数据库结构。这是一个完全不同的主题,但我只是想向您展示处理此问题的不同方法。 - 种子,你可以像处理结构一样处理种子。这是在获取结构后需要执行的操作,并且是一次性的。你不希望每次启动应用程序或数据库时都执行此操作,并且根据种子是一些基本的结构数据还是一些测试所需的数据,你很可能需要手动执行此操作,例如,你
mysql
使用客户端连接到数据库并主动运行 source 命令
概括
好的,本文涵盖了有关 MySql 的一些一般信息,并讨论了如何在其中获取一些结构化的东西,比如表,我们还讨论了初始/测试种子以及如何使用独立文件将其放入数据库中。
然后,我们讨论了为什么应该在 Docker 容器中运行数据库,而不是安装在主机上,并继续展示了如何使用传统的链接技术让两个容器相互通信。我们也花了一些时间抱怨 MySql 8 和mysql
Node.js 模块不同步的问题,这迫使我们不得不修复它。
一旦我们涵盖了遗留的链接位,我们就不能就此止步,而需要讨论链接容器的新方法,即使用网络。
最后,我们分享了一些关于数据库管理的一般准则,希望您现在已经了解如何向我们的应用程序添加数据库并继续开发应用程序。
在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris
鏂囩珷鏉ユ簮锛�https://dev.to/azure/docker-from-the-beginningpart-iii-2h51