この記事の前提
-
RunPodのアカウントがあり、Podを1台以上作ったことがある方を想定しています。Podの新規作成(
podFindAndDeployOnDemand)は扱いません -
Python 3.10 以降(
str | Noneという型の書き方を使っています。macOS標準の 3.9 で動かすならOptional[str]に読み替えてください) - 依存ライブラリは
requestsだけです - GraphQLそのものの入門は扱いません。ただしこの記事に出てくるクエリはコピーして使えば動くように書いています
- 最後の「おまけ」でSSHに触れますが、SSH鍵の作成とRunPodへの登録は済んでいる前提です
RunPodでGPUを借りて画像生成を回しています。使うときだけ立てて、終わったら落とす。これをブラウザのボタンではなくスクリプトからやりたくて、APIドキュメントを開きました。
そこで最初に手が止まりました。停止も再開も削除も、どのミューテーション(GraphQLで状態を変える操作のこと。RESTのPOST/DELETEに当たります)も podId を要求してきます。
(ちなみにこのページには作成 podFindAndDeployOnDemand・開始 podResume・停止 podStop は載っていますが、削除の podTerminate は出てきません。この記事では削除まで扱うので、そこは後ほど)
では、その podId はどこから持ってくるのか。
Podを削除して新しく作り直すと、IDは別物になります(停止して再開するだけなら同じIDのままです)。私は使い終わったPodをその都度削除しているので、翌日また同じ構成でデプロイしても、昨日のIDはもう通じません。メモ帳に控えても次のデプロイで無効になる。設定ファイルに書いても、書いた本人が更新を忘れる。「毎回変わる値を、どうやって手元で管理するか」——最初はそういう問題だと思っていました。
ところが、そもそも管理する必要がありませんでした。
結論:APIキーだけで「自分のPod一覧」が取れる
Pod IDは自分で管理するものではなく、使う直前にRunPodへ聞けばいい値でした。
RunPodのGraphQL APIには myself という入り口があります。
{ myself { pods { id name desiredStatus } } }
これだけで、そのAPIキーに紐づくアカウントのPodが全部返ってきます。IDを渡す必要はありません。APIキーが名刺の役割をしていて、差し出した時点で「あなたのPod」が特定できるからです。
REST APIに慣れていると面食らう点が2つあるので、先に触れておきます。波括弧の入れ子は、返ってくるJSONの形そのものです(myself の中の pods の中の各項目、という階層をそのまま書きます)。そして id name desiredStatus のように欲しい項目を自分で並べます——RESTのように「呼べば全項目入ったJSONが返る」のではなく、書かなかった項目は返ってきません。区切りはカンマではなく空白です。
なお、このクエリを打ち込む先は専用の画面ではありません。次の章で curl、そのあとPythonから送ります。
つまり手順はこうなります。
- APIキーでPod一覧を取る
- その中から対象を決める
- 決まったIDでstop / resume / terminateを呼ぶ
人間が覚えるのはAPIキーだけ。Pod IDは使い捨ての値として、使う直前に取りに行けば済みます。
事前に済ませておくこと(APIキーの発行)
本編に入る前に、1つだけ準備をお願いします。ここさえ終われば、以降の操作はすべてスクリプトから実行できます。
① コンソールでAPIキーを発行する
ログインするとこの設定ページが開くので、API Keys のセクションで作成します(左メニューの Settings からも辿れます)。
権限は All / Restricted / Read Only の3つから選びます。この記事のコードを全部動かすなら All を選んでください。
| 選択肢 | この記事でできること |
|---|---|
| All | 一覧・停止・再開・削除すべて(迷ったらこれ) |
| Restricted | APIごとに個別指定。None / Read/Write / Read Only から選ぶので、Podを操作する項目を Read/Write にする |
| Read Only | ⚠ 一覧を取るところまで。停止・再開・削除は通りません |
権限を絞りたい気持ちは分かりますが、Podの操作しかしないキーであれば All でも被害範囲は自分のPodだけです。まず動かしてみて、後から Restricted で締め直すほうが詰まりません。
⚠ キーの値が表示されるのは発行した瞬間だけです。 RunPod側はキーを保存していないので、後から見に行くことはできません。その場でコピーして、パスワード管理ツールなどに保管してください。無くしたら作り直しになります。
② 環境変数に入れる
まずは、いま開いているターミナルで試すだけなら1行です。
export RUNPOD_API_KEY="発行したキー"
⚠ この書き方はそのターミナルを閉じると消えます。 毎回使うなら、シェルの設定ファイルに書いておきます。
シェルの設定ファイルとは、ターミナルを開くたびに自動で読み込まれるファイルのことです。ホームディレクトリ(~ = /Users/あなたのユーザー名)に置かれた隠しファイルで、Finderでは既定で見えません(Command + Shift + . で表示できます)。
どのファイルを使うかは、シェルの種類で決まります。Macなら既定は zsh なので ~/.zshrc です(Catalina以降)。自分の環境を確かめるならこれで分かります。
echo $SHELL
# /bin/zsh と出れば → ~/.zshrc
# /bin/bash と出れば → ~/.bashrc
# zsh の場合(macOSの既定)
echo 'export RUNPOD_API_KEY="発行したキー"' >> ~/.zshrc
source ~/.zshrc
# bash の場合
echo 'export RUNPOD_API_KEY="発行したキー"' >> ~/.bashrc
source ~/.bashrc
source は「いま開いているターミナルにも反映する」ための一手間です。新しいタブを開き直しても同じことになります。
⚠ 設定ファイルに書くとキーが平文で残るので、他の人も使うマシンでは避けてください。その場合は、パスワード管理ツールから取り出して export する形にします。
⛔ そして、どの方法をとるにしてもコードやリポジトリには直接書かないでください。このキー1本で、あなたのPodを誰でも削除できてしまいます。
③ curlで1回試して、返ってくることを確かめる
curl -s https://api.runpod.io/graphql \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ myself { pods { id name desiredStatus } } }"}'
こう返ってきたら準備完了です。
{"data":{"myself":{"pods":[{"id":"abc123def456","name":"my-pod-20260808","desiredStatus":"RUNNING"}]}}}
この時点で、もうPod IDが手に入っています。 ここまで来て「わざわざ管理する必要はなかった」と分かりました。あとはこのリクエストをPythonに移していくだけです。
⚠ こう返ってきたら、キーが渡っていません
{"error":{}}
中身が空です。 「キーが無い」とも「権限が足りない」とも言ってくれないので、初見だと何が悪いのか見当がつきません。ほとんどの場合、原因は環境変数が入っていないことです。ターミナルを開き直して export が消えた、というのがいちばんよくある形です。
値そのものを画面に出さずに確認できます。
[ -n "$RUNPOD_API_KEY" ] && echo "設定あり" || echo "未設定"
「未設定」と出たら、②に戻ってもう一度 export してください。
なぜ、この準備だけ画面での操作になるのか
「IDを取りに行く話をしているのに、キーの発行はブラウザなのか」と思われたかもしれません。APIを呼ぶための鍵を、APIで作ることはできない——鍵を取りに行く操作にも鍵が要る、という鶏と卵の関係があるためです。実際、RunPodのREST APIにもキーを発行するエンドポイントはありません。
ただしこれは最初の1回だけです。キーはPodを作り直しても変わりません。 毎回振り直されるPod IDとは性質が違い、一度取れば据え置ける値だからこそ、事前準備として切り出せます。
この記事でやろうとしているのは、その切り分けです——据え置ける値だけを最初に用意し、毎回変わる値は取りに行く。
まず共通関数を作る(GraphQLへリクエストを送る部分)
このあとの操作すべてで使い回す部分です。依存は requests だけです。
import os
import sys
import requests
RUNPOD_GRAPHQL = "https://api.runpod.io/graphql"
def load_api_key() -> str:
key = os.environ.get("RUNPOD_API_KEY")
if key:
return key
print("エラー: 環境変数 RUNPOD_API_KEY を設定してください。")
sys.exit(1)
def graphql(api_key: str, query: str) -> dict:
resp = requests.post(
RUNPOD_GRAPHQL,
json={"query": query},
headers={"Authorization": f"Bearer {api_key}"},
timeout=15,
)
try:
result = resp.json()
except ValueError:
raise RuntimeError(
f"応答をJSONとして読めません (HTTP {resp.status_code}): {resp.text[:300]}"
)
if "data" not in result:
# さきほどの {"error":{}} がこれ。data ごと存在しないので、
# ここで止めておかないと呼び出し側が KeyError: 'data' で落ちる
raise RuntimeError(
f"APIキーが受け付けられませんでした (HTTP {resp.status_code}): {result}"
)
return result
小さな関数ですが、3か所に意図があります。
APIキーはURLではなくヘッダで送る。 RunPodのAPIはクエリ文字列での認証も受け付けますが、それをやると例外メッセージやシェルの履歴、プロキシのログにキーが平文で残ります。ヘッダに入れておけば、後でエラー内容を人に見せるときも気を使わずに済みます。
raise_for_status() を先に呼ばない。 RunPodは「GPUの空きがない」といった業務上のエラーを、HTTP 400 とレスポンスボディの errors 配列で返してくることがあります。ステータスコードだけで例外にすると、いちばん知りたい理由が入ったボディを捨ててしまいます。まずJSONとして読み、中身の判断は呼び出し側に任せます。
ただし data の欠落だけは、ここで止める。 準備のところで出てきた {"error":{}} には data キーそのものがありません。これを素通りさせると、このあと出てくる result["data"]["myself"] が軒並み KeyError: 'data' で落ちて、本当の原因(キーが渡っていない)とは似ても似つかないエラーだけが表示されます。data が無い=そもそも問い合わせが成立していない、なので分けて扱います。
なお、この記事で errors(複数形の配列)と書いているのはGraphQLが返す業務エラーで、準備のところで出た error(単数)とは別物です。前者はHTTP 200でも返ってきます——だからステータスコードだけでは成否を判定できません。
一覧を取る
def cmd_list(api_key: str):
result = graphql(api_key, """
{
myself {
pods {
id name desiredStatus
runtime { uptimeInSeconds gpus { gpuUtilPercent } }
}
}
}
""")
pods = result["data"]["myself"]["pods"]
if not pods:
print("Podが見つかりません。")
return
for p in pods:
uptime = f"{p['runtime']['uptimeInSeconds'] // 60}分" if p["runtime"] else ""
print(f"{p['id']:<20} {p['name']:<25} {p['desiredStatus']:<10} {uptime}")
runtime には「何分動いているか」「GPU使用率は何%か」が入りますが、停止中のPodでは中身が空になるのではなく、runtime ごと null で返ってきます。上のコードが if p["runtime"] else "" と存在チェックを挟んでいるのはこのためです。
これを省いて p["runtime"]["uptimeInSeconds"] と直接書くと、停止中のPodが一覧に1台でも混ざった瞬間に落ちます。
TypeError: 'NoneType' object is not subscriptable
None に対して ["uptimeInSeconds"] を取りに行った、という意味です。やっかいなのは、書いている間は一度も出ないところです。開発中はPodを起動したまま試すので runtime は必ず埋まっています。課金を止めるために夜Podを停止して、翌朝この一覧を見たときに初めて出ます(出しました)。
実行するとこう出ます。
abc123def456 my-pod-20260808 RUNNING 70分
Podが1台も無ければ Podが見つかりません。 とだけ出ます。これはエラーではなく、正常に問い合わせた結果0台だった、という意味です(キーが渡っていない場合は、この手前で APIキーが受け付けられませんでした と出て止まります)。
desiredStatus は RUNNING / EXITED のような文字列です。「いま動いているか」ではなく「どうあってほしいか」を表す値なので、起動要求を出した直後はまだ実体が追いついていないことがあります。
本題:ID解決を1つの関数に閉じ込める
一覧が取れれば、あとは「どれを操作するか」を決めるだけです。
def resolve_pod_id(api_key: str, pod_id: str | None) -> str:
"""pod_idが省略されたとき、Podが1台だけなら自動で選ぶ。"""
if pod_id:
return pod_id
result = graphql(api_key, "{ myself { pods { id name desiredStatus } } }")
pods = result["data"]["myself"]["pods"]
if not pods:
print("Podが1つもありません。")
sys.exit(1)
if len(pods) == 1:
p = pods[0]
print(f"対象Pod: {p['id']} ({p['name']})")
return p["id"]
print("複数のPodがあります。Pod IDを指定してください:")
for p in pods:
print(f" {p['id']} {p['name']} {p['desiredStatus']}")
sys.exit(1)
やっていることは単純です。1台ならそれを選ぶ。0台か2台以上なら止まる。
ここで「複数あったら稼働中のものを選ぶ」「いちばん新しいものを選ぶ」といった気の利いた処理を足したくなりますが、入れていません。推測で選んだ1台が外れだったとき、困るのは実行した後だからです。 迷ったら止まって人に聞く。呼び出し側から見れば、IDを渡せば指定どおりに動き、省略すれば1台構成の間だけ勝手に決めてくれる、という関係になります。
補足すると、私の最初の実装では0台のときも「複数のPodがあります」と表示していました。1台かどうかだけを見て、それ以外をまとめて同じ分岐に落としていたためです。実害は小さいものの、Podを全部消した状態で実行すると事実と違う案内が出ます。上のコードでは0台を先に分けています。分岐の設計で「1台」だけを特別扱いすると、0台が2台以上と同じ扱いに紛れ込みます。
stop / resume に載せる
ID解決を切り出しておくと、個々の操作は驚くほど短くなります。
def mutation_result(result: dict, field: str):
"""mutationの戻りから対象を取り出す。失敗していればNoneを返す。"""
if result.get("errors"):
print(f"エラー: {result['errors'][0]['message']}")
return None
pod = result["data"].get(field)
if pod is None:
print(f"エラー: {field} がnullを返しました。")
return None
return pod
def cmd_stop(api_key: str, pod_id: str | None = None):
pid = resolve_pod_id(api_key, pod_id)
result = graphql(api_key, f"""
mutation {{
podStop(input: {{ podId: "{pid}" }}) {{ id desiredStatus }}
}}
""")
pod = mutation_result(result, "podStop")
if pod is None:
return
print(f"停止を受け付けました: {pod['id']} → {pod['desiredStatus']}")
def cmd_start(api_key: str, pod_id: str | None = None):
pid = resolve_pod_id(api_key, pod_id)
result = graphql(api_key, f"""
mutation {{
podResume(input: {{ podId: "{pid}", gpuCount: 1 }}) {{ id desiredStatus }}
}}
""")
pod = mutation_result(result, "podResume")
if pod is None:
return
print(f"再開を受け付けました: {pod['id']} → {pod['desiredStatus']}")
まず、mutation の中の波括弧が二重になっている点について。これはGraphQLの記法ではなく、Pythonのf-stringの都合です。 f-string では {} が変数の差し込みに使われるので、波括弧そのものを出したいときは {{ と2つ重ねます。だから {pid} だけが一重で、GraphQLの構文としての波括弧が全部二重になっています。一覧を取るクエリ(f-stringではない)が一重だったのはそのためで、GraphQL側の書き方は変わっていません。
mutation というキーワードも、ここで初めて出てきます。読み取りだけのときは省略できて、状態を変える操作のときに付ける、という区別です。RESTのようにURLやHTTPメソッドで区別するのではなく、送る本文の側で宣言します。エンドポイントは読み取りも書き込みも https://api.runpod.io/graphql の1本で、常にPOSTです。
そして再開のところに落とし穴が2つあります。
podResume には gpuCount が要る。 停止時に何基だったかをAPIは覚えていてくれません。省略するとエラーになります。
GPUに空きがないと errors が返るか、data の中身が null で返る。 停止したPodを再開するのは「元の場所に戻る」操作に見えますが、実際にはその時点で空いているGPUを取り直しています。人気のGPUだと、止めている間に他の人に埋まっていることがあります。両方を見ているのはこのためで、素通りさせると TypeError: 'NoneType' object is not subscriptable が出て、本当の理由(空きがない)が見えなくなります。
この判定を mutation_result() に切り出したのは、同じ確認を stop 側でも省かないためです。最初は再開だけに書いていましたが、そうすると「停止が失敗したときだけ意味の分からないエラーで落ちる」という、揃っていない状態になります。
⚠ もう一点。出力を「停止しました」ではなく「停止を受け付けました」にしています。返ってくる desiredStatus は先ほど書いたとおり「どうあってほしいか」の値で、その時点で実体がそうなっている保証はありません。完了を断定するなら、この後の terminate でやっているように、改めて一覧を引いて確かめる必要があります。
⚠ 削除だけは、自動選択に頼らない
ここで、冒頭の宿題を回収します。削除の podTerminate は公式ドキュメントの Manage Pods ページに載っていません。 ただし呼べば動きます(この記事のコードは実際にこれで消しています)。載っていない以上いつ変わってもおかしくないので、後述するように成否は返り値ではなく削除後の一覧で確かめる書き方にしています。
そのうえで、terminate にだけは自分でルールを課しています。
削除するときは、たとえPodが1台でもIDを明示する。
# 停止・再開はこれでいい
python3 pod_manager.py stop
# 削除はこう書く
python3 pod_manager.py terminate abc123def456
理由は、自動選択が「いま自分のアカウントにPodが1台しかない」という状態への依存だからです。この前提は、思っているより簡単に崩れます。
- 別の端末やスクリプトが、同じアカウントで新しいPodを立てた
- 一覧を見て「これを消そう」と決めた後、実行するまでの間に構成が変わった
- 実は前のPodが残っていて、消したいのは新しいほうだった
停止なら、間違えても起動し直せば済みます。削除は戻せません。戻せない操作では、「消すつもりだったPod」と「実際に消えるPod」が同じであることを、引数で固定します。 便利さの適用範囲を、やり直せる操作までに限る、という線引きです。
実際、私は複数の作業を並行して走らせていた時期に、片方が終了処理としてPodを消し、もう片方がまだそれを使っていた、という場面に出くわしました。そのときは生成が終わった後だったので実害はありませんでしたが、「1台しかないから間違えようがない」という前提が成り立たない瞬間は確かにあります。
削除の前後でネットワークボリュームを数える
もう一つ、削除には安全弁を付けています。
RunPodのネットワークボリュームはPodとは別のリソースなので、Podを消しても巻き込まれません。ドキュメントにもそう書いてあります。ただ、そこには私のモデルデータが50GB入っています。「巻き込まれないはず」を、確認まで含めて自動化しました。
def cmd_terminate(api_key: str, pod_id: str | None = None):
# 削除だけは resolve_pod_id() を通さない。省略されたら自動で選ばず、止める。
if not pod_id:
print("エラー: terminate は Pod ID を省略できません。")
print(" python3 pod_manager.py list で確認してから、指定してください。")
sys.exit(1)
pid = pod_id
before = graphql(api_key, "{ myself { networkVolumes { id name size } } }")
volumes_before = before["data"]["myself"]["networkVolumes"]
graphql(api_key, f"""
mutation {{
podTerminate(input: {{ podId: "{pid}" }})
}}
""")
after = graphql(api_key, "{ myself { pods { id } networkVolumes { id name size } } }")
still_exists = any(p["id"] == pid for p in after["data"]["myself"]["pods"])
volumes_after = after["data"]["myself"]["networkVolumes"]
# ボリュームの確認は、Podの削除が成功したかどうかに関係なく先に行う
ids_after = {v["id"] for v in volumes_after}
missing = [v for v in volumes_before if v["id"] not in ids_after]
if missing:
print("⚠️ ネットワークボリュームが消えています。ただちに確認してください:")
for v in missing:
print(f" {v['name']} ({v['id']}, {v['size']}GB)")
elif volumes_after:
for v in volumes_after:
print(f"ボリューム残存確認OK: {v['name']} ({v['size']}GB)")
else:
print("ネットワークボリュームはありません(確認するものなし)。")
if still_exists:
print(f"エラー: Podがまだ存在しています: {pid}")
sys.exit(1)
print(f"ターミネートしました: {pid}")
ここでも myself が効いています。ボリュームの一覧もIDなしで引けるので、削除の前後で撮った2枚を突き合わせるだけで残存確認になります。ボリュームを1つも作っていない場合は、最後の行が出て終わります。
⚠ この順番には理由があります。ボリュームの確認を先に、Podの成否判定を後に置いています。 逆にすると、削除が失敗した経路で return した時点でボリュームの確認が丸ごと飛びます。確認したいのは「消えてほしくないものが無事か」であって、それは削除が成功したかどうかとは関係ありません。 安全弁のつもりで書いたコードが、いちばん様子のおかしい場面でだけ動かない、というのは避けたいところです。あわせて、失敗の経路は return(終了コード0)ではなく sys.exit(1) にしています——シェルから呼んだときに && で後続を止められます。
ついでに podTerminate の成否も、返り値ではなく削除後の一覧にそのPodが残っていないかで判定しています。podTerminate の戻り値は情報量が少なく、「送信できた」以上のことを教えてくれないからです。消えたかどうかは、消えた後の世界を見て確かめる。 これは他のAPIでも使える考え方だと思います。
コマンドとして呼べるようにする
ここまでの関数を1つのファイル(pod_manager.py)に並べて、最後にこれを足せば完成です。
def main():
if len(sys.argv) < 2:
print("使い方: python3 pod_manager.py <list|stop|start|terminate> [pod_id]")
print(" terminate だけは pod_id 必須(戻せない操作なので自動選択しない)")
sys.exit(1)
command = sys.argv[1]
pod_id = sys.argv[2] if len(sys.argv) > 2 else None
api_key = load_api_key()
if command == "list":
cmd_list(api_key)
elif command == "stop":
cmd_stop(api_key, pod_id)
elif command == "start":
cmd_start(api_key, pod_id)
elif command == "terminate":
cmd_terminate(api_key, pod_id)
else:
print(f"不明なコマンド: {command}")
print("使えるコマンド: list / stop / start / terminate")
sys.exit(1)
if __name__ == "__main__":
main()
サブコマンド名と関数名の対応はこうなります。⚠ 再開のコマンド名は start、GraphQL側のミューテーション名は podResume と、あえて揃えていません。「止めたものを動かす」ときに手が覚えているのは start のほうだからです。
| コマンド | 関数 | 中で呼ぶミューテーション |
|---|---|---|
list |
cmd_list() |
(読み取りのみ) |
stop |
cmd_stop() |
podStop |
start |
cmd_start() |
podResume |
terminate |
cmd_terminate() |
podTerminate |
おまけ:SSHの接続先も同じクエリで取れる
ここまでAPIの話をしてきましたが、Podの中に入って作業したいときもあります。私の場合、生成環境のプロセスを起動し直すためにSSHで入ります。
RunPodのSSH接続先はこの形です。
ssh <podHostId>@ssh.runpod.io -i ~/.ssh/id_ed25519
この podHostId も、myself から取れます。 別のIDに見えますが、中身は <Pod ID>-<接続用の識別子> という形で、Podに紐づいた値です。
{
myself {
pods {
id desiredStatus
machine { podHostId }
}
}
}
nm61usz9pa31cj-64410ffb のような文字列が返ってきます。これをそのままユーザー名の位置に置けば接続できます。
つまりダッシュボードの Connect → SSH タブを開いてコマンドをコピーしてくる作業も要りません。Podを作り直した直後、接続先が分からなくなったときも、このクエリ1本で組み立て直せます。私はこれに気づくまで、毎回ブラウザとターミナルを往復していました。
⚠ ひとつだけ落とし穴があります。このプロキシ経由のSSHは端末(PTY)を要求してくるので、コマンドを引数で渡す書き方は通りません。
# ❌ Your SSH client doesn't support PTY と言われる
ssh <podHostId>@ssh.runpod.io -i ~/.ssh/id_ed25519 "nvidia-smi"
# ⭕ 標準入力から流し込み、-tt を付ける
echo 'nvidia-smi; exit' | ssh -tt <podHostId>@ssh.runpod.io -i ~/.ssh/id_ed25519
<podHostId> は、上のクエリで返ってきた nm61usz9pa31cj-64410ffb のような文字列にそのまま置き換えてください。
スクリプトからPodの中のコマンドを実行したいときは、この形にしておくと安定します。
まとめ
| やりたいこと | 書き方 |
|---|---|
| 自分のPod一覧 |
{ myself { pods { id name desiredStatus } } } — IDは不要 |
| 停止 | podStop(input: { podId: ... }) |
| 再開 |
podResume(input: { podId: ..., gpuCount: 1 }) — gpuCount 必須 |
| 削除 |
podTerminate(input: { podId: ... }) — 成否は削除後の一覧で確認 |
クエリの書き方とは別に、この作業から学んだことが2つありました。
① 毎回変わる値は、覚えずに、使う直前に取りに行く
Pod IDは作り直すたびに変わります。こういう値をメモやシェル変数に控えると、それがいつ古くなったのかが分からなくなります。この記事では毎回 myself に聞きに行くことで、控えておく必要そのものを無くしました。
② 「自動で埋める」便利さは、やり直せる操作にだけ使う
「Podが1台なら自動で選ぶ」は、stop や start では快適です。しかし同じ仕組みを terminate に付けると、IDの指定し忘れがそのまま取り返しのつかない削除になります。同じ機能でも、失敗したときに元へ戻せるかどうかで、付けてよいかが変わります。
RunPodをスクリプトから回したいけれど「Pod IDはどこから取るのか」で止まっている方の、最初の一歩になれば嬉しいです。