跳转到主要内容

热门内容

今日:


总体:


最近浏览:


Chinese, Simplified

category

Azure应用程序网关可以通过Azure专用链接服务(PLS)连接到后端应用程序。有关更多信息,请参阅应用程序网关专用链接。

应用程序网关专用链接允许您通过跨越不同虚拟网络和Azure订阅的专用连接连接工作负载。配置后,专用端点将被放置在定义的虚拟网络的子网中,为希望与应用程序网关后面的服务通信的客户端应用程序提供专用IP地址。有关支持私有链接功能的其他PaaS服务的列表,请参阅什么是Azure私有链接?。

本文展示了如何使用Azure应用程序网关、Azure Web应用程序防火墙和Azure专用链接服务(PLS),通过应用程序网关入口控制器安全地公开和保护在Azure Kubernetes服务(AKS)中运行的工作负载。

先决条件


活动的Azure订阅。如果您没有,请在开始之前创建一个免费的Azure帐户。
Visual Studio代码与Bicep扩展一起安装在一个受支持的平台上。


架构


此示例提供了一组Bicep模块,用于在具有API服务器VNET集成、Azure CNI作为网络插件和动态IP分配的公共或私有AKS集群前部署和配置具有WAF策略作为区域第7层负载平衡器的Azure应用网关。该示例实现了一个场景,其中客户端应用程序使用SaaS提供商公开的服务。服务器应用程序工作负载运行在Azure Kubernetes Service(AKS)集群上,并通过应用程序网关入口控制器公开。Azure应用程序网关的前端IP配置配置为通过专用链接公开。前端IP地址是与应用程序网关相关联的IP地址。您可以将应用程序网关配置为具有公共IP地址、专用IP地址,或同时具有这两个地址。应用程序网关支持一个公共或一个专用IP地址。您的虚拟网络和公共IP地址必须与应用程序网关位于同一位置。

注释
在撰写本文时,不支持应用程序网关专用链接配置支持通过Azure专用端点将流量隧道传输到仅限专用IP的应用程序网关。

下图显示了示例部署的体系结构和网络拓扑:

部署脚本用于通过YAML清单创建示例httpbinweb应用程序。创建入口以通过Azure应用网关通过应用网关入口控制器公开Kubernetes服务。

Bicep模块是参数化的,因此您可以选择任何网络插件:


注释
该样本仅使用具有动态IP分配的Azure CNI进行了测试。Azure CNI Overlay当前不支持应用程序网关入口控制器。有关更多信息,请参阅Azure CNI覆盖的限制。

Bicep模块还允许为Azure Kubernetes服务(AKS)安装以下扩展和附加组件:

此外,此示例还展示了如何部署具有以下功能的Azure Kubernetes服务集群:

  • API服务器VNET集成允许您启用API服务器和群集节点之间的网络通信,而不需要专用链路或隧道。与API服务器VNET集成的AKS集群提供了一系列优势,例如,它们可以启用或禁用公共网络访问或专用集群模式,而无需重新部署集群。有关更多信息,请参阅使用API服务器VNet集成创建Azure Kubernetes服务集群。
  • Azure NAT网关,用于管理由AKS托管的工作负载启动的出站连接。
  • 事件驱动的自动缩放(KEDA)插件是一个单一用途的轻量级组件,致力于简化应用程序的自动缩放,是CNCF孵化项目。
  • Azure Kubernetes Service(AKS)的Dapr扩展允许您安装Dapr,这是一个可移植的、事件驱动的运行时,简化了在云和边缘上运行的弹性、无状态和有状态应用程序的构建,并包含了语言和开发人员框架的多样性。凭借其sidecar架构,Dapr可以帮助您应对构建微服务带来的挑战,并使您的代码与平台无关。
  • Flux V2扩展允许通过GitOps将工作负载部署到Azure Kubernetes Service(AKS)集群。有关更多信息,请参阅具有AKS和Azure Arc功能的Kubernetes的GitOps Flux v2配置
  • Vertical Pod Autoscaling允许您根据过去的使用情况自动设置每个工作负载的资源请求和容器限制。VPA使某些pod被调度到具有所需CPU和内存资源的节点上。有关更多信息,请参阅Kubernetes Vertical Pod Autoscaling。
  • 用于秘密存储的Azure密钥库提供商CSI驱动程序提供了多种基于身份访问Azure密钥库的方法。
  • Open Service Mesh插件是一个轻量级、可扩展的云原生服务网格,允许您为高度动态的微服务环境统一管理、保护和获得开箱即用的可观察性功能。


