容器化 Java 应用

已完成

在本单元中,你将容器化 Java 应用程序。

如前所述,容器直接在主机操作系统、内核和硬件上运行,本质上只是另一个系统进程。 容器所需的系统资源较少,因此内存占用更小、开销更少且应用程序启动速度更快。 这些是按需缩放的不错用例。

有 Windows 容器和 Linux 容器。 在本模块中,你将利用广泛使用的 Docker 运行时来生成 Linux 容器映像。 然后将 Linux 容器映像部署到本地计算机的主机操作系统。 最后,将 Linux 容器映像部署到 Azure Kubernetes 服务。

Docker 概述

Docker 运行时用于生成、拉取、运行和推送容器映像。 下图描述了这些用例,接着是每个用例/Docker 命令的说明。

Diagram showing Docker commands.

Docker 命令 说明
docker build 生成容器映像,实质上是 Docker 最终从映像创建正在运行的容器所需的指令/层。 此命令的结果是生成一个映像。
docker pull 容器从映像中初始化,这些映像拉取自注册表(例如 Azure 容器注册表),这也是拉取 Azure Kubernetes 服务的位置。 此命令的结果是将在 Azure 中对映像进行网络拉取。 请注意,你可以选择在本地拉取映像;这在生成需要应用程序可能同时需要的依赖项/层(例如应用程序服务器)的映像时很常见。
docker run 映像的运行中实例是一个容器,此命令执行所有必需的层来运行容器应用程序以及与运行中的容器应用程序进行交互。 此命令的结果是在主机操作系统上运行的应用程序进程。
docker push Azure 容器注册表将存储映像,以便它们随时可用,并与 Azure 的部署和缩放密切相关。

克隆 Java 应用程序

首先,你要将航空公司的航班预订系统存储库和 cd 克隆到航空公司 Web 应用程序项目文件夹中。

注意

如果在 CLI 选项卡中已成功创建 Azure Kubernetes 服务,请使用该选项卡,否则,如果它仍在运行,请在要克隆航空公司的航班预订系统的位置打开一个新的选项卡和 cd。

在 CLI 中运行以下命令:

git clone https://github.com/Azure-Samples/containerize-and-deploy-Java-app-to-Azure.git

在 CLI 中运行以下命令:

cd containerize-and-deploy-Java-app-to-Azure/Project/Airlines

注意

(可选)如果已安装 Java 和 Maven,则可在 CLI 中运行以下命令,体验一下在没有 Docker 的情况下生成应用程序。 如果没有安装 Java 和 Maven,则可以放心地跳到下一部分:构造 Docker 文件。 在此部分中,你将使用 Docker 来拉取将代表你执行生成的 Java 和 Maven。

(可选)如果已安装 Maven 和 JDK(8) 或更高版本,可以在 CLI 中运行以下命令:

mvn clean install

注意

我们已使用 mvn clean install 命令阐明不使用 Docker 多阶段生成的操作难题,我们将在后面进行介绍。 同样,此步骤是可选的;无论采用哪种方式,你都可以安全地继续,无需执行 Maven 命令。

Maven 应已成功生成航空公司的航班预订系统 Web 应用程序存档项目 FlightBookingSystemSample-0.0.-SNAPSHOT.war,如以下输出所示:

[INFO] Building war: /mnt/c/Users/chtrembl/dev/git/containerize-and-deploy-Java-app-to-Azure/Project/FlightBookingSystemSample/target/FlightBookingSystemSample-0.0.1-SNAPSHOT.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  17.698 s
[INFO] Finished at: 2021-09-28T15:18:07-04:00
[INFO] ------------------------------------------------------------------------

假设你是一名 Java 开发人员,并且刚刚构建了此 FlightBookingSystemSample-0.0.1-SNAPSHOT.war。 下一步可能是与操作工程师协作,以便将此项目部署到本地服务器或虚拟机上。 要使应用程序成功启动并运行,需要提供服务器和虚拟机,并配置所需的依赖项。 这是具有挑战性且非常耗时的工作,尤其是在应用程序负载增加时,更是如此。 有了容器,这些挑战就得到了缓解。

构造 Dockerfile

此时,你已准备好构造 Dockerfile。 Dockerfile 是一个文本文档,其中包含用户可在命令行上执行的所有命令,用于组装容器映像,其中每个都是层(可以缓存这些层以提高效率),它们基于彼此构建。

