使用 Azure Key Vault 配置 MicroProfile

本教程演示如何配置 MicroProfile 应用程序,以使用 MicroProfile 配置 API 从 Azure 密钥库检索机密。 开发人员受益于开放标准 MicroProfile 配置 API,用于检索和注入其微服务中的配置数据。

先决条件

  • Azure 订阅;如果没有 Azure 订阅,可激活 MSDN 订阅者权益或注册免费帐户
  • 适用于 Unix 类环境的 Azure CLI。 本文仅需要 Azure CLI 的 Bash 环境。
    • 安装 Azure CLI 并使用 az login 命令以交互方式登录,以在代码中使用DefaultAzureCredential之前登录到 Azure。
      az login
      
    • 本文至少需要 Azure CLI 版本 2.55.0。 如果你使用的是 Azure Cloud Shell,则表示已安装最新版本。
  • Azure Cloud Shell 预先安装了所有的先决条件。 更多内容请参阅《Azure Cloud Shell 快速入门》。
  • 如果在本地运行本指南中的命令(而不是使用 Azure Cloud Shell),请完成以下步骤:
    • 准备一台安装了类似于 Unix 的操作系统(例如 Ubuntu、macOS 或适用于 Linux 的 Windows 子系统)的本地计算机。
    • 安装 Java 标准版实现版本 17 或更高版本(例如 OpenJDK 的 Microsoft 内部版本)。
    • 安装 Maven 3.5.0 或更高版本。
    • 安装 cURL

使用 Azure 密钥库 连接 MicroProfile 配置

让我们快速了解 Azure 密钥库和 MicroProfile 配置 API 的组合功能。 下面是使用和@ConfigProperty注释@Inject的类中字段的代码片段。 name批注中指定的是在 Azure 密钥库中查找的机密的名称,如果未发现机密,defaultValue则会使用该机密。 Azure 密钥库中存储的机密值或默认值(如果不存在此类机密)会在运行时自动注入到字段中。 以这种方式注入属性值可提供许多好处。 例如,不再需要在构造函数和 setter 方法中传递值,并且配置将从代码外部化。 最强大的优势之一是为开发、测试和生产环境设置单独的值集。

@Inject
@ConfigProperty(name = "key-name", defaultValue = "Unknown")
String keyValue;

还可以命令性地访问 MicroProfile 配置,如以下示例所示:

public class DemoClass {
    @Inject
    Config config;

    public void method() {
        System.out.println("Hello: " + config.getValue("key-name", String.class));
    }
}

此示例使用 MicroProfileOpen Liberty 实现。 有关兼容实现的完整列表,请参阅 MicroProfile 兼容实现。 此示例还演示如何在 Azure 上容器化和运行应用程序。

此示例使用 MicroProfile 密钥库自定义 ConfigSource 库的低摩擦 Azure 扩展。 有关此库的详细信息,请参阅 库自述文件

下面是在本地计算机上运行此代码所要执行的步骤,从创建 Azure Key Vault 资源开始。

创建 Azure 密钥保管库资源

使用 Azure CLI 创建 Azure 密钥库资源,并使用两个机密填充该资源。

首先,登录到 Azure 并将订阅设置为当前活动订阅。

az login
az account set --subscription <subscription-id>

接下来,创建具有唯一名称的资源组, 例如 mp-kv-rg-ejb010424

export RESOURCE_GROUP_NAME=mp-kv-rg-ejb010424
az group create \
    --name ${RESOURCE_GROUP_NAME} \
    --location eastus

现在,创建具有唯一名称(例如 kvejb010424)的 Azure 密钥库 资源,添加两个机密,并将密钥库 URI 导出为环境变量。

export KEY_VAULT_NAME=kv-ejb010424
az keyvault create \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${KEY_VAULT_NAME}" \
    --location eastus

az keyvault secret set \
    --vault-name "${KEY_VAULT_NAME}" \
    --name secret \
    --value 1234
az keyvault secret set \
    --vault-name "${KEY_VAULT_NAME}" \
    --name anotherSecret \
    --value 5678

