使用应用程序路由网关 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-uri和kubernetes.azure.com/tls-cert-service-account侦听器 TLS 选项指定的、存储在 Azure 密钥保管库 中的证书来终止 TLS。 - 使用
ClusterExternalDNS和ExternalDNS自定义资源,根据您的Gateway、HTTPRoute和GRPCRoute资源的主机名,将 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 选项的每个监听器,操作器:
- 在
SecretProviderClass的命名空间中预配一个名为kv-gw-cert-<gateway-name>-<listener-name>的Gateway,并将其配置为使用工作负载身份验证从 Azure 密钥保管库 获取证书。 - 触发机密存储 CSI 驱动程序的 Azure 密钥保管库 提供程序,将证书同步为
kubernetes.io/tls命名空间中Gateway同名的 Kubernetes 机密。 - 将监听器的
tls.certificateRefs字段修补为引用已同步的 Kubernetes Secret。
DNS 集成
应用程序路由操作员通过两个自定义资源为你管理实例 external-dns :
| 自定义资源 | Scope |
|---|---|
ClusterExternalDNS(clusterexternaldnses.approuting.kubernetes.azure.com) |
群集范围。
Gateway
HTTPRoute
GRPCRoute监视群集中所有命名空间中的资源。 |
ExternalDNS(externaldnses.approuting.kubernetes.azure.com) |
命名空间范围。 仅监视与该自定义资源位于同一命名空间中的 Gateway、HTTPRoute 和 GRPCRoute 资源。 |
这两种自定义资源都接受可选 filters 选择器,以进一步缩小托管 external-dns 实例在其范围内观察到的资源范围:
| Filter | 它缩小的内容 |
|---|---|
filters.gatewayLabels |
限制控制器监视哪些 Gateway 资源。 |
filters.routeAndIngressLabels |
限制控制器监视哪些 HTTPRoute 和 Ingress 资源。 |
对于每个自定义资源,应用程序路由运算符:
- 部署托管的
external-dns实例,并将其配置为从HTTPRoute和GRPCRoute资源中获取记录,并以指定的 Azure DNS 区域为目标。 - 通过自定义资源的
identity字段中引用的 ServiceAccount,使用 Microsoft Entra Workload Identity 对 Azure DNS 进行身份验证。 - 向每个列出的 Azure DNS 区域发布 A 记录,用于每个绑定到作用域内受管
HTTPRoute资源的GRPCRoute或Gateway主机名。
先决条件
启用了以下 两 项功能的 AKS 群集:
-
应用程序路由加载项(
--enable-app-routing)。 此加载项在群集上部署应用程序路由运算符,这是协调本文中记录的 DNS 和 TLS 集成的组件。 在现有集群上,也可以使用az aks approuting enable启用此功能。 -
应用程序路由网关 API 实现(
--enable-app-routing-istio)。 此标志启用应用程序路由操作员与之集成的网关 API 控制平面(approuting-istioGatewayClass)。 在现有集群上,也可以使用az aks approuting gateway istio enable启用此功能。
这两个标志都必须启用:只启用其中一个,集成功能仍不可用。 您可以在创建集群时通过
az aks create启用这两项,也可以在现有集群上使用az aks update启用(或使用前文所述的approuting子命令)。-
应用程序路由加载项(
已在集群上启用 托管网关 API 安装。
群集上已启用 Microsoft Entra 工作负载标识 功能以及 OIDC 颁发程序。 您可以通过在
--enable-oidc-issuer或--enable-workload-identity上使用az aks create和az aks update标志来启用这两项功能。在群集上启用的适用于 Secrets Store CSI Driver 的 Azure 密钥保管库 提供程序加载项。 您可以通过使用带有
az aks enable-addons的--addons azure-keyvault-secrets-provider命令,或将--enable-kv传递给az aks approuting enable或az aks approuting update来启用该功能。Azure CLI 版本
2.86.0或更高版本。 运行az --version以查找版本azure-cli,然后运行az upgrade以升级。对你自己的身份拥有足够的 Azure 基于角色的访问控制 (RBAC) 权限,以创建角色分配和联合身份凭据。
Owner角色或Role Based 访问控制 Administrator和Managed Identity Contributor的组合就足够了。
注释
--attach-kv 上的 --attach-zones 和 az 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-a 和 app-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 不同,命名空间范围版本仅监视与该自定义资源位于同一命名空间中的 Gateway、HTTPRoute 和 GRPCRoute 资源。 与上一样 ClusterExternalDNS,可以选择使用 filters.gatewayLabels 和 filters.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 集成仅适用于具有
Gateway的gatewayClassName: approuting-istio资源。 尚不支持将应用程序路由加载项的 DNS 和 TLS 集成功能与 Istio 服务网格加载项GatewayClass或任何其他GatewayClass配合使用。 -
ClusterExternalDNS或ExternalDNS自定义资源最多可通过dnsZoneResourceIDs引用七个 Azure DNS 区域。 单个自定义资源中引用的所有区域必须位于同一Azure订阅和资源组中。 它们还必须是同一类型(公共或私有)。 - 当您删除
external-dns或ClusterExternalDNS自定义资源时,托管的ExternalDNS实例不会自动删除 DNS 记录。 若要删除孤立记录,请在删除自定义资源后直接从Azure DNS区域中删除它们。 - 目前不支持从
TLSRoute资源进行 DNS 记录对帐。 托管的external-dns实例仅从Gateway、HTTPRoute和GRPCRoute资源中获取记录。