4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZOZOAdvent Calendar 2024

Day 17

[client-go] GoでEKSにSTSを使ってアクセスする

Last updated at Posted at 2024-12-16

この記事はZOZO AdventCalender 2024シリーズ5の17日目の記事です。

GoでEKSにアクセスする際、localのkubeconfigを使う方法はよく知られていますが、Lambdaなどのserverless環境の場合はkubeconfigを用意するのが面倒です。
本記事では、EKSにアクセスする際に、STSを使って一時的なクレデンシャルを取得する方法を紹介します。
STSを使ってアクセスすることで、kubeconfigを準備する必要がなくなり、Lambdaなどのserverless環境でもclient-goを使ったk8s操作が実行しやすくなります。

client-goによるEKS接続

STSを使ったアクセス方法を説明する前に、まずはclient-goでkubeconfigを使ってEKSにアクセスする方法を説明します。
またCluster内で動作するPodからEKSのリソースにアクセスする場合のconfig取得方法についても触れたいと思います。

kubeconfigを使ったEKS接続

まずは、kubeconfigを使ってEKSにアクセスする方法を説明します。
事前に操作したいk8sクラスタへ接続可能なkubeconfigを準備しておきます。
あとは、コード内でkubeconfigを読み込んでclient-goのclientsetを作成するだけです。

main.go
package main

import (
  "fmt"
  "os"
  "path/filepath"

  "k8s.io/client-go/kubernetes"
  "k8s.io/client-go/tools/clientcmd"
  "k8s.io/client-go/util/homedir"
)

func main() {
	kubeconfig := os.Getenv("KUBECONFIG")
	if kubeconfig == "" {
		// KUBECONFIGが設定されていない場合はデフォルトのkubeconfigファイルのパスを使用
		kubeconfig = filepath.Join(homedir.HomeDir(), ".kube", "config")
	}
	
	// Kubernetesクライアントのセットアップ
	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	if err != nil {
		return nil, fmt.Errorf("failed to build config from flags: %w", err)
	}
	
	clientset := kubernetes.NewForConfig(config)
}

Cluster内で動作するPodからk8sリソースにアクセス

Cluster内からk8sリソースにアクセスする場合、InClusterConfigを使ってclientsetを作成します。

main.go
package main

import (
  "k8s.io/client-go/rest"
  "k8s.io/client-go/kubernetes"
)

func main() {
	config, err := rest.InClusterConfig()
	if err != nil {
	  // error処理
	}
	
	clientset := kubernetes.NewForConfig(config)
}

InClusterConfigの実装は、割とシンプルで環境変数や固定のpathから必要な情報を取得して、client-goのconfigを作成しています。
権限はマウントされたServiceAccountのものが使われます。

client-go with STS

次に、STSを使ってEKSにアクセスする方法を説明します。

STSとは?

AWS STS (Security Token Service) は、一時的なセキュリティ認証情報(トークン)を生成するAWSサービスです。
AWS STSを使用すると、以下の3つを提供する一時的な認証情報を取得できます

  • アクセスキーID(AccessKeyId)
  • シークレットアクセスキー(SecretAccessKey)
  • セッショントークン(SessionToken)

参考: IAMの一時的な認証情報

必要なIAM権限

前提事項として、適切な権限を持ったAWS Profileの設定が必要です。
トークンを取得する対象のEKSクラスターの詳細を取得するために、以下のIAM権限が必要になります。

  • eks:DescribeCluster

Lambdaなどのserverless環境で実行する場合は、IAM Roleを作成し、Lambda関数にアタッチすることで権限を付与します。

tokenの取得

はじめにawsコマンドを使って、トークンを取得する例を示します。