在生产环境中,我们强烈建议部署具有正常运行时间SLA的专用AKS集群。有关更多信息,请参阅具有公共DNS地址的专用AKS群集。或者,您可以部署公共AKS集群,并使用授权的IP地址范围安全访问API服务器。

Bicep模块为服务提供商部署以下Azure资源:

  • 微软网络/应用程序网关:Azure应用程序网关资源,用于通过Azure专用链接服务和应用程序网关入口控制器公开AKS托管的示例应用程序。
  • 微软网络/应用程序网关WebApplicationFirewallPolicies:Azure应用程序网关上的Azure Web应用程序防火墙(WAF)为您的Web应用程序提供集中保护。WAF保护您的web服务免受常见的漏洞攻击。它使您的服务对用户具有高可用性,并帮助您满足法规遵从性要求。您可以配置WAF策略,并将该策略与一个或多个前门前端关联以进行保护。此示例部署的WAF策略由三种类型的安全规则组成:
    • 自定义规则用于根据有效负载的内容、查询字符串、HTTP请求方法、调用方的IP地址等来阻止传入请求。此示例添加了几个客户规则来阻止来自给定IP范围的调用或在查询字符串中包含单词blockme的调用。
    • OWASP核心规则集提供了一种简单的方法来部署针对常见安全威胁(如SQL注入或跨站点脚本)的保护。
    • 机器人保护规则集可用于对来自已知机器人类别的请求执行自定义操作。
  • 微软ContainerService/managedClusters:一个公共或私有的AKS集群,由以下组件组成:
    • 专用子网中的系统节点池。默认节点池仅承载关键的系统pod和服务。工作节点具有节点污染,这会阻止应用程序pod在此节点池上进行调度。
    • 在专用子网中托管用户工作负载和工件的用户节点池。
  • 微软网络/虚拟网络:一个新的虚拟网络,有七个子网:
    • SystemSubnet:用于系统节点池的代理节点的子网。
    • UserSubnet:用于用户节点池的代理节点的子网。
    • PodSubnet:一个子网,用于动态地将私有IP地址分配给pods。
    • ApiServerSubnet:API服务器VNET集成将API服务器端点直接投影到部署AKS集群的虚拟网络中的此委派子网中。
    • AzureBastionSubnet:Azure堡垒主机的子网。
    • VmSubnet:用于连接到(专用)AKS集群和专用端点的跳转框虚拟机的子网。
    • AppGatewaySubnet:承载应用程序网关的子网。
  • 微软ManagedIdentity/userAssignedIdentities:AKS集群使用用户定义的托管标识来创建额外的资源,如Azure中的负载平衡器和托管磁盘。
  • 微软Compute/virtualMachines:Bicep模块可以选择创建一个跳转框虚拟机来管理私有AKS集群。
  • 微软网络/堡垒主机:在AKS集群虚拟网络中部署一个单独的Azure堡垒,为代理节点和虚拟机提供SSH连接。
  • 微软网络/natGateways:一个自带(BYO)Azure NAT网关,用于管理由AKS托管的工作负载发起的出站连接。NAT网关与SystemSubnet、UserSubnet和PodSubnet子网相关联。群集的outboundType属性设置为userAssignedNatGateway,以指定BYO NAT网关用于出站连接。注意:您可以在创建集群后更新outboundType,这将根据需要部署或删除资源,以将集群放入新的出口配置中。有关详细信息,请参阅创建集群后更新outboundType。
  • 微软存储/存储帐户:此存储帐户用于存储服务提供商和服务使用者虚拟机的引导诊断日志。引导诊断是一种调试功能,允许您查看控制台输出和屏幕截图以诊断虚拟机状态。
  • 微软ContainerRegistry/registers:Azure容器注册表(ACR),用于在所有容器部署的专用注册表中构建、存储和管理容器映像和工件。
  • 微软KeyVault/保管库:一个Azure密钥保管库,用于存储机密、证书和密钥,这些密钥可以由pod使用Azure密钥保管提供程序的机密存储CSI驱动程序装载为文件。有关详细信息,请参阅在AKS群集中使用Azure密钥存储提供程序的机密存储CSI驱动程序,以及提供访问Azure密钥存储提供方的身份以访问机密存储CSI-驱动程序。
  • 微软网络/私有端点:为以下每个资源创建一个Azure私有端点:
    • Azure容器注册表
    • Azure密钥保管库
    • Azure存储帐户
    • 部署专用AKS群集时的API服务器。
  • 微软网络/privateDnsZones:为以下每个资源创建一个Azure专用DNS区域:
    • Azure容器注册表
    • Azure密钥保管库
    • Azure存储帐户
    • 部署专用AKS群集时的API服务器。
  • 微软网络/网络安全组:承载虚拟机和Azure堡垒主机的子网由用于筛选入站和出站流量的Azure网络安全组保护。
  • 微软OperationalInsights/workspaces:集中的Azure日志分析工作区用于从所有Azure资源收集诊断日志和指标:
    • Azure Kubernetes服务集群
    • Azure密钥保管库
    • Azure网络安全组
    • Azure容器注册表
    • Azure存储帐户
    • Azure跳转框虚拟机
  • 微软Resources/deploymentScripts:部署脚本用于运行install-helm-carts-and-app.sh Bash脚本,该脚本通过YAML模板将httpbin web应用程序和通过helm将cert Manager安装到AKS集群。有关部署脚本的更多信息,请参阅在Bicep中使用部署脚本
    • NGINX入口控制器
  • 微软网络/privateDnsZones:客户端虚拟机使用Azure专用DNS区域将服务器应用程序的URL解析为专用终结点的专用IP地址。如果Kubernetes ingress对象的主机名等于httpbin.contoso.internal,则专用DNS区域的名称需要为contoso.internar,而将服务FQDN映射到专用端点的专用IP地址的a记录的名称则需要为httpbin。

