为 Windows Pods 和容器配置 GMSA

功能状态: Kubernetes v1.18 [stable]

此页面介绍了如何为将在 Windows 节点上运行的 Pod 和容器配置组管理服务帐户 (GMSA)。组管理服务帐户是一种特定类型的 Active Directory 帐户,它提供自动密码管理、简化的服务主体名称 (SPN) 管理,并能够将管理委派给多个服务器上的其他管理员。

在 Kubernetes 中,GMSA 凭据规约被配置为集群范围内的自定义资源。Windows Pod 以及 Pod 中的单个容器可以配置为使用 GMSA 进行基于域的功能(例如 Kerberos 认证),以便与其它 Windows 服务交互。

准备工作

你需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具已被配置为与你的集群通信。该集群应包含 Windows 工作节点。本节包含每个集群都需要执行的一次性初始步骤。

安装 GMSACredentialSpec CRD

需要在集群上配置一个 CustomResourceDefinition (CRD) 来定义自定义资源类型 GMSACredentialSpec,以支持 GMSA 凭据规约资源。下载 GMSA CRD 的 YAML 文件并将其保存为 gmsa-crd.yaml。然后,使用 kubectl apply -f gmsa-crd.yaml 安装该 CRD。

安装 Webhook 来验证 GMSA 用户

需要在 Kubernetes 集群上配置两个 Webhook,以便在 Pod 或容器级别填充和验证 GMSA 凭据规约引用。

  1. 一个准入时变更 Webhook,它将对 GMSA 的引用(通过 Pod 规约中的名称)扩展为 Pod 规约中 JSON 格式的完整凭据规约。

  2. 一个准入时校验 Webhook,用于确保对 GMSA 的所有引用都被 Pod 服务帐户授权使用。

安装上述 Webhook 和相关对象需要执行以下步骤:

  1. 创建证书密钥对(用于允许 Webhook 容器与集群通信)。

  2. 使用上述证书安装一个 Secret。

  3. 为核心 Webhook 逻辑创建 Deployment。

  4. 创建引用 Deployment 的准入时校验和变更 Webhook 配置。

可以使用脚本来部署和配置上述的 GMSA Webhook 和相关对象。运行该脚本时可以带上 --dry-run=server 选项,以便你检查将对集群进行的变更。

脚本使用的YAML 模板也可以用来手动部署 Webhook 和相关对象(并对参数进行适当替换)。

在 Active Directory 中配置 GMSA 和 Windows 节点

在 Kubernetes 中的 Pod 配置使用 GMSA 之前,需要在 Active Directory 中预配所需的 GMSA,如Windows GMSA 文档所述。Active Directory 需要配置 Windows 工作节点(属于 Kubernetes 集群),以便访问与所需 GMSA 相关联的 Secret 凭据,如Windows GMSA 文档所述。

创建 GMSA 凭据规约资源

安装 GMSACredentialSpec CRD 后(如前所述),可以配置包含 GMSA 凭据规约的自定义资源。GMSA 凭据规约不包含 Secret 或敏感数据。它是一个容器运行时可以用来描述容器所需 GMSA 信息给 Windows 的信息。GMSA 凭据规约可以使用实用 PowerShell 脚本生成 YAML 格式。

以下是手动生成 JSON 格式的 GMSA 凭据规约 YAML 文件并将其转换的步骤:

  1. 导入 CredentialSpec 模块ipmo CredentialSpec.psm1

  2. 使用 New-CredentialSpec 创建 JSON 格式的凭据规约。要创建一个名为 WebApp1 的 GMSA 凭据规约,请调用 New-CredentialSpec -Name WebApp1 -AccountName WebApp1 -Domain $(Get-ADDomain -Current LocalComputer)

  3. 使用 Get-CredentialSpec 显示 JSON 文件的路径。

  4. 将 credspec 文件从 JSON 格式转换为 YAML 格式,并添加必要的头部字段 apiVersionkindmetadatacredspec,使其成为可在 Kubernetes 中配置的 GMSACredentialSpec 自定义资源。