$ aws eks get-token --cluster-name <cluster_name>
{
    "kind": "ExecCredential",
    "apiVersion": "client.authentication.k8s.io/v1beta1",
    "spec": {},
    "status": {
        "expirationTimestamp": "2024-12-13T11:56:58Z",
        "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    }
}

適切な権限を持ったAWS Profileを設定している場合、aws eks get-tokenコマンドでトークンを取得できます。

次にGoでトークンを取得するには、aws-iam-authenticatorを使ってトークンを取得します。
aws-iam-authenticatorは、内部でAWS STSを使ってEKSのトークンを取得します。

package main

import (
  "fmt"
  "log"
  "sigs.k8s.io/aws-iam-authenticator/pkg/token"
)

func main()  {
  g, _ := token.NewGenerator(false, false)
  tk, err := g.Get("<cluster_name>")
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(tk)
}

※ これらのコマンドとコードは公式ドキュメントにも記載されているので、そちらも参考にしてください。

client-goでトークンを使ってEKS操作

最後に本題のclient-goでトークンを使ってEKSにアクセスする方法を説明します。
基本的には、clientsetを作成する際のConfigでトークンを設定するだけですが、HostやCAFileの設定も必要になります。

client-goによる接続の前に、必要な情報を取得するためのコードを例示します。
cluster情報の取得する関数定義で、AWS SDKを使って実装しています。

aws.go
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/eks"
	ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
)

type AWSClient struct {
	eks *eks.Client

	ctx context.Context
}

func NewAWSClient() (*AWSClient, error) {
	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion("ap-northeast-1"))
	if err != nil {
		return nil, errors.Join(err, fmt.Errorf("failed to load AWS config"))
	}
	return &AWSClient{
		eks: eks.NewFromConfig(cfg),
		ctx: context.Background(),
	}, nil
}

// cluster情報の取得
func (c *AWSClient) DescriceEKSCluster(clusterName string) (*ekstypes.Cluster, error) {
	out, err := c.eks.DescribeCluster(c.ctx, &eks.DescribeClusterInput{
		Name: aws.String(clusterName),
	})
	if err != nil {
		return nil, errors.Join(err, errors.New("failed to describe eks cluster"))
	}
	return out.Cluster, nil
}

次に、client-goでトークンを使ってClientsetを作成する関数を定義します。

k8s.go
package main

import (
	"context"
	"encoding/base64"
	"errors"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/aws-iam-authenticator/pkg/token"
)

func NewClientset(clusterInfo *ekstypes.Cluster) (*kubernetes.Clientset, error) {
	gen, err := token.NewGenerator(false, false)
	if err != nil {
		return nil, errors.Join(err, errors.New("failed to create k8s token generator"))
	}
	token, err := gen.Get(*clusterInfo.Name)
	if err != nil {
		return nil, errors.Join(err, errors.New("failed to get k8s token"))
	}
	ca, err := base64.StdEncoding.DecodeString(*clusterInfo.CertificateAuthority.Data)
	if err != nil {
		return nil, errors.Join(err, errors.New("failed to decode certificate authority"))
	}
	clientset, err := kubernetes.NewForConfig(&rest.Config{
		Host:            *clusterInfo.Endpoint,
		BearerToken:     token.Token,
		TLSClientConfig: rest.TLSClientConfig{CAData: ca},
	})
	if err != nil {
		return nil, errors.Join(err, errors.New("failed to create k8s clientset"))
	}
	return clientset, nil
}

最後に、これらの処理を使ってEKSにPodを作成するコードを例示します。

main.go
package main

func main() {
  awsClient, err := NewAWSClient()
  if err != nil {
    log.Fatal(err)
  }
  clusterInfo, err := awsClient.DescriceEKSCluster("<cluster_name>")
  if err != nil {
    log.Fatal(err)
  }
  clientset, err := NewClientset(clusterInfo)
  if err != nil {
    log.Fatal(err)
  }

  // Podを作成する
  pod := &corev1.Pod{
    // Podの設定
  }
  podsClient := clientset.CoreV1().Pods("<namespace>")
  _, err = podsClient.Create(context.Background(), pod, metav1.CreateOptions{})
  if err != nil {
    log.Fatal(err)
  }
}

まとめ

本記事では、GoでEKSにアクセスする際に、STSを使って一時的なクレデンシャルを取得する方法を紹介しました。
STSを使ってアクセスすることで、kubeconfigを準備する必要がなくなり、Lambdaなどのserverless環境でもclient-goを使ったk8s操作が実行しやすくなります。
セキュリティ的にも期限付きの一時的な認証情報を使うことで、漏洩リスクを軽減できるのでオススメです。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?