Bicep模块为服务使用者部署以下Azure资源:

  • 微软网络/虚拟网络:具有两个子网的新虚拟网络:
    • AzureBastionSubnet:Azure堡垒主机的子网。
    • VmSubnet:客户端虚拟机和专用端点的子网。
  • 微软计算/虚拟机:此客户端虚拟机可用于通过引用应用程序网关专用链接、Azure应用程序网关和应用程序网关入口控制器的Azure专用端点将示例应用程序调用为AKS托管的服务器应用程序。
    • 微软网络/堡垒主机:此Azure堡垒主机可用于通过SSH连接到客户端虚拟机。
    • 微软OperationalInsights/workspaces:集中的Azure日志分析工作区用于从客户端虚拟机收集诊断日志和指标
       
注释
您可以在visio文件夹下找到用于关系图的architecture.vsdx文件。

什么是Bicep?


Bicep是一种特定于领域的语言(DSL),它使用声明性语法来部署Azure资源。它提供了简洁的语法、可靠的类型安全性和对代码重用的支持。Bicep作为Azure中的代码解决方案,为您的基础设施提供最佳创作体验。

部署Bicep模块


您可以使用同一文件夹中的deploy.sh-Bash脚本在Bicep文件夹中部署Bicep模块。在部署Bicep模块之前,请在deploy.sh脚本和main.parameters.json参数文件中为以下参数指定一个值。

  • prefix:指定所有Azure资源的前缀。
  • authenticationType:指定访问虚拟机时的身份验证类型。sshPublicKey是建议的值。允许的值:sshPublicKey和password。
  • vmAdminUsername:指定虚拟机的管理员帐户的名称。
  • vmAdminPasswordOrKey:指定虚拟机的SSH密钥或密码。
  • aksClusterSshPublicKey:指定AKS集群代理节点的SSH密钥或密码。
  • aadProfileAdminGroupObjectIDs:当部署具有Microsoft Entra ID和Azure RBAC集成的AKS群集时,此数组参数包含将具有群集管理员角色的Microsoft Entra ID-组对象ID的列表。
  • keyVaultObjectIds:指定要在密钥保管库访问策略中配置的服务主体的对象ID。


我们建议从预先存在的Azure密钥库资源中读取敏感配置数据,如密码或SSH密钥。有关详细信息,请参阅在Bicep部署期间使用Azure密钥库传递安全参数值。

应用程序网关Bicep模块


