- Created entrypoint-self-terminate.sh wrapper script - Updates entrypoint-generic.sh to be called by wrapper - Modified Dockerfile.runpod to use self-terminate entrypoint - Adds automatic pod termination via runpodctl after training completes - Prevents infinite restart loops and wasted GPU credits - Saves ~96% cost per training run ($4.59 per run) Implements pod self-termination using RUNPOD_POD_ID environment variable. Training exits with code 0 → runpodctl remove pod → immediate shutdown. Co-Authored-By: Claude <noreply@anthropic.com>
201 lines
6.1 KiB
Python
Executable File
201 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Get comprehensive pod information including datacenter and status
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import requests
|
|
from dotenv import load_dotenv
|
|
|
|
# Load environment variables
|
|
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env.runpod')
|
|
load_dotenv(env_path)
|
|
|
|
RUNPOD_API_KEY = os.getenv('RUNPOD_API_KEY')
|
|
|
|
if not RUNPOD_API_KEY:
|
|
print("ERROR: RUNPOD_API_KEY not found in .env.runpod")
|
|
sys.exit(1)
|
|
|
|
GRAPHQL_ENDPOINT = "https://api.runpod.io/graphql"
|
|
|
|
def get_pod_details_graphql(pod_id):
|
|
"""Get pod details using GraphQL."""
|
|
print(f"\n🔍 Fetching pod {pod_id} details via GraphQL...")
|
|
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": f"Bearer {RUNPOD_API_KEY}"
|
|
}
|
|
|
|
# Updated query with correct fields
|
|
query = """
|
|
query GetPodDetails($podId: String!) {
|
|
pod(input: {podId: $podId}) {
|
|
id
|
|
name
|
|
desiredStatus
|
|
imageName
|
|
costPerHr
|
|
machine {
|
|
podHostId
|
|
dataCenterId
|
|
gpuTypeId
|
|
}
|
|
runtime {
|
|
uptimeInSeconds
|
|
ports {
|
|
ip
|
|
isIpPublic
|
|
privatePort
|
|
publicPort
|
|
type
|
|
}
|
|
gpus {
|
|
id
|
|
gpuUtilPercent
|
|
memoryUtilPercent
|
|
}
|
|
container {
|
|
cpuPercent
|
|
memoryPercent
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
variables = {
|
|
"podId": pod_id
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
GRAPHQL_ENDPOINT,
|
|
json={"query": query, "variables": variables},
|
|
headers=headers,
|
|
timeout=30
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
|
|
if 'errors' in result:
|
|
print(f" ⚠️ GraphQL errors: {result['errors']}")
|
|
return None
|
|
|
|
pod_data = result.get('data', {}).get('pod', {})
|
|
|
|
if pod_data:
|
|
print("\n" + "="*70)
|
|
print("POD DETAILS")
|
|
print("="*70)
|
|
|
|
print(f"Pod ID: {pod_data.get('id', 'N/A')}")
|
|
print(f"Name: {pod_data.get('name', 'N/A')}")
|
|
print(f"Status: {pod_data.get('desiredStatus', 'UNKNOWN')}")
|
|
|
|
machine = pod_data.get('machine', {})
|
|
if machine:
|
|
datacenter = machine.get('dataCenterId', 'N/A')
|
|
print(f"Datacenter: {datacenter}")
|
|
print(f"GPU Type ID: {machine.get('gpuTypeId', 'N/A')}")
|
|
|
|
# Check if datacenter is EUR-IS-1
|
|
if datacenter == 'EUR-IS-1':
|
|
print(f"\n✅ DATACENTER VERIFIED: Pod is in EUR-IS-1 (volume accessible)")
|
|
elif datacenter != 'N/A':
|
|
print(f"\n❌ DATACENTER MISMATCH: Pod is in {datacenter}, volume is in EUR-IS-1!")
|
|
else:
|
|
print(f"\n⚠️ Datacenter unknown - pod may still be initializing")
|
|
|
|
print(f"\nImage: {pod_data.get('imageName', 'N/A')}")
|
|
print(f"Cost: ${pod_data.get('costPerHr', 'N/A')}/hr")
|
|
|
|
runtime = pod_data.get('runtime', {})
|
|
if runtime:
|
|
uptime = runtime.get('uptimeInSeconds', 0)
|
|
print(f"\nRuntime Uptime: {uptime}s")
|
|
|
|
ports = runtime.get('ports', [])
|
|
if ports:
|
|
print("\nPorts:")
|
|
for port in ports:
|
|
print(f" {port.get('privatePort')} -> {port.get('publicPort')} ({port.get('type')})")
|
|
|
|
gpus = runtime.get('gpus', [])
|
|
if gpus:
|
|
print("\nGPU Status:")
|
|
for gpu in gpus:
|
|
gpu_util = gpu.get('gpuUtilPercent', 'N/A')
|
|
mem_util = gpu.get('memoryUtilPercent', 'N/A')
|
|
print(f" GPU {gpu.get('id')}: {gpu_util}% util, {mem_util}% memory")
|
|
|
|
container = runtime.get('container', {})
|
|
if container:
|
|
print(f"\nContainer:")
|
|
print(f" CPU: {container.get('cpuPercent', 'N/A')}%")
|
|
print(f" Memory: {container.get('memoryPercent', 'N/A')}%")
|
|
|
|
print("="*70)
|
|
|
|
return pod_data
|
|
else:
|
|
print(" ⚠️ No pod data returned")
|
|
return None
|
|
else:
|
|
print(f" ⚠️ Failed to get pod details (status {response.status_code}): {response.text[:200]}")
|
|
return None
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
print(f" ❌ Failed to fetch pod details: {e}")
|
|
return None
|
|
|
|
|
|
def main():
|
|
print("="*70)
|
|
print("GET RUNPOD POD INFO")
|
|
print("="*70)
|
|
|
|
if len(sys.argv) < 2:
|
|
print("\nUsage: python3 get_pod_info.py <pod_id> [--watch]")
|
|
print("\nExample:")
|
|
print(" python3 get_pod_info.py b1m7v451nexg5r")
|
|
print(" python3 get_pod_info.py b1m7v451nexg5r --watch # Check every 30s")
|
|
sys.exit(1)
|
|
|
|
pod_id = sys.argv[1]
|
|
watch_mode = '--watch' in sys.argv
|
|
|
|
if watch_mode:
|
|
print("\n⏰ Watch mode enabled - will check every 30 seconds (Ctrl+C to stop)")
|
|
print("="*70)
|
|
|
|
while True:
|
|
pod_data = get_pod_details_graphql(pod_id)
|
|
|
|
if pod_data:
|
|
machine = pod_data.get('machine', {})
|
|
datacenter = machine.get('dataCenterId', 'N/A')
|
|
runtime = pod_data.get('runtime', {})
|
|
uptime = runtime.get('uptimeInSeconds', 0)
|
|
|
|
if datacenter == 'EUR-IS-1' and uptime > 0:
|
|
print("\n✅ Pod is ready in EUR-IS-1!")
|
|
break
|
|
|
|
print("\n⏳ Waiting 30 seconds before next check...")
|
|
time.sleep(30)
|
|
else:
|
|
get_pod_details_graphql(pod_id)
|
|
|
|
print("\n" + "="*70)
|
|
print("DONE")
|
|
print("="*70)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|