以下 YAML 配置描述了一个名为 gmsa-WebApp1 的 GMSA 凭据规约。

apiVersion: windows.k8s.io/v1
kind: GMSACredentialSpec
metadata:
  name: gmsa-WebApp1  # This is an arbitrary name but it will be used as a reference
credspec:
  ActiveDirectoryConfig:
    GroupManagedServiceAccounts:
    - Name: WebApp1   # Username of the GMSA account
      Scope: CONTOSO  # NETBIOS Domain Name
    - Name: WebApp1   # Username of the GMSA account
      Scope: contoso.com # DNS Domain Name
  CmsPlugins:
  - ActiveDirectory
  DomainJoinConfig:
    DnsName: contoso.com  # DNS Domain Name
    DnsTreeName: contoso.com # DNS Domain Name Root
    Guid: 244818ae-87ac-4fcd-92ec-e79e5252348a  # GUID of the Domain
    MachineAccountName: WebApp1 # Username of the GMSA account
    NetBiosName: CONTOSO  # NETBIOS Domain Name
    Sid: S-1-5-21-2126449477-2524075714-3094792973 # SID of the Domain

上述凭据规约资源可以保存为 gmsa-Webapp1-credspec.yaml,并使用以下命令应用于集群:kubectl apply -f gmsa-Webapp1-credspec.yml

配置集群角色,以启用对特定 GMSA 凭据规约的 RBAC

每个 GMSA 凭据规约资源都需要定义一个集群角色。这会授权主体(通常是服务帐户)对特定的 GMSA 资源使用 use 动词。以下示例显示了一个授权使用上面创建的 gmsa-WebApp1 凭据规约的集群角色。将文件保存为 gmsa-webapp1-role.yaml 并使用 kubectl apply -f gmsa-webapp1-role.yaml 应用。

# Create the Role to read the credspec
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: webapp1-role
rules:
- apiGroups: ["windows.k8s.io"]
  resources: ["gmsacredentialspecs"]
  verbs: ["use"]
  resourceNames: ["gmsa-WebApp1"]

将角色分配给服务帐户以使用特定的 GMSA credspec

需要将服务帐户(Pod 将配置的服务帐户)绑定到上面创建的集群角色。这将授权服务帐户使用所需的 GMSA 凭据规约资源。以下示例展示了将 default 服务帐户绑定到 webapp1-role 集群角色,以便使用上面创建的 gmsa-WebApp1 凭据规约资源。

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: allow-default-svc-account-read-on-gmsa-WebApp1
  namespace: default
subjects:
- kind: ServiceAccount
  name: default
  namespace: default
roleRef:
  kind: ClusterRole
  name: webapp1-role
  apiGroup: rbac.authorization.k8s.io

在 Pod 规约中配置 GMSA 凭据规约引用

Pod 规约字段 securityContext.windowsOptions.gmsaCredentialSpecName 用于在 Pod 规约中指定对所需 GMSA 凭据规约自定义资源的引用。这会配置 Pod 规约中的所有容器使用指定的 GMSA。以下是一个带有填充的注解,用于引用 gmsa-WebApp1 的 Pod 规约示例。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: with-creds
  name: with-creds
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      run: with-creds
  template:
    metadata:
      labels:
        run: with-creds
    spec:
      securityContext:
        windowsOptions:
          gmsaCredentialSpecName: gmsa-webapp1
      containers:
      - image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
        imagePullPolicy: Always
        name: iis
      nodeSelector:
        kubernetes.io/os: windows

Pod 规约中的单个容器也可以使用 per-container securityContext.windowsOptions.gmsaCredentialSpecName 字段来指定所需的 GMSA credspec。例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: with-creds
  name: with-creds
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      run: with-creds
  template:
    metadata:
      labels:
        run: with-creds
    spec:
      containers:
      - image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
        imagePullPolicy: Always
        name: iis
        securityContext:
          windowsOptions:
            gmsaCredentialSpecName: gmsa-Webapp1
      nodeSelector:
        kubernetes.io/os: windows