下表包含用于部署Azure应用程序网关及其WAF策略的Bicep代码。请注意,只有当privateLinkEnabled参数的值为true时,模块才会配置应用程序网关专用链接。如果应用程序网关仅使用公共前端IP配置进行配置,则专用链接将使用此配置,否则将使用专用前端IP配置。

// Parameters
@description('Specifies the name of the Application Gateway.')
param name string
@description('Specifies the sku of the Application Gateway.')
param skuName string = 'WAF_v2'
@description('Specifies the frontend IP configuration type.')
@allowed([
 'Public'
 'Private'
 'Both'
])
param frontendIpConfigurationType string
@description('Specifies the name of the public IP adddress used by the Application Gateway.')
param publicIpAddressName string = '${name}PublicIp'
@description('Specifies the location of the Application Gateway.')
param location string
@description('Specifies the resource tags.')
param tags object
@description('Specifies the resource id of the subnet used by the Application Gateway.')
param subnetId string
@description('Specifies the resource id of the subnet used by the Application Gateway Private Link.')
param privateLinkSubnetId string
@description('Specifies the private IP address of the Application Gateway.')
param privateIpAddress string
@description('Specifies the availability zones of the Application Gateway.')
param availabilityZones array
@description('Specifies the workspace id of the Log Analytics used to monitor the Application Gateway.')
param workspaceId string
@description('Specifies the lower bound on number of Application Gateway capacity.')
param minCapacity int = 1
@description('Specifies the upper bound on number of Application Gateway capacity.')
param maxCapacity int = 10
@description('Specifies whether create or not a Private Link for the Application Gateway.')
param privateLinkEnabled bool = false
@description('Specifies the name of the WAF policy')
param wafPolicyName string = '${name}WafPolicy'
@description('Specifies the mode of the WAF policy.')
@allowed([
 'Detection'
 'Prevention'
])
param wafPolicyMode string = 'Prevention'
@description('Specifies the state of the WAF policy.')
@allowed([
 'Enabled'
 'Disabled '
])
param wafPolicyState string = 'Enabled'
@description('Specifies the maximum file upload size in Mb for the WAF policy.')
param wafPolicyFileUploadLimitInMb int = 100
@description('Specifies the maximum request body size in Kb for the WAF policy.')
param wafPolicyMaxRequestBodySizeInKb int = 128
@description('Specifies the whether to allow WAF to check request Body.')
param wafPolicyRequestBodyCheck bool = true
@description('Specifies the rule set type.')
param wafPolicyRuleSetType string = 'OWASP'
@description('Specifies the rule set version.')
param wafPolicyRuleSetVersion string = '3.2'
@description('Specifies the name of the Key Vault resource.')
param keyVaultName string
// Variables
var diagnosticSettingsName = 'diagnosticSettings'
var applicationGatewayResourceId = resourceId('Microsoft.Network/applicationGateways', name)
var keyVaultSecretsUserRoleDefinitionId = resourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
var gatewayIPConfigurationName = 'DefaultGatewayIpConfiguration'
var frontendPortName = 'DefaultFrontendPort'
var backendAddressPoolName = 'DefaultBackendPool'
var backendHttpSettingsName = 'DefaultBackendHttpSettings'
var httpListenerName = 'DefaultHttpListener'
var routingRuleName = 'DefaultRequestRoutingRule'
var privateLinkName = 'DefaultPrivateLink'
var publicFrontendIPConfigurationName = 'PublicFrontendIPConfiguration'
var privateFrontendIPConfigurationName = 'PrivateFrontendIPConfiguration'
var frontendIPConfigurationName = frontendIpConfigurationType == 'Public' ? publicFrontendIPConfigurationName :
 privateFrontendIPConfigurationName
