Mình đang câng viết 1 tool golang và sẽ access vào EKS để call tiếp các API của K8S
Chúng ta đi sơ qua 1 chút về

Đây là kubeconfig mà bạn hay sử dụng để access vào eks cluster.
Bạn có thể thấy khi access vào eks cluster thì kubeclt sẽ cần gõ một lệnh bắng aws cli để get token
Vậy là máy tính của bạn cần cài kubeclt and aws cli.
OK bậy giờ chúng ta sẽ code như thế nào?
package main import ( "context" "encoding/base64" "encoding/json" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/sts" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "log" "os/exec" "sigs.k8s.io/aws-iam-authenticator/pkg/token" ) // getEksToken retrieves the authentication token and CA data from the EKS cluster func getEksToken(region, clusterName string) (string, string, []byte, error) { // Create an AWS session (SDK v1) sess, err := session.NewSession(&aws.Config{ Region: aws.String(region), }) if err != nil { return "", "", nil, fmt.Errorf("unable to create AWS session, %v", err) } // Create an EKS client (SDK v1) eksClient := eks.New(sess) // Get cluster details to retrieve the API server URL and certificate authority data clusterDetails, err := eksClient.DescribeCluster(&eks.DescribeClusterInput{ Name: aws.String(clusterName), }) if err != nil { return "", "", nil, fmt.Errorf("unable to describe cluster: %v", err) } // Extract API server URL and certificate authority data apiServer := *clusterDetails.Cluster.Endpoint caDataBase64 := *clusterDetails.Cluster.CertificateAuthority.Data // Dereference *string to string // Decode the base64-encoded CA data to []byte caData, err := base64.StdEncoding.DecodeString(caDataBase64) if err != nil { return "", "", nil, fmt.Errorf("unable to decode CA data: %v", err) } // Use the AWS CLI to get the authentication token token, err := getAWSTokenUsingCLI(region, clusterName) if err != nil { return "", "", nil, fmt.Errorf("unable to get authentication token: %v", err) } return token, apiServer, caData, nil } // getAWSTokenUsingIAMAuthenticator generates the authentication token using the AWS IAM Authenticator func getAWSTokenUsingIAMAuthenticator(region, clusterName string) (string, error) { // Create an AWS session (SDK v1) sess, err := session.NewSession(&aws.Config{ Region: aws.String(region), }) if err != nil { return "", fmt.Errorf("unable to create AWS session: %v", err) } // Create an STS client (SDK v1) stsClient := sts.New(sess) // Create a token generator generator, err := token.NewGenerator(false, false) if err != nil { return "", fmt.Errorf("failed to create token generator: %v", err) } // Generate a token using the STS client to authenticate with the EKS cluster tk, err := generator.GetWithSTS(clusterName, stsClient) if err != nil { return "", fmt.Errorf("unable to generate token using IAM Authenticator: %v", err) } // Return the generated token return tk.Token, nil } // getAWSTokenUsingCLI runs `aws eks get-token` to retrieve the authentication token for EKS func getAWSTokenUsingCLI(region, clusterName string) (string, error) { // Run the AWS CLI command to get the token cmd := exec.Command("aws", "eks", "get-token", "--region", region, "--cluster-name", clusterName) output, err := cmd.Output() if err != nil { return "", fmt.Errorf("failed to run AWS CLI: %v", err) } // The output will be in JSON format, we need to extract the token from it var tokenResp struct { Status struct { Token string `json:"token"` } `json:"status"` } // Decode the JSON response into the struct err = json.Unmarshal(output, &tokenResp) if err != nil { return "", fmt.Errorf("failed to parse AWS CLI output: %v", err) } // Return the token return tokenResp.Status.Token, nil } func getK8sClient(token, apiServer string, caData []byte) (*kubernetes.Clientset, error) { // Dynamically create a kubeconfig in memory, including the CA data kubeconfig := clientcmd.NewDefaultClientConfig(api.Config{ Clusters: map[string]*api.Cluster{ "elearning-eks": { Server: apiServer, CertificateAuthorityData: caData, }, }, Contexts: map[string]*api.Context{ "elearning-eks": { Cluster: "elearning-eks", AuthInfo: "elearning-eks", }, }, CurrentContext: "elearning-eks", AuthInfos: map[string]*api.AuthInfo{ "elearning-eks": { Token: token, }, }, }, nil) // Create the Kubernetes client from the in-memory kubeconfig config, err := kubeconfig.ClientConfig() if err != nil { return nil, fmt.Errorf("unable to create Kubernetes config: %v", err) } clientset, err := kubernetes.NewForConfig(config) if err != nil { return nil, fmt.Errorf("unable to create Kubernetes client: %v", err) } return clientset, nil } func main() { // Specify the EKS cluster name and region clusterName := "elearning-eks" region := "eu-central-1" // Step 1: Get EKS authentication token and API server endpoint, along with CA data token, apiServer, caData, err := getEksToken(region, clusterName) if err != nil { log.Fatalf("Error getting EKS token: %v", err) } // Step 2: Set up the Kubernetes client dynamically using the token, API server URL, and CA certificate clientset, err := getK8sClient(token, apiServer, caData) if err != nil { log.Fatalf("Error creating Kubernetes client: %v", err) } // Step 3: Use the clientset to interact with Kubernetes // Example: List pods pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{}) if err != nil { log.Fatalf("Error listing pods: %v", err) } fmt.Println("Pods in cluster:") for _, pod := range pods.Items { fmt.Println(pod.Name) } // Example: List nodes nodes, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) if err != nil { log.Fatalf("Error listing nodes: %v", err) } fmt.Println("Nodes in cluster:") for _, node := range nodes.Items { fmt.Println(node.Name) } }
Bạn có thể thấy đầu tiên thì tool sẽ thự hiện Get EKS authentication token and API server endpoint, along with CA data
explain the code easily to understand: // getEksToken retrieves the authentication token and CA data from the EKS cluster func getEksToken(region, clusterName string) (string, string, []byte, error) { // Create an AWS session (SDK v1) sess, err := session.NewSession(&aws.Config{ Region: aws.String(region), }) if err != nil { return "", "", nil, fmt.Errorf("unable to create AWS session, %v", err) } // Create an EKS client (SDK v1) eksClient := eks.New(sess) // Get cluster details to retrieve the API server URL and certificate authority data clusterDetails, err := eksClient.DescribeCluster(&eks.DescribeClusterInput{ Name: aws.String(clusterName), }) if err != nil { return "", "", nil, fmt.Errorf("unable to describe cluster: %v", err) } // Extract API server URL and certificate authority data apiServer := *clusterDetails.Cluster.Endpoint caDataBase64 := *clusterDetails.Cluster.CertificateAuthority.Data // Dereference *string to string // Decode the base64-encoded CA data to []byte caData, err := base64.StdEncoding.DecodeString(caDataBase64) if err != nil { return "", "", nil, fmt.Errorf("unable to decode CA data: %v", err) } // Use the AWS CLI to get the authentication token token, err := getAWSTokenUsingCLI(region, clusterName) if err != nil { return "", "", nil, fmt.Errorf("unable to get authentication token: %v", err) } return token, apiServer, caData, nil }
1. Create an AWS Session:
sess, err := session.NewSession(&aws.Config{ Region: aws.String(region), })
Initializes a new AWS session configured for a specific region.
This session is used to create clients for AWS services, like EKS, to interact with AWS resources.
2. Create an EKS Client:
eksClient := eks.New(sess)
Creates a client for the EKS service using the AWS session.
The EKS client allows us to make API calls to the EKS service, such as retrieving cluster details.
3. Describe the EKS Cluster:
clusterDetails, err := eksClient.DescribeCluster(&eks.DescribeClusterInput{ Name: aws.String(clusterName), })
Calls the DescribeCluster API to get details about the specified EKS cluster.
We need the cluster’s API server URL and CA data to connect securely.
4. Extract API Server URL and CA Data:
apiServer := *clusterDetails.Cluster.Endpoint caDataBase64 := *clusterDetails.Cluster.CertificateAuthority.Data
Retrieves the API server URL and the base64-encoded CA data from the cluster details.
5. Decode CA Data:
caData, err := base64.StdEncoding.DecodeString(caDataBase64)
Decodes the base64-encoded CA data into a byte slice.
6. Get Authentication Token:
token, err := getAWSTokenUsingCLI(region, clusterName)
Calls a helper function to retrieve an authentication token using the AWS CLI.
The token is used to authenticate requests to the EKS API server.
7. Return the Results:
return token, apiServer, caData, nil
Lúc này thì nó sẽ chia ra làm 2 khi mà tool cần get token để access vào eks.
Sử dụng command AWS CLI để get token:
// getAWSTokenUsingCLI runs `aws eks get-token` to retrieve the authentication token for EKS func getAWSTokenUsingCLI(region, clusterName string) (string, error) { // Run the AWS CLI command to get the token cmd := exec.Command("aws", "eks", "get-token", "--region", region, "--cluster-name", clusterName) output, err := cmd.Output() if err != nil { return "", fmt.Errorf("failed to run AWS CLI: %v", err) } // The output will be in JSON format, we need to extract the token from it var tokenResp struct { Status struct { Token string `json:"token"` } `json:"status"` } // Decode the JSON response into the struct err = json.Unmarshal(output, &tokenResp) if err != nil { return "", fmt.Errorf("failed to parse AWS CLI output: %v", err) } // Return the token return tokenResp.Status.Token, nil }
The getAWSTokenUsingCLI function is designed to retrieve an authentication token for an Amazon EKS (Elastic Kubernetes Service) cluster using the AWS CLI. This token is necessary for authenticating requests to the EKS API server.
Chúng ta sẽ cần tìm hiểu chút về Parse JSON Output:
var tokenResp struct { Status struct { Token string `json:"token"` } `json:"status"` } err = json.Unmarshal(output, &tokenResp)
Defines a structure to match the expected JSON output format from the AWS CLI.
The AWS CLI returns the token in JSON format, so we need to parse this JSON to extract the token.
Trong 1 số trường hợp chúng ta không muốn client cài đặt aws cli trên máy:
// getAWSTokenUsingIAMAuthenticator generates the authentication token using the AWS IAM Authenticator func getAWSTokenUsingIAMAuthenticator(region, clusterName string) (string, error) { // Create an AWS session (SDK v1) sess, err := session.NewSession(&aws.Config{ Region: aws.String(region), }) if err != nil { return "", fmt.Errorf("unable to create AWS session: %v", err) } // Create an STS client (SDK v1) stsClient := sts.New(sess) // Create a token generator generator, err := token.NewGenerator(false, false) if err != nil { return "", fmt.Errorf("failed to create token generator: %v", err) } // Generate a token using the STS client to authenticate with the EKS cluster tk, err := generator.GetWithSTS(clusterName, stsClient) if err != nil { return "", fmt.Errorf("unable to generate token using IAM Authenticator: %v", err) } // Return the generated token return tk.Token, nil }
Có một điều mình cần announce hiện tại để get token thì chúng ta cần sử dụng aws-sdk-go (v1) không phải là aws-sdk-go-v2
- Create an AWS Session
sess, err := session.NewSession(&aws.Config{ Region: aws.String(region), })
Initializes a new AWS session configured for a specific region.
This session is used to create clients for AWS services, such as STS (Security Token Service), to interact with AWS resources.
2. Create an STS Client:
stsClient := sts.New(sess)
Creates a client for the AWS STS service using the AWS session.
The STS client is used to interact with AWS STS, which is involved in generating temporary credentials.
3. Create a Token Generator:
generator, err := token.NewGenerator(false, false)
The token generator is responsible for creating the authentication token that will be used to access the EKS cluster.
4. Generate the Token:
tk, err := generator.GetWithSTS(clusterName, stsClient)
Uses the token generator to create a token for the specified EKS cluster, utilizing the STS client.
The generated token is used to authenticate with the EKS API server, allowing secure access to the cluster.
Tới đây chúng ta đã có token, apiServer, CA data
token, apiServer, caData, err := getEksToken(region, clusterName)
Tiếp đến chúng ta cần tạo a Kubernetes clientset
func getK8sClient(token, apiServer string, caData []byte) (*kubernetes.Clientset, error) { // Dynamically create a kubeconfig in memory, including the CA data kubeconfig := clientcmd.NewDefaultClientConfig(api.Config{ Clusters: map[string]*api.Cluster{ "elearning-eks": { Server: apiServer, CertificateAuthorityData: caData, }, }, Contexts: map[string]*api.Context{ "elearning-eks": { Cluster: "elearning-eks", AuthInfo: "elearning-eks", }, }, CurrentContext: "elearning-eks", AuthInfos: map[string]*api.AuthInfo{ "elearning-eks": { Token: token, }, }, }, nil) // Create the Kubernetes client from the in-memory kubeconfig config, err := kubeconfig.ClientConfig() if err != nil { return nil, fmt.Errorf("unable to create Kubernetes config: %v", err) } clientset, err := kubernetes.NewForConfig(config) if err != nil { return nil, fmt.Errorf("unable to create Kubernetes client: %v", err) } return clientset, nil }
1. Create an In-Memory Kubeconfig:
kubeconfig := clientcmd.NewDefaultClientConfig(api.Config{ Clusters: map[string]*api.Cluster{ "elearning-eks": { Server: apiServer, CertificateAuthorityData: caData, }, }, Contexts: map[string]*api.Context{ "elearning-eks": { Cluster: "elearning-eks", AuthInfo: "elearning-eks", }, }, CurrentContext: "elearning-eks", AuthInfos: map[string]*api.AuthInfo{ "elearning-eks": { Token: token, }, }, }, nil)
Constructs a kubeconfig in memory with the necessary details to connect to the Kubernetes cluster.
- Components:Clusters:
- Defines the cluster with its API server URL and CA data.
- Contexts: Specifies the context, linking the cluster and authentication information.
- CurrentContext: Sets the active context to use for operations.
- AuthInfos: Provides the authentication token for accessing the cluster.
The kubeconfig contains all the information required to authenticate and connect to the Kubernetes API server.
2. Create Kubernetes Client Configuration:
config, err := kubeconfig.ClientConfig()
Converts the in-memory kubeconfig into a rest.Config object, which is used by the Kubernetes client.
The rest.Config object is necessary for creating a clientset that can interact with the Kubernetes API.
3. Create Kubernetes Clientset:
clientset, err := kubernetes.NewForConfig(config)
Uses the rest.Config to create a Kubernetes clientset.
The clientset is the main interface for interacting with the Kubernetes API, allowing operations like listing pods, nodes, etc.
Sau khi đã có clientSet chúng ta hoàn toàn có thể interact với K8S API
// Step 3: Use the clientset to interact with Kubernetes // Example: List pods pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{}) if err != nil { log.Fatalf("Error listing pods: %v", err) } fmt.Println("Pods in cluster:") for _, pod := range pods.Items { fmt.Println(pod.Name) } // Example: List nodes nodes, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) if err != nil { log.Fatalf("Error listing nodes: %v", err) } fmt.Println("Nodes in cluster:") for _, node := range nodes.Items { fmt.Println(node.Name) }