使用 golang 为 Kubernetes 创建 GCR 秘密

问题描述 投票:0回答:1

嘿,我正在尝试创建一个秘密,用于从我的注册表中提取图像,并且除 gcr 之外的所有其他注册表都在工作,下面是创建的秘密,

.dockerconfigjson: '{"auths":{"us-south1-docker.pkg.dev":{"username": "_json_key","password":"{\"type\":\"service_account\",\"project_id\":\"....\",\"private_key_id\":\"...\",\"private_key\":\"...\\n\",\"client_email\":\"...\",\"client_id\":\"....\",\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\",\"token_uri\":\"https://oauth2.googleapis.com/token\",\"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\",\"client_x509_cert_url\":\"https://www.googleapis.com/robot/v1/metadata/x509/...\",\"universe_domain\":\"googleapis.com\"}"}}}'

下面是处理 gcp 的代码,

    case "gcr", "gcp":
        url = "gcr.io"
        if image.URL != "" {
            url = image.URL
        }
        username = "_json_key"
        // as the gcp credentials are in the form of a json interface, we need to convert it to a json object
        // so we can save the file temporarily and then read it for creating the secret
        var gcpCredentials GcpCredentials
        // gcp is in interface format, so we need to convert it to json
        gcpCredentialsJson, err := json.Marshal(image.GCPCredentials)
        if err != nil {
            logrus.Errorf("Could not marshal the gcp credentials: %s", err)
            return err
        }
        err = json.Unmarshal(gcpCredentialsJson, &gcpCredentials)
        if err != nil {
            logrus.Errorf("Could not unmarshal the gcp credentials: %s", err)
            return err
        }
        // create a json file with the gcp credentials
        err = os.WriteFile("/tmp/gcp_credentials.json", gcpCredentialsJson, 0644)
        if err != nil {
            logrus.Errorf("Could not write the gcp credentials file: %s", err)
            return err
        }
        // read the file and get the contents
        contents, err := os.ReadFile("/tmp/gcp_credentials.json")
        if err != nil {
            logrus.Errorf("Could not read the gcp credentials file: %s", err)
            return err
        }
        password = string(contents)
dockerConfigJson := fmt.Sprintf(`{"auths":{"%s":{"username": "%s","password":"%s"}}}`, url, username, password)
    logrus.Infof("Creating docker config secret: %s", dockerConfigJson)
    secret := corev1.Secret{
        ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", secretName, strings.ToLower(image.Provider))},
        Type:       corev1.SecretTypeDockerConfigJson,
        Data:       map[string][]byte{".dockerconfigjson": []byte(dockerConfigJson)},
    }
    err := createSecret(&secret, clientset, namespace)

但是每次我尝试使用令牌提取图像时,都会出现以下错误,

Failed to pull image "us-south1-docker.pkg.dev/...": rpc error: code = Unknown desc = failed to pull and unpack image "...": failed to resolve reference "...": failed to authorize: failed to fetch oauth token: unexpected status from GET request to https://us-south1-docker.pkg.dev/v2/token?scope=repository%3A...Apull&service=us-south1-docker.pkg.dev: 401 Unauthorized               

Warning  Failed     29m (x4 over 31m)    kubelet            Error: ErrImagePull                                                                                                                                                           

Warning  Failed     29m (x6 over 31m)    kubelet            Error: ImagePullBackOff
go kubernetes
1个回答
0
投票

我宁愿直接在内存中处理 JSON,而不是写入和读取 JSON 文件:您可以避免文件权限、文件系统延迟或写入/读取文件错误等潜在问题,这些问题在容器化环境中或在以下情况下尤其可能出现问题:使用 Kubernetes 节点中的临时存储。

package main