var applicationGatewayZones = !empty(availabilityZones) ? availabilityZones : []
var publicFrontendIPConfiguration = {
 name: publicFrontendIPConfigurationName
 properties: {
   privateIPAllocationMethod: 'Dynamic'
   publicIPAddress: {
     id: applicationGatewayPublicIpAddress.id
   }
   privateLinkConfiguration: privateLinkEnabled && frontendIpConfigurationType == 'Public' ? {
     id: '${applicationGatewayResourceId}/privateLinkConfigurations/${privateLinkName}'
   } : null
 }
}
var privateFrontendIPConfiguration = {
 name: privateFrontendIPConfigurationName
 properties: {
   privateIPAllocationMethod: 'Static'
   privateIPAddress: privateIpAddress
   subnet: {
     id: subnetId
   }
   privateLinkConfiguration: privateLinkEnabled && frontendIpConfigurationType != 'Public'? {
     id: '${applicationGatewayResourceId}/privateLinkConfigurations/${privateLinkName}'
   } : null
 }
}
var frontendIPConfigurations = union(
 frontendIpConfigurationType == 'Public' ? array(publicFrontendIPConfiguration) : [],
 frontendIpConfigurationType == 'Private' ? array(privateFrontendIPConfiguration) : [],
 frontendIpConfigurationType == 'Both' ? concat(array(publicFrontendIPConfiguration), 
 array(privateFrontendIPConfiguration)) : []
)
var sku = union({
   name: skuName
   tier: skuName
 }, maxCapacity == 0 ? {
   capacity: minCapacity
 } : {})
var applicationGatewayProperties = union({
   sku: sku
   gatewayIPConfigurations: [
     {
       name: gatewayIPConfigurationName
       properties: {
         subnet: {
           id: subnetId
         }
       }
     }
   ]
   frontendIPConfigurations: frontendIPConfigurations
   frontendPorts: [
     {
       name: frontendPortName
       properties: {
         port: 80
       }
     }
   ]
   backendAddressPools: [
     {
       name: backendAddressPoolName
     }
   ]
   backendHttpSettingsCollection: [
     {
       name: backendHttpSettingsName
       properties: {
         port: 80
         protocol: 'Http'
         cookieBasedAffinity: 'Disabled'
         requestTimeout: 30
         pickHostNameFromBackendAddress: true
       }
     }
   ]
   httpListeners: [
     {
       name: httpListenerName
       properties: {
         frontendIPConfiguration: {
           id: '${applicationGatewayResourceId}/frontendIPConfigurations/${frontendIPConfigurationName}'
         }
         frontendPort: {
           id: '${applicationGatewayResourceId}/frontendPorts/${frontendPortName}'
         }
         protocol: 'Http'
       }
     }
   ]
   requestRoutingRules: [
     {
       name: routingRuleName
       properties: {
         ruleType: 'Basic'
         priority: 1000
         httpListener: {
           id: '${applicationGatewayResourceId}/httpListeners/${httpListenerName}'
         }
         backendAddressPool: {
           id: '${applicationGatewayResourceId}/backendAddressPools/${backendAddressPoolName}'
         }
         backendHttpSettings: {
           id: '${applicationGatewayResourceId}/backendHttpSettingsCollection/${backendHttpSettingsName}'
         }
       }
     }
   ]
   privateLinkConfigurations: privateLinkEnabled ? [
     {
       name: privateLinkName
       properties: {
         ipConfigurations: [
           {
             name: 'PrivateLinkDefaultIPConfiguration'
             properties: {
               privateIPAllocationMethod: 'Dynamic'
               subnet: {
                 id: privateLinkSubnetId
               }
             }
           }
         ]
       }
     }
   ] : []
   firewallPolicy: {
     id: wafPolicy.id
   }
 }, maxCapacity > 0 ? {
   autoscaleConfiguration: {
     minCapacity: minCapacity
     maxCapacity: maxCapacity
   }
 } : {})
