跳转至

第33章:用Docker进行部署

Docker是一种流行的容器化技术,对应用程序的部署方式产生了巨大影响。容器是一种隔离应用程序的方式,允许你在同一台服务器上运行多个应用程序。

使用一个容器,而不是一个成熟的虚拟机,允许你的容器化应用程序共享主机的更多资源。反过来,这为你的应用程序留下了更多的资源,而不是消耗它们来支持虚拟机本身。

Docker几乎可以在任何地方运行,所以它提供了一个很好的方式来规范你的应用程序应该如何运行,从本地测试到生产。

Note

如果你需要复习一下Docker术语--容器和镜像等概念--请查看我们的Docker教程:https://www.raywenderlich.com/9159-docker-on-macos-getting-started

Docker Compose

本章还将告诉你如何使用Docker ComposeDocker Compose是一种指定不同容器列表的方式,这些容器作为一个单元一起工作。这些容器共享同一个虚拟网络,使它们之间的合作变得简单。

例如,通过Docker Compose,你可以用一个命令同时启动你的Vapor应用和PostgreSQL数据库实例。它们可以相互通信,但与同一主机上运行的其他实例隔离。

为开发设置VaporPostgreSQL

首先设置一个简单的开发配置,在Linux环境下测试你的应用程序。为了方便调试任何出现的问题,这将是一个比你在生产中使用的更简单的配置。

Note

本章的示例项目与第21章"验证"末尾的项目相同。你可以使用它,也可以继续使用你现有的项目。

在你项目的主目录下,创建一个名为develop.Dockerfile的文件,并添加以下内容:

#1
FROM swift:5.3
#2
WORKDIR /app
#3
COPY . .
#4
RUN swift package clean
RUN swift build -c release  --enable-test-discovery
RUN mkdir /app/bin
RUN mv `swift build -c release --show-bin-path` /app/bin
EXPOSE 8080
#5
ENTRYPOINT ./bin/release/Run serve --env local \
  --hostname 0.0.0.0

一个Dockerfile提供了为你的应用程序创建Docker容器的"配方"。以下是这个文件的作用。

  1. 使用Docker Hub仓库中5.3版本的swift镜像作为起点。
  2. 告诉Docker使用/app作为其工作目录。
  3. 将你的项目复制到Docker容器中。
  4. 构建你的项目并将可执行文件移到容器内的/app/bin。注意使用--enable-test-discovery。即使你没有运行任何测试,Swift也需要这样来构建你的项目。
  5. 告诉Docker如何启动Vapor应用程序。

接下来,也是在你项目的主目录下,创建一个名为docker-compos-develop.yml的文件,并添加以下内容:

# 1
version: '3'
# 2
services:
  # 3
  til-app:
    # 4
    depends_on:
      - postgres
    # 5
    build:
      context: .
      dockerfile: develop.Dockerfile
    # 6
    ports: 
      - "8080:8080"
    environment:
      - DATABASE_HOST=postgres
      - DATABASE_PORT=5432
  # 7
  postgres:
    # 8
    image: "postgres"
    # 9
    environment:
      - POSTGRES_DB=vapor_database
      - POSTGRES_USER=vapor_username
      - POSTGRES_PASSWORD=vapor_password

  # 10
  start_dependencies:
    image: dadarek/wait-for-dependencies
    depends_on:
      - postgres
    command: postgres:5432

Docker Compose文件指定了整个应用的"配方"及其所有的依赖性。以下是这个文件的作用:

  1. 指定Docker Compose的版本。
  2. 为这个应用程序定义服务。
  3. TIL应用程序定义一个服务。
  4. 设置对postgres服务的依赖性,以便Docker Compose首先启动PostgreSQL容器。
  5. 在当前目录下建立develop.Dockerfile。这是你之前创建的Dockerfile
  6. 使8080端口在主机系统上可以访问,并注入DATABASE_HOST环境变量。Docker Compose有一个内部DNS解析器。这允许til-app容器以postgres主机名连接到postgres容器。同时设置数据库的端口。你可以在这里指定你的应用程序需要的任何其他环境变量值,例如GitHub OAuth凭证。
  7. PostgreSQL数据库定义一个服务。
  8. 使用标准的postgres图像。
  9. 设置必要的环境变量。
  10. Docker会一次性启动所有的容器,PostgreSQL需要几秒钟的时间来准备接受连接。如果TILappPostgreSQL准备好之前启动,TILapp将崩溃。这项服务提供了一种方法,在启动你的应用程序之前确保数据库正在运行。

