How can I get a Kubernetes clientset in GO using a JSON service account key?

huangapple go评论86阅读模式
英文:

How can I get a Kubernetes clientset in GO using a JSON service account key?

问题

我需要使用从JSON服务帐户密钥文件中提取的令牌来创建一个Kubernetes clientset。

我在配置中明确提供了这个令牌,但它仍然在寻找Google Application-Default凭据,并且因为找不到而崩溃。

以下是我的代码:

package main

import (
	"context"
	"encoding/base64"
	"fmt"
	"io/ioutil"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	gke "google.golang.org/api/container/v1"
	"google.golang.org/api/option"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/clientcmd/api"
)

const (
	projectID   = "my_project_id"
	clusterName = "my_cluster_name"
	scope       = "https://www.googleapis.com/auth/cloud-platform"
)

func main() {
	ctx := context.Background()

	// 读取JSON密钥并提取令牌
	data, err := ioutil.ReadFile("sa_key.json")
	if err != nil {
		panic(err)
	}
	creds, err := google.CredentialsFromJSON(ctx, data, scope)
	if err != nil {
		panic(err)
	}
	token, err := creds.TokenSource.Token()
	if err != nil {
		panic(err)
	}
	fmt.Println("token", token.AccessToken)

	// 创建GKE客户端
	tokenSource := oauth2.StaticTokenSource(token)
	gkeClient, err := gke.NewService(ctx, option.WithTokenSource(tokenSource))
	if err != nil {
		panic(err)
	}

	// 创建动态kube配置
	inMemKubeConfig, err := createInMemKubeConfig(ctx, gkeClient, token, projectID)
	if err != nil {
		panic(err)
	}

	// 使用它创建rest.Config
	config, err := clientcmd.NewNonInteractiveClientConfig(*inMemKubeConfig, clusterName, &clientcmd.ConfigOverrides{CurrentContext: clusterName}, nil).ClientConfig()
	if err != nil {
		panic(err)
	}

	// 创建clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err) // 这是代码崩溃的地方,因为它找不到Google ADCs
	}

	fmt.Printf("clientset %+v\n", clientset)
}

func createInMemKubeConfig(ctx context.Context, client *gke.Service, token *oauth2.Token, projectID string) (*api.Config, error) {
	k8sConf := api.Config{
		APIVersion: "v1",
		Kind:       "Config",
		Clusters:   map[string]*api.Cluster{},
		AuthInfos:  map[string]*api.AuthInfo{},
		Contexts:   map[string]*api.Context{},
	}

	// 在所有区域(“-”)中列出具有项目ID projectID的所有集群
	resp, err := client.Projects.Zones.Clusters.List(projectID, "-").Context(ctx).Do()
	if err != nil {
		return nil, err
	}

	for _, f := range resp.Clusters {
		name := fmt.Sprintf("gke_%s_%s_%s", projectID, f.Zone, f.Name) // 我的自定义命名约定
		cert, err := base64.StdEncoding.DecodeString(f.MasterAuth.ClusterCaCertificate)
		if err != nil {
			return nil, err
		}

		k8sConf.Clusters[name] = &api.Cluster{
			CertificateAuthorityData: cert,
			Server:                   "https://" + f.Endpoint,
		}

		k8sConf.Contexts[name] = &api.Context{
			Cluster:  name,
			AuthInfo: name,
		}

		k8sConf.AuthInfos[name] = &api.AuthInfo{
			Token: token.AccessToken,
			AuthProvider: &api.AuthProviderConfig{
				Name: "gcp",
				Config: map[string]string{
					"scopes": scope,
				},
			},
		}
	}
	return &k8sConf, nil
}

以下是错误消息:

panic: cannot construct google default token source: google: could not find default credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
英文:

I need to create a Kubernetes clientset using a token extracted from JSON service account key file.

I explicitly provide this token inside the config, however it still looks for Google Application-Default credentials, and crashes because it cannot find them.

Below is my code:

package main
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
gke "google.golang.org/api/container/v1"
"google.golang.org/api/option"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)
const (
projectID   = "my_project_id"
clusterName = "my_cluster_name"
scope       = "https://www.googleapis.com/auth/cloud-platform"
)
func main() {
ctx := context.Background()
// Read JSON key and extract the token
data, err := ioutil.ReadFile("sa_key.json")
if err != nil {
panic(err)
}
creds, err := google.CredentialsFromJSON(ctx, data, scope)
if err != nil {
panic(err)
}
token, err := creds.TokenSource.Token()
if err != nil {
panic(err)
}
fmt.Println("token", token.AccessToken)
// Create GKE client
tokenSource := oauth2.StaticTokenSource(token)
gkeClient, err := gke.NewService(ctx, option.WithTokenSource(tokenSource))
if err != nil {
panic(err)
}
// Create a dynamic kube config
inMemKubeConfig, err := createInMemKubeConfig(ctx, gkeClient, token, projectID)
if err != nil {
panic(err)
}
// Use it to create a rest.Config
config, err := clientcmd.NewNonInteractiveClientConfig(*inMemKubeConfig, clusterName, &clientcmd.ConfigOverrides{CurrentContext: clusterName}, nil).ClientConfig()
if err != nil {
panic(err)
}
// Create the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err) // this where the code crashes because it can't find the Google ADCs
}
fmt.Printf("clientset %+v\n", clientset)
}
func createInMemKubeConfig(ctx context.Context, client *gke.Service, token *oauth2.Token, projectID string) (*api.Config, error) {
k8sConf := api.Config{
APIVersion: "v1",
Kind:       "Config",
Clusters:   map[string]*api.Cluster{},
AuthInfos:  map[string]*api.AuthInfo{},
Contexts:   map[string]*api.Context{},
}
// List all clusters in project with id projectID across all zones ("-")
resp, err := client.Projects.Zones.Clusters.List(projectID, "-").Context(ctx).Do()
if err != nil {
return nil, err
}
for _, f := range resp.Clusters {
name := fmt.Sprintf("gke_%s_%s_%s", projectID, f.Zone, f.Name) // My custom naming convention
cert, err := base64.StdEncoding.DecodeString(f.MasterAuth.ClusterCaCertificate)
if err != nil {
return nil, err
}
k8sConf.Clusters[name] = &api.Cluster{
CertificateAuthorityData: cert,
Server:                   "https://" + f.Endpoint,
}
k8sConf.Contexts[name] = &api.Context{
Cluster:  name,
AuthInfo: name,
}
k8sConf.AuthInfos[name] = &api.AuthInfo{
Token: token.AccessToken,
AuthProvider: &api.AuthProviderConfig{
Name: "gcp",
Config: map[string]string{
"scopes": scope,
},
},
}
}
return &k8sConf, nil
}

and here is the error message:

panic: cannot construct google default token source: google: could not find default credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.

答案1

得分: 2

这是对我有效的解决方案。

它基于这个gist,正是我一直在寻找的。它使用了一个oauth2.TokenSource对象,可以提供各种类型的令牌,因此非常灵活。

我花了很长时间才找到这个解决方案,希望能对某人有所帮助!

package main