var applicationGatewayLogCategories = [
 'ApplicationGatewayAccessLog'
 'ApplicationGatewayFirewallLog'
 'ApplicationGatewayPerformanceLog'
]
var applicationGatewayMetricCategories = [
 'AllMetrics'
]
var applicationGatewayLogs = [for category in applicationGatewayLogCategories: {
 category: category
 enabled: true
}]
var applicationGatewayMetrics = [for category in applicationGatewayMetricCategories: {
 category: category
 enabled: true
}]
// Resources
resource applicationGatewayIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
 name: '${name}Identity'
 location: location
}
resource applicationGatewayPublicIpAddress 'Microsoft.Network/publicIPAddresses@2022-07-01' 
= if (frontendIpConfigurationType != 'Private') {
 name: publicIpAddressName
 location: location
 zones: applicationGatewayZones
 sku: {
   name: 'Standard'
 }
 properties: {
   publicIPAllocationMethod: 'Static'
 }
}
resource applicationGateway 'Microsoft.Network/applicationGateways@2022-07-01' = {
 name: name
 location: location
 tags: tags
 zones: applicationGatewayZones
 identity: {
   type: 'UserAssigned'
   userAssignedIdentities: {
     '${applicationGatewayIdentity.id}': {}
   }
 }
 properties: applicationGatewayProperties
}
resource wafPolicy 'Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2022-07-01' = {
 name: wafPolicyName
 location: location
 tags: tags
 properties: {
   customRules: [
     {
       name: 'BlockMe'
       priority: 1
       ruleType: 'MatchRule'
       action: 'Block'
       matchConditions: [
         {
           matchVariables: [
             {
               variableName: 'QueryString'
             }
           ]
           operator: 'Contains'
           negationConditon: false
           matchValues: [
             'blockme'
           ]
         }
       ]
     }
     {
       name: 'BlockEvilBot'
       priority: 2
       ruleType: 'MatchRule'
       action: 'Block'
       matchConditions: [
         {
           matchVariables: [
             {
               variableName: 'RequestHeaders'
               selector: 'User-Agent'
             }
           ]
           operator: 'Contains'
           negationConditon: false
           matchValues: [
             'evilbot'
           ]
           transforms: [
             'Lowercase'
           ]
         }
       ]
     }
   ]
   policySettings: {
     requestBodyCheck: wafPolicyRequestBodyCheck
     maxRequestBodySizeInKb: wafPolicyMaxRequestBodySizeInKb
     fileUploadLimitInMb: wafPolicyFileUploadLimitInMb
     mode: wafPolicyMode
     state: wafPolicyState
   }
   managedRules: {
     managedRuleSets: [
       {
         ruleSetType: wafPolicyRuleSetType
         ruleSetVersion: wafPolicyRuleSetVersion
       }
     ]
   }
 }
}
resource keyVault 'Microsoft.KeyVault/vaults@2021-10-01' existing = {
 name: keyVaultName
}
resource keyVaultSecretsUserApplicationGatewayIdentityRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
 scope: keyVault
 name: guid(keyVault.id, applicationGatewayIdentity.name, 'keyVaultSecretsUser')
 properties: {
   roleDefinitionId: keyVaultSecretsUserRoleDefinitionId
   principalType: 'ServicePrincipal'
   principalId: applicationGatewayIdentity.properties.principalId
 }
}
resource applicationGatewayDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
 name: diagnosticSettingsName
 scope: applicationGateway
 properties: {
   workspaceId: workspaceId
   logs: applicationGatewayLogs
   metrics: applicationGatewayMetrics
 }
}
// Outputs
output id string = applicationGateway.id
output name string = applicationGateway.name
output privateLinkFrontendIPConfigurationName string = privateLinkEnabled ? frontendIPConfigurationName : ''

部署脚本


该示例使用部署脚本来运行install-helm-carts-and-agic-sample.sh Bash脚本,该脚本通过YAML模板将httpbin web应用程序和以下包安装到通过helm的AKS集群。有关部署脚本的更多信息,请参阅在Bicep中使用部署脚本。该脚本还通过Helm安装证书管理器,并为应用程序网关入口控制器安装集群问题。

# Install kubectl
az aks install-cli --only-show-errors
# Get AKS credentials
az aks get-credentials \
 --admin \
 --name $clusterName \
 --resource-group $resourceGroupName \
 --subscription $subscriptionId \
 --only-show-errors
# Check if the cluster is private or not
private=$(az aks show --name $clusterName \
 --resource-group $resourceGroupName \
 --subscription $subscriptionId \
 --query apiServerAccessProfile.enablePrivateCluster \
 --output tsv)
# Install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -o get_helm.sh -s
chmod 700 get_helm.sh
./get_helm.sh &>/dev/null
# Add Helm repos
helm repo add jetstack https://charts.jetstack.io
# Update Helm repos
helm repo update
if [[ $private == 'true' ]]; then
 # Log whether the cluster is public or private
 echo "$clusterName AKS cluster is public"
 # Install certificate manager
 command="helm install cert-manager jetstack/cert-manager \
   --create-namespace \
   --namespace cert-manager \
   --set installCRDs=true \
   --set nodeSelector.\"kubernetes\.io/os\"=linux"
 az aks command invoke \
   --name $clusterName \
   --resource-group $resourceGroupName \
   --subscription $subscriptionId \
   --command "$command"
 
   # Create cluster issuer for the Application Gateway Ingress Controller (AGIC)
 command="cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
 name: letsencrypt-application-gateway