为了使你的应用程序活起来,在终端输入以下命令:

# 1
docker-compose -f docker-compose-develop.yml build
# 2
docker-compose -f docker-compose-develop.yml run --rm start_dependencies
# 3
docker-compose -f docker-compose-develop.yml up til-app

下面是这个的作用:

  1. 构建在docker-compos-develop.yml中定义的不同Docker镜像。
  2. 运行docker-compos-develop.yml中的start_dependencies服务,以确保PostgreSQL正在运行并准备就绪。
  3. 启动你的应用程序。

如果你收到一个错误,说明没有找到vapor数据库,按照下面的清理步骤,重试上面的命令,应用程序应该能成功启动。如果你的系统上有以前的Docker PostgreSQL镜像,可能会出现这个错误。

在你的浏览器中,访问http://localhost:8080,以验证应用程序已经启动和运行。当你准备好继续前进时,按Control-C来停止一切。然后,通过在终端输入以下内容来清理你的开发环境:

docker-compose -f docker-compose-develop.yml down
docker volume prune -f

这将关闭编译文件中任何正在运行的容器。然后,它将删除所有与你的应用程序相关的容器和网络定义。最后,它清理了任何你不能再访问的旧Docker存储。

为生产设置VaporPostgreSQL

你可以对你的Docker配置做一些改变,以简化生产环境中的应用管理。在这一节中,你将把你的应用程序分成一个"建设者"容器和一个生产镜像。你还将配置PostgreSQL容器,将其数据库保存在主机的文件系统中。这使得你的数据在你的应用程序及其配置的变化中持续存在。

Vapor模板已经包含一个适合生产的Docker文件,名为Dockerfile。在文本编辑器中打开该文件,检查它的内容。它看起来像这样:

# 1
FROM swift:5.3-focal as build