例如,航空公司的航班预订系统需要部署到应用程序服务器,并在其中运行。 应用程序服务器未打包在 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 中,它是 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 运行、侦听和处理 HTTP 请求、管理用户会话和帮助航班预订所需的外部依赖项。 如果这是一种传统的非容器化部署,则在向其部署 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 之前,操作工程师将在一些物理服务器和/或虚拟机上安装和配置应用程序服务器。 这些操作工程师还需要确保计算机上使用的 JDK(mvn clean install 使用它来编译 .war)实际上与应用程序服务器使用的同一 JRE 相对应。 管理这些依赖项非常困难且耗时。

通过 Dockerfile,你可以编写自动完成此操作所需的层(指令),通过在所需的步骤中分层来确保航空公司的航班预订系统拥有所有部署到 Docker 容器运行时所需的依赖项。 当你开始考虑在非计划的时间间隔内按需扩展时,这就非常引人注目了。 值得注意的是,每个层都利用了 Docker 缓存,其中包含每个指令里程碑的容器映像的状态,优化了计算时间和重复使用。 如果层不发生变化,则使用缓存的层。 缓存层的常见用例包括:Java 运行时、应用程序服务器和/或航空公司的航班预订系统 Web 应用程序的其他依赖项。 如果某个版本在以前的缓存层上发生更改,则会创建一个新的缓存条目。

下图描述了容器映像的各个层。 你会注意到,所有顶层都是在以前的只读层基础上构建的读/写航空公司的航班预订系统 Web 应用程序层,所有这些层都是通过 Dockerfile 中的命令生成的。

Diagram showing the Docker layers.

Docker 还具有多阶段生成概念,这一功能允许你创建一个较小的容器映像,具有更好的缓存和更小的安全占用空间,从而随着时间的推移改进 Dockerfile 的优化和维护;例如,你可以使用指令来完成应用程序 (FlightBookingSystemSample-0.0.1-SNAPSHOT.war) 的编译以及容器映像本身的生成,留下 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 编译的剩余部分待以后处理,从而减少占用空间。 从长远来看,当你开始考虑这些映像在网络中传播时,这将带来好处。 对于多阶段生成,可以在 Dockerfile 中使用多个 FROM 语句。 每个 FROM 指令都可以使用不同的基础映像,其中每个语句都以干净的盖板开始,删除缓存层中通常可能会缓存的所有不必要的文件。

必须确保应用程序是由与运行时将隔离在容器映像中的同一 JRE 对应的相同 JDK 生成的。 在下面的示例中,你有一个生成阶段,它利用特定版本的 Maven 和特定版本的 JDK 来编译 FlightBookingSystemSample-0.0.1-SNAPSHOT.war。 此阶段确保执行该阶段的任何 Docker 运行时都将获取 Dockerfile 作者指定的预期生成的字节代码(否则,操作工程师就必须将其 Java 和应用程序服务器运行时与开发人员的 Java 和应用程序服务器运行时交叉引用)。 然后,包阶段将使用特定版本的 Tomcat 和与生成阶段中的 JDK 相对应的 JRE。 同样,这样做是为了确保所有依赖项(Java 开发工具包 JDK、Java Runtime Environment JRE、应用程序服务器)都受到控制和隔离,以确保在运行此映像的所有计算机上执行预期行为。

同样值得注意的是,在此多阶段生成中,从技术上讲,无需在系统上安装 Maven 和 Java。 Docker 会拉取它们以用于生成应用程序和应用程序运行时,这样可以避免出现任何潜在的版本冲突和意外行为,当然,除非你是在 Docker 之外编译代码和生成项目。

下图根据 Dockerfile 中指定的命令描述了多阶段生成和每个阶段发生的情况。 在阶段 0,即生成阶段,编译航空公司的航班预订系统 Web 应用程序和生成 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 的阶段。 此阶段可确保用来编译此应用程序的 Maven 和 Java 版本的一致性。 一旦创建完 FlightBookingSystemSample-0.0.1-SNAPSHOT.war,这就是阶段 1(运行时阶段)所需的唯一层,之前的所有层都可以被丢弃。 然后,Docker 将使用这个来自阶段 0 的 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 层来构造运行时所需的剩余层;本示例中是配置应用程序服务器和启动应用程序。

Diagram showing the Docker multistage build.

在项目的根目录 (containerize-and-deploy-Java-app-to-Azure/Project/Airlines) 中,创建一个名为 Dockerfile 的文件:

