你当前正在访问 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 命令来检索密钥存储 CSI 驱动程序的 Azure 密钥保管库 提供程序使用的用户分配的托管标识的
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-uiYAML 清单以包含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.ymlYAML 清单部署应用和ingress.yml,以创建入口对象。 如果使用 Azure 公共 DNS 区域进行域名解析,则可以使用04-configure-dns.sh脚本。 此脚本可将 NGINX 入口控制器的公共 IP 地址与入口对象(会公开yelb-ui服务)使用的域进行关联。 此脚本会执行以下步骤:- 获取应用程序网关前端 IP 配置所用 Azure 公共 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 机密。 请务必注意,Secrets Store CSI Driver for 密钥保管库 只有在 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 Workspace 工作区。 通过启用日志,您可以深入了解应用程序网关中由 Azure Web 应用程序防火墙(WAF)执行的重要评估、匹配和阻止操作。 有关详细信息,请参阅应用程序网关的诊断日志。 还可以使用Log Analytics来检查防火墙日志中的数据。 在Log Analytics工作区中有防火墙日志时,可以查看数据、写入查询、创建可视化效果并将其添加到门户仪表板。 有关日志查询的详细信息,请参阅 Azure Monitor 日志查询概述。
使用 Kusto 查询深入挖掘数据
在建议的解决方案中,部署过程会自动配置 Azure 应用程序网关 资源,以将诊断日志和指标收集到 Azure Log Analytics 工作区。 通过启用日志,可以深入了解应用程序网关中由 Azure Web 应用程序防火墙 (WAF) 执行的评估、匹配和阻止操作。 有关详细信息,请参阅应用程序网关的诊断日志。
还可以使用Log Analytics来检查防火墙日志中的数据。 在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 防火墙 提高解决方案的安全性和威胁防护。 有关详细信息,请参阅以下文章:
如果使用 NGINX 入口控制器或任何其他 AKS 托管入口控制器来代替Azure 应用程序网关,则可以使用 Azure 防火墙检查传入和传出 AKS 群集的流量,并保护群集免受数据外泄和其他不需要的网络流量。 有关详细信息,请参阅以下文章:
供稿人
Microsoft维护本文。 本系列文章为以下参与者的原创作品:
主要作者:
- 保罗·萨尔瓦托里 |首席客户工程师
其他参与者:
- Ken Kilty | 首席 TPM
- Russell de Pina | 首席 TPM
- Erin Schaffer | 内容开发人员 2