# 2
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && rm -rf /var/lib/apt/lists/*

# 3
WORKDIR /build

# 4
COPY ./Package.* ./
RUN swift package resolve

# 5
COPY . .
RUN swift build --enable-test-discovery -c release

# 6
WORKDIR /staging
RUN cp "$(swift build --package-path /build -c release \
    --show-bin-path)/Run" ./
RUN [ -d /build/Public ] && \
    { mv /build/Public ./Public && chmod -R a-w ./Public; } \
    || true
RUN [ -d /build/Resources ] && \
    { mv /build/Resources ./Resources && \
    chmod -R a-w ./Resources; } || true

# 7
FROM swift:5.3-focal-slim

# 8
RUN export DEBIAN_FRONTEND=noninteractive \
    DEBCONF_NONINTERACTIVE_SEEN=true && \
    apt-get -q update && \
    apt-get -q dist-upgrade -y && \
    rm -r /var/lib/apt/lists/*

# 9
RUN useradd --user-group --create-home --system \
    --skel /dev/null --home-dir /app vapor
# 10
WORKDIR /app
# 11
COPY --from=build --chown=vapor:vapor /staging /app
# 12
USER vapor:vapor
# 13
EXPOSE 8080
# 14
ENTRYPOINT ["./Run"]
CMD ["serve", "--env", "production", "--hostname", 
     "0.0.0.0", "--port", "8080"]
  1. 使用Docker Hub仓库中5.3版本的"swift"镜像作为起点。这个容器只用于构建你的应用,一旦Docker构建了应用,你可以删除它。
  2. 更新系统包,然后清理工作文件。在构建基于LinuxDocker镜像时,这种清理是一种标准操作。它可以减少镜像的整体大小。
  3. 告诉Docker使用/build作为其工作目录。
  4. 复制Package.swiftPackage.resolved并解决应用程序的依赖关系。如果需要的话,这可以让Docker在两次构建之间缓存依赖关系。
  5. 将你的项目复制到Docker容器中。用发布配置构建项目。
  6. 创建一个暂存目录,并将可执行文件和任何所需的库复制到其中。如果存在Public目录和Resources目录,也要复制。例如,如果你使用Leaf,你就需要这样做。
  7. 将你的生产镜像建立在Swift的薄型Docker镜像上。它只包含运行Swift可执行文件所需的内容。这比构建Swift可执行文件所需的镜像小得多。
  8. 更新所有软件包,然后清理工作文件。
  9. 创建一个用户来运行可执行文件。这样可以避免以root身份运行可执行文件,因为root身份可能会有安全风险。
  10. 告诉Docker使用/app作为工作目录。
  11. 从构建者容器中复制文件。
  12. 将用户设置为步骤9中创建的用户。
  13. 暴露8080端口,以便客户可以连接到Docker容器中的Vapor应用程序。
  14. 告诉Docker如何启动Vapor应用程序。

接下来,也是在你项目的主目录下,用文本编辑器打开docker-compose.yml。这包含了一个生产就绪的合成文件。其内容看起来类似于以下内容:

# 1
version: '3.7'

# 2
volumes:
  db_data:

# 3
x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  DATABASE_HOST: db
  DATABASE_NAME: vapor_database
  DATABASE_USERNAME: vapor_username
  DATABASE_PASSWORD: vapor_password

# 4
services:
  # 5
  app:
    # 6
    image: tilapp:latest
    # 7
    build:
      context: .
    # 8
    environment:
      <<: *shared_environment
    # 9
    depends_on:
      - db
    # 10
    ports:
      - '8080:8080'
    # 11
    command: ["serve", "--env", "production", "--hostname", 
              "0.0.0.0", "--port", "8080"]
  # 12
  db:
    # 13
    image: postgres:12-alpine
    # 14
    volumes:
      - db_data:/var/lib/postgresql/data/pgdata
    # 15
    environment:
      PGDATA: /var/lib/postgresql/data/pgdata
      POSTGRES_USER: vapor_username
      POSTGRES_PASSWORD: vapor_password
      POSTGRES_DB: vapor_database
    ports:
      - '5432:5432'

编译文件还包含migraterevert的服务,但为了简洁起见,这里不包括这些。以下是编译文件的作用。

  1. 指定Docker Compose的版本。
  2. 指定该应用程序使用的卷的列表。
  3. 定义一些共享环境变量,如数据库凭证。这允许你在一个地方定义变量,并在不同的服务中分享它们,如主应用程序、迁移和恢复。你可以在这里指定你需要的任何变量,比如OAuth客户端的详细信息。
  4. 定义这个应用程序的服务。
  5. TIL应用程序定义一个服务。
  6. 为这个服务指定镜像。Docker Compose可以在不同的服务中重复使用镜像,因此你不必在不同的使用情况下重新构建它。
  7. 指定服务的构建环境。默认情况下,它使用前面讨论的Dockerfile
  8. 指定服务的任何环境变量。包括步骤2中的共享环境变量。
  9. 设置对db服务的依赖性,这样Docker Compose就会首先启动PostgreSQL容器(如果尚未启动的话)。
  10. 暴露8080端口,允许你在应用程序运行时连接到它。
  11. 指定用于启动应用程序的命令。迁移和恢复使用不同的命令。
  12. PostgreSQL数据库定义一个服务。
  13. 使用Alpine postgres镜像。这是一个完整的PostgreSQL数据库,运行在一个非常轻的容器中。
  14. ~/db_data到容器中设置一个持久化卷。这使得数据生活在主机系统的文件系统中,而不是在Docker容器中,并允许它在不同的启动中持续存在。
  15. 设置必要的环境变量。

Tips

Docker compose不允许镜像名称包含大写字母。在写这篇文章时,工具箱并没有考虑到这一点,所以你可能需要手动小写图像名称,如上所示。

首先,确保你停止之前章节中任何现有的PostgreSQL容器:

docker stop postgres

为了让你的应用程序活起来,在终端输入以下命令:

docker-compose build
docker-compose up -d db
docker-compose up app

这些命令建立不同的容器,在后台启动数据库,然后启动应用程序。

接下来去哪?

你已经看到了一些关于如何在Docker环境中运行你的应用的基本配方。由于Docker是如此的灵活,这些配方只是触及了你可用的可能性的表面。例如,你可能想让你的应用程序在主机的文件系统中保存上传的文件。或者,你可能想配置应用程序在Nginx代理服务器后面运行以获得安全的HTTPS访问。