为 Azure Kubernetes 服务 (AKS) 群集上的 Windows Server 节点启用组托管服务帐户 (GMSA)

组托管服务帐户 (GMSA) 是多个服务器的托管域帐户,提供自动密码管理、简化的服务主体名称 (SPN) 管理以及将管理委派给其他管理员的功能。 借助 Azure Kubernetes Service (AKS) 可以在 Windows Server 节点上启用 GMSA,使 Windows Server 节点上运行的容器能够与 GMSA 集成并由其管理。

先决条件

  • Kubernetes 1.19 或更高版本。 若要检查版本,请参阅检查可用升级。 若要升级版本,请参阅升级 AKS 群集
  • Azure CLI 2.35.0 或更高版本。 运行 az --version 即可查找版本。 如果需要进行安装或升级,请参阅安装 Azure CLI
  • AKS 群集启用的托管标识
  • 创建或更新 Azure 密钥保管库时所需的权限。
  • 在 Active Directory 域服务或本地 Active Directory 上配置 GMSA 所需的权限。
  • 域控制器必须已启用 Active Directory Web 服务,并且必须可由 AKS 群集通过端口 9389 访问。

注意

Azure 还提供一个专用 PowerShell 模块用于在 AKS 上配置 gMSA。 有关详细信息,请参阅 Azure Kubernetes 服务

在 Active Directory 域控制器上配置 GMSA

若要对 AKS 使用 GMSA,需要通过标准域用户凭据访问域控制器上配置的 GMSA 凭据。 若要在域控制器上配置 GMSA,请参阅组托管服务帐户入门。 对于标准域用户凭据,可以使用现有用户或创建新用户,前提是该用户有权访问 GMSA 凭据。

重要

必须使用 Active Directory 域服务或本地 Active Directory。 目前,无法使用 Microsoft Entra ID 通过 AKS 群集配置 GMSA。

将标准域用户凭据存储在 Azure 密钥保管库中

AKS 群集使用标准域用户凭据从域控制器访问 GMSA 凭据。 为了提供对 AKS 群集的这些凭据的安全访问,应将这些凭据存储在 Azure 密钥保管库中。

  1. 如果你没有 Azure 密钥保管库,请使用 az keyvault create 命令创建一个。

    az keyvault create --resource-group myResourceGroup --name myGMSAVault
    
  2. 使用 az keyvault secret set 命令将标准域用户凭据作为机密存储在密钥保管库中。 以下示例在 myGMSAVault 密钥保管库中存储包含密钥 GMSADomainUserCred 的域用户凭据。

    az keyvault secret set --vault-name myGMSAVault --name "GMSADomainUserCred" --value "$Domain\\$DomainUsername:$DomainUserPassword"
    

    注意

    请确保对域使用完全限定的域名。

可选:使用自定义 VNET 和自定义 DNS

需要通过 DNS 配置域控制器,以便 AKS 群集能够访问域控制器。 可以在 AKS 群集外部配置网络和 DNS,以允许群集访问域控制器。 或者,可以在 AKS 群集中使用 Azure CNI 配置自定义 VNet 和自定义 DNS,以提供对域控制器的访问。 有关详细信息,请参阅在 Azure Kubernetes 服务 (AKS) 中配置 Azure CNI 网络

可选:配置多个 DNS 服务器

如果要在 AKS 群集中为 Windows GMSA 配置多个 DNS 服务器,请不要指定 --gmsa-dns-serverv--gmsa-root-domain-name。 相反,可选择“自定义 DNS”并添加 DNS 服务器,在 VNet 中添加多个 DNS 服务器。

可选:为群集使用你自己的 kubelet 标识

群集 kubelet 标识需要能够访问你的密钥保管库,这样,AKS 群集才能访问你的密钥保管库。 当你创建启用了托管标识的群集时,系统会自动创建一个 kubelet 标识。

