使用应用程序路由网关 API 实现配置 Azure DNS 和 TLS

使用应用程序路由网关 API,用户可以轻松地使用自己的Azure 密钥保管库证书(包括自动域名发布)在 AKS 上公开 HTTPS 应用程序。 应用程序路由运算符与 Azure DNS 和 Azure 密钥保管库 集成,并会协调 SecretProviderClass(用于 TLS 证书的 Kubernetes Secret)、侦听器的 certificateRefs 字段以及单独的 external-dns Deployment,因此你无需手动管理这些资源。

本文将向您介绍如何操作:

  • 预配先决条件Azure资源(Azure DNS区域、Azure 密钥保管库、用户分配的托管标识、角色分配和联合标识凭据)。
  • 配置 Gateway 侦听器,以使用通过 kubernetes.azure.com/tls-cert-keyvault-urikubernetes.azure.com/tls-cert-service-account 侦听器 TLS 选项指定的、存储在 Azure 密钥保管库 中的证书来终止 TLS。
  • 使用 ClusterExternalDNSExternalDNS 自定义资源,根据您的 GatewayHTTPRouteGRPCRoute 资源的主机名,将 DNS 记录发布到 Azure DNS。

集成的工作原理

应用程序路由 Operator 提供了两种集成方式,用于自动创建原本需要手动创建的资源,从而通过自定义域名和 TLS 终止让 Gateway 资源上线。

TLS 集成

Gateway当资源使用 approuting-istio GatewayClass 和侦听器携带以下两个 TLS 选项时,应用程序路由操作员将终止 TLS 所需的资源与存储在Azure 密钥保管库中的证书进行协调:

