Play With Docker | Docker 应用程序部署教程(2)

参考:Docker 101 TutorialPlay with Docker


5. Persisting our DB

你应该注意到,我们的 todo list 在每次启动容器时都会被擦干净。为什么会这样?让我们深入了解一下容器是如何工作的。

The Container’s Filesystem

当一个容器运行时,它使用来自一个映像的不同层作为它的文件系统。每个容器还具有其自己的 暂存空间创建/更新/删除 文件。即使使用相同的映像,也不会在其他容器中看到任何更改。

Seeing this in Practice

为了了解这一点,我们将启动两个容器,并在每个容器中创建一个文件。你会看到,在一个容器中创建的文件在另一个容器中不可用。

  1. 启动一个 ubuntu 容器,该容器将创建一个名为 /data.txt 的文件,其随机数在 1 到 10000 之间。
    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
    这个命令的意思是,我们将启动 bash shell 并调用两个命令(这就是为什么会有&&)。第一部分选择一个随机数并将其写入 /data.txt。第二个命令只是监视文件以保持容器运行。
  2. 我们可以通过运行 exec 命令来验证我们看到的输出。要通过 docker ps 命令来得到 container’s ID:
    docker exec <container-id> cat /data.txt
    
    你会看到一个随机数。
  3. 现在,启动另一个 ubuntu 容器(使用同一个映像),然后我们会发现没有相同的文件
    docker run -it ubuntu ls /
    
    可以看到这里没有 data.txt 文件,这是因为它只在第一个容器的独立空间中。
  4. 我们继续,使用 docker rm -f 命令来删除第一个容器
Container Volumes

在前面的实验中,我们看到每个容器实际上都是只读的。虽然容器可以创建、更新和删除文件,但当容器被删除并与该容器隔离时,这些更改将丢失。有了卷( Volumes),我们可以改变这一切。

Volumes 提供了将容器的特定文件系统路径连接回主机的能力。如果装载了容器中的目录,则在主机上也会看到该目录中的更改。如果我们跨容器重新启动装载相同的目录,我们会看到相同的文件。

Volumes 有两种主要类型。最终,我们将同时使用两者,但我们先从 named volumes 开始。

Persisting our Todo Data

默认情况下,todo 应用程序将其数据存储在 /etc/todos/todo.db 的 SQLite 数据库中。如果你不熟悉 SQLite,请不用担心!它只是一个关系数据库,其中所有数据都存储在一个文件中。虽然这对于大型应用程序不是最佳选择,但适用于小型演示。稍后我们将讨论将其切换到实际的数据库引擎。

由于数据库是一个单独的文件,如果我们能够将该文件持久化到主机上并使其可供下一个容器使用,那么它应该能够从上一个容器停止的地方恢复。通过创建一个 volume 并将其附加(通常称为“装载”)到存储数据的目录中,我们可以持久化数据。当我们的容器写入 todo.db 文件,它将被持久化到 volume 中的主机。

如前所述,我们将使用 named volumes。将 named volumes 简单地看作一个数据桶。Docker 维护磁盘上的物理位置,你只需记住 volume 的名称。每次使用 volume 时,Docker 都会确保提供正确的数据。

  1. 通过 docker volume create 命令来创建一个 volume:

    docker volume create todo-db
    
  2. 启动 todo 应用程序的容器,但这次要加上 -v 标记来指定一个 volume mount。我们将使用 named volume 并且把它装载到 /etc/todos,它将捕获在该路径上创建的所有文件。

    docker run -dp 3000:3000 -v todo-db:/etc/todos docker-101
    
  3. 一旦容器启动,打开应用程序并将一些项目添加到你的 todo 列表中。
    5.1

  4. 删除 todo 应用程序的容器。使用 docker ps 获取 ID,然后使用 docker rm -f <ID> 删除它。

  5. 用上面相同的命令启动一个新的容器。

  6. 打开 todo app,你可以看到刚刚添加的 todo list。

  7. 检查完列表后,请继续移除容器。

Hooray! 你现在已经学会了如何持久化数据。

Pro-tip
虽然 named volume 和 bind mounts(稍后我们将讨论)是默认 Docker 引擎安装支持的两种主要 volume 类型,但有许多 volume 驱动程序插件可用于支持 NFS、SFTP、NetApp 等!一旦你开始在集群环境中的多个主机上运行容器(包括Swarm、Kubernetes等),这一点就显得尤为重要。

