教程:在 VS Code 中使用卷在容器应用中永久保存数据

本教程介绍如何在容器应用程序中保存数据。 运行或更新数据时,数据仍可用。 有两种用于保存数据的主要卷类型。 本教程重点介绍已命名的卷

此外,还将介绍绑定装载,它控制主机上的确切装载点。 可以使用绑定装载来保存数据,但它还可以将更多数据添加到容器中。 在处理应用程序时,可以使用绑定装载将源代码装载到容器中,让其看到代码更改、响应并立即看到更改。

本教程还介绍了图像分层、层缓存和多阶段生成。

本教程介绍如何:

  • 理解跨容器的数据。
  • 使用已命名的卷永久保存数据。
  • 使用绑定装载。
  • 查看映像层。
  • 缓存依赖项。
  • 了解多阶段生成。

先决条件

本教程继续学习上一教程, 使用 Visual Studio Code 创建和共享容器应用。 从这一项开始,其中包括先决条件。

了解跨容器的数据

在本部分中,你将启动两个容器,并在每个容器中创建一个文件。 在一个容器中创建的文件在另一个容器中不可用。

  1. 使用以下命令启动 ubuntu 容器:

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    此命令使用 && 启动对两个命令的调用。 第一部分选取单个随机数并将其写入 /data.txt。 第二个命令用于监控文件以确保容器持续运行。

  2. 在 VS Code 的容器资源管理器中,右键单击 ubuntu 容器并选择 “附加 Shell”。

    屏幕截图显示了容器工具扩展,其中选择了容器,并且在上下文菜单中选择了“连接 Shell”。

    此时会打开一个在 Ubuntu 容器中运行 shell 的终端。

  3. 运行以下命令以查看 /data.txt 文件的内容。

    cat /data.txt
    

    终端显示介于 1 和 10000 之间的数字。

    若要使用命令行查看此结果,请使用 docker ps 命令获取容器 ID,然后运行以下命令。

    docker exec <container-id> cat /data.txt
    
  4. 启动另一个 ubuntu 容器。

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
  5. 使用此命令查看文件夹内容。

    docker run -it ubuntu ls /
    

    该文件夹内应不包含 data.txt 文件,因为它只写入第一个容器的暂存空间。

  6. 选择这两个 Ubuntu 容器。 右键单击并选择删除。 在命令行中,可以使用 docker rm -f 命令将其删除。

使用已命名的卷永久保存待办事项数据

默认情况下,todo 应用将其数据存储在 SQLite 数据库/etc/todos/todo.db。 SQLite 数据库是存储单个文件的关系数据库。 此方法适用于小型项目。

可以在主机上保留单个文件。 当将其提供给下一个容器时,应用程序可以从中断的地方继续运行。 通过创建卷并将其附加或装载到存储数据的目录,可永久保存数据。 容器会写入 todo.db 文件,并数据永久保存在卷中的主机上

对于此部分,请使用已命名的卷。 Docker 维护磁盘上卷的物理位置。 参考卷的名称,Docker 会提供正确的数据。

  1. 使用 docker volume create 命令创建卷。

    docker volume create todo-db
    
  2. 在“容器”下,选择“入门”并右键单击。 选择 停止 以停止应用容器。

    若要从命令行停止容器,请使用 docker stop 命令。

  3. 使用以下命令启动“getting-started”容器

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    卷参数指定要装载的卷和位置 /etc/todos

  4. 刷新浏览器以重新加载应用。 如果已关闭浏览器窗口,请转到 http://localhost:3000/。 将某些项添加到待办事项列表中。

    屏幕截图显示了示例应用,其中的列表已添加多个项目。

  5. 移除待办事项应用的“getting-started”容器。 右键单击容器资源管理器中的容器,然后选择删除,或使用docker stopdocker rm 命令。

  6. 使用相同的命令启动新容器:

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    此命令将装载与之前相同的驱动器。 刷新浏览器。 添加的项仍在列表中。

  7. 再次移除“getting-started”容器

下面介绍的已命名的卷和绑定装载是默认 Docker 引擎安装支持的主要卷类型。

