はじめに
本記事は、KEP-5339 “Plugin for Credentials in ClusterProfile”(SIG-Multicluster)の要点と、EKS を題材にした簡易的な実装例をまとめた紹介記事です。
KEP: https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/5339-clusterprofile-plugin-credentials
概要
- ClusterProfile の
status.credentialProviders[].cluster
に server / CA を保持する。 - コントローラは
cp-creds.json
で exec plugin を登録し、実行時に渡されるKUBERNETES_EXEC_INFO
を入力としてトークンを取得する。 -
.spec.cluster.server
を基点に EKS クラスタを特定し、aws eks get-token
を呼び出す。 - 入出力は ExecCredential 形式。
全体フロー(Mermaid)
やってみた
サンプル一式: https://github.com/kahirokunn/cluster-inventory-api/tree/eks-example
1) コントローラ起動
go build controller_example.go
./controller_example -clusterprofile-provider-file ./cp-creds.json
2) cp-creds.json の中身
-clusterprofile-provider-file
のフラグで渡されているjsonファイルの中身です
{
"providers": [
{
"name": "eks",
"execConfig": {
"apiVersion": "client.authentication.k8s.io/v1beta1",
"args": null,
"command": "./eks-aws-auth-plugin.sh",
"env": null,
"provideClusterInfo": true
}
}
]
}
3) ClusterProfile をyamlで見た状態
apiVersion: multicluster.x-k8s.io/v1alpha1
kind: ClusterProfile
metadata:
name: my-cluster-1
spec:
displayName: my-cluster-1
clusterManager:
name: EKS-Fleet
status:
credentialProviders:
- name: eks
cluster:
server: https://xxx.gr7.ap-northeast-1.eks.amazonaws.com
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1J...
ExecCredential と exec plugin の流れ
-
ExecCredential
Kubernetes の exec 認証プラグインプロトコル。実行時に 入力は環境変数KUBERNETES_EXEC_INFO
、出力は標準出力へ ExecCredential を返します。
仕様: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats -
kubectl / client-go での利用
kubeconfig
のusers[].exec
、または本記事のように credentials provider のproviders[].execConfig
にコマンドを設定すると、認証時に当該コマンドが起動されます。KUBERNETES_EXEC_INFO
はその際にプラグインへ渡され、プラグインの標準出力の ExecCredential を kubectl/client-go が消費します。 -
今回の構成
providers[].execConfig.command
に./eks-aws-auth-plugin.sh
を指定。プラグインはKUBERNETES_EXEC_INFO
の.spec.cluster.server
と CA を手がかりにクラスタを特定し、最終的な ExecCredential を出力します。
修正セクションだけ差し替えます。(他の章はそのままでOK)
背景:aws eks update-kubeconfig
/ aws eks get-token
の“互換性”について
-
aws eks update-kubeconfig
- 機能: ローカルの
kubeconfig
を生成・更新するコマンド。 - 入力:
--name
等でクラスタ名の明示指定が必須。KUBERNETES_EXEC_INFO
(ExecCredential のspec.cluster.server
/ CA)を受け取る仕組みはない。 - 出力: ExecCredential JSON を標準出力で返さない。
kubeconfig
ファイルを書き換える動作のみ。
→ 結論: 「入力=KUBERNETES_EXEC_INFO
、出力= ExecCredential(JSON)/標準出力」という exec plugin の入出力要件と互換ではない。
- 機能: ローカルの
-
aws eks get-token
- 出力: ExecCredential 互換 JSON を標準出力に返す。
- 入力:
--cluster-name
の明示指定が必須。KUBERNETES_EXEC_INFO
を直接入力として受け取れない(server
/CA
だけからクラスタ名を解決する機能は持たない)。
→ 結論: 出力は互換だが、入力は互換ではない。KUBERNETES_EXEC_INFO
(server
/CA
)→ cluster-name への解決レイヤを別途用意する必要がある。
EKS用のexec plugin実装
処理の流れ:
-
KUBERNETES_EXEC_INFO
から.spec.cluster.server
を取得 - ホスト名から region を抽出
- 当該リージョンの EKS を列挙し、endpoint 完全一致でクラスタ名を特定
-
aws eks get-token
を実行
#!/usr/bin/env bash
set -euo pipefail
# -------- utils --------
err() { printf "[eks-exec-credential] %s\n" "$*" >&2; }
need() { command -v "$1" >/dev/null 2>&1 || { err "missing dependency: $1"; exit 1; }; }
normalize_host() { sed -E 's#^https?://##; s#/$##; s#:443$##'; }
need jq
need aws
# --- read ExecCredential ---
if [[ -z "${KUBERNETES_EXEC_INFO:-}" ]]; then
err "KUBERNETES_EXEC_INFO is empty. set provideClusterInfo: true"
exit 1
fi
REQ_API_VERSION="$(jq -r '.apiVersion // empty' <<<"$KUBERNETES_EXEC_INFO")"
SERVER="$(jq -r '.spec.cluster.server // empty' <<<"$KUBERNETES_EXEC_INFO")"
if [[ -z "$SERVER" || "$SERVER" == "null" ]]; then
err "spec.cluster.server is missing in KUBERNETES_EXEC_INFO"
exit 1
fi
NORM_SERVER="$(printf "%s" "$SERVER" | normalize_host)"
# --- region: infer from server hostname ---
HOST="${NORM_SERVER%%/*}"
REGION="$(printf "%s\n" "$HOST" \
| sed -nE 's#.*\.([a-z0-9-]+)\.eks(-fips)?\.amazonaws\.com(\.cn)?$#\1#p')"
if [[ -z "$REGION" ]]; then
err "failed to parse region from server hostname: ${SERVER}"
err "expected something like ...<random>.<suffix>.<region>.eks.amazonaws.com"
exit 1
fi
# --- tiny cache: endpoint -> cluster name ---
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/eks-exec-credential"
mkdir -p "$CACHE_DIR"
MAP_CACHE="$CACHE_DIR/endpoint-map-${REGION}.json"
if [[ ! -s "$MAP_CACHE" ]] || ! jq -e . >/dev/null 2>&1 <"$MAP_CACHE"; then
echo '{}' >"$MAP_CACHE"
fi
lookup_cache() { jq -r --arg k "$NORM_SERVER" '.[$k] // empty' <"$MAP_CACHE"; }
update_cache() {
local tmp; tmp="$(mktemp)"
jq --arg k "$NORM_SERVER" --arg v "$1" '.[$k]=$v' "$MAP_CACHE" >"$tmp" && mv "$tmp" "$MAP_CACHE"
}
match_endpoint() {
local name="$1"
local ep norm_ep
ep="$(aws eks describe-cluster --region "$REGION" --name "$name" \
--query 'cluster.endpoint' --output text 2>/dev/null || true)"
[[ -z "$ep" || "$ep" == "None" ]] && return 1
norm_ep="$(printf "%s" "$ep" | normalize_host)"
[[ "$norm_ep" == "$NORM_SERVER" ]]
}
CLUSTER_NAME=""
# 1) cache hit?
CACHED="$(lookup_cache || true)"
if [[ -n "$CACHED" ]] && match_endpoint "$CACHED"; then
CLUSTER_NAME="$CACHED"
fi
# 2) enumerate if needed
if [[ -z "$CLUSTER_NAME" ]]; then
err "resolving cluster in ${REGION} for ${NORM_SERVER}"
found=""
while IFS= read -r name; do
[[ -z "$name" ]] && continue
if match_endpoint "$name"; then
found="$name"
break
fi
done < <(aws eks list-clusters --region "$REGION" --output json | jq -r '.clusters[]?')
if [[ -z "$found" ]]; then
err "no matching EKS cluster for endpoint: ${SERVER} (region=${REGION})"
exit 1
fi
CLUSTER_NAME="$found"
update_cache "$CLUSTER_NAME" || true
fi
# --- fetch ExecCredential via aws CLI ---
TOKEN_JSON="$(aws eks get-token --region "$REGION" --cluster-name "$CLUSTER_NAME" --output json)"
printf "%s\n" "$TOKEN_JSON"
コントローラ側の利用例
rest.Config
を生成して Clientset を作成するサンプルです。
package main
import (
"context"
"encoding/base64"
"flag"
"log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1"
"sigs.k8s.io/cluster-inventory-api/apis/v1alpha1"
"sigs.k8s.io/cluster-inventory-api/pkg/credentials"
)
func main() {
credentialsProviders := credentials.SetupProviderFileFlag()
flag.Parse()
cpCreds, err := credentials.NewFromFile(*credentialsProviders)
if err != nil {
log.Fatalf("Got error reading credentials providers: %v", err)
}
caPEMBase64 := `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1J...`
caPEM, err := base64.StdEncoding.DecodeString(caPEMBase64)
if err != nil {
log.Fatalf("CA PEM base64 decode failed: %v", err)
}
// normally we would get this clusterprofile from the local cluster (maybe a watch?)
// and we would maintain the restconfigs for clusters we're interested in.
exampleClusterProfile := v1alpha1.ClusterProfile{
Spec: v1alpha1.ClusterProfileSpec{
DisplayName: "My Cluster",
},
Status: v1alpha1.ClusterProfileStatus{
CredentialProviders: []v1alpha1.CredentialProvider{
{
Name: "eks",
Cluster: clientcmdv1.Cluster{
Server: "https://xxx.gr7.ap-northeast-1.eks.amazonaws.com",
CertificateAuthorityData: caPEM,
},
},
},
},
}
restConfigForMyCluster, err := cpCreds.BuildConfigFromCP(&exampleClusterProfile)
if err != nil {
log.Fatalf("Got error generating restConfig: %v", err)
}
log.Printf("Got credentials: %v", restConfigForMyCluster)
// I can then use this rest.Config to build a k8s client.
// Build a client and list Pods in the default namespace
clientset, err := kubernetes.NewForConfig(restConfigForMyCluster)
if err != nil {
log.Fatalf("failed to create clientset: %v", err)
}
ctx := context.Background()
pods, err := clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
if err != nil {
log.Fatalf("failed to list pods: %v", err)
}
log.Printf("default namespace has %d pods", len(pods.Items))
for i, p := range pods.Items {
if i >= 10 {
log.Printf("... (truncated)")
break
}
log.Printf("pod: %s", p.Name)
}
}
トラブルシュート
-
no matching EKS cluster
→aws eks list-clusters --region <r>
の結果・権限・プロファイルを確認 -
x509: certificate signed by unknown authority
→certificate-authority-data
(base64)を確認
補足
ExecCredentialではextensionsを使う事で追加の値を渡せます。
KEP-5339: ClusterProfile の Credentials Plugin では、ExecCredential.extensions
を利用する仕様は定義されていません。
関連コード: https://github.com/kubernetes-sigs/cluster-inventory-api/blob/main/pkg/credentials/config.go#L133
参考
- KEP-5339(SIG-Multicluster)
https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/5339-clusterprofile-plugin-credentials - ExecCredential の入出力フォーマット
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats