はじめに
2026 年 4 月、Amazon EKS に「One-click cluster access through CloudShell」という機能がリリースされました。EKS のクラスタ詳細画面に表示される「接続」ボタンを押すと、AWS CloudShell が自動で立ち上がって、kubectl でクラスタ操作がすでに使える状態になっている、というものです。
ローカル環境に kubectl も AWS CLI も kubeconfig もインストール不要で、ブラウザだけでクラスタ操作ができます。緊急時のオペレーションや、kubectl の動作確認、軽い検証用途で十分使えそうな機能です。実際試してみたら、確かに「ワンクリック」で接続できました。
ただ、その瞬間に画面に流れたコマンドが気になりました。
source kubectl-connect <cluster-name>
「これって AWS CLI じゃないよね?でもどこから来たコマンド?」「source で実行してるってことは何か環境変数を設定してる?」「CloudShell じゃないと使えないの?」と、いくつかの疑問が連鎖したので、深掘りしてみることにしました。
TL;DR
-
kubectl-connectは AWS CLI ではなく、CloudShell に pre-installed されている bash スクリプト(/usr/local/bin/kubectl-connect、バージョン 1.0.0) - 内部で
aws eks update-kubeconfigを呼んで kubeconfig を生成しつつ、KUBECONFIG 環境変数の export と PS1 の書き換えをまとめて行うラッパー -
sourceで実行する理由は、exportした環境変数を呼び出し元のシェルに反映させるため。スクリプト内にも「sourced されてないと export が caller に伝わらないよ」という警告コードが入っている - 「接続」ボタンの実体は、CloudShell に対して
source kubectl-connect <cluster-name>というコマンド文字列を投入する仕組みらしい(CloudShell の.bashrcや/etc/profile.d/には何も登録されていなかった) - 依存は bash + AWS CLI + kubectl だけなので、スクリプトをコピーすればローカル PC でも動かせる
検証環境
| 項目 | 値 |
|---|---|
| AWS リージョン | ap-northeast-1(東京) |
| EKS クラスタ |
appsignals-fargate(Fargate-only、Terraform で構築) |
| CloudShell リージョン | ap-northeast-1 |
EKS クラスタは別の検証で構築済みのものを流用しています。本記事の核心は CloudShell 内の kubectl-connect スクリプトの解析なので、クラスタの構成自体は本筋ではありません。
新機能の紹介
What's New の発表内容
Amazon EKS now supports one-click cluster access through CloudShell より引用:
Amazon EKS now provides one-click cluster access directly from the AWS Management Console through AWS CloudShell, eliminating the need to install and configure kubectl, AWS CLI, or kubeconfig files locally.
「kubectl / AWS CLI / kubeconfig をローカルにインストール / 設定する必要がなくなる」というのが要点です。開発機がない、環境構築の時間がない、といった場面で、急遽クラスタを覗きたい人を救うのが狙いなのでしょう。
仕様まとめ
| 項目 | 内容 |
|---|---|
| 提供開始 | 2026 年 4 月 |
| 利用方法 | EKS Console → クラスタ詳細 → 「Connect」ボタン |
| サポート | パブリック / プライベート両方の API server endpoint |
| プライベートエンドポイント時 | CloudShell が VPC 環境を自動起動し、名前を尋ねる |
| 料金 | 追加料金なし |
| 対応リージョン | EKS が利用可能な全リージョン |
特に押さえておきたいのは、 パブリック / プライベート両方の API server endpoint に対応している 点です。本番運用では「セキュリティ要件で API server をプライベート化したい」という選択をすることが多いのですが、その場合、これまでは踏み台 EC2 や 今は亡きAWS Cloud9 を別途用意する必要がありました。本機能ではプライベートエンドポイントを選んでいる場合でも CloudShell 側で VPC 環境が自動で立ち上がるので、踏み台インスタンスの維持コストや構築の手間がそのまま消えます。これだけでも採用する理由になります。
ボタンの位置
Console で EKS クラスタ詳細画面を開くと、右上のアクションバーに「接続」(英語表示なら Connect)というボタンが現れます。これをクリックすると CloudShell が自動で立ち上がり、コマンドが流し込まれます。
実際にやってみた
ボタンを押した瞬間の挙動
「接続」を押すと、画面の下部に CloudShell が起動し、「コマンドを実行」という確認モーダルが現れました。コマンド欄には次の文字列が 既に入力された状態 で表示されています。
source kubectl-connect appsignals-fargate
ここで「実行」を押すと、コマンドがそのまま CloudShell ターミナルで実行されます。実行が完了するとプロンプトが次のように変化し、kubectl が即使える状態になります。
[k8s: appsignals-fargate] $ kubectl get nodes
NAME STATUS ROLES AGE VERSION
fargate-ip-10-20-1-xxx.ap-northeast-1.compute.internal Ready <none> 3m v1.35.0-eks-xxx
確かに、ローカルの kubectl 設定なしでクラスタに接続できています。
ただ、ここで気になるのは「source kubectl-connect」の kubectl-connect の正体と、なぜ source で実行する必要があるのか、です。次の章から深掘りしていきます。
深掘り
3-1. kubectl-connect って AWS CLI なの?
AWS CLI のコマンドは aws で始まるので、見た目から kubectl-connect は AWS CLI ではないことが分かります。実体を CloudShell で確認してみます。
$ which kubectl-connect
/usr/local/bin/kubectl-connect
$ type kubectl-connect
kubectl-connect is /usr/local/bin/kubectl-connect
$ declare -f kubectl-connect
(出力なし)
実行したコマンドはそれぞれ次のような目的です。
| コマンド | 何を確認しているか |
|---|---|
which kubectl-connect |
PATH 上の実体ファイルパスを表示。シェル関数やエイリアスは表示されない |
type kubectl-connect |
コマンドの種類(alias / function / builtin / file)を判定。alias for ... や function と返れば実体は別 |
declare -f kubectl-connect |
シェル関数として定義されている場合、その関数本体(中身)を表示。何も出なければ関数ではない |
3 つを組み合わせると「PATH 上のファイルなのか、シェル関数なのか、エイリアスなのか」が一通り判別できます。今回の出力からは、/usr/local/bin/ に直置きされた実ファイルで、シェル関数でもエイリアスでもないことが分かりました。続けて cat で中身を見てみます。
$ cat /usr/local/bin/kubectl-connect
#!/usr/bin/env bash
# kubectl-connect
VERSION="1.0.0"
...
bash スクリプト でした。バージョンは 1.0.0、コメントにスクリプト名がそのまま書いてあります。
スクリプトの末尾に main "$@" があり、関数定義の集合体になっています。中で AWS CLI を呼ぶ箇所が次のように整理されていました。
aws_cli() {
if command -v timeout >/dev/null 2>&1; then
AWS_MAX_ATTEMPTS=1 timeout "${AWS_CLI_TIMEOUT}s" aws --no-cli-pager "$@"
else
AWS_MAX_ATTEMPTS=1 aws --no-cli-pager \
--cli-connect-timeout "$AWS_CLI_TIMEOUT" \
--cli-read-timeout "$AWS_CLI_TIMEOUT" "$@"
fi
}
kubectl-connect は AWS CLI を呼ぶラッパースクリプト でした。AWS CLI 本体ではなく、bash で書かれたスクリプトが、aws eks update-kubeconfig 等を裏で実行しています。
タイムアウト制御(デフォルト 10 秒)や AWS_MAX_ATTEMPTS=1(リトライ無し)が指定されているので、ボタンクリック後に長く待たされることはなさそうです。
kubectl-connect スクリプト全文
#!/usr/bin/env bash
# kubectl-connect
VERSION="1.0.0"
# Exit codes
E_USAGE=2 E_CLUSTER=3 E_DEPEND=4
# Command name - hardcoded to avoid $0 being "bash" when sourced
CMD_NAME="kubectl-connect"
# Note: Strict mode (set -euo pipefail) not used to prevent shell exit when sourced
# Error handling is done explicitly throughout the script instead
# Defaults
AWS_CLI_TIMEOUT="${AWS_CLI_TIMEOUT:-10}"
# Logging - Reset debug flag for each script run to prevent persistence when sourced
KUBECTL_CONNECT_DEBUG=false
_kc_log() { printf '%s\n' "$*" >&2; }
_kc_debug() {
if [ "$KUBECTL_CONNECT_DEBUG" = "true" ]; then
_kc_log "[DEBUG] $*"
fi
}
_kc_error() { _kc_log "$*"; }
# Detect if script is being sourced or executed
_is_sourced() {
[[ "${BASH_SOURCE[0]}" != "${0}" ]]
}
# Help
usage() {
echo "Usage: $CMD_NAME [options] [COMMAND|CLUSTER_NAME]"
echo ""
echo "Configure kubectl to connect to an Amazon EKS cluster."
echo ""
echo "Commands:"
echo " list List all configured clusters"
echo ""
echo "Arguments:"
echo " CLUSTER_NAME Name of the EKS cluster to connect to"
echo ""
echo "Options:"
echo " --region REGION AWS region where the cluster is located"
echo " --role-arn ARN ARN of IAM role to assume for cluster access"
echo " --alias ALIAS Alias for the kubeconfig context"
echo " --debug Enable verbose logging"
echo " --help Show this help message"
echo " --version Show version information"
echo ""
echo "Examples:"
echo " $CMD_NAME list"
echo " $CMD_NAME my-cluster"
echo " $CMD_NAME --region us-west-2 my-cluster"
echo " $CMD_NAME --role-arn arn:aws:iam::123456789012:role/EksRole my-cluster"
}
# Helper functions
show_version() {
echo "$CMD_NAME version $VERSION";
}
get_configured_clusters() {
[ -d ~/.kube ] || return 1
local configs=()
local _orig_nullglob
_orig_nullglob=$(shopt -p nullglob)
shopt -s nullglob
configs=(~/.kube/config-*)
eval "$_orig_nullglob" # restore
(( ${#configs[@]} )) || return 1
printf '%s\n' "${configs[@]}"
}
list_clusters() {
local all_configs
if ! all_configs=$(get_configured_clusters); then
echo "No clusters configured yet."
echo ""
echo "To configure a cluster, run:"
echo " source $CMD_NAME CLUSTER_NAME"
return 0
fi
echo "Configured clusters:"
while IFS= read -r config_file; do
if [ -n "$config_file" ]; then
local cluster_name
cluster_name=$(basename "$config_file" | sed 's/^config-//')
# Check if this is the currently active cluster
if [ "${KUBECONFIG:-}" = "$config_file" ]; then
echo " • $cluster_name [CURRENT]"
else
echo " • $cluster_name"
fi
fi
done <<< "$all_configs"
echo ""
echo "To switch to a cluster, run:"
echo " source $CMD_NAME CLUSTER_NAME"
}
aws_cli() {
if command -v timeout >/dev/null 2>&1; then
AWS_MAX_ATTEMPTS=1 timeout "${AWS_CLI_TIMEOUT}s" aws --no-cli-pager "$@"
else
AWS_MAX_ATTEMPTS=1 aws --no-cli-pager \
--cli-connect-timeout "$AWS_CLI_TIMEOUT" \
--cli-read-timeout "$AWS_CLI_TIMEOUT" "$@"
fi
}
show_first_cluster_tips() {
echo "🎯 Quick Tips:"
echo " • Switch clusters: source $CMD_NAME OTHER-CLUSTER"
echo " • List clusters that are currently configured: $CMD_NAME list"
echo " • Get help: $CMD_NAME --help"
echo ""
}
check_dependencies() {
local missing_deps=""
if ! command -v kubectl >/dev/null 2>&1; then
missing_deps="kubectl"
fi
if ! command -v aws >/dev/null 2>&1; then
missing_deps="${missing_deps:+$missing_deps }aws"
fi
if [ -n "$missing_deps" ]; then
_kc_error "Missing required dependencies: $missing_deps"
_kc_error "Please install the missing tools and ensure they are in your PATH."
return $E_DEPEND
fi
}
# Main function - contains all the main logic
main() {
# Parse arguments
local CLUSTER_NAME=""
local REGION=""
local ROLE_ARN=""
local ALIAS=""
local COMMAND=""
while [ $# -gt 0 ]; do
case "$1" in
--help|-h) usage; return 0 ;;
--version|-V) show_version; return 0 ;;
--debug) KUBECTL_CONNECT_DEBUG=true; shift ;;
--region)
if [ $# -lt 2 ]; then
_kc_error "--region requires a value"
return $E_USAGE
fi
REGION="$2"; shift 2 ;;
--role-arn)
if [ $# -lt 2 ]; then
_kc_error "--role-arn requires a value"
return $E_USAGE
fi
ROLE_ARN="$2"; shift 2 ;;
--alias)
if [ $# -lt 2 ]; then
_kc_error "--alias requires a value"
return $E_USAGE
fi
ALIAS="$2"; shift 2 ;;
list) COMMAND="list"; shift ;;
-*) _kc_error "Unknown option: $1"; usage; return $E_USAGE ;;
*)
if [ -z "$CLUSTER_NAME" ]; then
CLUSTER_NAME="$1"
shift
else
_kc_error "Multiple cluster names specified. Only one cluster name is allowed."
return $E_USAGE
fi
;;
esac
done
# Reject conflicting command + cluster name
if [ "$COMMAND" = "list" ] && [ -n "$CLUSTER_NAME" ]; then
_kc_error "Cannot specify both 'list' command and a cluster name."
return $E_USAGE
fi
# Handle list command
if [ "$COMMAND" = "list" ]; then
list_clusters
return 0
fi
# Validate required arguments for cluster connection
if [ -z "$CLUSTER_NAME" ]; then
_kc_error "Cluster name is required."
usage
return $E_USAGE
fi
if ! [[ "$CLUSTER_NAME" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
_kc_error "Invalid cluster name format: $CLUSTER_NAME (only alphanumeric, hyphens, and underscores allowed)"
return $E_USAGE
fi
# Basic input validation
if [ -n "$REGION" ]; then
if ! [[ "$REGION" =~ ^[a-z]{2}(-[a-z0-9]+)+-[0-9]+$ ]]; then
_kc_error "Invalid region format: $REGION (expected e.g. us-west-2, us-gov-west-1)"
return $E_USAGE
fi
fi
if [ -n "$ROLE_ARN" ]; then
if ! [[ "$ROLE_ARN" =~ ^arn: ]]; then
_kc_error "Invalid role ARN format: $ROLE_ARN (expected arn:...)"
return $E_USAGE
fi
fi
# Set alias if not provided
if [ -z "$ALIAS" ]; then
ALIAS="$CLUSTER_NAME"
elif ! [[ "$ALIAS" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
_kc_error "Invalid alias format: $ALIAS (only alphanumeric, hyphens, and underscores allowed)"
return $E_USAGE
fi
# Check dependencies for cluster operations
check_dependencies || return $?
# Create .kube directory if it doesn't exist
mkdir -p ~/.kube
local KUBECONFIG_PATH="$HOME/.kube/config-$CLUSTER_NAME"
# Check if cluster is already configured
local AWS_SUCCESS
if [ -f "$KUBECONFIG_PATH" ]; then
echo "Cluster $CLUSTER_NAME already configured, switching to existing configuration"
_kc_debug "Using existing kubeconfig: $KUBECONFIG_PATH"
AWS_SUCCESS=true
else
# Build the AWS EKS update-kubeconfig command
echo "Configuring kubectl for EKS cluster: $CLUSTER_NAME"
# Build command arguments as an array
local EKS_ARGS=(eks update-kubeconfig --name "$CLUSTER_NAME" --kubeconfig ~/.kube/config-"$CLUSTER_NAME" --alias "$ALIAS")
if [ -n "$REGION" ]; then
EKS_ARGS+=(--region "$REGION")
_kc_debug "Using region: $REGION"
fi
if [ -n "$ROLE_ARN" ]; then
EKS_ARGS+=(--role-arn "$ROLE_ARN")
_kc_debug "Using role ARN: $ROLE_ARN"
fi
# Execute the AWS CLI command
if aws_cli "${EKS_ARGS[@]}"; then
AWS_SUCCESS=true
else
AWS_SUCCESS=false
rm -f "$KUBECONFIG_PATH"
fi
fi
if [ "$AWS_SUCCESS" = "true" ]; then
echo "Successfully configured kubectl for cluster: $CLUSTER_NAME"
# Save current KUBECONFIG so we can restore on error
local OLD_KUBECONFIG="${KUBECONFIG:-}"
# Set the KUBECONFIG environment variable before testing connectivity
export KUBECONFIG="$KUBECONFIG_PATH"
echo "Environment configured. KUBECONFIG is set to: $KUBECONFIG_PATH"
# Warn if not sourced — exports won't persist in the caller's shell
if ! _is_sourced; then
_kc_log "⚠️ Script was executed, not sourced. KUBECONFIG and PS1 changes will not persist."
_kc_log "To activate in your current shell, run:"
_kc_log " source $CMD_NAME $CLUSTER_NAME"
_kc_log "Or manually export:"
_kc_log " export KUBECONFIG=$KUBECONFIG_PATH"
fi
# Get current context for use throughout the validation process
local CURRENT_CONTEXT
CURRENT_CONTEXT=$(kubectl config current-context 2>/dev/null || echo "")
# Test cluster connectivity
_kc_debug "Testing cluster connectivity with: kubectl cluster-info --request-timeout=10s"
local cluster_output
if ! cluster_output=$(kubectl cluster-info --request-timeout=10s 2>&1); then
_kc_error "⚠️ Configuration complete, but unable to connect to cluster $CLUSTER_NAME"
_kc_error "Current context: $CURRENT_CONTEXT"
echo "$cluster_output" >&2 # Show kubectl output for troubleshooting
_kc_error "This may be due to:"
_kc_error " • Network connectivity issues"
_kc_error " • Invalid cluster credentials or permissions"
_kc_error " • Cluster does not exist or is not accessible"
_kc_error " • AWS credentials are incorrect or expired"
if [ -n "$OLD_KUBECONFIG" ]; then export KUBECONFIG="$OLD_KUBECONFIG"; else unset KUBECONFIG; fi
return $E_CLUSTER
fi
echo "✅ Successfully connected to cluster $CLUSTER_NAME"
echo ""
# Verify we're connected to the correct cluster
if [ -z "$CURRENT_CONTEXT" ]; then
_kc_error "Failed to get current kubectl context"
if [ -n "$OLD_KUBECONFIG" ]; then export KUBECONFIG="$OLD_KUBECONFIG"; else unset KUBECONFIG; fi
return $E_CLUSTER
fi
# Check if current context matches our expected alias (warn, don't fail)
if [ "$CURRENT_CONTEXT" != "$ALIAS" ]; then
_kc_log "⚠️ Context name mismatch: expected '$ALIAS' but current context is '$CURRENT_CONTEXT'"
_kc_log "This can happen if the kubeconfig was previously created with a different alias."
fi
# Update the shell prompt to indicate the current cluster.
# Strip any previous kubectl-connect PS1 prefix to prevent stacking.
local clean_ps1="${PS1:-\$ }"
# Shortest-prefix match removes "[k8s: <anything>] " prefix
clean_ps1="${clean_ps1#\[k8s: *\] }"
export PS1="[k8s: $ALIAS] $clean_ps1"
# Check if this is the first cluster being configured
local all_configs
if all_configs=$(get_configured_clusters); then
local existing_configs=""
while IFS= read -r config_file; do
if [ -n "$config_file" ] && [ "$config_file" != "$KUBECONFIG_PATH" ]; then
existing_configs="${existing_configs}${config_file}"
fi
done <<< "$all_configs"
if [ -z "$existing_configs" ]; then
# This is the first cluster - show quick tips
show_first_cluster_tips
fi
else
# No other clusters configured - this is definitely the first one
show_first_cluster_tips
fi
else
_kc_error "Failed to configure kubectl for cluster: $CLUSTER_NAME"
_kc_error "Please check your AWS credentials, cluster name, and permissions."
return $E_CLUSTER
fi
}
# Execute main function with all arguments
main "$@"
3-2. kubeconfig 取得コマンドってこと?
答えは Yes、ですがそれ以上のこともやっています。スクリプトの主処理を読むと、次の 4 つを一気にやっていました。
1. クラスタごとに個別の kubeconfig を作る
local KUBECONFIG_PATH="$HOME/.kube/config-$CLUSTER_NAME"
...
EKS_ARGS=(eks update-kubeconfig --name "$CLUSTER_NAME" --kubeconfig ~/.kube/config-"$CLUSTER_NAME" --alias "$ALIAS")
aws_cli "${EKS_ARGS[@]}"
通常 aws eks update-kubeconfig は ~/.kube/config という単一ファイルに追記する挙動ですが、kubectl-connect は クラスタごとに別ファイルとして ~/.kube/config-<クラスタ名> に保存します。
メリット / デメリットを整理するとこんな感じですかね...
メリット:
- クラスタごとに kubeconfig が独立しているので、ファイル単位で削除や差し替えが簡単
- 複数のターミナルタブで違うクラスタを同時に開きたい場合、それぞれのタブで
KUBECONFIGを別ファイルに向けるだけで済む(context 切り替えが不要) - 1 つのクラスタの認証情報を更新しても、他のクラスタの kubeconfig は触らないので安全
- パーミッション(
chmod)をクラスタごとに制御できる - 「どのクラスタの設定が壊れた」を切り分けやすい
デメリット:
-
kubectl config get-contextsで全クラスタを一覧したいときは、KUBECONFIG=$HOME/.kube/config-A:$HOME/.kube/config-Bのようにコロン区切りで複数ファイルを束ねる必要がある - ArgoCD CLI / Helm / Lens など、
~/.kube/configを前提に動く周辺ツールがある場合、明示的にKUBECONFIGを指定しないと参照してくれない - クラスタ数が多いとファイル数も増える(10 クラスタなら 10 ファイル)
- 標準的な kubectl のチュートリアル類は単一
~/.kube/config前提で書かれていることが多く、初学者には違和感があるかも...
トレードオフはあるものの、「複数クラスタを行き来する開発者・SRE」にはメリットの方が大きい設計です。CloudShell のような短命の作業環境では、特に「タブごとに違うクラスタを開く」運用が活きます。
2. KUBECONFIG 環境変数を export する
export KUBECONFIG="$KUBECONFIG_PATH"
echo "Environment configured. KUBECONFIG is set to: $KUBECONFIG_PATH"
export した変数は通常、コマンドを単体実行するとサブシェルで完結してしまい、呼び出し元のシェルには届きません。これが source 必須の理由です。スクリプト内にも親切な警告メッセージが入っていました。
if ! _is_sourced; then
_kc_log "⚠️ Script was executed, not sourced. KUBECONFIG and PS1 changes will not persist."
_kc_log "To activate in your current shell, run:"
_kc_log " source $CMD_NAME $CLUSTER_NAME"
fi
_is_sourced 関数は次のように定義されていて、BASH_SOURCE[0] と $0 の比較で判定しています。
_is_sourced() {
[[ "${BASH_SOURCE[0]}" != "${0}" ]]
}
3. プロンプト(PS1)を書き換えて、現在のクラスタを表示する
local clean_ps1="${PS1:-\$ }"
clean_ps1="${clean_ps1#\[k8s: *\] }"
export PS1="[k8s: $ALIAS] $clean_ps1"
PS1 という名前を見て「PowerShell?」と思う方もいるかもしれませんが、これは bash / zsh などの Unix 系シェルの環境変数で、 PowerShell とは全くの別物です。PS は Prompt String の略で、PS1 はコマンド入力前に表示される 1 次プロンプト(よく見る $ や # の手前にある「あの文字列」)を定義します。
ちなみに PS2 は複数行入力時の継続プロンプト(>)、PS3 は select コマンドのプロンプト、PS4 は set -x でのトレース出力プロンプトと、4 種類定義されています。普段意識する機会は PS1 がほとんどです。
kubectl-connect はこの PS1 を書き換えて、接続後にプロンプトの先頭に [k8s: <クラスタ名>] を追加します。これが冒頭で見せた [k8s: appsignals-fargate] $ の正体です。複数クラスタを行き来するときに「自分が今どのクラスタを操作してるか」を視覚的に思い出せる工夫です。
clean_ps1="${clean_ps1#\[k8s: *\] }" は、すでに前回設定された [k8s: ...] プレフィックスを最短マッチで剥がしてから、新しいプレフィックスを付け直すロジック。クラスタ切り替えのたびに [k8s: A][k8s: B] $ のように積み重ならない配慮がされています。
4. cluster-info で接続性をテスト、失敗時は KUBECONFIG を rollback
local OLD_KUBECONFIG="${KUBECONFIG:-}"
export KUBECONFIG="$KUBECONFIG_PATH"
if ! cluster_output=$(kubectl cluster-info --request-timeout=10s 2>&1); then
_kc_error "⚠️ Configuration complete, but unable to connect to cluster $CLUSTER_NAME"
...
if [ -n "$OLD_KUBECONFIG" ]; then
export KUBECONFIG="$OLD_KUBECONFIG"
else
unset KUBECONFIG
fi
return $E_CLUSTER
fi
接続性が確認できない場合、KUBECONFIG を旧値に戻して安全側に倒します。「kubeconfig は更新されたけど実は到達できない」状態のまま放置されないように、ロールバック処理も入っています。
3-3. CloudShell 以外で使えるか?
CloudShell 特有の依存はない
スクリプトの依存はシンプルで、bash と AWS CLI と kubectl があれば動きます。あとは mkdir / cat / sed / shopt / timeout のような標準コマンドを使っているくらい。CloudShell 専用の認証情報や VPC 環境の自動立ち上げのような仕組みはスクリプト本体には現れていないので、AWS CLI が動く環境なら、スクリプトをコピーすれば原理的には動くはずです。
CloudShell の起動時設定にも仕掛けは無さそう
kubectl-connect がどこからロードされているのかも確認しました。
$ ls -la ~/.bashrc ~/.bash_profile ~/.profile
-rw-r--r--. 1 cloudshell-user cloudshell-user 141 Feb 18 16:14 /home/cloudshell-user/.bash_profile
-rw-r--r--. 1 cloudshell-user cloudshell-user 558 Feb 18 16:14 /home/cloudshell-user/.bashrc
$ grep -i kube ~/.bashrc
(出力なし)
$ ls /etc/profile.d/ | grep -i kube
(出力なし)
$ cat /etc/profile.d/*kube* 2>/dev/null
(出力なし)
~/.bashrc にも /etc/profile.d/ にも kube 関連の登録は無く、CloudShell 起動時に自動で何かが source されているわけでもありませんでした。kubectl-connect は PATH 上にスクリプトとして直置きされているだけです。
「Connect」ボタンの実体(推測)
以上を踏まえると、AWS Console の「接続」ボタンは、CloudShell に対して source kubectl-connect <cluster-name> というコマンド文字列を入力欄に流し込んで実行させているだけ、と思えます。CloudShell を単体起動して同じコマンドを手で打っても同じ結果になることとも整合します。
「接続」ボタンはコマンド入力の自動化、というだけで、裏で特殊な API が動いているわけではなさそうです。
ローカルで使うときの実装案
スクリプトを /usr/local/bin/ 等にコピーして、PATH を通す。それだけで動くはずです。CloudShell との差分は次の 2 点くらい:
-
aws sso login --profile <profile>が事前に必要(CloudShell では Console セッションがそのまま使われる) -
kubectl-connectはデフォルト region を~/.aws/configのregionから取りに行く(無ければ--regionで指定)
シェルログイン時に kubectl-connect を毎回タブ補完したい場合、~/.bashrc 等に PATH 追加コードを入れておけば良いでしょう。
OS ごとの注意点
kubectl-connect は bash スクリプトなので、bash が動く環境であれば原則どこでも動かせます。macOS は zsh デフォルトでも source で問題なく走るはずで、Linux も大抵のディストリビューションが bash 標準なのでそのまま動くと思います。本記事ではこの後 macOS 上で実機検証してみます。
Windows はネイティブ環境(cmd や PowerShell)では bash スクリプトが走らないため動きません。WSL2 を入れて Linux 同等の bash 環境にすれば普通に使える想定で、Git Bash や Cygwin のような bash 互換シェルでも動く可能性があります(ただし aws / kubectl のインストール場所がパスに乗っているかは確認しないとハマりそうです)。Windows ネイティブでどうしても使いたい場合は、スクリプトを PowerShell に翻訳するか、WSL2 を経由するのが現実的でしょう。
3-4. 実行前後で何が変わるかを確認
最後に、source kubectl-connect <cluster-name> を実行する前後の差分を取って締めます。
実行前
$ echo "KUBECONFIG=$KUBECONFIG"
KUBECONFIG=
$ ls -la ~/.kube/
total 8
drwxr-xr-x. 2 cloudshell-user cloudshell-user 4096 May 2 02:28 .
drwxrwxrwx. 7 cloudshell-user cloudshell-user 4096 May 1 16:45 ..
$ echo "PS1=$PS1"
PS1=\W $
KUBECONFIG は空、~/.kube/ ディレクトリは存在するが中身も空、PS1 は \W $ (カレントディレクトリ + $)というデフォルトのプロンプト。きれいな初期状態です。
実行後
source kubectl-connect appsignals-fargate を実行すると、ターミナルにスクリプトの出力が並び、プロンプトが書き換わります。
~ $ source kubectl-connect appsignals-fargate
Configuring kubectl for EKS cluster: appsignals-fargate
Added new context appsignals-fargate to /home/cloudshell-user/.kube/config-appsignals-fargate
Successfully configured kubectl for cluster: appsignals-fargate
Environment configured. KUBECONFIG is set to: /home/cloudshell-user/.kube/config-appsignals-fargate
✅ Successfully connected to cluster appsignals-fargate
🎯 Quick Tips:
• Switch clusters: source kubectl-connect OTHER-CLUSTER
• List clusters that are currently configured: kubectl-connect list
• Get help: kubectl-connect --help
[k8s: appsignals-fargate] ~ $
スクリプトが aws eks update-kubeconfig を呼び、KUBECONFIG を export し、cluster-info で接続性を確認し、Quick Tips を表示し、最後に PS1 を書き換えるまでが、たった 1 コマンドで終わっていることが見えます。差分を改めてまとめると次のとおり:
| 観点 | 実行前 | 実行後 |
|---|---|---|
KUBECONFIG |
空 | /home/cloudshell-user/.kube/config-appsignals-fargate |
~/.kube/ |
空 |
config-appsignals-fargate(クラスタ専用ファイル)が生成 |
PS1(プロンプト) |
\W $ |
[k8s: appsignals-fargate] \W $ |
kubectl での操作 |
コンテキスト未設定で動かない | クラスタ操作が即可能 |
source で実行する 1 行のコマンドで、ここまで状態が整うのが今回の機能でした。
念のため echo で KUBECONFIG と PS1 を取り直すと、確かに値が変化していました。
[k8s: appsignals-fargate] ~ $ echo "KUBECONFIG=$KUBECONFIG"
KUBECONFIG=/home/cloudshell-user/.kube/config-appsignals-fargate
[k8s: appsignals-fargate] ~ $ echo "PS1=$PS1"
PS1=[k8s: appsignals-fargate] \W $
[k8s: appsignals-fargate] ~ $ ls -la ~/.kube/
total 16
drwxr-xr-x. 3 cloudshell-user cloudshell-user 4096 May 2 03:05 .
drwxrwxrwx. 7 cloudshell-user cloudshell-user 4096 May 1 16:45 ..
drwxr-x---. 4 cloudshell-user cloudshell-user 4096 May 2 03:05 cache
-rw-------. 1 cloudshell-user cloudshell-user 2316 May 2 03:05 config-appsignals-fargate
~/.kube/cache/ は aws eks get-token の認証キャッシュ用ディレクトリで、初回 kubectl 実行時に自動生成されたようです。config-appsignals-fargate がこの実行で生成された kubeconfig 本体です。
生成された kubeconfig の中身
config-appsignals-fargate を cat して中身を確認すると、典型的な EKS 用の kubeconfig になっていました(証明書の base64 値は長いので省略)。
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...(省略)...LS0K
server: https://<CLUSTER_ENDPOINT_ID>.gr7.ap-northeast-1.eks.amazonaws.com
name: arn:aws:eks:ap-northeast-1:<AWS_ACCOUNT_ID>:cluster/appsignals-fargate
contexts:
- context:
cluster: arn:aws:eks:ap-northeast-1:<AWS_ACCOUNT_ID>:cluster/appsignals-fargate
user: arn:aws:eks:ap-northeast-1:<AWS_ACCOUNT_ID>:cluster/appsignals-fargate
name: appsignals-fargate
current-context: appsignals-fargate
kind: Config
preferences: {}
users:
- name: arn:aws:eks:ap-northeast-1:<AWS_ACCOUNT_ID>:cluster/appsignals-fargate
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-1
- eks
- get-token
- --cluster-name
- appsignals-fargate
- --output
- json
command: aws
注目したいのは users: セクションで、exec: プラグイン形式 になっている点です。kubectl がクラスタにアクセスするたびに aws eks get-token --cluster-name appsignals-fargate をローカルで実行し、得られたトークンで認証するという仕組みです。これは aws eks update-kubeconfig が生成する標準形式そのままで、kubectl-connect が独自形式に書き換えているわけではないことが分かります。
list と --help も使える
スクリプトには list サブコマンドも用意されていて、設定済みクラスタの一覧と現在の選択状態を表示できます。
[k8s: appsignals-fargate] ~ $ kubectl-connect list
Configured clusters:
• appsignals-fargate [CURRENT]
To switch to a cluster, run:
source kubectl-connect CLUSTER_NAME
--help を見るとオプションも整理されていて、--region / --role-arn / --alias / --debug / --version などが用意されています。
[k8s: appsignals-fargate] ~ $ kubectl-connect --help | head -20
Usage: kubectl-connect [options] [COMMAND|CLUSTER_NAME]
Configure kubectl to connect to an Amazon EKS cluster.
Commands:
list List all configured clusters
Arguments:
CLUSTER_NAME Name of the EKS cluster to connect to
Options:
--region REGION AWS region where the cluster is located
--role-arn ARN ARN of IAM role to assume for cluster access
--alias ALIAS Alias for the kubeconfig context
--debug Enable verbose logging
--help Show this help message
--version Show version information
Examples:
kubectl-connect list
--role-arn で別 IAM ロールを assume してクラスタに接続する、--alias で kubeconfig のコンテキスト名を別名にする、といった応用が効きます。複数アカウント / 複数チーム運用も想定されています。
実際にクラスタ操作してみる
ここまでで kubectl が使える状態になっているので、実際にコマンドを叩いて動作確認しました。
[k8s: appsignals-fargate] ~ $ kubectl get nodes
NAME STATUS ROLES AGE VERSION
fargate-ip-10-20-15-98.ap-northeast-1.compute.internal Ready <none> 21m v1.35.3-eks-d6694b8
fargate-ip-10-20-20-63.ap-northeast-1.compute.internal Ready <none> 21m v1.35.3-eks-d6694b8
fargate-ip-10-20-29-195.ap-northeast-1.compute.internal Ready <none> 20m v1.35.3-eks-d6694b8
[k8s: appsignals-fargate] ~ $ kubectl cluster-info
Kubernetes control plane is running at https://<CLUSTER_ENDPOINT_ID>.gr7.ap-northeast-1.eks.amazonaws.com
CoreDNS is running at https://<CLUSTER_ENDPOINT_ID>.gr7.ap-northeast-1.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
[k8s: appsignals-fargate] ~ $ kubectl get pods -A | head -15
NAMESPACE NAME READY STATUS RESTARTS AGE
amazon-cloudwatch amazon-cloudwatch-observability-controller-manager-fb9fbccfw2nr 1/1 Running 0 24m
kube-system coredns-67f98d8869-gwhtz 1/1 Running 0 25m
kube-system coredns-67f98d8869-wd8zj 1/1 Running 0 25m
kubectl get nodes で Fargate ノードが 3 つ出てきました。Fargate の場合、Pod ごとに 1 つの「マイクロ VM」が割り当てられて、それが Kubernetes 上では Node として見えるので、起動中の Pod 数だけ Node が並びます。今回は coredns が 2 Pod、amazon-cloudwatch-observability-controller-manager が 1 Pod 動いているので 3 ノード見えるという内訳です。
kubectl cluster-info の出力も普通に返ってきているので、クラスタへの認証 + API 呼び出しのパスが完全に通っていることが確認できました。
これで「Console の Connect ボタン → CloudShell 起動 → source kubectl-connect <cluster> 実行 → 即 kubectl 操作可能」という end-to-end のフローが、内部実装も含めて見える化できました。
3-5. Mac の zsh で動かしてみる
最後に、3-3 で残した宿題を実機検証します。「CloudShell 以外でも、ローカル Mac から source kubectl-connect <cluster> でクラスタ接続できるか」、しかも Mac のデフォルトシェルである zsh のまま試してみます。
手順
- CloudShell で
cat /usr/local/bin/kubectl-connectした結果を、ローカル Mac の任意のパスに保存(例:scripts/kubectl-connect) chmod +x scripts/kubectl-connect- AWS CLI と kubectl がインストール済み +
aws sso login --profile developmentで認証済み - zsh のまま
source実行
# Mac の bash バージョン(参考)
$ bash --version | head -1
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)
# 必要ツール確認
$ which aws && aws --version
/usr/local/bin/aws
aws-cli/2.15.42 Python/3.11.8 Darwin/25.3.0 exe/x86_64 prompt/off
$ which kubectl && kubectl version --client
/opt/homebrew/bin/kubectl
Client Version: v1.30.0
実行前
% echo "KUBECONFIG=$KUBECONFIG"
KUBECONFIG=
% ls -la ~/.kube/
drwxr-x--- 4 xxxxx.xxxxx staff 128 4月 27 2024 cache
-rw------- 1 xxxxx.xxxxx staff 4787 5月 1 19:55 config
% echo "PS1=$PS1"
PS1=%F{cyan}%~%f%F{yellow}${vcs_info_msg_0_}%f %#
KUBECONFIG は空、~/.kube/config は別の検証で作った既存ファイルが残っている状態、PS1 は zsh の典型的なカラー記法(%F{cyan} 等)。
実行
zsh のまま source してみます。
% export AWS_PROFILE=development
% source ./scripts/kubectl-connect appsignals-fargate
Configuring kubectl for EKS cluster: appsignals-fargate
Added new context appsignals-fargate to /Users/xxxxx.xxxxx/.kube/config-appsignals-fargate
Successfully configured kubectl for cluster: appsignals-fargate
Environment configured. KUBECONFIG is set to: /Users/xxxxx.xxxxx/.kube/config-appsignals-fargate
✅ Successfully connected to cluster appsignals-fargate
get_configured_clusters:5: command not found: shopt
get_configured_clusters:6: command not found: shopt
🎯 Quick Tips:
• Switch clusters: source kubectl-connect OTHER-CLUSTER
• List clusters that are currently configured: kubectl-connect list
• Get help: kubectl-connect --help
[k8s: appsignals-fargate] %
get_configured_clusters:5: command not found: shopt という警告が 2 行出ました。これは shopt が bash 専用の組み込みコマンドで zsh には存在しないために発生していますが、結論としてはこの警告は無視して大丈夫 です。理由は次の 3 つです。
- スクリプト冒頭にコメントで
Strict mode (set -euo pipefail) not used to prevent shell exit when sourcedと明記されているとおり、厳格モードを使っていない設計なので、コマンドが見つからなくてもスクリプト全体は止まらず先に進む - 警告が出ているのは
get_configured_clustersという補助関数内で、ここは「Quick Tips を表示するために、これまでに設定済みクラスタの一覧を取りに行く」だけの処理。kubeconfig 生成や KUBECONFIG export 等のメイン処理はそれより前のロジックで先に完了している - 関数内では
configs=(~/.kube/config-*)という glob 展開で kubeconfig を取りに行きますが、source 直前に~/.kube/config-appsignals-fargateが生成されているため、shopt -s nullglobが効かなくても結果はちゃんとマッチする(shoptの有無で挙動が変わらない)
実際、kubeconfig の生成・KUBECONFIG の export・cluster-info での接続性テスト・PS1 の書き換え・Quick Tips の表示まで、肝心な処理はすべて完了 しています。プロンプトもしっかり [k8s: appsignals-fargate] プレフィックス付きに変わりました。zsh のカラー記法(%F{cyan} など)もそのまま保持されているのが見えます。
警告そのものをよりきれいに消したい場合は、本節後半の「shopt 警告の正体」で実装上のヒントに触れます。
実行後の状態
[k8s: appsignals-fargate] % echo "KUBECONFIG=$KUBECONFIG"
KUBECONFIG=/Users/xxxxx.xxxxx/.kube/config-appsignals-fargate
[k8s: appsignals-fargate] % ls -la ~/.kube/
drwxr-x--- 4 xxxxx.xxxxx staff 128 4月 27 2024 cache
-rw------- 1 xxxxx.xxxxx staff 4787 5月 1 19:55 config
-rw------- 1 xxxxx.xxxxx staff 2380 5月 2 12:22 config-appsignals-fargate
[k8s: appsignals-fargate] % echo "PS1=$PS1"
PS1=[k8s: appsignals-fargate] %F{cyan}%~%f%F{yellow}${vcs_info_msg_0_}%f %#
[k8s: appsignals-fargate] % kubectl get nodes
NAME STATUS ROLES AGE VERSION
fargate-ip-10-20-15-98.ap-northeast-1.compute.internal Ready <none> 38m v1.35.3-eks-d6694b8
fargate-ip-10-20-20-63.ap-northeast-1.compute.internal Ready <none> 38m v1.35.3-eks-d6694b8
fargate-ip-10-20-29-195.ap-northeast-1.compute.internal Ready <none> 37m v1.35.3-eks-d6694b8
~/.kube/config-appsignals-fargate がきれいに新規作成されているのに対して、 既存の ~/.kube/config は一切触られていない のも見逃せません。クラスタごとに kubeconfig を分離する設計のおかげで、既存の他クラスタ設定を壊さずに新しいクラスタを増やせます。
kubectl get nodes の結果は CloudShell から実行したときと一致しています。Fargate ノード 3 つ、v1.35.3-eks-d6694b8 という同じバージョンが返ってきて、ローカル Mac から問題なくクラスタ操作ができることが確認できました。
kubectl cluster-info も期待どおり:
[k8s: appsignals-fargate] % kubectl cluster-info
Kubernetes control plane is running at https://<CLUSTER_ENDPOINT_ID>.gr7.ap-northeast-1.eks.amazonaws.com
CoreDNS is running at https://<CLUSTER_ENDPOINT_ID>.gr7.ap-northeast-1.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
エンドポイントは EKS API サーバそのものに到達していて、CoreDNS 経由のサービス DNS も応答してきます。ローカル kubectl と AWS CLI さえあれば、CloudShell の機能は丸ごとローカルに持ち込めることが確認できました。
list / --help は警告なしで動く
ちなみに kubectl-connect list を PATH を通したうえで実行すると、shopt 警告は出ませんでした。
[k8s: appsignals-fargate] % export PATH="/Users/xxxxx.xxxxx/Desktop/ApplicationSignals-fargate/scripts:$PATH"
[k8s: appsignals-fargate] % kubectl-connect list
Configured clusters:
• appsignals-fargate [CURRENT]
To switch to a cluster, run:
source kubectl-connect CLUSTER_NAME
[k8s: appsignals-fargate] % kubectl-connect --help | head -10
Usage: kubectl-connect [options] [COMMAND|CLUSTER_NAME]
Configure kubectl to connect to an Amazon EKS cluster.
Commands:
list List all configured clusters
Arguments:
CLUSTER_NAME Name of the EKS cluster to connect to
なぜ list だと警告が出ないのか?理由はシンプルで、スクリプトの実行モードが違うからです。
| 実行方法 | 動作するシェル | shopt の挙動 |
|---|---|---|
source ./kubectl-connect ...(zsh 上で) |
呼び出し元の zsh で実行 |
shopt は zsh に存在しない → 警告 |
kubectl-connect list(PATH 経由で実行) |
shebang の #!/usr/bin/env bash に従って 新しい bash プロセス で実行 |
bash なので shopt 動く → 警告なし |
つまり、source で実行する処理(kubeconfig 生成、KUBECONFIG export、PS1 書き換え)は呼び出し元のシェルに環境変数を反映する都合上、zsh から呼ぶと zsh で評価されてしまう。一方、サブコマンド系(list / --help / --version 等)は普通にコマンドとして PATH 経由で呼び出せば bash で実行されるので、警告が出ない、というわけです。
実用的にはこういう住み分けになります:
- 「クラスタへの接続」だけは
source必須なので zsh で警告 2 行が出る(無害) - 「クラスタ一覧」「ヘルプ」「バージョン確認」は普通のコマンド実行で OK、警告は出ない
普段の運用で使うコマンドのうち、警告が出るのは初回接続のときだけ、と理解しておけば気にならないレベルです。
shopt 警告の正体
警告のソースは get_configured_clusters 関数の冒頭です。
get_configured_clusters() {
[ -d ~/.kube ] || return 1
local configs=()
local _orig_nullglob
_orig_nullglob=$(shopt -p nullglob) # ← この行
shopt -s nullglob # ← この行
configs=(~/.kube/config-*)
...
}
shopt は bash 専用の組み込みコマンド で、zsh には同名のコマンドがありません(zsh では setopt を使う)。そのため、zsh から呼び出すと command not found のエラーが出ます。
ただし、スクリプトは set -euo pipefail のような厳格モードを意図的に使っていない(ファイル冒頭にもコメントで Strict mode (set -euo pipefail) not used to prevent shell exit when sourced と書かれている)ので、エラーが出ても処理は止まらず先に進みます。configs=(~/.kube/config-*) という glob 展開も、zsh のデフォルト挙動(マッチしないファイルがある場合はエラーになる)はあるものの、source 時点で ~/.kube/config-appsignals-fargate がすでに作られているため、結果としてマッチが成功します。
警告は出るものの、機能としては最後まで動き切ります。本格的に zsh ネイティブ対応するには shopt を setopt null_glob に書き換えれば良さそうですが、現状でも実用上は問題なく使えます。
「CloudShell 限定の機能」と思っていたものが、実はスクリプトをコピーすればローカルでも同じ体験ができるだけのものだった、というのが今回の検証の収穫です。既存の ~/.kube/config には触らない設計のおかげで、運用中のクラスタ設定が壊れる心配もありません。普段使いのターミナルでも [k8s: <クラスタ名>] プロンプトが手に入るので、複数クラスタを行き来する運用にそのまま流用できそうです。
おわりに
「Connect」ボタンの裏では、bash スクリプトが kubeconfig を生成し、環境変数を export し、プロンプトを書き換えて、接続性を確認する、という 4 ステップが走っていました。AWS Console の「Connect」ボタン自体は、このスクリプトを CloudShell に流し込むだけの薄い仕組みのようです。
実装が読めてしまうと、ローカル環境への持ち込みも視野に入ってきます。複数クラスタを行き来する運用で、~/.kube/config-<name> でファイル分離 + プロンプトでクラスタ表示、というパターンは普段の運用にも転用できそうです。


![source kubectl-connect 実行後の CloudShell 画面。出力 + プロンプトが [k8s: appsignals-fargate] に変化している](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F1276335%2F03667f0b-d6c3-4773-bccb-0af6af0eb30d.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=ceba0432e80f0938ddace86d8f7cc1b3)