财产 命名卷 绑定装载
主机位置 Docker 选择 你控制
装载示例(使用 -v my-volume:/usr/local/data /path/to/data:/usr/local/data
使用容器内容填充新卷 是的
支持卷驱动程序 是的

有许多卷驱动程序插件可用于支持 NFS、SFTP、NetApp 等。 这些插件对于在群集环境(如 Swarm 或 Kubernetes)中的多个主机上运行容器尤其重要。

如果您想知道 Docker 到底 将数据存储在什么位置,请运行以下命令。

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 是存储数据的实际位置。 在大多数计算机上,需要根访问权限才能从主机访问此目录。

使用绑定装载

使用绑定装载,你可以控制主机上的确切装载点。 此方法保留数据,但通常用于向容器提供更多数据。 可以使用绑定装载将源代码挂载到容器中,这样它可以监测代码更改作出响应,同时你可以立即查看更改。

若要运行容器以支持开发工作流,请执行以下步骤:

  1. 移除所有 getting-started 容器。

  2. app 文件夹中,运行以下命令。

    docker run -dp 3000:3000 -w /app -v ${PWD}:/app node:lts-alpine sh -c "yarn install && yarn run dev"
    

    此命令包含以下参数。

    • -dp 3000:3000 与以前相同。 在分离模式下运行并创建端口映射。
    • -w /app 容器内的工作目录。
    • -v ${PWD}:/app" Bind 将当前目录从容器中的主机装载到 /app 目录中。
    • node:lts-alpine 要使用的映像。 此映像是 Dockerfile 中应用的基础映像
    • sh -c "yarn install && yarn run dev" 一个命令。 它使用 sh 启动 shell,并运行 yarn install 来安装所有依赖项。 然后它会运行 yarn run dev。 如果你查看 package.json,将看到 dev 脚本正在启动 nodemon
  3. 可以使用 docker logs来监视日志。

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

    在此列表中看到最终条目时,应用正在运行。

    观看完日志后,请在终端窗口中选择任意键,或在外部窗口中选择 ctrl +

  4. 在 VS Code 中,打开 src/static/js/app.js。 更改第 109 行上 添加项 按钮的文本。

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    保存更改。

  5. 刷新浏览器。 你应该会看到更改。

    屏幕截图显示了示例应用,其中包含按钮上的新文本。

  6. 删除node:lts-alpine容器。

  7. app 文件夹中,运行以下命令以删除 node_modules 在前面的步骤中创建的文件夹。

    rm -r node_modules
    

查看映像层

可以查看构成图像的层。 运行 docker image history 命令以查看用于在映像中创建每个层的命令。

  1. 使用 docker image history,查看在本教程前面部分创建的 getting-started 映像中的层

    docker image history getting-started
    

    结果应类似于此输出。

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "/app/src/ind…   0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    每行都表示图像中的一个层。 底部显示的是基本输出,顶部显示的是最新的输出。 使用此信息,可以查看每个层的大小,帮助诊断大型图像。

  2. 几行被截断。 如果添加 --no-trunc 参数,你将获得完整的输出。

    docker image history --no-trunc getting-started
    

缓存依赖项

层更改后,必须重新创建所有下游层。 下面还是 Dockerfile

FROM node:lts-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]

Dockerfile 中的每个命令都会成为映像中的新层。 为了最大程度地减少层数,可以重构 Dockerfile 以支持依赖项缓存。 对于基于节点的应用程序,这些依赖项在 package.json 文件中定义。

方法是首先仅复制该文件,安装依赖项,然后 然后 复制其他所有内容。 该过程仅在 package.json 发生更改时重新创建 yarn 依赖项。

  1. 先更新要在 中复制的 Dockerfile,安装依赖项,然后再复制其他所有内容package.json。 下面是新文件:

    FROM node:lts-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "/app/src/index.js"]
    
  2. 使用 docker build生成新映像。

    docker build -t getting-started .
    

    应会看到类似于以下结果的输出:

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:lts-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    所有层都已重建。 这是预期的结果,因为更改了 Dockerfile

  3. src/static/index.html进行更改。 例如,将标题更改为“The Awesome Todo App”。

  4. 现在,再次使用 docker build 生成 Docker 映像。 这一次,输出看起来应该稍有不同。

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:lts-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    由于使用的是生成缓存,因此这个过程应该更快。

多阶段生成

多阶段生成是一种难以置信的强大工具,可帮助使用多个阶段创建映像。 它们有几个优点:

  • 将生成时依赖项与运行时依赖项分开
  • 通过仅传送应用需要运行的内容来减小整体图像大小

本部分提供了简短的示例。

Maven/Tomcat 示例

生成基于 Java 的应用程序时,需要 JDK 才能将源代码编译为 Java 字节码。 生产中不需要该 JDK。 你可能正在使用 Maven 或 Gradle 等工具来帮助构建应用。 最终图像中也不需要这些工具。

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

此示例使用一个阶段(build)使用 Maven 执行实际的 Java 生成。 第二个阶段(从“FROM tomcat”开始)从 build 阶段复制文件。 最终图像只是创建过程的最后一个阶段,可以使用 --target 参数来覆盖该阶段。

React 示例

生成 React 应用程序时,需要 Node 环境才能将 JavaScript 代码、Sass 样式表等编译为静态 HTML、JavaScript 和 CSS。 如果不执行服务器端渲染,则甚至不需要生产版本的 Node 环境。

FROM node:lts-alpine AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

此示例使用 node:lts-alpine 映像执行生成,该生成将层缓存最大化,然后将输出复制到 nginx 容器中。

清理资源

请保留您迄今为止所做的一切,以继续这一系列教程。

后续步骤

你已了解用于保存容器应用数据的选项。

接下来要做什么?