在本文中,你會將 Yelb 應用程式部署到你在 前一篇文章中建立的 Azure Kubernetes Service (AKS) 叢集。
檢查環境
在部署應用程式前,請使用以下指令確保你的 AKS 叢集已正確配置:
用
kubectl get namespace指令列出叢集中的命名空間。kubectl get namespace如果你用應用程式路由外掛安裝 NGINX 入口控制器,你應該會在輸出中看到命名空間:
app-routing-systemNAME 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-IPnginx服務是私有 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 應用程式
如果您想使用透過 HTTP 在應用程式閘道終止 TLS,並調用 Yelb 的方法來部署範例,您可以在 資料夾中找到 Bash 指令碼和 YAML 範本,以部署 http 應用程式。
如果你想使用使用 Azure 應用程式閘道 的端對端 TLS 架構來部署範例,你可以在 資料夾中找到用於部署網頁應用程式的 Bash 腳本和 YAML 範本。
在本文剩餘部分,我們將引導您使用端對端 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的 用戶指派管理身份,該身份由 Azure Key Vault Provider for Secrets Store CSI Driver 使用。keyVault.bicep模組 金鑰保存庫管理員角色到附加元件的使用者指派受控識別,以讓它能檢索 Kubernetes Ingress 用來透過 NGINX ingress 控制器公開yelb-ui服務的憑證。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,在啟用 ModSecurity 開放原始碼 Web 應用程式防火牆 (WAF) 的情況下安裝 NGINX 輸入控制器。#!/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 Ingress 物件,使yelb-ui服務能對公開網際網路開放。#!/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 Key Vault 中讀取。
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 manifest 來部署應用程式,並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 Key Vault 取得 TLS 憑證,並產生 ingress 物件的 Kubernetes 秘密。 請務必注意,只有在 中包含 SecretProviderClass 和磁碟區定義時,deployment才會建立包含 TLS 憑證的 Kubernetes 祕密。 為確保 TLS 憑證能正確從 Azure Key Vault 取得並儲存在 ingress 物件所使用的 Kubernetes 秘密中,我們需要對 yelb-ui 部署的 YAML 清單做以下修改:
- 使用
csi volume驅動程式新增secrets-store.csi.k8s.io定義,該驅動程式參考負責從 Azure Key Vault 取得 TLS 憑證的SecretProviderClass物件。 - 加入
volume mount,以從 Azure Key Vault 以秘密方式讀取憑證。
欲了解更多資訊,請參閱 設定秘密儲存 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 監視器 中的
使用 Kusto 查詢探索資料
在所提解決方案中,部署流程會自動將 Azure 應用程式閘道 資源配置為收集診斷日誌與指標,以收集至 Azure Log Analytics 工作空間。 啟用日誌後,您可以深入了解應用閘道內 Azure Web 應用程式防火牆(WAF) 所執行的評估、匹配與阻擋。 如需詳細資訊,請參閱 應用程式閘道的診斷記錄。
你也可以使用 Log Analytics 來檢視防火牆日誌中的資料。 當你在 Log Analytics 工作區中擁有防火牆日誌後,你可以查看資料、撰寫查詢、建立視覺化,並將它們加入入口網站儀表板。 欲了解更多日誌查詢資訊,請參閱 Azure 監視器 中日誌查詢概述。
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 Protection 以及 Azure 防火牆 來提升解決方案的安全性與威脅防護。 如需詳細資訊,請參閱下列文章:
- 教學:用 Azure DDoS Network Protection 保護您的應用程式閘道
- 虛擬網路的防火牆和應用程式閘道
- 具有 Azure 防火牆 和應用閘道功能的 零信任 網路,用於 Web 應用程式
如果你用 NGINX 入口控制器或其他 AKS 託管的入口控制器代替 Azure 應用程式閘道,你可以用 Azure 防火牆 來檢查 AKS 叢集的流量,保護叢集免受資料外洩及其他不想要的網路流量。 如需詳細資訊,請參閱下列文章:
貢獻者們
Microsoft 維護本文。 下列參與者最初撰寫了這份內容:
主要作者:
- Paolo Salvatori |首席客戶工程師
其他貢獻者:
- 肯·基爾蒂 | 首席技術計劃經理
- Russell de Pina | Principal TPM
- 愛琳·沙弗 |內容開發人員 2