import (
    "encoding/json"
    "fmt"
    "log"

    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func createGCRSecret(clientset *Clientset, image Image, secretName, namespace string) error {
    username := "_json_key"
    var gcpCredentialsJson []byte
    var err error

    // Assuming 'GCPCredentials' is a valid JSON string already
    gcpCredentialsJson, err = json.Marshal(image.GCPCredentials)
    if err != nil {
        log.Fatalf("Could not marshal the gcp credentials: %v", err)
        return err
    }

    dockerConfigJson := fmt.Sprintf(`{"auths":{"%s":{"username":"%s","password":"%s"}}}`,
        image.URL, username, string(gcpCredentialsJson))

    secret := corev1.Secret{
        ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", secretName, strings.ToLower(image.Provider))},
        Type:       corev1.SecretTypeDockerConfigJson,
        Data:       map[string][]byte{".dockerconfigjson": []byte(dockerConfigJson)},
    }

    err = createSecret(&secret, clientset, namespace)
    if err != nil {
        log.Fatalf("Failed to create Kubernetes secret: %v", err)
        return err
    }

    log.Printf("Successfully created docker config secret: %s", secretName)
    return nil
}

但是您的 GCPCredentials 必须已经是有效的 JSON 字符串:如果 JSON 密钥格式不正确,则编组/解组步骤可能会在到达 Kubernetes 秘密创建之前失败。

401 Unauthorized
错误通常指向以下问题:

  • 凭据没有正确的权限。
  • 凭证格式正确,但有些不正确(例如,过期或撤销)。
  • Kubernetes 配置使用这些凭据的方式可能存在问题,或者注册表 URL 不匹配。

因此请检查与 JSON 凭据关联的服务帐户是否具有必要的角色。对于 GCR(ArtifactRegistry,因为原始的 ContainerRegistry 已被弃用),通常需要像

roles/storage.objectViewer
这样的角色来拉取镜像(请参阅“Cloud Storage 的 IAM 角色”)。

有时部分凭证会被更新或更改。仔细检查服务帐户 JSON 密钥中的整套凭据是否正确且有效。

确保注册表 URL (

image.URL
) 设置正确。注册表 URL 中的错误可能会导致身份验证问题,因为凭据可能与预期的注册表端点不匹配。

Kubernetes 秘密是命名空间资源。这意味着它们只能在创建它们的名称空间内访问。当您创建用于从私有注册表中提取映像的密钥时,您必须确保在与将使用它的 Pod 相同的命名空间中创建该密钥。

创建密钥时,指定运行工作负载(Pod)的命名空间。这是通过在秘密定义的

namespace
中设置
ObjectMeta
参数来完成的:

secret := corev1.Secret{
    ObjectMeta: metav1.ObjectMeta{
        Name: fmt.Sprintf("%s-%s", secretName, strings.ToLower(image.Provider)),
        Namespace: namespace,  // Make sure this is the correct namespace
    },
    Type: corev1.SecretTypeDockerConfigJson,
    Data: map[string][]byte{".dockerconfigjson": []byte(dockerConfigJson)},
}

当您调用函数在 Kubernetes API 中创建密钥时,请确保它引用预期的命名空间。这通常涉及使用正确配置的 Kubernetes 客户端集来定位正确的命名空间。

在 pod 的 YAML 配置中,在

imagePullSecrets
部分指定密钥。这告诉 Kubernetes 在拉取容器镜像时使用指定的密钥:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  containers:
  - name: example-container
    image: us-south1-docker.pkg.dev/project-id/example-image:tag
  imagePullSecrets:
  - name: [your-secret-name]

有时,您可能对不同的 pod 有特定的服务帐户,尤其是在更复杂的设置或多租户集群中。确保与 pod 关联的服务帐户具有读取密钥的权限。这通常通过基于角色的访问控制 (RBAC) 设置进行管理。
例如,此角色将允许服务帐户读取特定命名空间中的机密:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: [your-namespace]
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

以及对应的RoleBinding:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: [your-namespace]
subjects:
- kind: ServiceAccount
  name: [your-service-account]
  namespace: [your-namespace]
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
© www.soinside.com 2019 - 2024. All rights reserved.