spec:
 acme:
   server: https://acme-v02.api.letsencrypt.org/directory
   email: $email
   privateKeySecretRef:
     name: letsencrypt
   solvers:
   - http01:
       ingress:
         class: azure/application-gateway
         podTemplate:
           spec:
             nodeSelector:
               "kubernetes.io/os": linux
EOF"
 az aks command invoke \
   --name $clusterName \
   --resource-group $resourceGroupName \
   --subscription $subscriptionId \
   --command "$command"
 # Create a namespace for the application
 command="kubectl create namespace $namespace"
 az aks command invoke \
   --name $clusterName \
   --resource-group $resourceGroupName \
   --subscription $subscriptionId \
   --command "$command"
 # Create a deployment and service for the application
 command="cat <<EOF | kubectl apply -n $namespace -f -
apiVersion: apps/v1
kind: Deployment
metadata:
 name: httpbin
spec:
 replicas: 3
 selector:
   matchLabels:
     app: httpbin
 template:
   metadata:
     labels:
       app: httpbin
   spec:
     topologySpreadConstraints:
     - maxSkew: 1
       topologyKey: topology.kubernetes.io/zone
       whenUnsatisfiable: DoNotSchedule
       labelSelector:
         matchLabels:
           app: httpbin
     - maxSkew: 1
       topologyKey: kubernetes.io/hostname
       whenUnsatisfiable: DoNotSchedule
       labelSelector:
         matchLabels:
           app: httpbin
     nodeSelector:
       "kubernetes.io/os": linux
     containers:
     - image: docker.io/kennethreitz/httpbin
       imagePullPolicy: IfNotPresent
       name: httpbin
       resources:
         requests:
           memory: "64Mi"
           cpu: "125m"
         limits:
           memory: "128Mi"
           cpu: "250m"
       ports:
       - containerPort: 80
       env:
       - name: PORT
         value: "80"
---
apiVersion: v1
kind: Service
metadata:
 name: httpbin
spec:
 ports:
   - port: 80
     targetPort: 80
     protocol: TCP
 type: ClusterIP
 selector:
   app: httpbin
EOF"
 az aks command invoke \
   --name $clusterName \
   --resource-group $resourceGroupName \
   --subscription $subscriptionId \
   --command "$command"
 # Create an ingress resource for the application
 command="cat <<EOF | kubectl apply -n $namespace -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: httpbin
spec:
 ingressClassName: azure/application-gateway
 rules:
 - host: $hostName
   http:
     paths:
     - path: /
       pathType: Prefix
       backend:
         service:
           name: httpbin
           port:
             number: 80
EOF"
 az aks command invoke \
   --name $clusterName \
   --resource-group $resourceGroupName \
   --subscription $subscriptionId \
   --command "$command"
else
 # Log whether the cluster is public or private
 echo "$clusterName AKS cluster is public"
 # Install certificate manager
 helm install cert-manager jetstack/cert-manager \
   --create-namespace \
   --namespace cert-manager \
   --set installCRDs=true \
   --set nodeSelector."kubernetes\.io/os"=linux
 # Create cluster issuer for the Application Gateway Ingress Controller (AGIC)
 cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
 name: letsencrypt-application-gateway
spec:
 acme:
   server: https://acme-v02.api.letsencrypt.org/directory
   email: $email
   privateKeySecretRef:
     name: letsencrypt
   solvers:
   - http01:
       ingress:
         class: azure/application-gateway
         podTemplate:
           spec:
             nodeSelector:
               "kubernetes.io/os": linux
EOF
 # Create a namespace for the application
 kubectl create namespace $namespace
 # Create a deployment and service for the application
 cat <<EOF | kubectl apply -n $namespace -f -
apiVersion: apps/v1
kind: Deployment
metadata:
 name: httpbin
