你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

使用机密存储扩展提取用于在已启用 Azure Arc 的 Kubernetes 群集中进行脱机访问的机密

适用于 Kubernetes 的 Azure Key Vault 机密存储扩展(“SSE”)自动将机密从 Azure Key Vault 同步到已启用 Azure Arc 的 Kubernetes 群集以供脱机访问。 这意味着,即使以半断开连接状态运行 Kubernetes 群集,也可使用 Azure Key Vault 存储、维护和轮换机密。 同步的机密存储在群集机密存储中,使其可作为 Kubernetes 机密以所有常用方式使用:作为数据卷装载,或作为环境变量公开给 Pod 中的容器。

同步的机密是关键业务资产,因此 SSE 通过隔离的命名空间和节点、基于角色的访问控制 (RBAC) 策略以及机密同步器的有限权限来保护它们。 为了获得额外的保护,对群集上的 Kubernetes 机密存储进行加密

提示

对于必需进行脱机访问的场景,或者若需要将机密同步到 Kubernetes 机密存储中,建议使用 SSE。 如果不需要这些功能,可使用 Azure Key Vault 机密提供程序扩展,在已启用 Arc 的 Kubernetes 群集中管理机密。 不建议在群集中并行运行联机 Azure Key Vault 机密提供程序扩展和脱机 SSE。

本文介绍如何将 SSE 安装并配置为已启用 Azure Arc 的 Kubernetes 扩展

重要

SSE 目前为预览版。 有关 beta 版本、预览版或尚未正式发布的版本的 Azure 功能所适用的法律条款,请参阅 Microsoft Azure 预览版的补充使用条款

先决条件

  • 一个已连接到 Azure Arc、运行 Kubernetes 版本 1.27 或更高版本且位于支持的区域(美国东部、美国东部 2、美国西部、美国西部 2、美国西部 3、西欧、北欧)之一的群集。 该区域由用于创建 Arc 群集的资源组区域定义。
  • 本指南中的示例使用 K3s 群集。
  • 确保满足群集扩展的常规先决条件,包括最新版本的 k8s-extension Azure CLI 扩展。
  • 需要 cert-manager 才能支持 TLS 进行群集内日志通信。 本指南后面的示例将指导你完成安装。 有关 cert-manager 的详细信息,请参阅 cert-manager.io

安装 Azure CLI 并登录(如果尚未安装)。

az login

在开始之前,设置用于配置 Azure 和群集资源的环境变量。 如果已有托管标识、Azure Key Vault 或此处列出的其他资源,请更新环境变量中的名称以反映这些资源。

export RESOURCE_GROUP="AzureArcTest"
export CLUSTER_NAME="AzureArcTest1"
export LOCATION="EastUS"
export SUBSCRIPTION="$(az account show --query id --output tsv)"
az account set --subscription "${SUBSCRIPTION}"
export AZURE_TENANT_ID="$(az account show -s $SUBSCRIPTION --query tenantId --output tsv)"
export CURRENT_USER="$(az ad signed-in-user show --query userPrincipalName --output tsv)"
export KEYVAULT_NAME="my-kv"
export KEYVAULT_SECRET_NAME="my-secret"
export USER_ASSIGNED_IDENTITY_NAME="my-identity"
export FEDERATED_IDENTITY_CREDENTIAL_NAME="my-credential"
export KUBERNETES_NAMESPACE="my-namespace"
export SERVICE_ACCOUNT_NAME="my-service-account"

配置标识以访问机密

若要访问并同步给定的 Azure Key Vault 机密,SSE 需要访问具有相应 Azure 权限以访问该机密的 Azure 托管标识。 该托管标识必须通过工作负载联合身份验证链接到 Kubernetes 服务帐户。 该 Kubernetes 服务帐户是你在 Kubernetes Pod 或其他工作负载中用于从 Kubernetes 机密存储访问机密的帐户。 SSE 使用关联的联合 Azure 托管标识,将机密从 Azure Key Vault 拉取到 Kubernetes 机密存储。 以下各节介绍如何对此进行设置。

