第33章:用Docker
进行部署¶
Docker
是一种流行的容器化技术,对应用程序的部署方式产生了巨大影响。容器是一种隔离应用程序的方式,允许你在同一台服务器上运行多个应用程序。
使用一个容器,而不是一个成熟的虚拟机,允许你的容器化应用程序共享主机的更多资源。反过来,这为你的应用程序留下了更多的资源,而不是消耗它们来支持虚拟机本身。
Docker
几乎可以在任何地方运行,所以它提供了一个很好的方式来规范你的应用程序应该如何运行,从本地测试到生产。
Note
如果你需要复习一下Docker
术语--容器和镜像等概念--请查看我们的Docker
教程:https://www.raywenderlich.com/9159-docker-on-macos-getting-started。
Docker Compose
¶
本章还将告诉你如何使用Docker Compose
。Docker Compose
是一种指定不同容器列表的方式,这些容器作为一个单元一起工作。这些容器共享同一个虚拟网络,使它们之间的合作变得简单。
例如,通过Docker Compose
,你可以用一个命令同时启动你的Vapor
应用和PostgreSQL
数据库实例。它们可以相互通信,但与同一主机上运行的其他实例隔离。
为开发设置Vapor
和PostgreSQL
¶
首先设置一个简单的开发配置,在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
容器的"配方"。以下是这个文件的作用。
- 使用
Docker Hub
仓库中5.3
版本的swift
镜像作为起点。 - 告诉
Docker
使用/app
作为其工作目录。 - 将你的项目复制到
Docker
容器中。 - 构建你的项目并将可执行文件移到容器内的
/app/bin
。注意使用--enable-test-discovery
。即使你没有运行任何测试,Swift
也需要这样来构建你的项目。 - 告诉
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
文件指定了整个应用的"配方"及其所有的依赖性。以下是这个文件的作用:
- 指定
Docker Compose
的版本。 - 为这个应用程序定义服务。
- 为
TIL
应用程序定义一个服务。 - 设置对
postgres
服务的依赖性,以便Docker Compose
首先启动PostgreSQL
容器。 - 在当前目录下建立
develop.Dockerfile
。这是你之前创建的Dockerfile
。 - 使
8080
端口在主机系统上可以访问,并注入DATABASE_HOST
环境变量。Docker Compose
有一个内部DNS
解析器。这允许til-app
容器以postgres
主机名连接到postgres
容器。同时设置数据库的端口。你可以在这里指定你的应用程序需要的任何其他环境变量值,例如GitHub OAuth
凭证。 - 为
PostgreSQL
数据库定义一个服务。 - 使用标准的
postgres
图像。 - 设置必要的环境变量。
Docker
会一次性启动所有的容器,PostgreSQL
需要几秒钟的时间来准备接受连接。如果TILapp
在PostgreSQL
准备好之前启动,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
下面是这个的作用:
- 构建在
docker-compos-develop.yml
中定义的不同Docker
镜像。 - 运行
docker-compos-develop.yml
中的start_dependencies
服务,以确保PostgreSQL
正在运行并准备就绪。 - 启动你的应用程序。
如果你收到一个错误,说明没有找到vapor
数据库,按照下面的清理步骤,重试上面的命令,应用程序应该能成功启动。如果你的系统上有以前的Docker PostgreSQL
镜像,可能会出现这个错误。
在你的浏览器中,访问http://localhost:8080,以验证应用程序已经启动和运行。当你准备好继续前进时,按Control-C
来停止一切。然后,通过在终端输入以下内容来清理你的开发环境:
docker-compose -f docker-compose-develop.yml down
docker volume prune -f
这将关闭编译文件中任何正在运行的容器。然后,它将删除所有与你的应用程序相关的容器和网络定义。最后,它清理了任何你不能再访问的旧Docker
存储。
为生产设置Vapor
和PostgreSQL
¶
你可以对你的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"]
- 使用
Docker Hub
仓库中5.3
版本的"swift"
镜像作为起点。这个容器只用于构建你的应用,一旦Docker
构建了应用,你可以删除它。 - 更新系统包,然后清理工作文件。在构建基于
Linux
的Docker
镜像时,这种清理是一种标准操作。它可以减少镜像的整体大小。 - 告诉
Docker
使用/build
作为其工作目录。 - 复制
Package.swift
和Package.resolved
并解决应用程序的依赖关系。如果需要的话,这可以让Docker
在两次构建之间缓存依赖关系。 - 将你的项目复制到
Docker
容器中。用发布配置构建项目。 - 创建一个暂存目录,并将可执行文件和任何所需的库复制到其中。如果存在
Public
目录和Resources
目录,也要复制。例如,如果你使用Leaf
,你就需要这样做。 - 将你的生产镜像建立在
Swift
的薄型Docker
镜像上。它只包含运行Swift
可执行文件所需的内容。这比构建Swift
可执行文件所需的镜像小得多。 - 更新所有软件包,然后清理工作文件。
- 创建一个用户来运行可执行文件。这样可以避免以
root
身份运行可执行文件,因为root
身份可能会有安全风险。 - 告诉
Docker
使用/app
作为工作目录。 - 从构建者容器中复制文件。
- 将用户设置为步骤
9
中创建的用户。 - 暴露
8080
端口,以便客户可以连接到Docker
容器中的Vapor
应用程序。 - 告诉
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'
编译文件还包含migrate
和revert
的服务,但为了简洁起见,这里不包括这些。以下是编译文件的作用。
- 指定
Docker Compose
的版本。 - 指定该应用程序使用的卷的列表。
- 定义一些共享环境变量,如数据库凭证。这允许你在一个地方定义变量,并在不同的服务中分享它们,如主应用程序、迁移和恢复。你可以在这里指定你需要的任何变量,比如
OAuth
客户端的详细信息。 - 定义这个应用程序的服务。
- 为
TIL
应用程序定义一个服务。 - 为这个服务指定镜像。
Docker Compose
可以在不同的服务中重复使用镜像,因此你不必在不同的使用情况下重新构建它。 - 指定服务的构建环境。默认情况下,它使用前面讨论的
Dockerfile
。 - 指定服务的任何环境变量。包括步骤
2
中的共享环境变量。 - 设置对
db
服务的依赖性,这样Docker Compose
就会首先启动PostgreSQL
容器(如果尚未启动的话)。 - 暴露
8080
端口,允许你在应用程序运行时连接到它。 - 指定用于启动应用程序的命令。迁移和恢复使用不同的命令。
- 为
PostgreSQL
数据库定义一个服务。 - 使用
Alpine postgres
镜像。这是一个完整的PostgreSQL
数据库,运行在一个非常轻的容器中。 - 从
~/db_data
到容器中设置一个持久化卷。这使得数据生活在主机系统的文件系统中,而不是在Docker
容器中,并允许它在不同的启动中持续存在。 - 设置必要的环境变量。
Tips
Docker compose
不允许镜像名称包含大写字母。在写这篇文章时,工具箱并没有考虑到这一点,所以你可能需要手动小写图像名称,如上所示。
首先,确保你停止之前章节中任何现有的PostgreSQL
容器:
docker stop postgres
为了让你的应用程序活起来,在终端输入以下命令:
docker-compose build
docker-compose up -d db
docker-compose up app
这些命令建立不同的容器,在后台启动数据库,然后启动应用程序。
接下来去哪?¶
你已经看到了一些关于如何在Docker
环境中运行你的应用的基本配方。由于Docker
是如此的灵活,这些配方只是触及了你可用的可能性的表面。例如,你可能想让你的应用程序在主机的文件系统中保存上传的文件。或者,你可能想配置应用程序在Nginx
代理服务器后面运行以获得安全的HTTPS
访问。