この記事は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を作成するだけです。
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を作成します。
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を使って実装しています。
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を作成する関数を定義します。
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を作成するコードを例示します。
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操作が実行しやすくなります。
セキュリティ的にも期限付きの一時的な認証情報を使うことで、漏洩リスクを軽減できるのでオススメです。