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)
}