在群集上启用工作负载标识

如果群集尚未连接到 Azure Arc,请遵循这些步骤。 在这些步骤中,在 connect 命令中启用工作负载标识:

az connectedk8s connect --name ${CLUSTER_NAME} --resource-group ${RESOURCE_GROUP} --enable-oidc-issuer --enable-workload-identity

如果群集已连接到 Azure Arc,请使用 update 命令启用工作负载标识:

az connectedk8s update --name ${CLUSTER_NAME} --resource-group ${RESOURCE_GROUP} --enable-oidc-issuer --enable-workload-identity

配置群集以启用令牌验证

你的群集必须配置为使用新的颁发者 URL (service-account-issuer) 颁发服务帐户令牌,该 URL 使 Microsoft Entra ID 能够找到验证这些令牌所需的公钥。 这些公钥适用于群集自己的服务帐户令牌颁发者,由于你在上面设置的 --enable-oidc-issuer 选项,它们是在该 URL 上获取和托管的。

另外,你可选择通过配置 OwnerReferencesPermissionEnforcement 许可控制器,将 SSE 自身的权限限制配置为在控制平面中运行的特权资源。 此许可控制器限制了 SSE 可在多大程度上更改群集中的其他对象。

Kubernetes 群集必须运行 Kubernetes 版本 1.27 或更高版本。

  1. 使用颁发者 URL 字段和权限强制措施配置 kube-apiserver。 以下示例适用于 k3s 群集。 群集可能有不同的方法来更改 API 服务器参数:--kube-apiserver-arg="--service-account-issuer=${SERVICE_ACCOUNT_ISSUER}" and --kube-apiserver-arg="--enable-admission-plugins=OwnerReferencesPermissionEnforcement"

    • 获取服务帐户颁发者 URL。

      export SERVICE_ACCOUNT_ISSUER="$(az connectedk8s show --name ${CLUSTER_NAME} --resource-group ${RESOURCE_GROUP} --query "oidcIssuerProfile.issuerUrl" --output tsv)"
      echo $SERVICE_ACCOUNT_ISSUER
      
    • 打开 K3s 服务器配置文件。

      sudo nano /etc/systemd/system/k3s.service
      
    • 编辑服务器配置,使其看起来如下例所示,将 SERVICE_ACCOUNT_ISSUER 替换为 echo $SERVICE_ACCOUNT_ISSUER 的以上输出<>,不要忘记包括此 URL 的尾随正斜杠:

      ExecStart=/usr/local/bin/k3s \
        server --write-kubeconfig-mode=644 \
           --kube-apiserver-arg="--service-account-issuer=<SERVICE_ACCOUNT_ISSUER>" \
           --kube-apiserver-arg="--enable-admission-plugins=OwnerReferencesPermissionEnforcement"
      
  2. 重启 kube-apiserver。

    sudo systemctl daemon-reload
    sudo systemctl restart k3s
    

创建 Azure Key Vault

创建 Azure Key Vault 并添加机密。 如果已有一个 Azure Key Vault 和机密,可跳过此节。

  1. 创建 Azure 密钥保管库:

    az keyvault create --resource-group "${RESOURCE_GROUP}" --location "${LOCATION}" --name "${KEYVAULT_NAME}" --enable-rbac-authorization
    
  2. 向自己授予对保管库的“机密管理人员”权限,使你可创建机密:

    az role assignment create --role "Key Vault Secrets Officer" --assignee ${CURRENT_USER} --scope /subscriptions/${SUBSCRIPTION}/resourcegroups/${RESOURCE_GROUP}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME}
    
  3. 创建一个机密并对其进行更新,这样你就有两个版本:

    az keyvault secret set --vault-name "${KEYVAULT_NAME}" --name "${KEYVAULT_SECRET_NAME}" --value 'Hello!'
    az keyvault secret set --vault-name "${KEYVAULT_NAME}" --name "${KEYVAULT_SECRET_NAME}" --value 'Hello2'
    