export AZURE_KEYVAULT_URL=$(az keyvault show \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${KEY_VAULT_NAME}" \
    --query properties.vaultUri \
    --output tsv)
echo $AZURE_KEYVAULT_URL

将库配置为稍后使用示例时,需要环境变量 AZURE_KEYVAULT_URL 。 使终端保持打开状态,并稍后将其用于在本地运行应用。

就这么简单! 现在,你已使用两个机密在 Azure 中运行密钥库。 现在可以克隆示例存储库并将其配置为在应用中使用此资源。

在本地启动和运行

此示例基于 GitHub 上提供的示例应用程序。 切换到之前打开的终端,并运行以下命令以克隆存储库并在本地运行应用:

git clone https://github.com/Azure/azure-microprofile.git
cd azure-microprofile
git checkout 20240116
cd integration-tests/open-liberty-sample
mvn package liberty:run

如果看到有关 You are in 'detached HEAD' state的消息,则此消息是可以放心忽略的。

注意

该库使用 默认 Azure 凭据 在 Azure 中进行身份验证。

由于已在本地通过 Azure CLI az login 命令对帐户进行身份验证,DefaultAzureCredential请使用该帐户进行身份验证以访问 Azure 密钥库。

等到看到类似于 The defaultServer server is ready to run a smarter planet的输出。 打开新终端并运行以下命令以测试示例:

# Get the value of secret "secret" stored in the Azure key vault. You should see 1234 in the response.
echo $(curl -s http://localhost:9080/config/value/secret -X GET)

# Get the value of secret "anotherSecret" stored in the Azure key vault. You should see 5678 in the response.
echo $(curl -s http://localhost:9080/config/value/anotherSecret -X GET)

# Get the names of secrets stored in the Azure key vault. You should see ["anotherSecret","secret"] in the response.
echo $(curl -s http://localhost:9080/config/propertyNames -X GET)

# Get the name-value paris of secrets stored in the Azure key vault. You should see {"anotherSecret":"5678","secret":"1234"} in the response.
echo $(curl -s http://localhost:9080/config/properties -X GET)

应会看到注释中所述的预期输出。 切换回运行应用的终端。 按 Ctrl + C 可停止应用。

检查示例应用

让我们更深入地了解 MicroProfile 配置的工作原理,而 MicroProfile 密钥库自定义 ConfigSource 库尤其有效。

库依赖项

在应用中包含以下 Maven 依赖项的 MicroProfile 密钥库 Custom ConfigSource:

<dependency>
  <groupId>com.azure.microprofile</groupId>
  <artifactId>azure-microprofile-config-keyvault</artifactId>
</dependency>

连接 Azure 密钥库

azure-microprofile-config-keyvault库将应用连接到 Azure 密钥库,而无需引入对 Azure API 的任何直接依赖项。 该库提供 MicroProfile 配置规范 ConfigSource 接口的实现,该接口知道如何从 Azure 密钥库读取。 Open Liberty 运行时提供了 MicroProfile 配置实现的其余部分。 有关规范的链接,请参阅 后续步骤

该库定义将 azure.keyvault.url 应用绑定到特定密钥保管库的配置属性。 MicroProfile 配置规范定义了“环境变量映射规则”,用于在运行时发现配置属性的值(例如 azure.keyvault.url)。 这些规则之一指出属性转换为环境变量。 该属性 azure.keyvault.url 会导致查阅环境变量 AZURE_KEYVAULT_URL

示例应用中的关键类

让我们检查前面 cURL 命令已调用的 REST 资源。 此 REST 资源在项目中的integration-tests/open-liberty-sampleConfigResource.java中定义。

@Path("/config")
public class ConfigResource {

    @Inject
    private Config config;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/value/{name}")
    public String getConfigValue(@PathParam("name") String name) {
        return config.getConfigValue(name).getValue();
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/propertyNames")
    public Set<String> getConfigPropertyNames() {
        ConfigSource configSource = getConfigSource(AzureKeyVaultConfigSource.class.getSimpleName());
        return configSource.getPropertyNames();
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/properties")
    public Map<String, String> getConfigProperties() {
        ConfigSource configSource = getConfigSource(AzureKeyVaultConfigSource.class.getSimpleName());
        return configSource.getProperties();
    }

    private ConfigSource getConfigSource(String name) {
        return StreamSupport.stream(config.getConfigSources().spliterator(), false)
                .filter(source -> source.getName().equals(name))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("ConfigSource not found: " + name));
    }
}

该方法 getConfigValue() 使用注入 Config 的实现从应用程序配置源查找值。 通过 MicroProfile Config 规范定义的搜索算法找到对实现的任何值查找 Config 。 该azure-microprofile-config-keyvault库将 Azure 密钥库添加为配置源。

该方法 getConfigSource() 可避免搜索算法并直接转到 AzureKeyVaultConfigSource 解析属性。 此方法由 getConfigPropertyNames()getConfigProperties() 方法使用。

在 Azure 容器应用上运行

在本部分中,你将容器化应用、配置用户分配的托管标识以访问 Azure 密钥库,并在 Azure 容器应用上部署容器化应用。

切换回在本地运行应用的终端,并在整个部分中使用它。

设置Azure 容器注册表

使用Azure 容器注册表来容器化应用并存储应用映像。

首先,创建具有唯一名称的Azure 容器注册表,例如 acrejb010424

export ACR_NAME=acrejb010424
az acr create \
    --resource-group $RESOURCE_GROUP_NAME \
    --name $ACR_NAME \
    --sku Basic \
    --admin-enabled

在此命令返回后等待几分钟,然后继续。

容器化应用

接下来,容器化应用并将应用映像推送到Azure 容器注册表。 请确保在示例应用的路径中, 例如 azure-microprofile/integration-tests/open-liberty-sample

az acr build \
    --registry ${ACR_NAME} \
    --image open-liberty-mp-azure-keyvault:latest \
    .

应会看到以类似 Run ID: ca1 was successful after 1m28s消息结尾的生成输出。 如果没有看到类似的消息,请在继续操作之前进行故障排除并解决问题。

在稍后在 Azure 容器应用上部署应用时,使用以下命令检索访问映像所需的连接信息。

export ACR_LOGIN_SERVER=$(az acr show \
    --name $ACR_NAME \
    --query 'loginServer' \
    --output tsv)
export ACR_USER_NAME=$(az acr credential show \
    --name $ACR_NAME \
    --query 'username' \
    --output tsv)
export ACR_PASSWORD=$(az acr credential show \
    --name $ACR_NAME \
    --query 'passwords[0].value' \
    --output tsv)

设置用户分配的托管标识

如前所述,库使用 默认 Azure 凭据 在 Azure 中进行身份验证。 将应用部署到 Azure 容器应用时,将环境变量AZURE_CLIENT_ID设置为将 DefaultAzureCredential 配置为作为用户定义的托管标识进行身份验证,该标识有权访问 Azure 密钥库,稍后会分配给 Azure 容器应用。

首先,使用以下命令创建具有唯一名称的用户分配托管标识, 例如 uamiejb010424。 有关详细信息,请参阅创建用户分配的托管标识

export USER_ASSIGNED_IDENTITY_NAME=uamiejb010424
az identity create \
    --resource-group ${RESOURCE_GROUP_NAME} \
    --name ${USER_ASSIGNED_IDENTITY_NAME}

接下来,使用以下命令授予从 Azure 密钥库获取和列出机密的权限。 有关详细信息,请参阅 “分配访问策略”。

export USER_ASSIGNED_IDENTITY_OBJECT_ID="$(az identity show \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --query 'principalId' \
    --output tsv)"

az keyvault set-policy --name "${KEY_VAULT_NAME}" \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --secret-permissions get list \
    --object-id "${USER_ASSIGNED_IDENTITY_OBJECT_ID}"

输出必须包含以下 JSON 才能被视为成功:

"permissions": {
  "certificates": null,
  "keys": null,
  "secrets": [
    "list",
    "get"
  ],
  "storage": null
}

如果输出不包含此 JSON,请在继续操作之前进行故障排除并解决问题。

然后,使用以下命令检索用户分配的托管标识的 ID 和客户端 ID,以便稍后可以将其分配给 Azure 容器应用以访问 Azure 密钥库:

export USER_ASSIGNED_IDENTITY_ID="$(az identity show \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --query 'id' \
    --output tsv)"
export USER_ASSIGNED_IDENTITY_CLIENT_ID="$(az identity show \
    --name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --query 'clientId' \
    --output tsv)"
echo $USER_ASSIGNED_IDENTITY_ID
echo $USER_ASSIGNED_IDENTITY_CLIENT_ID

在 Azure 容器应用上部署应用

将应用容器化,并配置了用户分配的托管标识以访问 Azure 密钥库。 现在可以在 Azure 容器应用上部署容器化应用。

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

export ACA_ENV=acaenvejb010424
az containerapp env create \
    --resource-group $RESOURCE_GROUP_NAME \
    --location eastus \
    --name $ACA_ENV

接下来,使用 az containerapp create 命令创建具有唯一名称(例如 acaappejb010424)的容器应用实例,以便在从容器注册表拉取映像后运行应用,如以下示例所示:

export ACA_NAME=acaappejb010424
az containerapp create \
    --resource-group ${RESOURCE_GROUP_NAME} \
    --name ${ACA_NAME} \
    --environment ${ACA_ENV} \
    --image ${ACR_LOGIN_SERVER}/open-liberty-mp-azure-keyvault:latest  \
    --registry-server $ACR_LOGIN_SERVER \
    --registry-username $ACR_USER_NAME \
    --registry-password $ACR_PASSWORD \
    --user-assigned ${USER_ASSIGNED_IDENTITY_ID} \
    --env-vars \
        AZURE_CLIENT_ID=${USER_ASSIGNED_IDENTITY_CLIENT_ID} \
        AZURE_KEYVAULT_URL=${AZURE_KEYVAULT_URL} \
    --target-port 9080 \
    --ingress 'external'

注意

使用参数 --user-assigned ${USER_ASSIGNED_IDENTITY_ID}将用户分配的托管标识分配给容器应用实例。

容器应用实例可以使用参数--env-vars AZURE_CLIENT_ID=${USER_ASSIGNED_IDENTITY_CLIENT_ID} AZURE_KEYVAULT_URL=${AZURE_KEYVAULT_URL}中提供的两个环境变量来访问 Azure 密钥库。 请记住, AZURE_KEYVAULT_URL 由于 MicroProfile 配置规范定义的环境变量映射规则,因此会查阅环境变量。

然后,使用以下命令检索完全限定的 URL 以访问应用:

export APP_URL=https://$(az containerapp show \
    --resource-group ${RESOURCE_GROUP_NAME} \
    --name ${ACA_NAME} \
    --query properties.configuration.ingress.fqdn \
    --output tsv)

最后,再次运行以下命令以测试在容器应用实例上运行的示例:

# Get the value of secret "secret" stored in the Azure key vault. You should see 1234 in the response.
echo $(curl -s ${APP_URL}/config/value/secret -X GET)

# Get the value of secret "anotherSecret" stored in the Azure key vault. You should see 5678 in the response.
echo $(curl -s  ${APP_URL}/config/value/anotherSecret -X GET)

# Get the names of secrets stored in the Azure key vault. You should see ["anotherSecret","secret"] in the response.
echo $(curl -s  ${APP_URL}/config/propertyNames -X GET)

# Get the name-value paris of secrets stored in the Azure key vault. You should see {"anotherSecret":"5678","secret":"1234"} in the response.
echo $(curl -s  ${APP_URL}/config/properties -X GET)

应会看到注释中所述的预期输出。 如果未看到它们,应用仍可能正在启动。 等待一段时间,然后重试。

清理资源

若要避免 Azure 费用,应清除不需要的资源。 不再需要资源时,运行以下命令以清理资源。

az keyvault delete \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${KEY_VAULT_NAME}"

az keyvault purge \
    --name "${KEY_VAULT_NAME}" \
    --no-wait

az group delete \
    --name ${RESOURCE_GROUP_NAME} \
    --yes \
    --no-wait

后续步骤

可以从以下参考中了解详细信息: