Linode Cloud Service Samples
Linode cloud platform examples including Linode instances, Object Storage, Load Balancers, and Kubernetes
Key Facts
- Category
- Cloud Computing
- Items
- 4
- Format Families
- text, yaml
Sample Overview
Linode cloud platform examples including Linode instances, Object Storage, Load Balancers, and Kubernetes This sample set belongs to Cloud Computing and can be used to test related workflows inside Elysia Tools.
💻 Linode Instance Management python
🟢 simple
⭐⭐
Create, configure, and manage Linode instances using the Linode API v4
⏱️ 20 min
🏷️ linode, api, instances, infrastructure
Prerequisites:
Linode account, API token, Python knowledge
# Linode Instance Management
# Python - linode_manager.py + requirements.txt
import os
import time
import requests
import json
from datetime import datetime
class LinodeManager:
def __init__(self, personal_access_token):
self.token = personal_access_token
self.base_url = "https://api.linode.com/v4"
self.headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
"X-Filter": json.dumps({})
}
def _make_request(self, method, endpoint, data=None, params=None):
"""Make HTTP request to Linode API"""
url = f"{self.base_url}/{endpoint}"
if params:
# Remove X-Filter header when using params
headers = {k: v for k, v in self.headers.items() if k != "X-Filter"}
response = requests.request(method, url, headers=headers, json=data, params=params)
else:
response = requests.request(method, url, headers=self.headers, json=data)
response.raise_for_status()
return response.json()
def create_linode(self, label, region, type, image, root_pass=None, ssh_keys=None, tags=None):
"""Create a new Linode instance"""
data = {
"label": label,
"region": region,
"type": type,
"image": image,
"root_pass": root_pass or self._generate_password(),
"authorized_keys": ssh_keys or [],
"tags": tags or [],
"private_ip": True,
"backups_enabled": False
}
try:
result = self._make_request("POST", "linode/instances", data)
linode = result["data"]
print(f"✅ Linode '{label}' created successfully!")
print(f" ID: {linode['id']}")
print(f" Status: {linode['status']}")
print(f" IPv4: {linode['ipv4']}")
print(f" IPv6: {linode['ipv6']}")
print(f" Region: {linode['region']}")
print(f" Type: {linode['type']}")
print(f" Image: {linode['image']}")
if linode.get('tags'):
print(f" Tags: {', '.join(linode['tags'])}")
return linode
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error creating Linode: {error_detail['errors'][0]['reason']}")
return None
def list_linodes(self, tags=None):
"""List all Linode instances"""
params = {}
if tags:
params['tags'] = tags
try:
result = self._make_request("GET", "linode/instances", params=params)
linodes = result["data"]
print(f"📋 Found {len(linodes)} Linode instance(s):")
print("-" * 70)
for linode in linodes:
status_emoji = {
"running": "🟢",
"offline": "🔴",
"booting": "🟡",
"rebooting": "🔄",
"shutting_down": "🟠"
}.get(linode["status"], "⚪")
print(f"{status_emoji} {linode['label']} (ID: {linode['id']})")
print(f" Status: {linode['status']}")
print(f" IPv4: {', '.join(linode['ipv4'])}")
print(f" Region: {linode['region']}")
print(f" Type: {linode['type']}")
print(f" Image: {linode['image']}")
print(f" Created: {linode['created']}")
if linode.get('tags'):
print(f" Tags: {', '.join(linode['tags'])}")
print()
return linodes
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error listing Linodes: {error_detail['errors'][0]['reason']}")
return []
def get_linode_details(self, linode_id):
"""Get detailed information about a specific Linode"""
try:
result = self._make_request("GET", f"linode/instances/{linode_id}")
linode = result["data"]
print(f"ℹ️ Linode Details for '{linode['label']}':")
print("-" * 60)
print(f"ID: {linode['id']}")
print(f"Label: {linode['label']}")
print(f"Status: {linode['status']}")
print(f"Group: {linode.get('group', 'None')}")
print(f"Region: {linode['region']}")
print(f"Type: {linode['type']}")
print(f"Image: {linode['image']}")
print(f"Specs: {linode['specs']['disk']}GB disk, {linode['specs']['memory']}MB RAM, {linode['specs']['vcpus']} CPU")
print(f"Transfer: {linode['specs']['transfer']}GB/month")
print(f"IPv4: {', '.join(linode['ipv4'])}")
print(f"IPv6: {linode['ipv6']}")
print(f"Private IPv4: {linode['ipv4_private']}")
print(f"Created: {linode['created']}")
print(f"Updated: {linode['updated']}")
if linode.get('tags'):
print(f"Tags: {', '.join(linode['tags'])}")
return linode
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error getting Linode details: {error_detail['errors'][0]['reason']}")
return None
def power_on_linode(self, linode_id):
"""Power on a Linode"""
try:
result = self._make_request("POST", f"linode/instances/{linode_id}/boot")
action = result["data"]
print(f"🔌 Power on initiated for Linode {linode_id}")
print(f" Action ID: {action['id']}")
print(f" Status: {action['status']}")
return action
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error powering on Linode: {error_detail['errors'][0]['reason']}")
return None
def power_off_linode(self, linode_id):
"""Power off a Linode"""
try:
result = self._make_request("POST", f"linode/instances/{linode_id}/shutdown")
action = result["data"]
print(f"🔌 Power off initiated for Linode {linode_id}")
print(f" Action ID: {action['id']}")
print(f" Status: {action['status']}")
return action
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error powering off Linode: {error_detail['errors'][0]['reason']}")
return None
def reboot_linode(self, linode_id):
"""Reboot a Linode"""
try:
result = self._make_request("POST", f"linode/instances/{linode_id}/reboot")
action = result["data"]
print(f"🔄 Reboot initiated for Linode {linode_id}")
print(f" Action ID: {action['id']}")
print(f" Status: {action['status']}")
return action
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error rebooting Linode: {error_detail['errors'][0]['reason']}")
return None
def delete_linode(self, linode_id):
"""Delete a Linode (PERMANENT!)"""
# First get linode details for confirmation
details_result = self._make_request("GET", f"linode/instances/{linode_id}")
linode = details_result["data"]
print(f"⚠️ WARNING: You are about to delete Linode:")
print(f" Label: {linode['label']}")
print(f" ID: {linode['id']}")
print(f" Status: {linode['status']}")
print(f" IPv4: {', '.join(linode['ipv4'])}")
confirm = input("\nAre you sure you want to delete this Linode? This cannot be undone! (yes/no): ")
if confirm.lower() != 'yes':
print("❌ Deletion cancelled")
return False
try:
self._make_request("DELETE", f"linode/instances/{linode_id}")
print(f"✅ Linode {linode_id} deleted successfully")
return True
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error deleting Linode: {error_detail['errors'][0]['reason']}")
return False
def resize_linode(self, linode_id, new_type):
"""Resize a Linode to a different type"""
data = {"type": new_type}
try:
result = self._make_request("POST", f"linode/instances/{linode_id}/resize", data)
action = result["data"]
print(f"📏 Resize initiated for Linode {linode_id}")
print(f" New type: {new_type}")
print(f" Action ID: {action['id']}")
print(f" Status: {action['status']}")
return action
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error resizing Linode: {error_detail['errors'][0]['reason']}")
return None
def create_snapshot(self, linode_id, label):
"""Create a snapshot backup of a Linode"""
data = {"label": label}
try:
result = self._make_request("POST", f"linode/instances/{linode_id}/backups", data)
backup = result["data"]
print(f"📸 Snapshot '{label}' creation initiated")
print(f" Backup ID: {backup['id']}")
print(f" Status: {backup['status']}")
return backup
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error creating snapshot: {error_detail['errors'][0]['reason']}")
return None
def list_regions(self):
"""List available Linode regions"""
try:
result = self._make_request("GET", "regions")
regions = result["data"]
print(f"🌍 Available Linode Regions:")
print("-" * 40)
for region in regions:
print(f"{region['id']:6} - {region['label']} ({region['country']})")
if region.get('capabilities'):
caps = ', '.join(region['capabilities'][:3]) # Show first 3 capabilities
print(f" Capabilities: {caps}{'...' if len(region['capabilities']) > 3 else ''}")
print()
return regions
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error listing regions: {error_detail['errors'][0]['reason']}")
return []
def list_linode_types(self):
"""List available Linode types"""
try:
result = self._make_request("GET", "linode/types")
types = result["data"]
print(f"💻 Available Linode Types:")
print("-" * 70)
for linode_type in types:
price_monthly = next((p['monthly'] for p in linode_type['price'] if p['label'] == 'Monthly'), 0)
print(f"{linode_type['id']:20} - ${price_monthly:.2f}/month")
print(f"{'':20} {linode_type['label']}")
print(f"{'':20} {linode_type['vcpus']} CPU, {linode_type['memory']}MB RAM, {linode_type['disk']}GB disk")
print(f"{'':20} Transfer: {linode_type['transfer']}GB/month")
print()
return types
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error listing Linode types: {error_detail['errors'][0]['reason']}")
return []
def list_available_images(self):
"""List available Linode images"""
try:
result = self._make_request("GET", "images")
images = result["data"]
print(f"🖼️ Available Linode Images:")
print("-" * 70)
# Filter out deprecated and custom images
available_images = [img for img in images if img['status'] == 'available' and not img['deprecated']]
for image in available_images[:20]: # Show first 20 images
print(f"{image['id']:30} - {image['label']}")
print(f"{'':30} Vendor: {image.get('vendor', 'Unknown')}")
print(f"{'':30} Size: {image.get('size', 'Unknown')}MB")
print()
if len(available_images) > 20:
print(f"... and {len(available_images) - 20} more images")
return images
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error listing images: {error_detail['errors'][0]['reason']}")
return []
def get_action_status(self, action_id):
"""Check the status of an action"""
try:
result = self._make_request("GET", f"account/actions/{action_id}")
action = result["data"]
status_emoji = {
"pending": "⏳",
"running": "🔄",
"finished": "✅",
"failed": "❌",
"notification": "📢"
}.get(action['status'], "⚪")
print(f"{status_emoji} Action {action_id} Status: {action['status']}")
print(f" Type: {action['type']}")
print(f" Started: {action['started']}")
print(f" Completed: {action.get('completed', 'N/A')}")
print(f" Percent Complete: {action.get('percent_complete', 0)}%")
if action.get('entity'):
print(f" Entity: {action['entity']['label']} (ID: {action['entity']['id']})")
return action
except requests.exceptions.HTTPError as e:
error_detail = e.response.json()
print(f"❌ Error getting action status: {error_detail['errors'][0]['reason']}")
return None
def _generate_password(self, length=16):
"""Generate a random password"""
import random
import string
chars = string.ascii_letters + string.digits + "!@#$%^&*"
password = ''.join(random.choice(chars) for _ in range(length))
return password
# Example usage
if __name__ == "__main__":
# Get API token from environment variable
api_token = os.environ.get('LINODE_API_TOKEN')
if not api_token:
print("❌ Please set LINODE_API_TOKEN environment variable")
exit(1)
# Initialize manager
manager = LinodeManager(api_token)
# Example workflow
try:
print("🚀 Linode Management Tool")
print("=" * 50)
# List available regions, types, and images
print("\n1. Available Regions:")
manager.list_regions()
print("\n2. Available Linode Types:")
manager.list_linode_types()
print("\n3. Available Images:")
manager.list_available_images()
# Example: Create a Linode (uncomment to use)
# print("\n4. Creating a new Linode...")
# linode = manager.create_linode(
# label="my-test-server",
# region="us-east", # Newark
# type="g6-nanode-1", # $5/month
# image="linode/ubuntu22.04", # Ubuntu 22.04
# tags=["test", "automation"]
# )
# List all Linodes
print("\n5. Current Linodes:")
manager.list_linodes()
except KeyboardInterrupt:
print("\n\n👋 Goodbye!")
except Exception as e:
print(f"\n❌ Unexpected error: {e}")
# requirements.txt
"""
requests>=2.31.0
python-dotenv>=1.0.0
"""
💻 Object Storage Integration python
🟡 intermediate
⭐⭐⭐
Manage files and objects in Linode Object Storage using Python and AWS S3 SDK
⏱️ 30 min
🏷️ linode, object storage, s3, files, buckets
Prerequisites:
Linode account, Object Storage access keys, Python knowledge
# Linode Object Storage Manager
# Python - object_storage_manager.py + requirements.txt
import os
import mimetypes
import json
from datetime import datetime, timedelta
from boto3 import Session
from botocore.client import Config
import argparse
class LinodeObjectStorage:
def __init__(self, access_key, secret_key, region='us-east-1'):
"""Initialize Linode Object Storage client"""
# Linode Object Storage is S3-compatible
self.session = Session()
self.client = self.session.client(
's3',
endpoint_url=f'https://{region}.linodeobjects.com',
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
config=Config(signature_version='s3v4'),
region_name=region
)
self.region = region
def create_bucket(self, bucket_name, private=False):
"""Create a new bucket"""
try:
if private:
# Create private bucket
self.client.create_bucket(
Bucket=bucket_name,
ACL='private'
)
else:
# Create public bucket (default)
self.client.create_bucket(Bucket=bucket_name)
print(f"✅ Bucket '{bucket_name}' created successfully")
print(f" Endpoint: https://{bucket_name}.{self.region}.linodeobjects.com")
print(f" Access: {'Private' if private else 'Public'}")
return True
except Exception as e:
print(f"❌ Error creating bucket: {str(e)}")
return False
def list_buckets(self):
"""List all buckets"""
try:
response = self.client.list_buckets()
buckets = response['Buckets']
print(f"📦 Found {len(buckets)} bucket(s):")
print("-" * 70)
for bucket in buckets:
print(f"📁 {bucket['Name']}")
print(f" Created: {bucket['CreationDate'].strftime('%Y-%m-%d %H:%M:%S')}")
print(f" URL: https://{bucket['Name']}.{self.region}.linodeobjects.com")
print()
return buckets
except Exception as e:
print(f"❌ Error listing buckets: {str(e)}")
return []
def delete_bucket(self, bucket_name, force=False):
"""Delete a bucket (must be empty unless forced)"""
if force:
# First delete all objects in the bucket
print(f"🗑️ Emptying bucket '{bucket_name}'...")
self._empty_bucket(bucket_name)
try:
self.client.delete_bucket(Bucket=bucket_name)
print(f"✅ Bucket '{bucket_name}' deleted successfully")
return True
except Exception as e:
print(f"❌ Error deleting bucket: {str(e)}")
return False
def _empty_bucket(self, bucket_name):
"""Delete all objects in a bucket"""
try:
# List all objects
response = self.client.list_objects_v2(Bucket=bucket_name)
objects = response.get('Contents', [])
if objects:
# Delete all objects
delete_keys = [{'Key': obj['Key']} for obj in objects]
self.client.delete_objects(Bucket=bucket_name, Delete={'Objects': delete_keys})
print(f" Deleted {len(objects)} objects")
# Handle versions if versioning is enabled
response = self.client.list_object_versions(Bucket=bucket_name)
versions = response.get('Versions', []) + response.get('DeleteMarkers', [])
if versions:
delete_keys = []
for version in versions:
delete_keys.append({
'Key': version['Key'],
'VersionId': version.get('VersionId')
})
self.client.delete_objects(Bucket=bucket_name, Delete={'Objects': delete_keys})
print(f" Deleted {len(versions)} versioned objects")
except Exception as e:
print(f"❌ Error emptying bucket: {str(e)}")
def upload_file(self, file_path, bucket_name, object_key=None, public=True, metadata=None):
"""Upload a file to Object Storage"""
if not os.path.exists(file_path):
print(f"❌ File not found: {file_path}")
return None
# Generate object key if not provided
if not object_key:
object_key = os.path.basename(file_path)
try:
# Determine content type
content_type, _ = mimetypes.guess_type(file_path)
if not content_type:
content_type = 'application/octet-stream'
# Prepare extra args
extra_args = {
'ContentType': content_type,
'Metadata': metadata or {}
}
# Set ACL
if public:
extra_args['ACL'] = 'public-read'
# Upload file
print(f"📤 Uploading '{file_path}' to '{bucket_name}/{object_key}'...")
self.client.upload_file(
file_path,
bucket_name,
object_key,
ExtraArgs=extra_args
)
file_url = f"https://{bucket_name}.{self.region}.linodeobjects.com/{object_key}"
print(f"✅ File uploaded successfully!")
print(f" URL: {file_url}")
print(f" Size: {os.path.getsize(file_path)} bytes")
print(f" Type: {content_type}")
return {
'bucket': bucket_name,
'key': object_key,
'url': file_url,
'size': os.path.getsize(file_path),
'type': content_type
}
except Exception as e:
print(f"❌ Error uploading file: {str(e)}")
return None
def download_file(self, bucket_name, object_key, local_path=None):
"""Download a file from Object Storage"""
try:
if not local_path:
local_path = os.path.basename(object_key)
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(local_path), exist_ok=True)
print(f"📥 Downloading '{bucket_name}/{object_key}' to '{local_path}'...")
self.client.download_file(bucket_name, object_key, local_path)
print(f"✅ File downloaded successfully!")
print(f" Path: {local_path}")
print(f" Size: {os.path.getsize(local_path)} bytes")
return local_path
except Exception as e:
print(f"❌ Error downloading file: {str(e)}")
return None
def list_objects(self, bucket_name, prefix='', max_keys=100):
"""List objects in a bucket"""
try:
paginator = self.client.get_paginator('list_objects_v2')
print(f"📋 Objects in bucket '{bucket_name}':")
if prefix:
print(f" Prefix: {prefix}")
print("-" * 80)
objects = []
count = 0
for page in paginator.paginate(Bucket=bucket_name, Prefix=prefix, PaginationConfig={'MaxItems': max_keys}):
for obj in page.get('Contents', []):
count += 1
objects.append(obj)
# Format size
size = self._format_size(obj['Size'])
print(f"📄 {obj['Key']}")
print(f" Size: {size}")
print(f" Last Modified: {obj['LastModified'].strftime('%Y-%m-%d %H:%M:%S')}")
print(f" ETag: {obj['ETag']}")
# Generate public URL
public_url = f"https://{bucket_name}.{self.region}.linodeobjects.com/{obj['Key']}"
print(f" URL: {public_url}")
print()
print(f"📊 Total objects shown: {count}")
return objects
except Exception as e:
print(f"❌ Error listing objects: {str(e)}")
return []
def delete_object(self, bucket_name, object_key):
"""Delete an object from a bucket"""
try:
self.client.delete_object(Bucket=bucket_name, Key=object_key)
print(f"🗑️ Object '{object_key}' deleted successfully")
return True
except Exception as e:
print(f"❌ Error deleting object: {str(e)}")
return False
def generate_presigned_url(self, bucket_name, object_key, expiration=3600):
"""Generate a presigned URL for temporary access"""
try:
url = self.client.generate_presigned_url(
'get_object',
Params={'Bucket': bucket_name, 'Key': object_key},
ExpiresIn=expiration
)
expires_at = datetime.now() + timedelta(seconds=expiration)
print(f"🔗 Presigned URL generated:")
print(f" Object: {object_key}")
print(f" URL: {url}")
print(f" Expires: {expires_at.strftime('%Y-%m-%d %H:%M:%S')}")
print(f" Duration: {expiration} seconds")
return url
except Exception as e:
print(f"❌ Error generating presigned URL: {str(e)}")
return None
def sync_directory(self, local_dir, bucket_name, prefix='', delete=False, exclude_patterns=None):
"""Sync a local directory to Object Storage"""
try:
print(f"🔄 Starting sync from '{local_dir}' to '{bucket_name}'...")
uploaded = 0
updated = 0
deleted = 0
errors = 0
# Get existing objects in bucket
existing_objects = {}
if prefix:
existing_objects = {obj['Key']: obj for obj in self.list_objects(bucket_name, prefix)}
else:
existing_objects = {obj['Key']: obj for obj in self.list_objects(bucket_name)}
# Walk local directory
for root, dirs, files in os.walk(local_dir):
for file in files:
# Check exclude patterns
if exclude_patterns:
should_exclude = False
for pattern in exclude_patterns:
if pattern in file:
should_exclude = True
break
if should_exclude:
continue
local_path = os.path.join(root, file)
relative_path = os.path.relpath(local_path, local_dir).replace('\\', '/')
object_key = f"{prefix}/{relative_path}" if prefix else relative_path
# Check if file needs to be uploaded
needs_upload = True
if object_key in existing_objects:
local_mtime = datetime.fromtimestamp(os.path.getmtime(local_path))
remote_mtime = existing_objects[object_key]['LastModified'].replace(tzinfo=None)
if local_mtime <= remote_mtime:
needs_upload = False
print(f"⏭️ Skipping up-to-date: {object_key}")
else:
print(f"🔄 Updating: {object_key}")
else:
print(f"📤 Uploading: {object_key}")
if needs_upload:
metadata = {
'local_path': local_path,
'sync_time': datetime.now().isoformat()
}
result = self.upload_file(local_path, bucket_name, object_key, public=False, metadata=metadata)
if result:
if object_key in existing_objects:
updated += 1
else:
uploaded += 1
else:
errors += 1
# Delete remote objects that no longer exist locally (if delete=True)
if delete:
local_files = set()
for root, dirs, files in os.walk(local_dir):
for file in files:
relative_path = os.path.relpath(os.path.join(root, file), local_dir).replace('\\', '/')
object_key = f"{prefix}/{relative_path}" if prefix else relative_path
local_files.add(object_key)
for object_key in existing_objects:
if object_key not in local_files:
if self.delete_object(bucket_name, object_key):
deleted += 1
else:
errors += 1
print(f"\n✅ Sync completed:")
print(f" Uploaded: {uploaded}")
print(f" Updated: {updated}")
print(f" Deleted: {deleted}")
print(f" Errors: {errors}")
return {
'uploaded': uploaded,
'updated': updated,
'deleted': deleted,
'errors': errors
}
except Exception as e:
print(f"❌ Error syncing directory: {str(e)}")
return None
def set_bucket_website(self, bucket_name, index_file='index.html', error_file='error.html'):
"""Configure bucket for static website hosting"""
try:
website_config = {
'ErrorDocument': {'Key': error_file},
'IndexDocument': {'Suffix': index_file}
}
self.client.put_bucket_website(
Bucket=bucket_name,
WebsiteConfiguration=website_config
)
# Set bucket policy for public read access
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": f"arn:aws:s3:::{bucket_name}/*"
}
]
}
self.client.put_bucket_policy(
Bucket=bucket_name,
Policy=json.dumps(policy)
)
website_url = f"http://{bucket_name}.website-{self.region}.linodeobjects.com"
print(f"🌐 Static website configured for bucket '{bucket_name}'")
print(f" Website URL: {website_url}")
print(f" Index file: {index_file}")
print(f" Error file: {error_file}")
return website_url
except Exception as e:
print(f"❌ Error configuring website: {str(e)}")
return None
def _format_size(self, size_bytes):
"""Format file size in human readable format"""
if size_bytes == 0:
return "0B"
size_names = ["B", "KB", "MB", "GB", "TB"]
i = 0
size = float(size_bytes)
while size >= 1024.0 and i < len(size_names) - 1:
size /= 1024.0
i += 1
return f"{size:.2f}{size_names[i]}"
def main():
"""Command-line interface"""
parser = argparse.ArgumentParser(description='Linode Object Storage Manager')
parser.add_argument('command', choices=[
'create-bucket', 'list-buckets', 'delete-bucket',
'upload', 'download', 'list-objects', 'delete-object',
'presigned-url', 'sync', 'website'
])
parser.add_argument('--bucket', required=False, help='Bucket name')
parser.add_argument('--file', required=False, help='File path')
parser.add_argument('--key', required=False, help='Object key')
parser.add_argument('--local-path', required=False, help='Local file path')
parser.add_argument('--directory', required=False, help='Local directory path')
parser.add_argument('--prefix', required=False, default='', help='Object prefix')
parser.add_argument('--public', action='store_true', help='Make object public')
parser.add_argument('--private', action='store_true', help='Make bucket private')
parser.add_argument('--force', action='store_true', help='Force delete bucket')
parser.add_argument('--delete', action='store_true', help='Delete remote files when syncing')
parser.add_argument('--exclude', required=False, nargs='*', help='Exclude patterns')
parser.add_argument('--expiration', type=int, default=3600, help='Presigned URL expiration (seconds)')
args = parser.parse_args()
# Load credentials from environment
access_key = os.environ.get('LINODE_ACCESS_KEY')
secret_key = os.environ.get('LINODE_SECRET_KEY')
region = os.environ.get('LINODE_OBJECT_REGION', 'us-east-1')
if not access_key or not secret_key:
print("❌ Please set LINODE_ACCESS_KEY and LINODE_SECRET_KEY environment variables")
return
storage = LinodeObjectStorage(access_key, secret_key, region)
# Execute command
try:
if args.command == 'create-bucket':
if not args.bucket:
print("❌ Bucket name is required")
return
storage.create_bucket(args.bucket, args.private)
elif args.command == 'list-buckets':
storage.list_buckets()
elif args.command == 'delete-bucket':
if not args.bucket:
print("❌ Bucket name is required")
return
storage.delete_bucket(args.bucket, args.force)
elif args.command == 'upload':
if not args.bucket or not args.file:
print("❌ Bucket name and file path are required")
return
storage.upload_file(args.file, args.bucket, args.key, args.public)
elif args.command == 'download':
if not args.bucket or not args.key:
print("❌ Bucket name and object key are required")
return
storage.download_file(args.bucket, args.key, args.local_path)
elif args.command == 'list-objects':
if not args.bucket:
print("❌ Bucket name is required")
return
storage.list_objects(args.bucket, args.prefix)
elif args.command == 'delete-object':
if not args.bucket or not args.key:
print("❌ Bucket name and object key are required")
return
storage.delete_object(args.bucket, args.key)
elif args.command == 'presigned-url':
if not args.bucket or not args.key:
print("❌ Bucket name and object key are required")
return
storage.generate_presigned_url(args.bucket, args.key, args.expiration)
elif args.command == 'sync':
if not args.bucket or not args.directory:
print("❌ Bucket name and directory path are required")
return
storage.sync_directory(args.directory, args.bucket, args.prefix, args.delete, args.exclude)
elif args.command == 'website':
if not args.bucket:
print("❌ Bucket name is required")
return
storage.set_bucket_website(args.bucket)
except KeyboardInterrupt:
print("\n\n👋 Goodbye!")
except Exception as e:
print(f"❌ Error: {str(e)}")
if __name__ == "__main__":
main()
# requirements.txt
"""
boto3>=1.29.0
python-dotenv>=1.0.0
mimetypes>=1.0
"""
💻 Load Balancer Configuration text
🟡 intermediate
⭐⭐⭐
Set up and manage Linode Load Balancers for high availability applications
⏱️ 35 min
🏷️ linode, load balancer, terraform, infrastructure, ha
Prerequisites:
Linode account, Terraform knowledge, Load Balancer service, SSH key
# Linode Load Balancer Configuration
# Using Terraform for infrastructure as code
# terraform/main.tf
terraform {
required_providers {
linode = {
source = "linode/linode"
version = "~> 2.0"
}
}
required_version = ">= 1.0"
}
provider "linode" {
token = var.linode_token
}
# Variables
variable "linode_token" {
description = "Linode API token"
type = string
sensitive = true
}
variable "region" {
description = "Linode region"
type = string
default = "us-east"
}
variable "app_label" {
description = "Label prefix for resources"
type = string
default = "myapp"
}
# Create backend Linodes
resource "linode_instance" "web_servers" {
count = 3
label = "${var.app_label}-web-${count.index}"
image = "linode/ubuntu22.04"
region = var.region
type = "g6-standard-2"
authorized_keys = [var.ssh_public_key]
root_pass = random_password.root_password.result
tags = ["web", "production"]
# User data script for web server setup
user_data = templatefile("${path.module}/cloud-init/web-server.sh", {
hostname = "${var.app_label}-web-${count.index}"
})
private_ip = true
# Wait for SSH before completing
connection {
type = "ssh"
user = "root"
host = self.ip_address
timeout = "2m"
}
}
# Create database Linode
resource "linode_instance" "database" {
label = "${var.app_label}-database"
image = "linode/ubuntu22.04"
region = var.region
type = "g6-standard-2"
authorized_keys = [var.ssh_public_key]
root_pass = random_password.root_password.result
tags = ["database", "production"]
# User data script for database setup
user_data = templatefile("${path.module}/cloud-init/database.sh", {
hostname = "${var.app_label}-database"
})
private_ip = true
}
# Create Load Balancer
resource "linode_loadbalancer" "main" {
label = "${var.app_label}-lb"
region = var.region
# Protocol configuration
protocol = "https"
# HTTPS configuration
port = 443
# Algorithm
algorithm = "roundrobin"
# Session stickiness
session_stickiness = "none"
# Health check
health_check {
protocol = "http"
path = "/health"
port = 80
check_interval = 10
response_timeout = 5
healthy_threshold = 2
unhealthy_threshold = 3
}
# SSL certificate (can also use linode_certificate resource)
ssl_cert = <<-EOT
${var.ssl_certificate}
EOT
ssl_key = <<-EOT
${var.ssl_private_key}
EOT
tags = ["loadbalancer", "production"]
}
# Attach backend Linodes to Load Balancer
resource "linode_nodebalancer_node" "web_servers" {
count = length(linode_instance.web_servers)
nodebalancer_id = linode_loadbalancer.main.id
label = "${var.app_label}-web-${count.index}"
address = linode_instance.web_servers[count.index].ip_address
port = 80
weight = 100
mode = "accept"
}
# Backend service configuration for port 80 redirect
resource "linode_nodebalancer_config" "http_redirect" {
nodebalancer_id = linode_loadbalancer.main.id
port = 80
protocol = "http"
# Redirect HTTP to HTTPS
algorithm = "roundrobin"
health_check {
protocol = "http"
path = "/health"
port = 80
check_interval = 10
response_timeout = 5
healthy_threshold = 2
unhealthy_threshold = 3
}
# SSL offloading - redirect to HTTPS
ssl_cert = ""
ssl_key = ""
rules {
hostname = "yourdomain.com"
path = "/*"
service = linode_loadbalancer_config.https.id
priority = 1
}
tags = ["redirect", "production"]
}
# HTTPS backend configuration
resource "linode_nodebalancer_config" "https" {
nodebalancer_id = linode_loadbalancer.main.id
port = 443
protocol = "https"
algorithm = "roundrobin"
health_check {
protocol = "http"
path = "/health"
port = 80
check_interval = 10
response_timeout = 5
healthy_threshold = 2
unhealthy_threshold = 3
}
tags = ["https", "production"]
}
# Cloud-init scripts directory
# terraform/cloud-init/web-server.sh
#!/bin/bash
# Cloud-init script for web servers
# Set hostname
hostname="${hostname}"
echo "127.0.0.1 ${hostname}" >> /etc/hosts
echo "${hostname}" > /etc/hostname
# Update system
apt-get update
apt-get upgrade -y
# Install Nginx and Node.js
apt-get install -y nginx curl
# Install Node.js 18
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs
# Create app directory
mkdir -p /var/www/app
cd /var/www/app
# Create simple Express.js app
cat > app.js << 'EOF'
const express = require('express');
const app = express();
const port = 80;
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
hostname: require('os').hostname(),
timestamp: new Date().toISOString()
});
});
app.get('/', (req, res) => {
res.send('Hello from web server!');
});
app.listen(port, '0.0.0.0', () => {
console.log(`Server running on port ${port}`);
});
EOF
# Create package.json
cat > package.json << 'EOF'
{
"name": "web-app",
"version": "1.0.0",
"main": "app.js",
"dependencies": {
"express": "^4.18.0"
},
"scripts": {
"start": "node app.js"
}
}
EOF
# Install dependencies
npm install
# Configure Nginx as reverse proxy
cat > /etc/nginx/sites-available/default << 'EOF'
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://localhost:80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
EOF
# Enable and start services
systemctl enable nginx
systemctl start nginx
# Create systemd service for app
cat > /etc/systemd/system/webapp.service << 'EOF'
[Unit]
Description=Web Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable webapp
systemctl start webapp
# Configure firewall (if UFW is available)
if command -v ufw >/dev/null 2>&1; then
ufw --force enable
ufw allow ssh
ufw allow 80
ufw allow 443
fi
echo "Web server setup complete!"
# terraform/cloud-init/database.sh
#!/bin/bash
# Cloud-init script for database server
# Set hostname
hostname="${hostname}"
echo "127.0.0.1 ${hostname}" >> /etc/hosts
echo "${hostname}" > /etc/hostname
# Update system
apt-get update
apt-get upgrade -y
# Install PostgreSQL 14
apt-get install -y postgresql postgresql-contrib
# Configure PostgreSQL
# Allow connections from Linode private network
cat >> /etc/postgresql/14/main/postgresql.conf << 'EOF'
listen_addresses = '*'
EOF
# Configure pg_hba.conf for private network access
cat >> /etc/postgresql/14/main/pg_hba.conf << 'EOF'
# Allow private network connections
host all all 10.0.0.0/8 md5
EOF
# Create database and user
sudo -u postgres psql << 'EOF'
CREATE DATABASE myapp;
CREATE USER myappuser WITH PASSWORD 'secretpassword';
GRANT ALL PRIVILEGES ON DATABASE myapp TO myappuser;
ALTER USER myappuser CREATEDB;
EOF
# Enable and start PostgreSQL
systemctl enable postgresql
systemctl restart postgresql
# Configure firewall
if command -v ufw >/dev/null 2>&1; then
ufw --force enable
ufw allow ssh
ufw allow 5432
fi
echo "Database server setup complete!"
# terraform/variables.tf
variable "ssh_public_key" {
description = "SSH public key for Linode instances"
type = string
}
variable "ssl_certificate" {
description = "SSL certificate for HTTPS"
type = string
}
variable "ssl_private_key" {
description = "SSL private key for HTTPS"
type = string
sensitive = true
}
# terraform/outputs.tf
output "loadbalancer_ip" {
description = "Load Balancer IPv4 address"
value = linode_loadbalancer.main.ipv4
}
output "loadbalancer_hostname" {
description = "Load Balancer hostname"
value = linode_loadbalancer.main.hostname
}
output "web_server_ips" {
description = "Web server IP addresses"
value = linode_instance.web_servers.*.ip_address
}
output "database_ip" {
description = "Database server IP address"
value = linode_instance.database.ip_address
}
# Manual CLI commands for Load Balancer management
"""
# List load balancers
linode-cli load-balancers list
# Create load balancer
linode-cli load-balancers create \
--label "my-lb" \
--region us-east \
--protocol https \
--port 443 \
--algorithm roundrobin \
--check-interval 10 \
--check-timeout 5 \
--check-path /health \
--check_attempts 3
# Create backend configuration
linode-cli load-balancers config-create 12345 \
--protocol https \
--port 443 \
--algorithm roundrobin
# Add node to load balancer
linode-cli load-balancers node-create 12345 \
--label "node-1" \
--address 192.168.1.100 \
--port 80 \
--weight 100
# Update SSL certificate
linode-cli load-balancers update 12345 \
--ssl-cert /path/to/cert.pem \
--ssl-key /path/to/key.pem
# Get load balancer details
linode-cli load-balancers view 12345
# Delete load balancer
linode-cli load-balancers delete 12345
"""
💻 LKE Kubernetes Deployment text
🔴 complex
⭐⭐⭐⭐
Deploy and manage containerized applications on Linode Kubernetes Engine (LKE)
⏱️ 45 min
🏷️ linode, kubernetes, lke, containers, k8s
Prerequisites:
Linode account, LKE enabled, kubectl installed, Docker knowledge
# Linode Kubernetes Engine (LKE) Deployment
# Complete Kubernetes application deployment
# 1. Create LKE cluster using Linode CLI or Terraform
# terraform/lke-cluster.tf
resource "linode_lke_cluster" "main" {
label = "production-cluster"
k8s_version = "1.28"
region = "us-east"
tags = ["production", "k8s"]
}
# Node pool configuration
resource "linode_lke_node_pool" "main_nodes" {
cluster_id = linode_lke_cluster.main.id
type = "g6-standard-2"
count = 3
autoscaler {
min = 1
max = 5
}
}
# Additional node pool for specific workloads
resource "linode_lke_node_pool" "workers" {
cluster_id = linode_lke_cluster.main.id
type = "g6-standard-4"
count = 2
tags = ["workers", "high-memory"]
}
# 2. Kubernetes manifests for application deployment
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
name: production
environment: production
---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
NODE_ENV: "production"
LOG_LEVEL: "info"
REDIS_HOST: "redis-service"
REDIS_PORT: "6379"
DB_HOST: "postgresql-service"
DB_PORT: "5432"
DB_NAME: "myapp"
API_BASE_URL: "https://api.myapp.com"
---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
data:
# Base64 encoded values
db-password: c2VjdXJlZGF0YWJhc2VwYXNzd29yZA== # securedatabasepassword
jwt-secret: c3VwZXJzZWNyZXRqd3R0b2tlbg== # supersecretjwttoken
api-key: c3VwZXJzZWNyZXRhcGlrZXk= # supersecretapikey
redis-password: cmVkaXNwYXNzd29yZA== # redispassword
---
# k8s/storage.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: production
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: linode-block-storage
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-pvc
namespace: production
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: linode-block-storage
---
# k8s/postgresql.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgresql
namespace: production
spec:
serviceName: postgresql-service
replicas: 1
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- name: postgresql
image: postgres:15
env:
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: app-config
key: DB_NAME
- name: POSTGRES_USER
value: myappuser
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
exec:
command:
- pg_isready
- -U
- myappuser
- -d
- myapp
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- myappuser
- -d
- myapp
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
storageClassName: linode-block-storage
---
# k8s/postgresql-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgresql-service
namespace: production
spec:
selector:
app: postgresql
ports:
- port: 5432
targetPort: 5432
type: ClusterIP
---
# k8s/redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
command:
- redis-server
- --requirepass
- "$(REDIS_PASSWORD)"
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: redis-password
ports:
- containerPort: 6379
volumeMounts:
- name: redis-storage
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "250m"
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: redis-storage
persistentVolumeClaim:
claimName: redis-pvc
---
# k8s/redis-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: production
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
type: ClusterIP
---
# k8s/application.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
labels:
app: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: your-registry/web-app:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secrets
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
---
# k8s/application-service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-app-service
namespace: production
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- yourdomain.com
- api.yourdomain.com
secretName: app-tls
rules:
- host: yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app-service
port:
number: 80
- host: api.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app-service
port:
number: 80
---
# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 60
- type: Pods
value: 2
periodSeconds: 60
selectPolicy: Max
---
# k8s/vpa.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: web-app
maxAllowed:
cpu: 1
memory: 1Gi
minAllowed:
cpu: 100m
memory: 128Mi
---
# k8s/network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: web-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
- podSelector:
matchLabels:
app: postgresql
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 3000
egress:
- to:
- podSelector:
matchLabels:
app: postgresql
ports:
- protocol: TCP
port: 5432
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
- to: []
ports:
- protocol: TCP
port: 53
- protocol: UDP
port: 53
- protocol: TCP
port: 443
---
# k8s/poddisruptionbudget.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app: web-app
---
# k8s/cronjob-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
namespace: production
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: pg-backup
image: postgres:15
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
command:
- /bin/bash
- -c
- |
pg_dump -h postgresql-service -U myappuser -d myapp | gzip > /backup/backup-$(date +%Y%m%d-%H%M%S).sql.gz
volumeMounts:
- name: backup-storage
mountPath: /backup
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc
restartPolicy: OnFailure
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
---
# k8s/monitoring.yaml
apiVersion: v1
kind: ServiceMonitor
metadata:
name: web-app-metrics
namespace: production
labels:
app: web-app
spec:
selector:
matchLabels:
app: web-app
endpoints:
- port: metrics
interval: 30s
path: /metrics
---
# k8s/security.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
namespace: production
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
# 3. Deployment scripts
# scripts/deploy.sh
#!/bin/bash
set -e
# Configure kubectl to connect to LKE cluster
LINODE_API_TOKEN="${LINODE_API_TOKEN}"
CLUSTER_ID="12345"
# Get kubeconfig
linode-cli lke kubeconfig-view $CLUSTER_ID > ~/.kube/config-lke
export KUBECONFIG=~/.kube/config-lke
# Create namespace
kubectl create namespace production --dry-run=client -o yaml | kubectl apply -f -
# Apply all manifests
echo "Applying Kubernetes manifests..."
kubectl apply -f k8s/
# Wait for deployments
echo "Waiting for deployments to be ready..."
kubectl rollout status deployment/web-app -n production --timeout=300s
kubectl rollout status deployment/postgresql -n production --timeout=300s
kubectl rollout status deployment/redis -n production --timeout=300s
# Check pod status
echo "Checking pod status..."
kubectl get pods -n production
echo "Deployment completed successfully!"
# 4. Cleanup script
# scripts/cleanup.sh
#!/bin/bash
set -e
echo "Cleaning up Kubernetes resources..."
kubectl delete -f k8s/ --ignore-not-found=true
echo "Cleaning up namespace..."
kubectl delete namespace production --ignore-not-found=true
echo "Cleanup completed!"
# 5. CLI commands for LKE management
"""
# Create LKE cluster
linode-cli lke cluster-create \
--label production-cluster \
--region us-east \
--k8s-version 1.28 \
--node-pools.count 3 \
--node-pools.type g6-standard-2 \
--node-pools.autoscaler.min 1 \
--node-pools.autoscaler.max 5
# List clusters
linode-cli lke clusters list
# Get kubeconfig
linode-cli lke kubeconfig-view <cluster-id> > ~/.kube/config-lke
# Add additional node pool
linode-cli lke nodepool-add <cluster-id> \
--type g6-standard-4 \
--count 2 \
--tags workers,high-memory
# Scale node pool
linode-cli lke nodepool-update <cluster-id> <nodepool-id> \
--count 5
# Recycle nodes
linode-cli lke node-recycle <cluster-id> <node-id>
# Delete cluster
linode-cli lke cluster-delete <cluster-id>
"""