创建用户分配的托管标识

接下来,创建用户分配的托管标识,并向其授予访问 Azure Key Vault 的权限。 如果已有一个具有对 Azure Key Vault 的“Key Vault 读取者”和“Key Vault 机密用户”权限的托管标识,则可跳过本节。 有关详细信息,请参阅创建用户分配的托管标识将 Azure RBAC 机密、密钥和证书权限与 Key Vault 搭配使用

  1. 创建用户分配的托管标识:

    az identity create --name "${USER_ASSIGNED_IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --location "${LOCATION}" --subscription "${SUBSCRIPTION}"
    
  2. 向标识授予“Key Vault 读取者”和“Key Vault 机密”用户权限。 在这些命令成功之前,你可能需要等待一段时间以复制标识创建:

    export USER_ASSIGNED_CLIENT_ID="$(az identity show --resource-group "${RESOURCE_GROUP}" --name "${USER_ASSIGNED_IDENTITY_NAME}" --query 'clientId' -otsv)"
    az role assignment create --role "Key Vault Reader" --assignee "${USER_ASSIGNED_CLIENT_ID}" --scope /subscriptions/${SUBSCRIPTION}/resourcegroups/${RESOURCE_GROUP}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME}
    az role assignment create --role "Key Vault Secrets User" --assignee "${USER_ASSIGNED_CLIENT_ID}" --scope /subscriptions/${SUBSCRIPTION}/resourcegroups/${RESOURCE_GROUP}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME}
    

创建联合标识凭据

为需要访问机密的工作负载创建 Kubernetes 服务帐户。 然后,创建联合标识凭据,以在托管标识、OIDC 服务帐户颁发者和 Kubernetes 服务帐户之间建立链接。

  1. 创建一个将与托管标识联合的 Kubernetes 服务帐户。 使用关联的用户分配的托管标识的详细信息,对它进行批注。

    kubectl create ns ${KUBERNETES_NAMESPACE}
    
    cat <<EOF | kubectl apply -f -
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: ${SERVICE_ACCOUNT_NAME}
        namespace: ${KUBERNETES_NAMESPACE}
    EOF
    
  2. 创建联合标识凭据:

    az identity federated-credential create --name ${FEDERATED_IDENTITY_CREDENTIAL_NAME} --identity-name ${USER_ASSIGNED_IDENTITY_NAME} --resource-group ${RESOURCE_GROUP} --issuer ${SERVICE_ACCOUNT_ISSUER} --subject system:serviceaccount:${KUBERNETES_NAMESPACE}:${SERVICE_ACCOUNT_NAME}
    

安装并使用 SSE

SSE 以 Azure Arc 扩展的形式提供。 已启用 Azure Arc 的 Kubernetes 群集可通过已启用 Azure Arc 的 Kubernetes 扩展进行扩展。 扩展在已连接的群集上启用 Azure 功能,并为扩展安装和生命周期管理提供 Azure 资源管理器驱动的体验。

安装 cert-manager 和 trust-manager

如要在群集服务之间针对日志展开安全通信,cert-managertrust-manager 是必要项,必须在安装 Arc 扩展之前安装。

  1. 安装 cert-manager。

    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml
    
  2. 安装 trust-manager。

    helm repo add jetstack https://charts.jetstack.io
    helm repo update
    helm upgrade trust-manager jetstack/trust-manager --install --namespace cert-manager --wait
    

安装 SSE

  • 使用以下命令,将 SSE 安装到已启用 Arc 的群集:

    az k8s-extension create \
       --cluster-name ${CLUSTER_NAME} \
       --cluster-type connectedClusters \
       --extension-type microsoft.azure.secretstore \
       --resource-group ${RESOURCE_GROUP} \
       --release-train preview \
       --name ssarcextension \
       --scope cluster 
    

    如果需要,可选择通过添加 --configuration-settings rotationPollIntervalInSeconds=<time_in_seconds> 来修改默认轮换轮询间隔:

    参数名称 说明 默认值
    rotationPollIntervalInSeconds 指定 SSE 检查或更新其管理的机密的速度。 3600(1 小时)