spec:
 replicas: 3
 selector:
   matchLabels:
     app: httpbin
 template:
   metadata:
     labels:
       app: httpbin
   spec:
     topologySpreadConstraints:
     - maxSkew: 1
       topologyKey: topology.kubernetes.io/zone
       whenUnsatisfiable: DoNotSchedule
       labelSelector:
         matchLabels:
           app: httpbin
     - maxSkew: 1
       topologyKey: kubernetes.io/hostname
       whenUnsatisfiable: DoNotSchedule
       labelSelector:
         matchLabels:
           app: httpbin
     nodeSelector:
       "kubernetes.io/os": linux
     containers:
     - image: docker.io/kennethreitz/httpbin
       imagePullPolicy: IfNotPresent
       name: httpbin
       resources:
         requests:
           memory: "64Mi"
           cpu: "125m"
         limits:
           memory: "128Mi"
           cpu: "250m"
       ports:
       - containerPort: 80
       env:
       - name: PORT
         value: "80"
---
apiVersion: v1
kind: Service
metadata:
 name: httpbin
spec:
 ports:
   - port: 80
     targetPort: 80
     protocol: TCP
 type: ClusterIP
 selector:
   app: httpbin
EOF
 # Create an ingress resource for the application
 cat <<EOF | kubectl apply -n $namespace -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: httpbin
spec:
 ingressClassName: azure-application-gateway
 rules:
 - host: $hostName
   http:
     paths:
     - path: /
       pathType: Prefix
       backend:
         service:
           name: httpbin
           port:
             number: 80
EOF
fi
# Create output as JSON file
echo '{}' |
 jq --arg x 'prometheus' '.prometheus=$x' |
 jq --arg x 'cert-manager' '.certManager=$x' |
 jq --arg x 'ingress-basic' '.nginxIngressController=$x' >$AZ_SCRIPTS_OUTPUT_PATH

httpbinweb应用程序是通过YAML模板部署的。特别地,入口对象用于通过HTTP协议使用应用网关入口控制器来公开应用程序。默认的ingress主机名是httpbin.contoso.internal,但您可以使用main.bicep模块中的以下参数控制主机名:

Bicep

@description('Specifies the subdomain of the Kubernetes ingress object.')
param subdomain string = 'httpbin'
@description('Specifies the domain of the Kubernetes ingress object.')
param domain string = 'contoso.internal'


可以很容易地修改ingress对象以通过HTTPS公开服务器,并为TLS终止提供证书。您可以使用脚本安装的证书管理器来颁发Let's Encrypt证书。有关更多信息,请参阅在应用程序网关上为AKS集群使用LetsEncrypt.org证书。特别是,证书管理器可以在Azure DNS中创建然后删除DNS-01记录,但它需要首先向Azure进行身份验证。建议的身份验证方法是使用AAD工作负载标识的托管标识。

测试应用程序


如果部署成功,您应该能够从客户端虚拟机访问AKS托管的httpbin web应用程序,如下所示:

  • 导航到Azure门户并通过Azure Bastion连接到客户端虚拟机。
  • 运行nslookup httpbin.contoso.internal命令。如果您自定义了ingress对象和专用DNS区域使用的子域和域,请确保将httpbin.contoso.internal替换为subdomain.domain。该命令应返回客户端虚拟机用于调用httpbin web应用程序的ApplicationGatewayPrivateEndpoint的专用IP地址,如下图所示。


  • 调用httpbinweb应用程序公开的任何REST API方法,例如/headers。如果调用成功,您应该会看到如下图所示的结果。


审查部署的资源


使用Azure门户、Azure CLI或Azure PowerShell列出资源组中已部署的资源。

Azure CLI
Azure CLI

az resource list --resource-group <resource-group-name>


PowerShell
Azure PowerShell


Get-AzResource -ResourceGroupName <resource-group-name>


清理资源


当您不再需要创建的资源时,只需删除资源组即可。这将删除所有Azure资源。

Azure CLI
 

az group delete --name <resource-group-name>


PowerShell


Azure PowerShell

Remove-AzResourceGroup -Name <resource-group-name>


接下来的步骤


您可以更改入口对象使用的默认主机名,并使用域的TLS/SSL证书通过HTTPS公开后端服务。有关更多信息,请参阅在应用程序网关上为AKS集群使用LetsEncrypt.org证书。如果你使用Azure DNS来管理你的域,你可以扩展Bicep模块,为你的前门自动创建一个自定义域,并在你的公共DNS区域中创建一个CNAME DNS记录。

本文地址
最后修改
星期三, June 12, 2024 - 16:08
Article