可以在创建群集后为标识授予对密钥保管库的访问权限,也可以通过以下步骤创建自己的标识,以便在创建群集之前使用:

  1. 使用 az identity create 命令创建 kubelet 标识。

    az identity create --name myIdentity --resource-group myResourceGroup
    
  2. 使用 az identity list 命令获取标识的 ID,并将其设置为名为 MANAGED_ID 的变量。

    MANAGED_ID=$(az identity list --query "[].id" -o tsv)
    
  3. 若要授予标识对密钥保管库的访问权限,请使用 az keyvault set-policy 命令。

    az keyvault set-policy --name "myGMSAVault" --object-id $MANAGED_ID --secret-permissions get
    

在新 AKS 群集中启用 GMSA

  1. 创建在群集创建期间使用的管理员凭据。 以下命令提示你输入一个用户名,并将其设置为 WINDOWS_USERNAME,供在之后的命令中使用。

    echo "Please enter the username to use as administrator credentials for Windows Server nodes on your cluster: " && read WINDOWS_USERNAME 
    
  2. 使用 az aks create 命令并指定以下参数创建 AKS 群集:

    • --enable-windows-gmsa:为群集启用 GMSA。
    • --gmsa-dns-server:DNS 服务器的 IP 地址。
    • --gmsa-root-domain-name:DNS 服务器的根域名。
    DNS_SERVER=<IP address of DNS server>
    ROOT_DOMAIN_NAME="contoso.com"
    
    az aks create \
        --resource-group myResourceGroup \
        --name myAKSCluster \
        --vm-set-type VirtualMachineScaleSets \
        --network-plugin azure \
        --load-balancer-sku standard \
        --windows-admin-username $WINDOWS_USERNAME \
        --enable-windows-gmsa \
        --gmsa-dns-server $DNS_SERVER \
        --gmsa-root-domain-name $ROOT_DOMAIN_NAME \
        --generate-ssh-keys
    

    注意

    • 如果使用自定义 VNet,则需要使用 vnet-subnet-id 参数指定 VNet ID,并且可能还需要根据配置添加 docker-bridge-addressdns-service-ipservice-cidr 参数。

    • 如果你为 kubelet 标识创建了自己的标识,请使用 assign-kubelet-identity 参数指定该标识。

    • 指定 --gmsa-dns-server--gmsa-root-domain-name 参数时,DNS 转发规则将添加到 kube-system/coredns ConfigMap。 此规则将 $ROOT_DOMAIN_NAME 的 DNS 请求从 Pod 转发到 $DNS_SERVER

      $ROOT_DOMAIN_NAME:53 {
          errors
          cache 30
          log
          forward . $DNS_SERVER
      }
      
  3. 使用 az aks nodepool add 命令添加 Windows Server 节点池。

    az aks nodepool add \
        --resource-group myResourceGroup \
        --cluster-name myAKSCluster \
        --os-type Windows \
        --name npwin \
        --node-count 1
    

在现有群集上启用 GMSA

  • 在使用 az aks update 命令启用 Windows Server 节点和托管标识的现有群集上启用 GMSA。

    az aks update \
        --resource-group myResourceGroup \
        --name myAKSCluster \
        --enable-windows-gmsa \
        --gmsa-dns-server $DNS_SERVER \
        --gmsa-root-domain-name $ROOT_DOMAIN_NAME
    

授予 kubelet 标识对密钥保管库的访问权限

注意

如果你提供了自己的标识作为 kubelet 标识,请跳过此步骤。

  • 使用 az keyvault set-policy 命令授予 kubelet 标识对密钥保管库的访问权限。

    MANAGED_ID=$(az aks show -g myResourceGroup -n myAKSCluster --query "identityProfile.kubeletidentity.objectId" -o tsv)
    az keyvault set-policy --name "myGMSAVault" --object-id $MANAGED_ID --secret-permissions get
    