当在集群中应用填充了 GMSA 字段的 Pod 规约(如上所述)时,会发生以下一系列事件:

  1. 准入时变更 Webhook 解析并将对 GMSA 凭据规约资源的所有引用展开为 GMSA 凭据规约的内容。

  2. 准入时校验 Webhook 确保与 Pod 关联的服务帐户被授权在指定的 GMSA 凭据规约上使用 use 动词。

  3. 容器运行时会使用指定的 GMSA 凭据规约配置每个 Windows 容器,以便容器可以在 Active Directory 中扮演 GMSA 的身份,并使用该身份访问域中的服务。

使用主机名或 FQDN 对网络共享进行身份认证

如果你在使用主机名或 FQDN 从 Pod 连接 SMB 共享时遇到问题,但能通过其 IPv4 地址访问共享,请确保在 Windows 节点上设置了以下注册表键。

reg add "HKLM\SYSTEM\CurrentControlSet\Services\hns\State" /v EnableCompartmentNamespace /t REG_DWORD /d 1

然后,需要重新创建正在运行的 Pod 以使行为变更生效。关于此注册表键用途的更多信息可在此处找到。

故障排查

如果你在环境中让 GMSA 工作时遇到困难,可以采取一些故障排查步骤。

首先,确保 credspec 已传递给 Pod。为此,你需要通过 exec 进入你的一个 Pod,并检查 nltest.exe /parentdomain 命令的输出。

在以下示例中,Pod 未正确获取 credspec:

kubectl exec -it iis-auth-7776966999-n5nzr powershell.exe

nltest.exe /parentdomain 导致以下错误:

Getting parent domain failed: Status = 1722 0x6ba RPC_S_SERVER_UNAVAILABLE

如果你的 Pod 正确获取了 credspec,接下来检查与域的通信。首先,从 Pod 内部快速执行 nslookup 查找你的域的根。

这会告诉我们 3 件事:

  1. Pod 可以到达 DC。
  2. DC 可以到达 Pod。
  3. DNS 正在正常工作。

如果 DNS 和通信测试通过,接下来你需要检查 Pod 是否已与域建立安全通道通信。为此,再次通过 exec 进入你的 Pod,然后运行 nltest.exe /query 命令。

nltest.exe /query

结果如下:

I_NetLogonControl failed: Status = 1722 0x6ba RPC_S_SERVER_UNAVAILABLE

这表明,由于某种原因,Pod 无法使用 credspec 中指定的帐户登录到域。你可以尝试运行以下命令来修复安全通道:

nltest /sc_reset:domain.example

如果命令成功,你将看到类似以下的输出:

Flags: 30 HAS_IP  HAS_TIMESERV
Trusted DC Name \\dc10.domain.example
Trusted DC Connection Status Status = 0 0x0 NERR_Success
The command completed successfully

如果以上步骤纠正了错误,你可以通过将以下 lifecycle hook 添加到你的 Pod 规约中来自动化该步骤。如果未能纠正错误,你需要再次检查你的 credspec 并确认其正确且完整。

        image: registry.domain.example/iis-auth:1809v1
        lifecycle:
          postStart:
            exec:
              command: ["powershell.exe","-command","do { Restart-Service -Name netlogon } while ( $($Result = (nltest.exe /query); if ($Result -like '*0x0 NERR_Success*') {return $true} else {return $false}) -eq $false)"]
        imagePullPolicy: IfNotPresent

如果你将上述所示的 lifecycle 部分添加到你的 Pod 规约中,Pod 将执行列出的命令来重启 netlogon 服务,直到 nltest.exe /query 命令无错误退出。

最后修改于 2024 年 7 月 10 日下午 2:05 PST: Fix gMSA credspec definitions (97bd20e5a9)