TLS 选项密钥 价值
kubernetes.azure.com/tls-cert-keyvault-uri 用于获取 TLS 证书的 Azure 密钥保管库 证书 URI。 使用不带版本的 URI(例如 https://<vault>.vault.azure.cn/certificates/<cert>),以便 Operator 自动获取 Azure 密钥保管库 中轮换后的证书。
kubernetes.azure.com/tls-cert-service-account Gateway 位于同一命名空间中的 Kubernetes ServiceAccount 的名称。 ServiceAccount 必须通过 Microsoft Entra 工作负载标识绑定到用户分配的托管标识,并且该托管标识必须在目标 Azure 密钥保管库 上具有 密钥保管库 Secrets User 角色。

对于同时包含这两个 TLS 选项的每个监听器,操作器:

  1. SecretProviderClass 的命名空间中预配一个名为 kv-gw-cert-<gateway-name>-<listener-name>Gateway,并将其配置为使用工作负载身份验证从 Azure 密钥保管库 获取证书。
  2. 触发机密存储 CSI 驱动程序的 Azure 密钥保管库 提供程序,将证书同步为kubernetes.io/tls命名空间中Gateway同名的 Kubernetes 机密。
  3. 将监听器的 tls.certificateRefs 字段修补为引用已同步的 Kubernetes Secret。

DNS 集成

应用程序路由操作员通过两个自定义资源为你管理实例 external-dns

自定义资源 Scope
ClusterExternalDNSclusterexternaldnses.approuting.kubernetes.azure.com 群集范围。 Gateway HTTPRoute GRPCRoute监视群集中所有命名空间中的资源。
ExternalDNSexternaldnses.approuting.kubernetes.azure.com 命名空间范围。 仅监视与该自定义资源位于同一命名空间中的 GatewayHTTPRouteGRPCRoute 资源。

这两种自定义资源都接受可选 filters 选择器,以进一步缩小托管 external-dns 实例在其范围内观察到的资源范围:

Filter 它缩小的内容
filters.gatewayLabels 限制控制器监视哪些 Gateway 资源。
filters.routeAndIngressLabels 限制控制器监视哪些 HTTPRouteIngress 资源。

对于每个自定义资源,应用程序路由运算符:

  1. 部署托管的external-dns实例,并将其配置为从HTTPRouteGRPCRoute资源中获取记录,并以指定的 Azure DNS 区域为目标。
  2. 通过自定义资源的 identity 字段中引用的 ServiceAccount,使用 Microsoft Entra Workload Identity 对 Azure DNS 进行身份验证。
  3. 向每个列出的 Azure DNS 区域发布 A 记录,用于每个绑定到作用域内受管 HTTPRoute 资源的 GRPCRouteGateway 主机名。

先决条件

注释

--attach-kv 上的 --attach-zonesaz aks approuting update 标志(以及 az aks approuting zone 子命令)是为基于 NGINX 的旧版模式而设计的;在这种模式下,Application Routing 加载项自有的用户分配托管标识被授予对单个 Azure 密钥保管库 和 DNS 区域的 Azure RBAC 访问权限。 本文所述的网关 API 集成不使用它们。 新体验基于 Microsoft Entra 工作负载标识,而不是该加载项的托管身份,因此你需要创建自己的用户分配的托管身份,向其授予适当的 Azure DNS 和 Azure 密钥保管库 角色,并创建联合身份凭据,将其绑定到你在 Gateway 侦听器 TLS 选项以及 ExternalDNS/ClusterExternalDNS 自定义资源中引用的 Kubernetes ServiceAccounts。

设置以下环境变量。 该演练在后续的每条命令中都会重复使用它们:

export RESOURCE_GROUP=<resource-group-name>
export CLUSTER=<cluster-name>
export LOCATION=<azure-region>

获取 kubectl 的集群凭据:

az aks get-credentials --resource-group $RESOURCE_GROUP --name $CLUSTER

创建Azure基础结构

创建Azure DNS区域

如果已有希望应用程序路由操作员在其中管理记录的Azure DNS区域,则可以跳过此步骤并将值ZONE_NAME分配给现有区域名称。

export ZONE_NAME=<dns-zone-name>
az network dns zone create --resource-group $RESOURCE_GROUP --name $ZONE_NAME
export ZONE_ID=$(az network dns zone show --resource-group $RESOURCE_GROUP --name $ZONE_NAME --query id -o tsv)

创建Azure 密钥保管库和证书

创建存储 TLS 证书的Azure 密钥保管库。 将保管库配置为使用 Azure RBAC 进行授权,这是建议的权限模型

export KV_NAME=<key-vault-name>
az keyvault create \
  --name $KV_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --enable-rbac-authorization true

注释

若要在下一步创建证书,你自己的 Azure 身份需要在保管库上具有 密钥保管库 Certificates Officer 角色(或 密钥保管库 Administrator)。 在继续之前,在保管库上授予此角色。

在Azure 密钥保管库中创建自签名通配符证书。 对于生产部署,请改用 az keyvault certificate import 导入由证书颁发机构(CA)签名的证书。

cat > cert-policy.json <<EOF
{
  "issuerParameters": { "name": "Self" },
  "keyProperties": { "exportable": true, "keyType": "RSA", "keySize": 2048, "reuseKey": false },
  "secretProperties": { "contentType": "application/x-pkcs12" },
  "x509CertificateProperties": {
    "subject": "CN=*.${ZONE_NAME}",
    "subjectAlternativeNames": { "dnsNames": ["*.${ZONE_NAME}", "${ZONE_NAME}"] },
    "validityInMonths": 12,
    "keyUsage": ["digitalSignature", "keyEncipherment"]
  }
}
EOF

az keyvault certificate create \
  --vault-name $KV_NAME \
  --name approuting-demo-cert \
  --policy @cert-policy.json

捕获未转换的证书 URI。 应用程序路由运算符使用此 URI 来配置 SecretProviderClass。 未转换的 URI 可确保操作员在轮换证书时在Azure 密钥保管库中选取新的证书版本。

export CERT_URI=$(az keyvault certificate show \
  --vault-name $KV_NAME \
  --name approuting-demo-cert \
  --query id -o tsv | sed 's|/[^/]*$||')
echo "Cert URI: $CERT_URI"

创建用户分配的托管标识并授予Azure RBAC 角色

创建一个用户分配的托管标识,供应用程序路由操作器的 external-dns 部署和网关侦听器的 TLS 同步使用,以向 Azure DNS 和 Azure 密钥保管库 进行身份验证。

export UAMI_NAME=<managed-identity-name>
az identity create --resource-group $RESOURCE_GROUP --name $UAMI_NAME --location $LOCATION
export UAMI_CLIENT_ID=$(az identity show --resource-group $RESOURCE_GROUP --name $UAMI_NAME --query clientId -o tsv)
export UAMI_PRINCIPAL_ID=$(az identity show --resource-group $RESOURCE_GROUP --name $UAMI_NAME --query principalId -o tsv)

向托管标识授予目标 Azure DNS 区域上的 DNS Zone Contributor 角色,以及目标 Azure 密钥保管库 上的 密钥保管库 Secrets User 角色:

az role assignment create \
  --assignee-object-id $UAMI_PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal \
  --role "DNS Zone Contributor" \
  --scope $ZONE_ID

az role assignment create \
  --assignee-object-id $UAMI_PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal \
  --role "Key Vault Secrets User" \
  --scope $(az keyvault show --name $KV_NAME --query id -o tsv)

创建命名空间、服务帐户和联合标识凭据

Application Routing 运算符的 TLS 和 DNS 集成均通过使用联合身份凭据 (FIC),借助与用户分配的托管标识绑定的 Kubernetes ServiceAccount 向 Azure 进行身份验证。 需要进行身份验证的每个 (namespace, ServiceAccount) 对都需要一个 FIC。

获取集群的 OIDC 颁发者 URL:

export OIDC_ISSUER=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER --query oidcIssuerProfile.issuerUrl -o tsv)