安装 GMSA 凭据规范

  1. 使用 az aks get-credentials 命令将 kubectl 配置为连接到你的 Kubernetes 群集。

    az aks get-credentials --resource-group myResourceGroup --name myAKSCluster
    
  2. 创建名为 gmsa-spec.yaml 的新 YAML,并粘贴到以下 YAML 中。 请务必将占位符替换为你自己的值。

    apiVersion: windows.k8s.io/v1
    kind: GMSACredentialSpec
    metadata:
      name: aks-gmsa-spec  # This name can be changed, but it will be used as a reference in the pod spec
    credspec:
      ActiveDirectoryConfig:
        GroupManagedServiceAccounts:
        - Name: $GMSA_ACCOUNT_USERNAME
          Scope: $NETBIOS_DOMAIN_NAME
        - Name: $GMSA_ACCOUNT_USERNAME
          Scope: $DNS_DOMAIN_NAME
        HostAccountConfig:
          PluginGUID: '{CCC2A336-D7F3-4818-A213-272B7924213E}'
          PortableCcgVersion: "1"
          PluginInput: "ObjectId=$MANAGED_ID;SecretUri=$SECRET_URI"  # SECRET_URI takes the form https://$akvName.vault.azure.cn/secrets/$akvSecretName
      CmsPlugins:
     - ActiveDirectory
      DomainJoinConfig:
        DnsName: $DNS_DOMAIN_NAME
        DnsTreeName: $DNS_ROOT_DOMAIN_NAME
        Guid:  $AD_DOMAIN_OBJECT_GUID
        MachineAccountName: $GMSA_ACCOUNT_USERNAME
        NetBiosName: $NETBIOS_DOMAIN_NAME
        Sid: $GMSA_SID
    

注意

AKS 已在版本 v20230903 中将 GMSACredentialSpecapiVersionwindows.k8s.io/v1alpha1 升级到 windows.k8s.io/v1

  1. 创建名为 gmsa-role.yaml 的新 YAML,并粘贴到以下 YAML 中。

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: aks-gmsa-role
    rules:
    - apiGroups: ["windows.k8s.io"]
      resources: ["gmsacredentialspecs"]
      verbs: ["use"]
      resourceNames: ["aks-gmsa-spec"]
    
  2. 创建名为 gmsa-role-binding.yaml 的新 YAML,并粘贴到以下 YAML 中。

    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: allow-default-svc-account-read-on-aks-gmsa-spec
      namespace: default
    subjects:
    - kind: ServiceAccount
      name: default
      namespace: default
    roleRef:
      kind: ClusterRole
      name: aks-gmsa-role
      apiGroup: rbac.authorization.k8s.io
    
  3. 使用 kubectl apply 命令应用 gmsa-spec.yaml、gmsa-role.yaml 和 gmsa-role-binding.yaml 中的更改。

    kubectl apply -f gmsa-spec.yaml
    kubectl apply -f gmsa-role.yaml
    kubectl apply -f gmsa-role-binding.yaml
    