配置 SSE

通过定义 Kubernetes 自定义资源的实例,使用有关 Azure Key Vault 以及要同步到群集的机密的信息来配置已安装的扩展。 创建两种类型的自定义资源:

  • 用于定义与 Key Vault 的连接的 SecretProviderClass 对象。
  • 要同步的每个机密的 SecretSync 对象。

创建 SecretProviderClass 资源

SecretProviderClass 资源用于定义与 Azure Key Vault 的连接、用于访问保管库的标识、要同步的机密以及要在本地保留的每个机密的版本数。

对于每个要同步的 Azure Key Vault、每个用于访问 Azure Key Vault 的标识以及每个目标 Kubernetes 命名空间,需要单独的 SecretProviderClass

按照以下示例,使用 Key Vault 和机密的适当值创建一个或多个 SecretProviderClass YAML 文件。

cat <<EOF > spc.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: secret-provider-class-name                      # Name of the class; must be unique per Kubernetes namespace
  namespace: ${KUBERNETES_NAMESPACE}                    # Kubernetes namespace to make the secrets accessible in
spec:
  provider: azure
  parameters:
    clientID: "${USER_ASSIGNED_CLIENT_ID}"               # Managed Identity Client ID for accessing the Azure Key Vault with.
    keyvaultName: ${KEYVAULT_NAME}                       # The name of the Azure Key Vault to synchronize secrets from.
    objects: |
      array:
        - |
          objectName: ${KEYVAULT_SECRET_NAME}            # The name of the secret to sychronize.
          objectType: secret
          objectVersionHistory: 2                       # [optional] The number of versions to synchronize, starting from latest.
    tenantID: "${AZURE_TENANT_ID}"                       # The tenant ID of the Key Vault 
EOF

创建 SecretSync 对象

每个同步的机密还需要一个 SecretSync 对象,以定义特定于群集的信息。 在此处指定信息,例如群集中的机密名称以及群集中存储的每个机密版本的名称。

按照以下模板,为每个机密创建一个 SecretSync 对象 YAML 文件。 Kubernetes 命名空间应与匹配的 SecretProviderClass 的命名空间匹配。

cat <<EOF > ss.yaml
apiVersion: secret-sync.x-k8s.io/v1alpha1
kind: SecretSync
metadata:
  name: secret-sync-name                                  # Name of the object; must be unique per Kubernetes namespace
  namespace: ${KUBERNETES_NAMESPACE}                      # Kubernetes namespace
spec:
  serviceAccountName: ${SERVICE_ACCOUNT_NAME}             # The Kubernetes service account to be given permissions to access the secret.
  secretProviderClassName: secret-provider-class-name     # The name of the matching SecretProviderClass with the configuration to access the AKV storing this secret
  secretObject:
    type: Opaque
    data:
    - sourcePath: ${KEYVAULT_SECRET_NAME}/0                # Name of the secret in Azure Key Vault with an optional version number (defaults to latest)
      targetKey: ${KEYVAULT_SECRET_NAME}-data-key0         # Target name of the secret in the Kubernetes secret store (must be unique)
    - sourcePath: ${KEYVAULT_SECRET_NAME}/1                # [optional] Next version of the AKV secret. Note that versions of the secret must match the configured objectVersionHistory in the secrets provider class 
      targetKey: ${KEYVAULT_SECRET_NAME}-data-key1         # [optional] Next target name of the secret in the K8s secret store
EOF

应用配置 CR

使用 kubectl apply 命令应用配置自定义资源 (CR):

kubectl apply -f ./spc.yaml
kubectl apply -f ./ss.yaml

