在 Azure 容器应用上使用 Quarkus 部署 Java 应用程序

本文介绍如何使用简单的 CRUD 应用程序在 Microsoft Azure 容器应用上快速部署 Red Hat Quarkus。 该应用程序是具有 JavaScript 前端和 REST 终结点的“待办事项列表”。 Azure Database for PostgreSQL 灵活服务器为应用提供暂留层。 本文介绍如何在本地测试应用并将其部署到容器应用。

先决条件

  • Azure 订阅。 如果还没有 Azure 订阅,可以在开始前创建一个免费帐户
  • 准备一台安装了类似于 Unix 的操作系统(例如 Ubuntu、macOS 或适用于 Linux 的 Windows 子系统)的本地计算机。
  • 安装 Java SE 实现版本 17 或更高版本 - 例如,Microsoft 版 OpenJDK
  • 安装 Maven 3.9.8 或更高版本。
  • 安装适用于 OS 的 DockerPodman
  • 安装 jq
  • 安装 cURL
  • 安装 Quarkus CLI 3.12.1 或更高版本。
  • 安装 Azure CLI 以运行 Azure CLI 命令。
    • 通过使用 az login 命令登录到 Azure CLI。 若要完成身份验证过程,请遵循终端中显示的步骤。 有关其他登录选项,请参阅使用 Azure CLI 登录到 Azure
    • 出现提示时,请在首次使用时安装 Azure CLI 扩展。 有关扩展详细信息,请参阅使用和管理 Azure CLI 的扩展
    • 运行 az version 以查找安装的版本和依赖库。 若要升级到最新版本,请运行 az upgrade。 本文需要的 Azure CLI 最低版本为 2.61.0。

创建应用项目

使用以下命令克隆本文的示例 Java 项目。 该示例位于 GitHub 上。

git clone https://github.com/Azure-Samples/quarkus-azure
cd quarkus-azure
git checkout 2024-10-14
cd aca-quarkus

如果看到有关处于拆离的 HEAD 状态的消息,可以放心忽略此消息。 由于本文不需要任何提交,因此拆离的 HEAD 状态是合适的。

在本地测试 Quarkus 应用

本部分的步骤介绍如何在本地运行应用。

Quarkus 支持在开发和测试模式下自动预配未配置的服务。 Quarkus 将此功能称为开发服务。 假设你有 Quarkus 功能,例如连接到数据库服务。 你想要测试应用,但尚未完全配置与真实数据库的连接。 Quarkus 会自动启动相关服务的存根版本,并将应用程序连接到该服务。 更多信息请参阅 Quarkus 文档中的《开发服务概述》 。

请确保容器环境(Docker 或 Podman)正在运行,并使用以下命令进入 Quarkus 开发模式:

quarkus dev

使用 mvn quarkus:dev 取代 quarkus dev 通过 Maven 完成相同的操作。

系统可能会询问你是否要发送 Quarkus 开发模式使用情况的遥测数据。 如果询问,请根据需要回答。

Quarkus 开发模式支持实时重载和后台编译。 对应用源代码进行任何修改后,刷新浏览器就可以看到更改。 错误页会显示任何编译或部署问题。 Quarkus 开发模式在 5005 端口上侦听调试程序。 如果要在运行之前等待附加调试程序,请在命令行输入 -Dsuspend。 如果根本不需要调试程序,可以使用 -Ddebug=false

输出应如以下示例所示:

__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
INFO  [io.quarkus] (Quarkus Main Thread) quarkus-todo-demo-app-aca 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.2.0.Final) started in 14.826s. Listening on: http://localhost:8080
INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-validator, jdbc-postgresql, narayana-jta, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]

--
Tests paused
Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

在运行 Quarkus 开发模式的终端上按 w 键。 w 键将打开默认 Web 浏览器并显示 Todo 应用程序。 还可以直接在 http://localhost:8080 访问应用程序 GUI。

待办事项示例应用程序的屏幕截图。