import (
	"context"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	gke "google.golang.org/api/container/v1"
	"google.golang.org/api/option"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

const (
	googleAuthPlugin = "gcp"
	projectID        = "my_project"
	clusterName      = "my_cluster"
	zone             = "my_cluster_zone"
	scope            = "https://www.googleapis.com/auth/cloud-platform"
)

type googleAuthProvider struct {
	tokenSource oauth2.TokenSource
}

// 即使我们不使用它们,也需要这些函数
// 以便googleAuthProvider是一个rest.AuthProvider接口
func (g *googleAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
	return &oauth2.Transport{
		Base:   rt,
		Source: g.tokenSource,
	}
}
func (g *googleAuthProvider) Login() error { return nil }

func main() {
	ctx := context.Background()

	// 从JSON SA密钥中提取令牌
	data, err := ioutil.ReadFile("sa_key.json")
	if err != nil {
		panic(err)
	}
	creds, err := google.CredentialsFromJSON(ctx, data, scope)
	if err != nil {
		panic(err)
	}
	token, err := creds.TokenSource.Token()
	if err != nil {
		panic(err)
	}
	tokenSource := oauth2.StaticTokenSource(token)

	// 使用令牌进行身份验证
	// 如果它是nil,则使用Google ADC
	if err := rest.RegisterAuthProviderPlugin(googleAuthPlugin,
		func(clusterAddress string, config map[string]string, persister rest.AuthProviderConfigPersister) (rest.AuthProvider, error) {
			var err error
			if tokenSource == nil {
				tokenSource, err = google.DefaultTokenSource(ctx, scope)
				if err != nil {
					return nil, fmt.Errorf("failed to create google token source: %+v", err)
				}
			}
			return &googleAuthProvider{tokenSource: tokenSource}, nil
		}); err != nil {
		log.Fatalf("Failed to register %s auth plugin: %v", googleAuthPlugin, err)
	}

	gkeClient, err := gke.NewService(ctx, option.WithTokenSource(tokenSource))
	if err != nil {
		panic(err)
	}

	clientset, err := getClientSet(ctx, gkeClient, projectID, org, env)
	if err != nil {
		panic(err)
	}

	// 演示确保它工作正常
	pods, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
	if err != nil {
		panic(err)
	}
	log.Printf("集群中有%d个Pod", len(pods.Items))
	for _, pod := range pods.Items {
		fmt.Println(pod.Name)
	}
}

func getClientSet(ctx context.Context, client *gke.Service, projectID, name string) (*kubernetes.Clientset, error) {
	// 获取集群信息
	cluster, err := client.Projects.Zones.Clusters.Get(projectID, zone, name).Context(ctx).Do()
	if err != nil {
		panic(err)
	}

	// 解码集群CA证书
	cert, err := base64.StdEncoding.DecodeString(cluster.MasterAuth.ClusterCaCertificate)
	if err != nil {
		return nil, err
	}

	// 使用集群信息构建配置
	config := &rest.Config{
		TLSClientConfig: rest.TLSClientConfig{
			CAData: cert,
		},
		Host:         "https://" + cluster.Endpoint,
		AuthProvider: &clientcmdapi.AuthProviderConfig{Name: googleAuthPlugin},
	}

	return kubernetes.NewForConfig(config)
}
英文:

Here's what worked for me.

It is based on this gist
and it's exactly what I was looking for. It uses an oauth2.TokenSource object which can be fed with a variety of token types so it's quite flexible.

It took me a long time to find this solution so I hope this helps somebody!

package main
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net/http"
gke "google.golang.org/api/container/v1"
"google.golang.org/api/option"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
const (
googleAuthPlugin = "gcp"
projectID        = "my_project"
clusterName      = "my_cluster"
zone             = "my_cluster_zone"
scope            = "https://www.googleapis.com/auth/cloud-platform"
)
type googleAuthProvider struct {
tokenSource oauth2.TokenSource
}
// These funcitons are needed even if we don't utilize them
// So that googleAuthProvider is an rest.AuthProvider interface
func (g *googleAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &oauth2.Transport{
Base:   rt,
Source: g.tokenSource,
}
}
func (g *googleAuthProvider) Login() error { return nil }
func main() {
ctx := context.Background()
// Extract a token from the JSON SA key
data, err := ioutil.ReadFile("sa_key.json")
if err != nil {
panic(err)
}
creds, err := google.CredentialsFromJSON(ctx, data, scope)
if err != nil {
panic(err)
}
token, err := creds.TokenSource.Token()
if err != nil {
panic(err)
}
tokenSource := oauth2.StaticTokenSource(token)
// Authenticate with the token
// If it's nil use Google ADC
if err := rest.RegisterAuthProviderPlugin(googleAuthPlugin,
func(clusterAddress string, config map[string]string, persister rest.AuthProviderConfigPersister) (rest.AuthProvider, error) {
var err error
if tokenSource == nil {
tokenSource, err = google.DefaultTokenSource(ctx, scope)
if err != nil {
return nil, fmt.Errorf("failed to create google token source: %+v", err)
}
}
return &googleAuthProvider{tokenSource: tokenSource}, nil
}); err != nil {
log.Fatalf("Failed to register %s auth plugin: %v", googleAuthPlugin, err)
}
gkeClient, err := gke.NewService(ctx, option.WithTokenSource(tokenSource))
if err != nil {
panic(err)
}
clientset, err := getClientSet(ctx, gkeClient, projectID, org, env)
if err != nil {
panic(err)
}
// Demo to make sure it works
pods, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
if err != nil {
panic(err)
}
log.Printf("There are %d pods in the cluster", len(pods.Items))
for _, pod := range pods.Items {
fmt.Println(pod.Name)
}
}
func getClientSet(ctx context.Context, client *gke.Service, projectID, name string) (*kubernetes.Clientset, error) {
// Get cluster info
cluster, err := client.Projects.Zones.Clusters.Get(projectID, zone, name).Context(ctx).Do()
if err != nil {
panic(err)
}
// Decode cluster CA certificate
cert, err := base64.StdEncoding.DecodeString(cluster.MasterAuth.ClusterCaCertificate)
if err != nil {
return nil, err
}
// Build a config using the cluster info
config := &rest.Config{
TLSClientConfig: rest.TLSClientConfig{
CAData: cert,
},
Host:         "https://" + cluster.Endpoint,
AuthProvider: &clientcmdapi.AuthProviderConfig{Name: googleAuthPlugin},
}
return kubernetes.NewForConfig(config)
}

huangapple
  • 本文由 发表于 2022年8月17日 18:00:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/73386475.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定