SSE 自动查找机密,并开始将它们同步到群集。

查看配置选项

若要查看这两种自定义资源类型的其他配置选项,请使用 kubectl describe 命令检查群集中的 CRD:

# Get the name of any applied CRD(s)
kubectl get crds -o custom-columns=NAME:.metadata.name

# View the full configuration options and field parameters for a given CRD
kubectl describe crd secretproviderclass
kubectl describe crd secretsync

观察要同步到群集的机密

应用配置后,机密就开始以安装 SSE 时指定的节奏自动同步到集群。

查看已同步的机密

运行以下命令,查看已同步到群集的机密:

# View a list of all secrets in the namespace
kubectl get secrets -n ${KUBERNETES_NAMESPACE}

# View details of all secrets in the namespace
kubectl get secrets -n ${KUBERNETES_NAMESPACE} -o yaml

查看上次同步状态

若要查看给定机密的最新同步状态,请使用 SecretSync 对象的 kubectl describe 命令。 输出包括机密创建时间戳、机密的版本以及每个同步事件的详细状态消息。 此输出可用于诊断连接或配置错误,并观察机密值何时更改。

kubectl describe secretsync secret-sync-name -n ${KUBERNETES_NAMESPACE}

查看机密值

若要查看现在存储在 Kubernetes 机密存储中的已同步机密值,请使用以下命令:

kubectl get secret secret-sync-name -n ${KUBERNETES_NAMESPACE} -o jsonpath="{.data.${KEYVAULT_SECRET_NAME}-data-key0}" | base64 -d
kubectl get secret secret-sync-name -n ${KUBERNETES_NAMESPACE} -o jsonpath="{.data.${KEYVAULT_SECRET_NAME}-data-key1}" | base64 -d

故障排除

SSE 是一个 Kubernetes 部署,其中包含一个带有两个容器的 Pod:控制器(用于管理在群集中存储机密)和提供程序(用于管理对 Azure Key Vault 的访问及从中拉取机密)。 每个已同步的机密都有一个 SecretSync 对象,其中包含该机密从 Azure Key Vault 到群集机密存储的同步状态。

若要排查问题,请首先查看 SecretSync 对象的状态,如查看上次同步状态中所述。 下表列出了常见状态类型、其含义以及解决错误的潜在故障排除步骤。

