写在最前

在实际生产中,由于安全合规要求,即便是内网 Harbor 镜像仓库也不能设为公开访问,必须启用身份认证。但如果手动在每个 Deployment 中配置 imagePullSecrets,不仅繁琐,还容易遗漏,带来维护成本和上线风险。

通过引入 Kyverno 策略控制器,我们可以在 Pod 创建前自动注入镜像拉取凭证imagePullSecrets),无需修改任何已有的 YAML 配置,做到安全、自动、无感知地接入私有镜像仓库。这种方式符合企业级 DevOps 自动化和安全治理的双重需求。

https://kyverno.io/policies/other/add-imagepullsecrets/add-imagepullsecrets/

1. 安装部署

kubectl create -f https://github.com/kyverno/kyverno/releases/download/v1.14.0/install.yaml

2. 配置流程

你只需要执行 kubectl create -f add-imagepullsecrets.yaml 把策略部署上去,之后只要 Pod 重建或者新建,Kyverno 就会自动帮它加上 imagePullSecrets: [my-secret],完全不用你去改 Deployment。

而且这个策略支持按镜像地址匹配,比如你可以设置只对 harbor.tanqidi.com/* 这种镜像才注入秘钥,也可以更精细地指定某个项目或镜像路径,控制非常灵活。

这种好处是如果一个集群有多个harbor镜像仓库,每个镜像仓库的域名和账号都不一样,可以使用这种方式轻松完成pod创建前自动注入不同厂库的secret。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-imagepullsecrets
  annotations:
    policies.kyverno.io/title: Add imagePullSecrets
    policies.kyverno.io/category: Sample
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/minversion: 1.6.0
    policies.kyverno.io/description: >-
      Images coming from certain registries require authentication in order to pull them,
      and the kubelet uses this information in the form of an imagePullSecret to pull
      those images on behalf of your Pod. This policy searches for images coming from a
      registry called `corp.reg.com` and, if found, will mutate the Pod to add an
      imagePullSecret called `my-secret`.
spec:
  rules:
  - name: add-imagepullsecret
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      patchStrategicMerge:
        spec:
          containers:
          - <(image): "harbor.tanqidi.com/*"
          imagePullSecrets:
          - name: my-secret

3. 自动分发 secrets

这里我还没有深入研究kyverno自动分发资源的功能姑且先使用shell脚本来完成分发,一般做到这一步就足够了,考虑到一般镜像仓库的账号和密码不会频繁变更,这种方式通常是可行的。

提前在default创建my-secret的镜像仓库地址和账号密码,同步到所有ns中,先删除再创建。

#!/bin/bash
# 用法: ./sync_resource.sh <资源类型> <源namespace> <资源名>
# 例如: ./sync_resource.sh secret default my-secret

RESOURCE_TYPE="$1"   # 资源类型,例如 secret, configmap
SOURCE_NS="$2"       # 源 namespace
RESOURCE_NAME="$3"   # 资源名

if [ -z "$RESOURCE_TYPE" ] || [ -z "$SOURCE_NS" ] || [ -z "$RESOURCE_NAME" ]; then
    echo "用法: $0 <资源类型> <源namespace> <资源名>"
    exit 1
fi

# 检查源资源是否存在
if ! kubectl get "$RESOURCE_TYPE" "$RESOURCE_NAME" -n "$SOURCE_NS" &>/dev/null; then
    echo "❌ 源资源不存在: $RESOURCE_TYPE/$RESOURCE_NAME in namespace $SOURCE_NS"
    exit 1
fi

# 遍历所有 namespace
for ns in $(kubectl get ns --no-headers | awk '{print $1}'); do
    echo ">>> 同步到 namespace: $ns"

    # 如果目标 namespace 已有该资源,先删除
    kubectl delete "$RESOURCE_TYPE" "$RESOURCE_NAME" -n "$ns" --ignore-not-found

    # 从源导出并导入到目标 namespace
    kubectl get "$RESOURCE_TYPE" "$RESOURCE_NAME" -n "$SOURCE_NS" -o yaml \
    | sed "s/namespace: $SOURCE_NS/namespace: $ns/" \
    | kubectl apply -n "$ns" -f -
done

echo "✅ $RESOURCE_TYPE/$RESOURCE_NAME 已同步到所有 namespace"