对于每个计划部署使用 TLS 集成的 Gateway 资源或 ExternalDNS 资源的命名空间,请创建该命名空间、用于 ServiceAccount 的联合身份凭据以及 ServiceAccount 本身。 以下示例创建了两个命名空间 app-aapp-b,每个命名空间中都有一个名为 approuting-demo-sa 的 ServiceAccount:

export SA_NAME=approuting-demo-sa
for ns in app-a app-b; do
  kubectl create namespace $ns

  az identity federated-credential create \
    --identity-name $UAMI_NAME \
    --resource-group $RESOURCE_GROUP \
    --name approuting-demo-fic-$ns \
    --issuer $OIDC_ISSUER \
    --subject "system:serviceaccount:$ns:$SA_NAME" \
    --audiences "api://AzureADTokenExchange"

  kubectl apply -n $ns -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: $SA_NAME
  annotations:
    azure.workload.identity/client-id: $UAMI_CLIENT_ID
  labels:
    azure.workload.identity/use: "true"
EOF
done

azure.workload.identity/client-id 注释将该 ServiceAccount 与托管标识相关联,而 azure.workload.identity/use: "true" 标签则指示 Microsoft Entra Workload Identity webhook 将联合令牌投射到使用该 ServiceAccount 的 Pod 中。 Application Routing 操作器的 TLS 和 DNS 集成要成功通过 Azure 身份验证,这两项都是必需的。

在网关上配置 TLS 终止

在每个命名空间中部署示例 httpbin 工作负荷:

for ns in app-a app-b; do
  kubectl apply -n $ns -f https://raw.githubusercontent.com/istio/istio/release-1.27/samples/httpbin/httpbin.yaml
done

在每个命名空间中创建一个Gateway资源,其中包含一个 HTTPS 侦听器,该侦听器通过 TLS 选项引用Azure 密钥保管库证书。 每个Gateway区域使用其自己的Azure DNS子主机(例如,a.<zone>b.<zone>):