Diving into our Volume

很多人经常会问 “当我使用 named volume 时,Docker 实际上在哪里存储我的数据?” 如果你想知道,可以使用 Docker volume inspect 命令。

docker volume inspect todo-db
[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint 是磁盘上存储数据的实际位置。请注意,在大多数计算机上,你将需要具有 root 用户访问权限才能从主机访问此目录。但是,它就在那里!

Recap

至此,我们有了一个可以正常运行的应用程序,可以在重启后继续运行!我们可以向投资者展示它,并希望他们能抓住我们的眼光!

但是,我们较早时看到,每次更改重建映像都需要花费大量时间。必须有一种更好的方式进行更改,对不对?使用 bind mounts(我们在前面已经暗示过),有更好的方法!让我们现在来看一下!


6. Using Bind Mounts

在上一章中,我们讨论并使用 named volume 将数据持久化到数据库中。如果我们只想存储数据,named volume 是很好的,因为我们不必担心数据存储在哪里。

通过 bind mounts,我们可以控制主机上的确切挂载点。我们可以使用它来持久化数据,但通常用于向容器中提供额外的数据。在处理应用程序时,我们可以使用绑定挂载将源代码挂载到容器中,让它看到代码更改、响应,并让我们立即看到更改。

对于基于节点的应用程序,nodemon 是一个很好的工具,可以监视文件更改,然后重新启动应用程序。在大多数其他语言和框架中都有相应的工具

Quick Volume Type Comparisons

bind mounts 和 named volume 是 Docker 引擎附带的两种主要类型的 volumes。但是,可以使用其他 volumes 驱动程序来支持其他用例(SFTP、Ceph、NetApp、S3等)。

Named VolumesBind Mounts
Host LocationDocker choosesYou control
Mount Example (using -v)my-volume:/usr/local/data/path/to/data:/usr/local/data
用容器内容填充新卷YesNo
Supports Volume DriversYesNo
Starting a Dev-Mode Container

要运行容器以支持开发工作流,我们将执行以下操作:

  • 将源代码装入容器
  • 安装所有依赖项,包括“dev”依赖项
  • 启动 nodemon 以监视文件系统更改

开始吧

  1. 确保没有运行任何以前的 docker-101 容器。

  2. 运行以下命令。我们将解释之后发生的事情:

    docker run -dp 3000:3000 \
    -w /app -v $PWD:/app \
    node:10-alpine \
    sh -c "yarn install && yarn run dev"
    
    • -dp 3000:3000 - 和之前一样。在分离(后台)模式下运行并创建端口映射
    • -w /app - 设置运行命令的 “工作目录” 或当前目录
    • node:10-alpine - 要使用的映像。请注意,这是来自我们刚才的 todo-app 的 Dockerfile 的基本映像
    • sh -c "yarn install && yarn run dev" - 这是我们要执行的命令。我们使用 sh(alpine 没有 bash)启动 shell,运行 yarn install 来安装所有依赖项,然后运行 yarn run dev。如果查看 package.json,我们将看到 dev 脚本正在启动 nodemon
  3. 您可以使用 docker logs -f <container id> 查看日志。当你看到这个你就知道你准备好了。。。

    docker logs -f <container-id>
    $ nodemon src/index.js
    [nodemon] 1.19.2
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching dir(s): *.*
    [nodemon] starting `node src/index.js`
    Using sqlite database at /etc/todos/todo.db
    Listening on port 3000
    

    看完日志后,按 Ctrl + C 退出。

  4. 现在,让我们对应用程序进行更改。在 src/static/js/app.js 文件中,我们将 “Add Item” 按钮更改为仅说 “Add”。此更改将在第 109 行上。

    -    {submitting ? 'Adding...' : 'Add Item'}
    +    {submitting ? 'Adding...' : 'Add'}
    
  5. 只需刷新页面(或打开页面),你应该立即在浏览器中看到所做的更改。重新启动 Nodejs 服务器可能需要花费几秒钟的时间,因此,如果出现错误,请尝试在几秒钟后刷新。
    6.1

  6. 随时进行你想进行的其他任何更改。完成后,停止容器并使用 docker build -t docker-101 . 构建新映像。

对于本地开发,使用 bind mounts 是非常常见的。优点是 dev 机器不需要安装所有的构建工具和环境。只需一个 docker run 命令,dev 环境就可以启动并准备就绪。我们将在以后的步骤中讨论 Docker Compose,因为这将有助于简化我们的命令。

Recap

为了准备生产,我们需要将数据库从 SQLite 迁移到更好的数据库。为简单起见,我们将保留使用关系数据库,并将我们的应用程序切换为使用 MySQL 数据库。但是,我们应该如何运行 MySQL?我们如何允许容器彼此对话?接下来我们再说!


7. Multi-Container Apps

到目前为止,我们一直在使用单容器应用程序。但是,我们现在想将 MySQL 添加到应用程序堆栈中。下面的问题经常出现:MySQL 将在哪里运行?在同一个容器中安装或单独运行?一般来说,每个容器都应该只做一件事并把它做好。有几个原因:

  • 你很有可能必须以与数据库不同的方式扩展 API 和前端
  • 单独的容器可以单独进行版本控制和更新版本
  • 尽管你可能在本地使用数据库的容器,但可能要在生产环境中使用数据库的托管服务。而且,你不想把你的数据库引擎和你的应用一起发布。
  • 运行多个进程将需要一个进程管理器(容器仅启动一个进程),这增加了容器启动/关闭的复杂性

还有更多的原因。因此,我们将更新我们的应用程序以如下方式工作:
7.1

Container Networking

我们知道,容器运行在被隔绝的进程上,即使在同一个机器上,它对别的进程和容器一无所知。那么,我们如何让一个容器与另一个容器通信呢?答案是网络。

但是你不必要成为一个网络工程师(Hooray!),只需简单遵循以下规则:

如果两个容器在同一网络上,则它们可以相互通信。如果不是,就不行。

Starting MySQL

将容器放到一个网络上有两种方法:

  • 在开始时分配它,
  • 连接现有容器。

现在,我们将首先创建网络,并在启动时附加 MySQL 容器。

  1. Create the network.

    docker network create todo-app
    
  2. 启动一个 MySQL 容器并将其连接到网络。我们还将定义一些环境变量,数据库将使用这些变量来初始化数据库(请参见 MySQL Docker Hub 列表中的 “Environment Variables” 部分)。

    docker run -d \
    --network todo-app --network-alias mysql \
    -v todo-mysql-data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -e MYSQL_DATABASE=todos \
    mysql:5.7
    

    你看到我们指定了 --network alias 标志。我们一会儿再谈。

    Pro-tip
    注意到我们在这里使用了一个名为 todo-mysql-data 的 volume,并将其挂载在 /var/lib/mysql 上,mysql 就是在这里存储数据的。但是,我们从未运行 docker volume create 命令。Docker 意识到我们想要使用一个 named volume,并自动为我们创建一个。

  3. 要确认数据库已启动并运行,请连接到数据库并验证它是否已连接。

    docker exec -it <mysql-container-id> mysql -p
    

    出现密码提示时,输入密码(123456)。在 MySQL Shell 中,列出数据库并确认你看到了 todos 数据库。

    mysql> SHOW DATABASES;
    

    你应该看到如下输出:

    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    | todos              |
    +--------------------+
    5 rows in set (0.00 sec)
    
Connecting to MySQL

现在我们知道 MySQL 已启动并正在运行,但是,问题是…How?如果我们在同一网络上运行另一个容器,我们如何找到该容器(记住每个容器都有自己的 IP 地址)?

为了弄清楚,我们将使用 nicolaka / netshoot 容器,该容器附带了许多工具,这些工具可用于故障排除或调试网络问题。

  1. 使用 nicolaka/netshoot 映像启动一个新容器。确保将其连接到同一网络。

    docker run -it --network todo-app nicolaka/netshoot
    
  2. 在容器内部,我们将使用 dig 命令,这是一个有用的 DNS 工具。我们将查找主机名 mysql 的 IP 地址。

    dig mysql
    

    你会得到这样的输出:

    ; <<>> DiG 9.14.1 <<>> mysql
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;mysql.             IN  A
    
    ;; ANSWER SECTION:
    mysql.          600 IN  A   172.23.0.2
    
    ;; Query time: 0 msec
    ;; SERVER: 127.0.0.11#53(127.0.0.11)
    ;; WHEN: Tue Oct 01 23:47:24 UTC 2019
    ;; MSG SIZE  rcvd: 44
    

    在 “ANSWER SECTION” 部分,您将看到 mysqlA 记录,解析为 172.23.0.2(你的IP地址很可能跟这里不同)。虽然 mysql 通常不是有效的主机名,但 Docker 能够将其解析为具有该网络别名的容器的 IP 地址(还记得我们前面使用的 --network-alias 标志吗?)。

    这意味着。。。我们的应用程序只需要连接到一个名为 mysql 的主机,它就能与之通信!再简单不过了!

Running our App with MySQL

todo 应用程序支持设置一些环境变量来指定 MySQL 连接设置:

  • MYSQL_HOST - 正在运行的 MySQL 服务器的主机名
  • MYSQL_USER - 用于连接的用户名
  • MYSQL_PASSWORD - 用于连接的密码
  • MYSQL_DB - 连接后要使用的数据库

Warning

  • 虽然通常可以使用 env vars(环境变量)设置连接设置来进行开发,但是在生产环境中运行应用程序时,强烈建议不要使用它。 Docker 的前安全主管 Diogo Monica 写了一篇很棒的博客文章,解释了为什么。
  • 一种更安全的机制是使用容器编排框架提供的私密安全支持。在大多数情况下,这些机密会作为文件安装在正在运行的容器中。你会看到许多应用程序(包括 MySQL 映像和 todo 应用程序)也支持带有 _FILE 后缀的 env vars,以指向包含该密码文件的文件。
  • 例如,设置 MYSQL_PASSWORD_FILE 环境变量将导致应用程序引用文件的内容用作连接密码。但是,现在 Docker 没有做任何事情来支持这些环境变量。你的应用程序需要知道如何查找变量并获取文件内容。

解释完所有这些之后,让我们开始我们的 dev-ready 容器!

  1. 我们将指定上面的每个环境变量,并将容器连接到我们的应用程序网络。

      docker run -dp 3000:3000 \
      -w /app -v $PWD:/app \
      --network todo-app \
      -e MYSQL_HOST=mysql \
      -e MYSQL_USER=root \
      -e MYSQL_PASSWORD=secret \
      -e MYSQL_DB=todos \
      node:10-alpine \
      sh -c "yarn install && yarn run dev"
    
  2. 如果我们查看容器的日志(docker logs <container id>),我们应该会看到一条消息,表示它正在使用 mysql 数据库。

    # Previous log messages omitted
    $ nodemon src/index.js
    [nodemon] 1.19.2
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching dir(s): *.*
    [nodemon] starting `node src/index.js`
    Connected to mysql db at host mysql
    Listening on port 3000
    
  3. 在浏览器中打开应用程序,然后向待办事项列表中添加一些项目。

  4. 连接到 mysql 数据库并证明这些项已经写入数据库。记住,密码是 123456。

    docker exec -ti <mysql-container-id> mysql -p todos
    

    在 mysql shell 中,运行以下命令:

    mysql> select * from todo_items;
    +--------------------------------------+--------------------+-----------+
    | id                                   | name               | completed |
    +--------------------------------------+--------------------+-----------+
    | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! |         0 |
    | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome!        |         0 |
    +--------------------------------------+--------------------+-----------+
    

    显然,你的数据表看起来会有所不同,因为它包含你添加的项目。但是,它们就存储在这里!

Recap

至此,我们有了一个应用程序,该应用程序现在将其数据存储在单独容器中的外部数据库中。我们了解了一些有关容器网络的知识,并了解了如何使用 DNS 执行服务发现(service discovery)。

但是,很有可能您会对启动该应用程序所需做的一切感到有点不知所措。因为我们必须创建一个网络,启动容器,指定所有环境变量,公开端口等等!这要记住很多,这肯定会使事情很难传递给其他人。

在下一节中,我们将讨论 Docker Compose。使用 Docker Compose,我们可以更轻松地共享我们的应用程序堆栈,并让其他人用一个简单的命令来启动它们!


8. Using Docker Compose

9. Image Building Best Practices
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值