你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
在本文中,你将 Yelb 应用部署到你在上一篇文章中创建的 Azure Kubernetes 服务 (AKS) 群集。
检查环境
在部署应用之前,确保使用以下命令正确配置 AKS 群集:
使用
kubectl get namespace
命令列出群集中的命名空间。kubectl get namespace
如果是使用应用程序路由加载项安装的 NGINX 入口控制器,则应查看输出的
app-routing-system
命名空间:NAME STATUS AGE app-routing-system Active 4h28m cert-manager Active 109s dapr-system Active 4h18m default Active 4h29m gatekeeper-system Active 4h28m kube-node-lease Active 4h29m kube-public Active 4h29m kube-system Active 4h29m
如果是通过 Helm 安装了 NGINX 入口控制器,则应查看输出的
ingress-basic
命名空间:NAME STATUS AGE cert-manager Active 7m42s dapr-system Active 11m default Active 21m gatekeeper-system Active 20m ingress-basic Active 7m19s kube-node-lease Active 21m kube-public Active 21m kube-system Active 21m prometheus Active 8m9s
使用
app-routing-system
获取ingress-basic
或kubectl get service command
命名空间的服务详细信息。kubectl get service --namespace <namespace-name> -o wide
如果使用的是应用程序路由附加项,应将
EXTERNAL-IP
服务的nginx
视作一个专用 IP 地址。 该地址是 AKS 群集的kubernetes-internal
专用负载均衡器中某个前端 IP 配置的专用 IP:NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx LoadBalancer 172.16.55.104 10.240.0.7 80:31447/TCP,443:31772/TCP,10254:30459/TCP 4h28m app=nginx
如果使用的是 Helm,应将
EXTERNAL-IP
服务的nginx-ingress-ingress-nginx-controller
视作一个专用 IP 地址。 该地址是 AKS 群集的kubernetes-internal
专用负载均衡器中某个前端 IP 配置的专用 IP。NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-ingress-ingress-nginx-controller LoadBalancer 172.16.42.152 10.240.0.7 80:32117/TCP,443:32513/TCP 7m31s nginx-ingress-ingress-nginx-controller-admission ClusterIP 172.16.78.85 <none> 443/TCP 7m31s nginx-ingress-ingress-nginx-controller-metrics ClusterIP 172.16.109.138 <none> 10254/TCP 7m31s
准备部署 Yelb 应用
如果要使用位于应用程序网关的 TLS 终止以及通过 HTTP 的 Yelb 调用方法部署示例,可以查找 Bash 脚本和 YAML 模板在 文件中部署 http
应用。
如果要使用使用 Azure 应用程序网关实现端到端 TLS 体系结构部署示例,可以查找 Bash 脚本和 YAML 模板在 https
文件中部署 Web 应用。
在本文剩余部分,我们将引导你完成使用端到端 TLS 方法部署示例应用的整个流程。
自定义变量
运行任何脚本前,需要先在
00-variables.sh
文件中自定义变量的值。 所有脚本中都包含该文件,其中包含以下变量:# Azure subscription and tenant RESOURCE_GROUP_NAME="<aks-resource-group>" SUBSCRIPTION_ID="$(az account show --query id --output tsv)" SUBSCRIPTION_NAME="$(az account show --query name --output tsv)" TENANT_ID="$(az account show --query tenantId --output tsv)" AKS_CLUSTER_NAME="<aks-name>" AGW_NAME="<application-gateway-name>" AGW_PUBLIC_IP_NAME="<application-gateway-public-ip-name>" DNS_ZONE_NAME="<your-azure-dns-zone-name-eg-contoso.com>" DNS_ZONE_RESOURCE_GROUP_NAME="<your-azure-dns-zone-resource-group-name>" DNS_ZONE_SUBSCRIPTION_ID="<your-azure-dns-zone-subscription-id>" # NGINX ingress controller installed via Helm NGINX_NAMESPACE="ingress-basic" NGINX_REPO_NAME="ingress-nginx" NGINX_REPO_URL="https://kubernetes.github.io/ingress-nginx" NGINX_CHART_NAME="ingress-nginx" NGINX_RELEASE_NAME="ingress-nginx" NGINX_REPLICA_COUNT=3 # Specify the ingress class name for the ingress controller # - nginx: Unmanaged NGINX ingress controller installed via Helm # - webapprouting.kubernetes.azure.com: Managed NGINX ingress controller installed via AKS application routing add-on INGRESS_CLASS_NAME="webapprouting.kubernetes.azure.com" # Subdomain of the Yelb UI service SUBDOMAIN="<yelb-application-subdomain>" # URL of the Yelb UI service URL="https://$SUBDOMAIN.$DNS_ZONE_NAME" # Secret provider class KEY_VAULT_NAME="<key-vault-name>" KEY_VAULT_CERTIFICATE_NAME="<key-vault-resource-group-name>" KEY_VAULT_SECRET_PROVIDER_IDENTITY_CLIENT_ID="<key-vault-secret-provider-identity-client-id>" TLS_SECRET_NAME="yelb-tls-secret" NAMESPACE="yelb"
可以运行以下 az aks show 命令来检索
clientId
使用的用户分配的托管标识的 。keyVault.bicep
模块密钥保管库管理员角色到附加项的用户分配托管标识,让它检索用于通过 NGINX 入口控制器公开yelb-ui
服务的 Kubernetes 入口使用的证书。az aks show \ --name <aks-name> \ --resource-group <aks-resource-group-name> \ --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId \ --output tsv \ --only-show-errors
如果使用的是与本示例一起提供的 Bicep 模块部署的 Azure 基础结构,可以继续部署 Yelb 应用。 如果要将应用部署在 AKS 群集中,可以使用以下脚本配置环境。 可以使用
02-create-nginx-ingress-controller.sh
安装 NGINX 入口控制器,并启用 ModSecurity 开源 Web 应用程序防火墙 (WAF)。#!/bin/bash # Variables source ./00-variables.sh # Check if the NGINX ingress controller Helm chart is already installed result=$(helm list -n $NGINX_NAMESPACE | grep $NGINX_RELEASE_NAME | awk '{print $1}') if [[ -n $result ]]; then echo "[$NGINX_RELEASE_NAME] NGINX ingress controller release already exists in the [$NGINX_NAMESPACE] namespace" else # Check if the NGINX ingress controller repository is not already added result=$(helm repo list | grep $NGINX_REPO_NAME | awk '{print $1}') if [[ -n $result ]]; then echo "[$NGINX_REPO_NAME] Helm repo already exists" else # Add the NGINX ingress controller repository echo "Adding [$NGINX_REPO_NAME] Helm repo..." helm repo add $NGINX_REPO_NAME $NGINX_REPO_URL fi # Update your local Helm chart repository cache echo 'Updating Helm repos...' helm repo update # Deploy NGINX ingress controller echo "Deploying [$NGINX_RELEASE_NAME] NGINX ingress controller to the [$NGINX_NAMESPACE] namespace..." helm install $NGINX_RELEASE_NAME $NGINX_REPO_NAME/$nginxChartName \ --create-namespace \ --namespace $NGINX_NAMESPACE \ --set controller.nodeSelector."kubernetes\.io/os"=linux \ --set controller.replicaCount=$NGINX_REPLICA_COUNT \ --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \ --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz fi # Get values helm get values $NGINX_RELEASE_NAME --namespace $NGINX_NAMESPACE
部署应用程序
运行以下
03-deploy-yelb.sh
脚本部署 Yelb 应用和 Kubernetes 入口对象,让yelb-ui
服务可以访问公共 Internet。#!/bin/bash # Variables source ./00-variables.sh # Check if namespace exists in the cluster result=$(kubectl get namespace -o jsonpath="{.items[?(@.metadata.name=='$NAMESPACE')].metadata.name}") if [[ -n $result ]]; then echo "$NAMESPACE namespace already exists in the cluster" else echo "$NAMESPACE namespace does not exist in the cluster" echo "creating $NAMESPACE namespace in the cluster..." kubectl create namespace $NAMESPACE fi # Create the Secret Provider Class object echo "Creating the secret provider class object..." cat <<EOF | kubectl apply -f - apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: namespace: $NAMESPACE name: yelb spec: provider: azure secretObjects: - secretName: $TLS_SECRET_NAME type: kubernetes.io/tls data: - objectName: $KEY_VAULT_CERTIFICATE_NAME key: tls.key - objectName: $KEY_VAULT_CERTIFICATE_NAME key: tls.crt parameters: usePodIdentity: "false" useVMManagedIdentity: "true" userAssignedIdentityID: $KEY_VAULT_SECRET_PROVIDER_IDENTITY_CLIENT_ID keyvaultName: $KEY_VAULT_NAME objects: | array: - | objectName: $KEY_VAULT_CERTIFICATE_NAME objectType: secret tenantId: $TENANT_ID EOF # Apply the YAML configuration kubectl apply -f yelb.yml echo "waiting for secret $TLS_SECRET_NAME in namespace $namespace..." while true; do if kubectl get secret -n $NAMESPACE $TLS_SECRET_NAME >/dev/null 2>&1; then echo "secret $TLS_SECRET_NAME found!" break else printf "." sleep 3 fi done # Create chat-ingress cat ingress.yml | yq "(.spec.ingressClassName)|="\""$INGRESS_CLASS_NAME"\" | yq "(.spec.tls[0].hosts[0])|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" | yq "(.spec.tls[0].secretName)|="\""$TLS_SECRET_NAME"\" | yq "(.spec.rules[0].host)|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" | kubectl apply -f - # Check the deployed resources within the yelb namespace: kubectl get all -n yelb
更新
yelb-ui
YAML 清单以包括csi volume
定义和volume mount
,将证书读取为来自 Azure 密钥保管库的密钥。
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: yelb
name: yelb-ui
spec:
replicas: 1
selector:
matchLabels:
app: yelb-ui
tier: frontend
template:
metadata:
labels:
app: yelb-ui
tier: frontend
spec:
containers:
- name: yelb-ui
image: mreferre/yelb-ui:0.7
ports:
- containerPort: 80
volumeMounts:
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: yelb
现在,就可以部署应用了。 此脚本使用
yelb.yml
YAML 清单部署应用和ingress.yml
,以创建入口对象。 如果域名解析使用的是 Azure 公共 DNS 区域,可以部署04-configure-dns.sh
脚本。 此脚本可将 NGINX 入口控制器的公共 IP 地址与入口对象(会公开yelb-ui
服务)使用的域进行关联。 此脚本会执行以下步骤:- 检索应用程序网关的前端 IP 配置使用的 Azure 公共 IP 的公共 IP 地址。
- 检查
A
服务使用的子域是否存在一个yelb-ui
记录。 - 如果
A
记录不存在,则脚本会创建该记录。
source ./00-variables.sh
# Get the address of the Application Gateway Public IP
echo "Retrieving the address of the [$AGW_PUBLIC_IP_NAME] public IP address of the [$AGW_NAME] Application Gateway..."
PUBLIC_IP_ADDRESS=$(az network public-ip show \
--resource-group $RESOURCE_GROUP_NAME \
--name $AGW_PUBLIC_IP_NAME \
--query ipAddress \
--output tsv \
--only-show-errors)
if [[ -n $PUBLIC_IP_ADDRESS ]]; then
echo "[$PUBLIC_IP_ADDRESS] public IP address successfully retrieved for the [$AGW_NAME] Application Gateway"
else
echo "Failed to retrieve the public IP address of the [$AGW_NAME] Application Gateway"
exit
fi
# Check if an A record for todolist subdomain exists in the DNS Zone
echo "Retrieving the A record for the [$SUBDOMAIN] subdomain from the [$DNS_ZONE_NAME] DNS zone..."
IPV4_ADDRESS=$(az network dns record-set a list \
--zone-name $DNS_ZONE_NAME \
--resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
--subscription $DNS_ZONE_SUBSCRIPTION_ID \
--query "[?name=='$SUBDOMAIN'].ARecords[].IPV4_ADDRESS" \
--output tsv \
--only-show-errors)
if [[ -n $IPV4_ADDRESS ]]; then
echo "An A record already exists in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$IPV4_ADDRESS] IP address"
if [[ $IPV4_ADDRESS == $PUBLIC_IP_ADDRESS ]]; then
echo "The [$IPV4_ADDRESS] ip address of the existing A record is equal to the ip address of the ingress"
echo "No additional step is required"
continue
else
echo "The [$IPV4_ADDRESS] ip address of the existing A record is different than the ip address of the ingress"
fi
# Retrieving name of the record set relative to the zone
echo "Retrieving the name of the record set relative to the [$DNS_ZONE_NAME] zone..."
RECORDSET_NAME=$(az network dns record-set a list \
--zone-name $DNS_ZONE_NAME \
--resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
--subscription $DNS_ZONE_SUBSCRIPTION_ID \
--query "[?name=='$SUBDOMAIN'].name" \
--output tsv \
--only-show-errors 2>/dev/null)
if [[ -n $RECORDSET_NAME ]]; then
echo "[$RECORDSET_NAME] record set name successfully retrieved"
else
echo "Failed to retrieve the name of the record set relative to the [$DNS_ZONE_NAME] zone"
exit
fi
# Remove the A record
echo "Removing the A record from the record set relative to the [$DNS_ZONE_NAME] zone..."
az network dns record-set a remove-record \
--ipv4-address $IPV4_ADDRESS \
--record-set-name $RECORDSET_NAME \
--zone-name $DNS_ZONE_NAME \
--resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
--subscription $DNS_ZONE_SUBSCRIPTION_ID \
--only-show-errors 1>/dev/null
if [[ $? == 0 ]]; then
echo "[$IPV4_ADDRESS] ip address successfully removed from the [$RECORDSET_NAME] record set"
else
echo "Failed to remove the [$IPV4_ADDRESS] ip address from the [$RECORDSET_NAME] record set"
exit
fi
fi
# Create the A record
echo "Creating an A record in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address..."
az network dns record-set a add-record \
--zone-name $DNS_ZONE_NAME \
--resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
--subscription $DNS_ZONE_SUBSCRIPTION_ID \
--record-set-name $SUBDOMAIN \
--ipv4-address $PUBLIC_IP_ADDRESS \
--only-show-errors 1>/dev/null
if [[ $? == 0 ]]; then
echo "A record for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address successfully created in [$DNS_ZONE_NAME] DNS zone"
else
echo "Failed to create an A record for the $SUBDOMAIN subdomain with [$PUBLIC_IP_ADDRESS] IP address in [$DNS_ZONE_NAME] DNS zone"
fi
注释
在部署 Yelb 应用和创建 ingress
对象之前,此脚本会先生成一个 SecretProviderClass
来检索 Azure 密钥保管库中的 TLS 证书,并为 ingress
对象生成 Kubernetes 密钥。 注意事项:密钥保管库的密钥存储 CSI 驱动程序仅在 SecretProviderClass
中包含 deployment
和卷定义时,才会创建包含 TLS 密钥的 Kubernetes 密钥。 为确保从 Azure 密钥保管库中正确检索到 TLS 证书并将其存储在 ingress
对象使用的 Kubernetes 密钥中,需要对 yelb-ui
部署的 YAML 清单进行以下修改:
- 使用
csi volume
驱动程序添加secrets-store.csi.k8s.io
定义,引用负责从 Azure 密钥保管库中检索 TLS 证书的SecretProviderClass
对象。 - 包括
volume mount
以将证书读取为 Azure 密钥保管库中的密钥。
有关详细信息,请参阅设置机密存储 CSI 驱动程序以启用使用 TLS 的 NGINX 入口控制器。
测试应用程序
使用 05-call-yelb-ui.sh
脚本调用 yelb-ui
服务,模拟 SQL 注入、XSS 攻击,并观察 ModSecurity 的托管规则集如何阻止恶意请求。
#!/bin/bash
# Variables
source ./00-variables.sh
# Call REST API
echo "Calling Yelb UI service at $URL..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL
# Simulate SQL injection
echo "Simulating SQL injection when calling $URL..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleSQLInjection%27%20--
# Simulate XSS
echo "Simulating XSS when calling $URL..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E
# A custom rule blocks any request with the word blockme in the querystring.
echo "Simulating query string manipulation with the 'blockme' word in the query string..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users?task=blockme
Bash 脚本应生成以下输出(其中第一次调用成功),而 ModSecurity 规则会阻止以下两次调用:
Calling Yelb UI service at https://yelb.contoso.com...
HTTP Status: 200
Simulating SQL injection when calling https://yelb.contoso.com...
HTTP Status: 403
Simulating XSS when calling https://yelb.contoso.com...
HTTP Status: 403
Simulating query string manipulation with the 'blockme' word in the query string...
HTTP Status: 403
监视应用程序
在提议的解决方案中,部署流程会自动将 Azure 应用程序网关资源配置为将诊断日志和指南收集到 Azure Log Analytics 工作区工作区。 通过启用日志,可以获得有价值的洞察,深入了解应用程序网关内 Azure Web 应用程序防火墙 (WAF) 执行的评估、匹配和阻止。 有关详细信息,请参阅应用程序网关的诊断日志。 还可以使用日志分析检查防火墙日志里的数据。 当 Log Analytics 工作区中有防火墙日志时,你可以查看数据、编写查询、创建可视化效果,并将这些内容添加到门户仪表板。 有关日志查询的详细信息,请参阅 Azure Monitor 中的日志查询概述。
使用 Kusto 查询深入挖掘数据
在提议的解决方案中,部署流程会自动将 Azure 应用程序网关资源配置为将诊断日志和指南收集到 Azure Log Analytics 工作区。 通过启用日志,可以获得洞察,深入了解应用程序网关内 Azure Web 应用程序防火墙 (WAF) 执行的评估、匹配和阻止。 有关详细信息,请参阅应用程序网关的诊断日志。
还可以使用日志分析检查防火墙日志里的数据。 当 Log Analytics 工作区中有防火墙日志时,你可以查看数据、编写查询、创建可视化效果,并将这些内容添加到门户仪表板。 有关日志查询的详细信息,请参阅 Azure Monitor 中的日志查询概述。
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| limit 10
使用特定于资源的表时,也可以使用以下查询访问原始的防火墙日志数据。 有关特定于资源的表的详细信息,请参阅监控数据引用文档。
AGWFirewallLogs
| limit 10
获取到数据后,可以更深入地挖掘数据并创建图表或可视化内容。 以下是可以利用的 KQL 查询的其他一些示例:
按 IP 匹配/阻止的请求
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize count() by clientIp_s, bin(TimeGenerated, 1m)
| render timechart
按 URI 匹配/阻止的请求
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize count() by requestUri_s, bin(TimeGenerated, 1m)
| render timechart
排名靠前的匹配的规则
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize count() by ruleId_s, bin(TimeGenerated, 1m)
| where count_ > 10
| render timechart
前五个匹配的规则组
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize Count=count() by details_file_s, action_s
| top 5 by Count desc
| render piechart
查看已部署的资源
可以使用 Azure CLI 或 Azure PowerShell 列出资源组中部署的资源。
使用 [az resource list
][az-resource-list] 命令列出资源组中部署的资源。
az resource list --resource-group <resource-group-name>
如果不再需要本教程中创建的资源,可以使用 Azure CLI 或 Azure PowerShell 删除资源组。
使用 az group delete
命令删除资源组及其关联的资源。
az group delete --name <resource-group-name>
后续步骤
可以使用 Azure DDoS 防护和 Azure 防火墙增强解决方案的安全与威胁防护等级。 有关详细信息,请参阅以下文章:
如果在 Azure 应用程序网关中使用 NGINX 入口控制器或任何其他 AKS 托管的入口控制器,则可以使用 Azure 防火墙检测进出 AKS 群集的流量,保护群集不发生数据外泄以及其他不需要的网络流量。 有关详细信息,请参阅以下文章:
- 使用 Azure 防火墙保护 Azure Kubernetes 服务 (AKS) 群集
- 使用 Azure 防火墙帮助保护 Azure Kubernetes 服务 (AKS) 群集
供稿人
Microsoft维护本文。 本系列文章为以下参与者的原创作品:
主要作者:
- 保罗·萨尔瓦托里 |首席客户工程师
其他参与者:
- Ken Kilty | 首席 TPM
- Russell de Pina | 首席 TPM
- Erin Schaffer | 内容开发人员 2