for pair in "app-a:a" "app-b:b"; do
  ns=${pair%%:*}
  sub=${pair##*:}
  fqdn=${sub}.${ZONE_NAME}
  kubectl apply -n $ns -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: ${sub}-gateway
  labels:
    app: approuting-demo
    zone: ${sub}
spec:
  gatewayClassName: approuting-istio
  listeners:
  - name: https
    hostname: $fqdn
    port: 443
    protocol: HTTPS
    tls:
      mode: Terminate
      options:
        kubernetes.azure.com/tls-cert-keyvault-uri: $CERT_URI
        kubernetes.azure.com/tls-cert-service-account: $SA_NAME
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: ${sub}-route
spec:
  parentRefs:
  - name: ${sub}-gateway
  hostnames: ["$fqdn"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /get
    backendRefs:
    - name: httpbin
      port: 8000
EOF
done

等待每个 Gateway 达到 Programmed 条件:

kubectl wait -n app-a --for=condition=programmed gateway a-gateway --timeout=300s
kubectl wait -n app-b --for=condition=programmed gateway b-gateway --timeout=300s

验证应用程序路由操作器是否创建了 SecretProviderClass,以及用于 Secrets Store CSI 驱动程序的 Azure 密钥保管库 提供程序是否已将证书同步到每个命名空间中的 kubernetes.io/tls 机密:

kubectl get secretproviderclass,secret -n app-a
kubectl get secretproviderclass,secret -n app-b

一个命名空间的示例输出:

NAME                                                                        AGE
secretproviderclass.secrets-store.csi.x-k8s.io/kv-gw-cert-a-gateway-https   2m

NAME                                TYPE                DATA   AGE
secret/kv-gw-cert-a-gateway-https   kubernetes.io/tls   2      2m

使用 ClusterExternalDNS 配置Azure DNS记录

部署一个集群范围的 external-dns 实例,该实例通过应用 Gateway 自定义资源,为任何命名空间中的 ClusterExternalDNS 资源发布 A 记录。

kubectl apply -f - <<EOF
apiVersion: approuting.kubernetes.azure.com/v1alpha1
kind: ClusterExternalDNS
metadata:
  name: demo-cluster-dns
spec:
  resourceName: demo-cluster-dns
  resourceNamespace: app-a
  dnsZoneResourceIDs:
  - $ZONE_ID
  resourceTypes:
  - gateway
  identity:
    type: workloadIdentity
    serviceAccount: $SA_NAME
EOF

resourceNamespace 字段指定应用程序路由操作员在其中部署托管 external-dns 实例的命名空间。 引用的 identity.serviceAccount ServiceAccount 必须存在于该命名空间中。

大约一分钟后,Azure DNS 区域中会出现两条 A 记录——每个 Gateway 对应一条:

az network dns record-set a list --resource-group $RESOURCE_GROUP --zone-name $ZONE_NAME -o table
Name    ResourceGroup              Ttl    Type    AutoRegistered    Metadata
------  -------------------------  -----  ------  ----------------  --------
a       <your-rg>                  300    A       False
b       <your-rg>                  300    A       False

使用限定到命名空间的 ExternalDNS 配置 Azure DNS 记录

若要仅发布一部分 Gateway 资源的记录,请使用命名空间范围的 ExternalDNS 自定义资源。 与 ClusterExternalDNS 不同,命名空间范围版本仅监视与该自定义资源位于同一命名空间中的 GatewayHTTPRouteGRPCRoute 资源。 与上一样 ClusterExternalDNS,可以选择使用 filters.gatewayLabelsfilters.routeAndIngressLabels 选择器进一步缩小范围。

首先,删除上一步中的 ClusterExternalDNS

kubectl delete clusterexternaldns demo-cluster-dns

Gateway中部署一个新的app-a,标签为zone: c,并带有相应的HTTPRoute

kubectl apply -n app-a -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: c-gateway
  labels:
    app: approuting-demo
    zone: c
spec:
  gatewayClassName: approuting-istio
  listeners:
  - name: https
    hostname: c.${ZONE_NAME}
    port: 443
    protocol: HTTPS
    tls:
      mode: Terminate
      options:
        kubernetes.azure.com/tls-cert-keyvault-uri: $CERT_URI
        kubernetes.azure.com/tls-cert-service-account: $SA_NAME
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: c-route
spec:
  parentRefs:
  - name: c-gateway
  hostnames: ["c.${ZONE_NAME}"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /get
    backendRefs:
    - name: httpbin
      port: 8000
EOF

kubectl wait -n app-a --for=condition=programmed gateway c-gateway --timeout=300s

使用针对ExternalDNS的标签筛选器,在app-a中应用zone=c命名空间作用域资源:

kubectl apply -n app-a -f - <<EOF
apiVersion: approuting.kubernetes.azure.com/v1alpha1
kind: ExternalDNS
metadata:
  name: demo-ns-dns
spec:
  resourceName: demo-ns-dns
  dnsZoneResourceIDs:
  - $ZONE_ID
  resourceTypes:
  - gateway
  identity:
    type: workloadIdentity
    serviceAccount: $SA_NAME
  filters:
    gatewayLabels: "zone=c"
EOF

有两条作用域规则适用:

  • ExternalDNS 的命名空间范围不包括 b-gateway,因为它位于 app-b 命名空间中。
  • zone=c标签筛选器排除a-gateway,因为它位于app-a但已标记zone=a

应用程序路由操作器为 c.${ZONE_NAME} 发布新的 A 记录:

az network dns record-set a list --resource-group $RESOURCE_GROUP --zone-name $ZONE_NAME -o table

验证 TLS 终止的 HTTPS 流量

通过 Azure DNS 区域的权威名称服务器解析 Gateway 的主机名,并发送 HTTPS 请求:

NS=$(az network dns zone show --resource-group $RESOURCE_GROUP --name $ZONE_NAME --query 'nameServers[0]' -o tsv | sed 's/\.$//')
GATEWAY_IP=$(dig +short @${NS} a.${ZONE_NAME} | tail -1)
curl -k -I --resolve "a.${ZONE_NAME}:443:${GATEWAY_IP}" "https://a.${ZONE_NAME}/get"

应会看到响应 HTTP/2 200 。 网关提供的 TLS 证书是从Azure 密钥保管库同步的证书。 如果您导入了由 CA 签名的证书,请将 -k 替换为 --cacert <path-to-ca-chain>,以验证证书链。

注释

此示例用于 curl --resolve 绕过本地 DNS 解析并将请求定向到网关的外部 IP。 在将 DNS 区域委托给注册机构之前,此方法可用于测试。 对于生产用途,请将域注册机构配置为将区域委托给返回az network dns zone show --query 'nameServers'的Azure DNS名称服务器。

局限性

  • TLS 集成仅适用于具有 GatewaygatewayClassName: approuting-istio 资源。 尚不支持将应用程序路由加载项的 DNS 和 TLS 集成功能与 Istio 服务网格加载项GatewayClass 或任何其他 GatewayClass 配合使用。
  • ClusterExternalDNSExternalDNS 自定义资源最多可通过 dnsZoneResourceIDs 引用七个 Azure DNS 区域。 单个自定义资源中引用的所有区域必须位于同一Azure订阅和资源组中。 它们还必须是同一类型(公共或私有)。
  • 当您删除 external-dnsClusterExternalDNS 自定义资源时,托管的 ExternalDNS 实例不会自动删除 DNS 记录。 若要删除孤立记录,请在删除自定义资源后直接从Azure DNS区域中删除它们。
  • 目前不支持从 TLSRoute 资源进行 DNS 记录对帐。 托管的external-dns实例仅从GatewayHTTPRouteGRPCRoute资源中获取记录。

后续步骤