vi Dockerfile

将以下内容添加到 Dockerfile,然后通过以下方式保存并退出:按 ESC,然后键入 :wq! 并按 Enter

#
# Build stage
#
FROM maven:3.6.0-jdk-11-slim AS build
WORKDIR /build
COPY pom.xml .
COPY src ./src
COPY web ./web
RUN mvn clean package

#
# Package stage
#
FROM tomcat:8.5.72-jre11-openjdk-slim
COPY tomcat-users.xml /usr/local/tomcat/conf
COPY --from=build /build/target/*.war /usr/local/tomcat/webapps/FlightBookingSystemSample.war
EXPOSE 8080
CMD ["catalina.sh", "run"]

注意

或者,你的项目根目录中的 Dockerfile_Solution 包含所需的内容。

此 Dockerfile 生成阶段包含 6 个指令:

Docker 命令 说明
FROM FROM maven 是用于生成此 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 的基础层,它是特定版本的 Maven 和特定版本的 JDK,用于确保在运行此生成的所有计算机上发生相同的字节代码编译。
WORKDIR WORKDIR 用于在任何给定时间定义容器的工作目录;在本例中,编译的项目将位于此位置。
COPY COPY 从 Docker 客户端的当前目录中添加文件。 设置 Maven 编译所需的文件,Docker 上下文将需要 pom.xml。
COPY 设置 Maven 进行编译所需的文件。 Docker 上下文需要使用包含航空公司的航班预订系统 Web 应用程序的 src 文件夹。
COPY 设置 Maven 进行编译所需的文件。 Web Docker 上下文需要使用包含航空公司的航班预订系统 Web 应用程序依赖项的文件夹。
运行 RUN mvn clean package 指令用于在当前映像的基础上执行任何命令。 在本例中,RUN 用于执行 Maven 生成,这将编译 FlightBookingSystemSample-0.0.1-SNAPSHOT.war

此 Docker 文件的包阶段包含五个指令:

Docker 命令 说明
FROM FROM tomcat 是生成此容器映像时所基于的基本层。 航空公司的航班预订系统容器映像将是基于 tomcat 映像生成的映像。 Docker 运行时将尝试在本地查找 tomcat 映像。 如果它没有此版本,将从注册表中拉取一个。 如果检查此处引用的 tomcat 映像,你会发现它是使用多个其他层生成的,所有这些层使其可以作为一个打包的应用程序服务器容器映像重用,以便用户在部署 Java 应用程序时使用。 出于模块目的,我们选择并测试了 tomcat:8.5.72-jre11-openjdk-slim。 请注意,当 Docker 识别出这第二个 FROM 指令后,第一个生成阶段的所有先前层都会消失。
COPY COPY tomcat-users.xml 将管理航空公司的航班预订系统用户的 tomcat-users.xml 文件(使用 Tomcat 标识在源代码管理中进行管理;这通常发生在外部标识管理系统中)复制到 tomcat 容器映像中,这样,每次创建容器映像时,它都存在于容器映像中。
ADD ADD target/*.war /usr/local/tomcat/webapps/FlightBookingSystemSample.war 会将 maven 编译的 FlightBookingSystemSample-0.0.1-SNAPSHOT.war 复制到 tomcat 映像 webapps 文件夹,以确保当 Tomcat 初始化时,它会找到要安装在应用程序服务器上的 FlightBookingSystemSample-0.0.1-SNAPSHOT.war
EXPOSE 需要 EXPOSE 8080,因为 Tomcat 被配置为侦听端口 8080 上的流量。 这可确保 Docker 进程将侦听此端口。
CMD CMD 指令设置运行容器时要执行的命令。 在本例中,CMD ["catalina.sh", "run"] 指示 Docker 初始化 Tomcat 应用程序服务器。

注意

如果 FROM tomcat 行上没有版本标记,则将应用最新的版本。 通常,你将需要利用版本标记(记住,将应用缓存,因此,如果层不断变化,会产生带宽、延迟、计算时间和/或未经测试的生成/层的副作用)。 在本模块中,我们预先选择了特定的 Maven、Tomcat 和 Java JRE/JDK 标记,这些标记经过测试,可在运行时用于 FlightBookingSystemSample-0.0.1-SNAPSHOT.war

有关 Dockerfile 构造的详细信息,请参阅 Dockerfile 参考