请尝试在待办事项列表中选择待办事项。 UI 用删除线文本样式提示选择。 还可以通过键入“验证待办事项应用”并按 ENTER 键将新的待办事项添加到待办事项列表,如以下屏幕截图所示:

待办事项示例应用程序添加新事项的屏幕截图。

访问 RESTful API (/api) 以获取存储在本地 PostgreSQL 数据库中的所有待办事项:

curl --verbose http://localhost:8080/api | jq .

输出应如以下示例所示:

* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 664
< Content-Type: application/json;charset=UTF-8
<
{ [664 bytes data]
100   664  100   664    0     0  13278      0 --:--:-- --:--:-- --:--:-- 15441
* Connection #0 to host localhost left intact
[
  {
    "id": 1,
    "title": "Introduction to Quarkus Todo App",
    "completed": false,
    "order": 0,
    "url": null
  },
  {
    "id": 2,
    "title": "Quarkus on Azure App Service",
    "completed": false,
    "order": 1,
    "url": "https://learn.microsoft.com/en-us/azure/developer/java/eclipse-microprofile/deploy-microprofile-quarkus-java-app-with-maven-plugin"
  },
  {
    "id": 3,
    "title": "Quarkus on Azure Container Apps",
    "completed": false,
    "order": 2,
    "url": "https://learn.microsoft.com/en-us/training/modules/deploy-java-quarkus-azure-container-app-postgres/"
  },
  {
    "id": 4,
    "title": "Quarkus on Azure Functions",
    "completed": false,
    "order": 3,
    "url": "https://learn.microsoft.com/en-us/azure/azure-functions/functions-create-first-quarkus"
  },
  {
    "id": 5,
    "title": "Verify Todo apps",
    "completed": false,
    "order": 5,
    "url": null
  }
]

q 键退出 Quarkus 开发模式。

创建 Azure 资源以运行 Quarkus 应用

本部分的步骤介绍如何创建以下 Azure 资源来运行 Quarkus 示例应用:

  • Azure Database for PostgreSQL 灵活服务器
  • Azure 容器注册表
  • Azure Container Apps

其中一些资源必须在 Azure 订阅范围内具有唯一的名称。 若要确保这种唯一性,可以使用首字母缩写、序列、日期、后缀模式。 若要应用此模式,请通过首字母缩写、序列号、当日日期和特定资源后缀来命名资源,例如,rg 表示“资源组”。 以下环境变量使用此模式。 将占位符值替换为自己的值UNIQUE_VALUELOCATION,并在终端中运行命令。

export UNIQUE_VALUE=<your unique value, such as mjg101424>
export RESOURCE_GROUP_NAME=${UNIQUE_VALUE}rg-passwordless
export LOCATION=<your desired Azure region for deploying your resources - for example, eastus>
export REGISTRY_NAME=${UNIQUE_VALUE}regpasswordless
export DB_SERVER_NAME=${UNIQUE_VALUE}dbpasswordless
export DB_NAME=demodb
export ACA_ENV=${UNIQUE_VALUE}envpasswordless
export ACA_NAME=${UNIQUE_VALUE}acapasswordless

接下来,请使用以下命令创建资源组:

az group create \
    --name $RESOURCE_GROUP_NAME \
    --location $LOCATION

创建 Azure Database for PostgreSQL 灵活服务器

Azure Database for PostgreSQL 灵活服务器是一种完全托管的数据库服务,旨在为数据库管理功能和配置设置提供更精细的控制和灵活性。 本部分介绍如何使用 Azure CLI 创建 Azure Database for PostgreSQL 灵活服务器实例。 若要了解更多信息,请参考快速入门:使用 Azure CLI 创建 Azure Database for PostgreSQL 灵活服务器实例

使用以下命令创建 Azure Database for PostgreSQL 灵活服务器实例:

az postgres flexible-server create \
    --name $DB_SERVER_NAME \
    --resource-group $RESOURCE_GROUP_NAME \
    --database-name $DB_NAME \
    --public-access None \
    --sku-name Standard_B1ms \
    --tier Burstable \
    --active-directory-auth Enabled

创建服务器、数据库、管理员用户和防火墙规则需要几分钟时间。 如果命令成功,输出将类似于以下示例:

{
  "connectionString": "postgresql://REDACTED:REDACTED@<DB_SERVER_NAME>.postgres.database.azure.com/<DB_NAME>?sslmode=require",
  "databaseName": "<DB_NAME>",
  "host": "<DB_SERVER_NAME>.postgres.database.azure.com",
  "id": "/subscriptions/REDACTED/resourceGroups/<RESOURCE_GROUP_NAME>/providers/Microsoft.DBforPostgreSQL/flexibleServers/<DB_SERVER_NAME>",
  "location": "East US",
  "password": "REDACTED",
  "resourceGroup": "<RESOURCE_GROUP_NAME>",
  "skuname": "Standard_B1ms",
  "username": "REDACTED",
  "version": "13"
}

使用以下命令将当前已登录用户添加为 Microsoft Entra Admin 的 Azure Database for PostgreSQL 灵活服务器实例:

ENTRA_ADMIN_NAME=$(az ad signed-in-user show --query userPrincipalName -o tsv)
az postgres flexible-server ad-admin create \
    --resource-group $RESOURCE_GROUP_NAME \
    --server-name $DB_SERVER_NAME \
    --display-name $ENTRA_ADMIN_NAME \
    --object-id $(az ad signed-in-user show --query id -o tsv)

成功的输出是一个包含属性 "type": "Microsoft.DBforPostgreSQL/flexibleServers/administrators" 的 JSON 对象。

创建 Microsoft Azure 容器注册表实例

由于 Quarkus 是一种云原生技术,因此它内置了对创建在容器应用中运行的容器的支持。 容器应用完全依赖于拥有一个容器注册表,从中可以找到要运行的容器映像。 容器应用内置了对 Azure 容器注册表的支持。

使用 az acr create 命令以创建容器注册表实例。 以下示例创建 n 个以环境变量 ${REGISTRY_NAME} 的值命名的容器注册表实例:

az acr create \
    --resource-group $RESOURCE_GROUP_NAME \
    --location ${LOCATION} \
    --name $REGISTRY_NAME \
    --sku Basic

很快,你应该会在 JSON 输出中看到以下行:

  "provisioningState": "Succeeded",
  "publicNetworkAccess": "Enabled",
  "resourceGroup": "<YOUR_RESOURCE_GROUP>",

使用以下命令获取容器注册表实例的登录服务器:

export LOGIN_SERVER=$(az acr show \
    --name $REGISTRY_NAME \
    --query 'loginServer' \
    --output tsv)
echo $LOGIN_SERVER

将 Docker 连接到容器注册表实例

登录到 Azure 容器注册表实例。 登录后可以推送映像。 使用以下命令登录到注册表:

az acr login --name $REGISTRY_NAME

如果使用 Podman 而不是 Docker,请对命令进行必要的更改。

如果已成功登录到容器注册表实例,则应在命令输出的末尾看到 Login Succeeded

创建环境

Azure 容器应用中的环境围绕一组容器应用创建安全边界。 部署到相同环境的容器应用部署在同一虚拟网络中,并将日志写入同一个 Log Analytics 工作区。 使用 az containerapp env create 命令创建环境,如以下示例所示:

az containerapp env create \
    --resource-group $RESOURCE_GROUP_NAME \
    --location $LOCATION \
    --name $ACA_ENV

如果系统要求你安装扩展,请回答 Y

自定义云原生配置

作为一种云原生技术,Quarkus 提供了自动生成容器映像的能力。 有关详细信息,请参阅容器映像。 然后,开发人员可以将应用程序映像部署到目标容器化平台,例如 Azure 容器应用。

若要生成容器映像,请使用以下命令在本地终端中添加 container-image-jib 扩展:

quarkus ext add container-image-jib

Quarkus 修改 POM,以确保扩展包含在 <dependencies> 中。 如果系统要求你安装名为 JBang 的内容,请回答并允许安装。

输出应如以下示例所示:

[SUCCESS] ✅  Extension io.quarkus:quarkus-container-image-jib has been installed

打开pom.xml文件,应会看到扩展添加的container-image-jib以下依赖项:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-container-image-jib</artifactId>
</dependency>

然后,将以下依赖项添加到 pom.xml 文件中,以支持使用 Azure Database for PostgreSQL 灵活服务器进行无密码身份验证:

<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-identity-extensions</artifactId>
    <version>1.1.20</version>
</dependency>

作为一项云原生技术,Quarkus 支持配置文件概念。 Quarkus 拥有以下三个内置的配置文件:

  • dev - 处于开发模式时激活。
  • test - 在运行测试时激活。
  • prod - 未在开发或测试模式下运行时的默认配置文件。

Quarkus 根据需要支持任意数量的命名配置文件。

本部分中的剩余步骤指导取消注释和自定义 src/main/resources/application.properties 文件中的值。 通过删除前导 # %prod.,确保取消所有以 # 开头行的注释。

%prod. 前缀表明在 prod 配置文件中运行时这些属性处于活动状态。 有关配置文件的详细信息,请参阅Quarkus 文档

检查数据库配置

取消注释属性后,src/main/resources/application.properties 文件中的数据库配置应如以下示例所示:

# Database configurations
%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.jdbc.url=
%prod.quarkus.datasource.username=
%prod.quarkus.datasource.password=
%prod.quarkus.hibernate-orm.database.generation=create
%prod.quarkus.hibernate-orm.sql-load-script=no-file

删除属性 %prod.quarkus.datasource.password ,因为在 Azure Database for PostgreSQL 灵活服务器中使用无密码身份验证时不需要此属性。 使用以下示例所示更新其他数据库连接相关属性 %prod.quarkus.datasource.jdbc.url%prod.quarkus.datasource.username 值。 最终配置应如以下示例所示:

# Database configurations
%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://${AZURE_POSTGRESQL_HOST}:${AZURE_POSTGRESQL_PORT}/${AZURE_POSTGRESQL_DATABASE}?\
authenticationPluginClassName=com.azure.identity.extensions.jdbc.postgresql.AzurePostgresqlAuthenticationPlugin\
&sslmode=require
%prod.quarkus.datasource.username=${AZURE_POSTGRESQL_USERNAME}
%prod.quarkus.hibernate-orm.database.generation=create
%prod.quarkus.hibernate-orm.sql-load-script=no-file

本文后面的服务连接器无密码扩展在运行时由 Azure 容器应用环境提供的值${AZURE_POSTGRESQL_HOST}${AZURE_POSTGRESQL_DATABASE}${AZURE_POSTGRESQL_PORT}${AZURE_POSTGRESQL_USERNAME}

通常,你不会期望数据库中持久化的数据被删除,并在生产环境中用示例数据重新填充。 这就是为什么你可以看到 quarkus.hibernate-orm.database.generation 的模式被指定为 create,这样应用只有在初始启动时不存在模式时才会创建模式。 此外,数据库不会预先填充任何示例数据,因为 hibernate-orm.sql-load-script 被指定为 no-file。 此设置与之前在开发模式下在本地运行应用时不同。 在开发模式下,quarkus.hibernate-orm.database.generationhibernate-orm.sql-load-script 的默认值分别为 drop-and-createimport.sql,这意味着应用总是删除并重新创建数据库架构,并加载 import.sql 中定义的数据。 import.sql 文件是 Quarkus 提供的一个便利工具。 如果 Quarkus jar 中存在 src/main/resources/import.sql 文件,并且 hibernate-orm.sql-load-script 属性的值为 import.sql,则此文件中的 SQL DML 语句将在应用启动时执行。

使用 Azure Database for PostgreSQL 灵活服务器在本地测试 Quarkus 应用

将 Quarkus 应用部署到 Azure 容器应用之前,请在本地测试与 Azure Database for PostgreSQL 灵活服务器实例的连接。

首先,使用以下命令将本地 IP 地址添加到 Azure Database for PostgreSQL 灵活服务器实例防火墙规则:

export AZ_LOCAL_IP_ADDRESS=$(curl -s https://whatismyip.akamai.com)

az postgres flexible-server firewall-rule create \
    --resource-group $RESOURCE_GROUP_NAME \
    --name $DB_SERVER_NAME \
    --rule-name $DB_SERVER_NAME-database-allow-local-ip \
    --start-ip-address $AZ_LOCAL_IP_ADDRESS \
    --end-ip-address $AZ_LOCAL_IP_ADDRESS

接下来,在上一终端中设置以下环境变量。 这些环境变量用于从本地运行的 Quarkus 应用连接到 Azure Database for PostgreSQL 灵活服务器实例:

export AZURE_POSTGRESQL_HOST=${DB_SERVER_NAME}.postgres.database.azure.com
export AZURE_POSTGRESQL_PORT=5432
export AZURE_POSTGRESQL_DATABASE=${DB_NAME}
export AZURE_POSTGRESQL_USERNAME=${ENTRA_ADMIN_NAME}

在本地运行 Quarkus 应用以测试与 Azure Database for PostgreSQL 灵活服务器实例的连接。 使用以下命令在生产模式下启动应用:

mvn clean package -DskipTests
java -jar target/quarkus-app/quarkus-run.jar

打开新的 Web 浏览器以 http://localhost:8080 访问 Todo 应用程序。 在开发模式下本地运行应用时,应会看到与在本地运行应用时相同的 Todo 应用,而无需任何待办事项。

Control+C 停止应用。

生成容器映像并将其推送到容器注册表

现在,运行以下命令以生成应用程序。 此命令使用 Jib 扩展来生成容器映像。

export TODO_QUARKUS_IMAGE_NAME=todo-quarkus-aca
export TODO_QUARKUS_IMAGE_TAG=${LOGIN_SERVER}/${TODO_QUARKUS_IMAGE_NAME}:1.0
quarkus build -Dquarkus.container-image.build=true -Dquarkus.container-image.image=${TODO_QUARKUS_IMAGE_TAG} --no-tests 

输出应 BUILD SUCCESS 结尾。

可以使用命令行podman(CLI)验证容器映像是否也docker生成,如以下示例所示:

docker images | grep ${TODO_QUARKUS_IMAGE_NAME}

输出与以下示例类似:

<LOGIN_SERVER_VALUE>/todo-quarkus-aca   1.0       0804dfd834fd   2 minutes ago   407MB

使用以下命令将容器映像推送到容器注册表:

docker push ${TODO_QUARKUS_IMAGE_TAG}

输出应类似于以下示例:

The push refers to repository [<LOGIN_SERVER_VALUE>/todo-quarkus-aca]
188a550fce3d: Pushed
4e3afea591e2: Pushed
1db0eba807a6: Pushed
c72d9ccda0b2: Pushed
d7819b8a2d18: Pushed
d0e5cba6b262: Pushed
e0bac91f0f10: Pushed
1.0: digest: sha256:f9ccb476e2388efa0dfdf817625a94f2247674148a69b7e4846793e63c8be994 size: 1789

将 Quarkus 应用部署到 Azure 容器应用

将应用映像推送到容器注册表后,请使用以下命令创建容器应用实例,在从容器注册表拉取映像后运行该应用:

az containerapp create \
    --resource-group $RESOURCE_GROUP_NAME \
    --name $ACA_NAME \
    --image $TODO_QUARKUS_IMAGE_TAG \
    --environment $ACA_ENV \
    --registry-server $LOGIN_SERVER \
    --registry-identity system \
    --target-port 8080 \
    --ingress 'external' \
    --min-replicas 1

成功的输出是一个包含属性 "type": "Microsoft.App/containerApps" 的 JSON 对象。

然后,使用服务连接器将 Azure Database for PostgreSQL 灵活服务器实例连接到容器应用,请执行以下步骤:

  1. 使用以下命令安装 Azure CLI 的服务连接器无密码扩展:

    az extension add --name serviceconnector-passwordless --upgrade --allow-preview true
    
  2. 使用以下命令将数据库连接到具有系统分配的托管标识的容器应用:

    az containerapp connection create postgres-flexible \
        --resource-group $RESOURCE_GROUP_NAME \
        --name $ACA_NAME \
        --target-resource-group $RESOURCE_GROUP_NAME \
        --server $DB_SERVER_NAME \
        --database $DB_NAME \
        --system-identity \
        --container $ACA_NAME
    

    成功的输出是一个包含属性 "type": "microsoft.servicelinker/linkers" 的 JSON 对象。

使用以下命令获取完全限定的 URL 以访问 Todo 应用程序:

export QUARKUS_URL=https://$(az containerapp show \
    --resource-group $RESOURCE_GROUP_NAME \
    --name $ACA_NAME \
    --query properties.configuration.ingress.fqdn -o tsv)
echo $QUARKUS_URL

${QUARKUS_URL} 打开新的 Web 浏览器。 如果网页未正确呈现,请等待一段时间并刷新页面。

然后,添加一个文本内容为 Deployed the Todo app to Container Apps 的新待办事项。 选择此项目以将其标记为已完成。

在容器应用中运行的 Todo 示例应用的屏幕截图。

访问 RESTful API (/api),以获取存储在 Azure Database for PostgreSQL 中的所有 Todo 项,如以下示例所示:

curl --verbose -k ${QUARKUS_URL}/api | jq .

输出应如以下示例所示:

* Connected to <aca-name>.<random-id>.eastus.azurecontainerapps.io (20.231.235.79) port 443 (#0)
> GET /api HTTP/2
> Host: <aca-name>.<random-id>.eastus.azurecontainerapps.io
> user-agent: curl/7.88.1
> accept: */*
>
< HTTP/2 200
< content-length: 88
< content-type: application/json;charset=UTF-8
<
[
  {
    "id": 1,
    "title": "Deployed the Todo app to Container Apps",
    "completed": true,
    "order": 1,
    "url": null
  }
]

验证数据库是否已更新

运行以下命令,验证数据库是否已使用新的待办事项进行更新:

export ACCESS_TOKEN=$(az account get-access-token --resource-type oss-rdbms --output tsv --query accessToken)
az postgres flexible-server execute \
    --admin-user $ENTRA_ADMIN_NAME \
    --admin-password $ACCESS_TOKEN \
    --name $DB_SERVER_NAME \
    --database-name $DB_NAME \
    --querytext "select * from todo;"

如果系统要求你安装扩展,请回答 Y

输出应类似于以下示例,并且应该在前面所示的 Todo 应用 GUI 中包含相同的项:

Successfully connected to <DB_SERVER_NAME>.
Ran Database Query: 'select * from todo;'
Retrieving first 30 rows of query output, if applicable.
Closed the connection to <DB_SERVER_NAME>
[
  {
    "completed": true,
    "id": 1,
    "ordering": 1,
    "title": "Deployed the Todo app to Container Apps",
    "url": null
  }
]

完成后,请使用以下命令删除允许本地 IP 地址访问 Azure Database for PostgreSQL 灵活服务器实例的防火墙规则:

az postgres flexible-server firewall-rule delete \
    --resource-group $RESOURCE_GROUP_NAME \
    --name $DB_SERVER_NAME \
    --rule-name $DB_SERVER_NAME-database-allow-local-ip \
    --yes

清理资源

若要避免 Azure 费用,应清除不需要的资源。 如果不再需要群集,请使用 az group delete 命令来删除资源组、容器服务、容器注册表和所有的相关资源。

git reset --hard
docker rmi ${TODO_QUARKUS_IMAGE_TAG}
az group delete --name $RESOURCE_GROUP_NAME --yes --no-wait

你可能还想使用 docker rmi 删除 Quarkus 开发模式生成的 postgrestestcontainers 容器映像。

后续步骤