SecretSync 状态类型 详细信息 进一步修复/调查的步骤
CreateSucceeded 机密已成功创建。 不适用
CreateFailedProviderError 由于提供程序存在一些问题(与 Azure Key Vault 的连接),机密创建失败。 此失败可能是由于 Internet 连接、标识同步机密的权限不足、SecretProviderClass 配置错误或其他问题造成的。 使用以下命令查看提供程序的日志,进一步调查:
kubectl get pods -n azure-secret-store
kubectl logs <secret-sync-controller-pod-name> -n azure-secret-store --container='provider-azure-installer'
CreateFailedInvalidLabel 机密创建失败,因为该机密已存在,但没有 SSE 用于管理其机密的正确 Kubernetes 标签。 移除现有标签和机密,并允许 SSE 重新创建机密:kubectl delete secret <secret-name>
若要强制 SSE 以比配置的轮换轮询间隔更快的速度重新创建机密,请删除 SecretSync 对象 (kubectl delete secretsync <secret-name>),并重新应用机密同步类 (kubectl apply -f <path_to_secret_sync>)。
CreateFailedInvalidAnnotation 机密创建失败,因为该机密已存在,但没有 SSE 用于管理其机密的正确 Kubernetes 注释。 移除现有注释和机密,并允许 SSE 重新创建机密:kubectl delete secret <secret-name>
若要强制 SSE 以比配置的轮换轮询间隔更快的速度重新创建机密,请删除 SecretSync 对象 (kubectl delete secretsync <secret-name>),并重新应用机密同步类 (kubectl apply -f <path_to_secret_sync>)。
UpdateNoValueChangeSucceeded SSE 在配置的轮询间隔结束时检查了 Azure Key Vault 是否有更新,但没有要同步的更改。 不适用
UpdateValueChangeOrForceUpdateSucceeded SSE 检查了 Azure Key Vault 是否有更新,并成功更新了值。 不适用
UpdateFailedInvalidLabel 机密更新失败,因为 SSE 用于管理其机密的机密标签已修改。 移除现有标签和机密,并允许 SSE 重新创建机密:kubectl delete secret <secret-name>
若要强制 SSE 以比配置的轮换轮询间隔更快的速度重新创建机密,请删除 SecretSync 对象 (kubectl delete secretsync <secret-name>),并重新应用机密同步类 (kubectl apply -f <path_to_secret_sync>)。
UpdateFailedInvalidAnnotation 机密更新失败,因为 SSE 用于管理其机密的机密注释已修改。 移除现有注释和机密,并允许 SSE 重新创建机密:kubectl delete secret <secret-name>
若要强制 SSE 以比配置的轮换轮询间隔更快的速度重新创建机密,请删除 SecretSync 对象 (kubectl delete secretsync <secret-name>),并重新应用机密同步类 (kubectl apply -f <path_to_secret_sync>)。
UpdateFailedProviderError 由于提供程序存在一些问题(与 Azure Key Vault 的连接),机密更新失败。 此失败可能是由于 Internet 连接、标识同步机密的权限不足、SecretProviderClass 配置或其他问题造成的。 使用以下命令查看提供程序的日志,进一步调查:
kubectl get pods -n azure-secret-store
kubectl logs <secret-sync-controller-pod-name> -n azure-secret-store --container='provider-azure-installer'
UserInputValidationFailed 机密更新失败,因为机密同步类配置不正确(例如无效的机密类型)。 查看机密同步类定义并更正任何错误。 然后,删除 SecretSync 对象 (kubectl delete secretsync <secret-name>),删除机密同步类 (kubectl delete -f <path_to_secret_sync>),并重新应用机密同步类 (kubectl apply -f <path_to_secret_sync>)。
ControllerSpcError 机密更新失败,因为 SSE 未能获取提供程序类或提供程序类配置错误。 查看提供程序类并更正任何错误。 然后,删除 SecretSync 对象 (kubectl delete secretsync <secret-name>),删除提供程序类 (kubectl delete -f <path_to_provider>),并重新应用提供程序类 (kubectl apply -f <path_to_provider>)。
ControllerInternalError 由于 SSE 中的内部错误,机密更新失败。 有关详细信息,请查看 SSE 日志或事件:
kubectl get pods -n azure-secret-store
kubectl logs <secret-sync-controller-pod-name> -n azure-secret-store --container='manager'
SecretPatchFailedUnknownError 在修补 Kubernetes 机密值期间,机密更新失败。 如果机密被 SSE 以外的其他人修改,或 SSE 的更新过程中出现问题,则可能会发生此失败。 尝试删除机密和 SecretSync 对象,然后通过重新应用机密同步 CR 让 SSE 重新创建机密:
kubectl delete secret <secret-name>
kubectl delete secretsync <secret-name>
kubectl apply -f <path_to_secret_sync>

移除 SSE

若要移除 SSE 并停止同步机密,请使用 az k8s-extension delete 命令将其卸载:

az k8s-extension delete --name ssarcextension --cluster-name $CLUSTER_NAME  --resource-group $RESOURCE_GROUP  --cluster-type connectedClusters    

卸载扩展不会从群集中删除机密、SecretSync 对象或 CRD。 必须使用 kubectl 直接删除这些对象。

删除 SecretSync CRD 会删除所有 SecretSync 对象,且默认会删除所有拥有的机密,但在以下情况下,机密可能会保留:

  • 你修改了任何机密的所有权。
  • 你更改了群集中的垃圾回收设置,包括设置不同的终结器

在上述情况下,必须使用 kubectl 直接删除机密。

后续步骤