验证 GMSA 安装

  1. 创建名为 gmsa-demo.yaml 的新 YAML,并粘贴到以下 YAML 中。

    ---
    kind: ConfigMap
    apiVersion: v1
    metadata:
      labels:
       app: gmsa-demo
      name: gmsa-demo
      namespace: default
    data:
      run.ps1: |
       $ErrorActionPreference = "Stop"
    
       Write-Output "Configuring IIS with authentication."
    
       # Add required Windows features, since they are not installed by default.
       Install-WindowsFeature "Web-Windows-Auth", "Web-Asp-Net45"
    
       # Create simple ASP.NET page.
       New-Item -Force -ItemType Directory -Path 'C:\inetpub\wwwroot\app'
       Set-Content -Path 'C:\inetpub\wwwroot\app\default.aspx' -Value 'Authenticated as <B><%=User.Identity.Name%></B>, Type of Authentication: <B><%=User.Identity.AuthenticationType%></B>'
    
       # Configure IIS with authentication.
       Import-Module IISAdministration
       Start-IISCommitDelay
       (Get-IISConfigSection -SectionPath 'system.webServer/security/authentication/windowsAuthentication').Attributes['enabled'].value = $true
       (Get-IISConfigSection -SectionPath 'system.webServer/security/authentication/anonymousAuthentication').Attributes['enabled'].value = $false
       (Get-IISServerManager).Sites[0].Applications[0].VirtualDirectories[0].PhysicalPath = 'C:\inetpub\wwwroot\app'
       Stop-IISCommitDelay
    
       Write-Output "IIS with authentication is ready."
    
       C:\ServiceMonitor.exe w3svc
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: gmsa-demo
      name: gmsa-demo
      namespace: default
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: gmsa-demo
      template:
        metadata:
          labels:
            app: gmsa-demo
        spec:
          securityContext:
            windowsOptions:
              gmsaCredentialSpecName: aks-gmsa-spec
          containers:
          - name: iis
            image: mcr.azk8s.cn/windows/servercore/iis:windowsservercore-ltsc2019
            imagePullPolicy: IfNotPresent
            command:
             - powershell
            args:
              - -File
              - /gmsa-demo/run.ps1
            volumeMounts:
              - name: gmsa-demo
                mountPath: /gmsa-demo
          volumes:
          - configMap:
              defaultMode: 420
              name: gmsa-demo
            name: gmsa-demo
          nodeSelector:
            kubernetes.io/os: windows
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: gmsa-demo
      name: gmsa-demo
      namespace: default
    spec:
      ports:
      - port: 80
        targetPort: 80
      selector:
        app: gmsa-demo
      type: LoadBalancer
    
  2. 使用 kubectl apply 命令应用来自 gmsa-demo.yaml 的更改。

    kubectl apply -f gmsa-demo.yaml
    
  3. 使用 kubectl get service 命令获取示例应用程序的 IP 地址。

    kubectl get service gmsa-demo --watch
    

    起初,gmsa-demo 服务的 EXTERNAL-IP 显示为“挂起”:

    NAME               TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
    gmsa-demo          LoadBalancer   10.0.37.27   <pending>     80:30572/TCP   6s
    
  4. EXTERNAL-IP 地址从 pending 更改为实际公共 IP 地址时,请使用 CTRL-C 停止 kubectl 监视进程。

    以下示例输出显示向服务分配了有效的公共 IP 地址:

    gmsa-demo  LoadBalancer   10.0.37.27   EXTERNAL-IP   80:30572/TCP   2m
    
  5. 打开 Web 浏览器访问 gmsa-demo 服务的外部 IP 地址。

  6. 使用 $NETBIOS_DOMAIN_NAME\$AD_USERNAME 和密码进行身份验证,确认是否看到了 Authenticated as $NETBIOS_DOMAIN_NAME\$AD_USERNAME, Type of Authentication: Negotiate

禁用现有群集上的 GMSA

  • 使用命令“az aks update”在具有 Windows Server 节点的现有群集上禁用 GMSA。

    az aks update \
        --resource-group myResourceGroup \
        --name myAKSCluster \
        --disable-windows-gmsa 
    

注意

可以使用命令“az aks update”在现有群集上重新启用 GMSA。

疑难解答

加载页面时系统不提示身份验证

如果页面可加载,但系统未提示进行身份验证,请使用 kubectl logs POD_NAME 命令显示 Pod 的日志,并验证是否看到“使用身份验证的 IIS 已准备就绪”。

注意

默认情况下,Windows 容器不会在 kubectl 上显示日志。 若要使 Windows 容器能够显示日志,需要在 Windows 映像上嵌入日志监视器工具。 有关详细信息,请参阅 Windows 容器工具

尝试加载页面时连接超时

如果在尝试加载页面时出现连接超时,请使用 kubectl get pods --watch 命令验证示例应用是否正在运行。 有时,示例应用服务的外部 IP 地址在示例应用 Pod 运行之前可用。

Pod 无法启动并且 Pod 事件中显示 winapi 错误

如果在运行 kubectl get pods --watch 命令并等待几分钟后 Pod 未启动,请使用 kubectl describe pod POD_NAME 命令。 如果 Pod 事件中显示 winapi 错误,则有可能是 GMSA 凭据规范配置中有错误。 验证 gmsa-spec.yaml 中的所有替换值是否正确,重新运行 ,然后重新部署示例应用程序。