DigitalOcean 示例
DigitalOcean云平台示例,包括Droplets、Kubernetes、Spaces和App Platform
💻 Droplet管理API python
🟢 simple
⭐⭐
使用API创建、管理和监控DigitalOcean Droplets
⏱️ 15 min
🏷️ digitalocean, api, droplets, infrastructure
Prerequisites:
DigitalOcean account, API token, Python knowledge
# DigitalOcean Droplet Management
# Python - droplet_manager.py + requirements.txt
import os
import time
import requests
from datetime import datetime
import json
class DigitalOceanManager:
def __init__(self, api_token):
self.api_token = api_token
self.base_url = "https://api.digitalocean.com/v2"
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
def create_droplet(self, name, region, size, image, ssh_keys=None, tags=None):
"""Create a new DigitalOcean Droplet"""
url = f"{self.base_url}/droplets"
data = {
"name": name,
"region": region,
"size": size,
"image": image,
"ssh_keys": ssh_keys or [],
"backups": False,
"ipv6": True,
"user_data": None,
"private_networking": False,
"tags": tags or []
}
try:
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
droplet_info = response.json()['droplet']
print(f"✅ Droplet '{name}' created successfully!")
print(f" ID: {droplet_info['id']}")
print(f" IP: {droplet_info['networks']['v4'][0]['ip_address']}")
return droplet_info
except requests.exceptions.RequestException as e:
print(f"❌ Error creating droplet: {e}")
if response.text:
error_detail = json.loads(response.text)
print(f" Details: {error_detail.get('message', 'Unknown error')}")
return None
def list_droplets(self, tag_name=None):
"""List all droplets or droplets with specific tag"""
url = f"{self.base_url}/droplets"
if tag_name:
url += f"?tag_name={tag_name}"
try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
droplets = response.json()['droplets']
print(f"📋 Found {len(droplets)} droplet(s):")
print("-" * 60)
for droplet in droplets:
status_emoji = "🟢" if droplet['status'] == 'active' else "🟡"
ip = droplet['networks']['v4'][0]['ip_address'] if droplet['networks']['v4'] else "N/A"
print(f"{status_emoji} {droplet['name']}")
print(f" ID: {droplet['id']}")
print(f" IP: {ip}")
print(f" Status: {droplet['status']}")
print(f" Region: {droplet['region']['slug']}")
print(f" Size: {droplet['size_slug']}")
if droplet.get('tags'):
print(f" Tags: {', '.join(droplet['tags'])}")
print()
return droplets
except requests.exceptions.RequestException as e:
print(f"❌ Error listing droplets: {e}")
return []
def get_droplet_info(self, droplet_id):
"""Get detailed information about a specific droplet"""
url = f"{self.base_url}/droplets/{droplet_id}"
try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
droplet = response.json()['droplet']
print(f"ℹ️ Droplet Information for '{droplet['name']}'")
print("-" * 50)
print(f"ID: {droplet['id']}")
print(f"Status: {droplet['status']}")
print(f"Region: {droplet['region']['name']} ({droplet['region']['slug']})")
print(f"Size: {droplet['size_slug']}")
print(f"CPUs: {droplet['vcpus']}")
print(f"Memory: {droplet['memory']} MB")
print(f"Disk: {droplet['disk']} GB")
print(f"Image: {droplet['image']['distribution']} {droplet['image']['name']}")
# Network information
if droplet['networks']['v4']:
for network in droplet['networks']['v4']:
print(f"IP ({network['type']}): {network['ip_address']}")
print(f"Created: {droplet['created_at']}")
if droplet.get('tags'):
print(f"Tags: {', '.join(droplet['tags'])}")
return droplet
except requests.exceptions.RequestException as e:
print(f"❌ Error getting droplet info: {e}")
return None
def power_off_droplet(self, droplet_id):
"""Power off a droplet"""
url = f"{self.base_url}/droplets/{droplet_id}/actions"
data = {"type": "power_off"}
try:
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
action = response.json()['action']
print(f"🔌 Power off initiated for Droplet {droplet_id}")
print(f" Action ID: {action['id']}")
print(f" Status: {action['status']}")
return action
except requests.exceptions.RequestException as e:
print(f"❌ Error powering off droplet: {e}")
return None
def power_on_droplet(self, droplet_id):
"""Power on a droplet"""
url = f"{self.base_url}/droplets/{droplet_id}/actions"
data = {"type": "power_on"}
try:
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
action = response.json()['action']
print(f"🔌 Power on initiated for Droplet {droplet_id}")
print(f" Action ID: {action['id']}")
print(f" Status: {action['status']}")
return action
except requests.exceptions.RequestException as e:
print(f"❌ Error powering on droplet: {e}")
return None
def delete_droplet(self, droplet_id):
"""Delete a droplet (PERMANENT!)"""
url = f"{self.base_url}/droplets/{droplet_id}"
# Confirm deletion
confirm = input(f"⚠️ Are you sure you want to delete Droplet {droplet_id}? This cannot be undone! (yes/no): ")
if confirm.lower() != 'yes':
print("❌ Deletion cancelled")
return False
try:
response = requests.delete(url, headers=self.headers)
response.raise_for_status()
print(f"✅ Droplet {droplet_id} deleted successfully")
return True
except requests.exceptions.RequestException as e:
print(f"❌ Error deleting droplet: {e}")
return False
def create_snapshot(self, droplet_id, snapshot_name):
"""Create a snapshot of a droplet"""
url = f"{self.base_url}/droplets/{droplet_id}/actions"
data = {
"type": "snapshot",
"name": snapshot_name
}
try:
response = requests.post(url, headers=self.headers, json=data)
response.raise_for_status()
action = response.json()['action']
print(f"📸 Snapshot '{snapshot_name}' creation initiated")
print(f" Action ID: {action['id']}")
print(f" Status: {action['status']}")
return action
except requests.exceptions.RequestException as e:
print(f"❌ Error creating snapshot: {e}")
return None
def get_action_status(self, action_id):
"""Check the status of an action"""
url = f"{self.base_url}/actions/{action_id}"
try:
response = requests.get(url, headers=self.headers)
response.raise_for_status()
action = response.json()['action']
return action
except requests.exceptions.RequestException as e:
print(f"❌ Error getting action status: {e}")
return None
# Example usage
if __name__ == "__main__":
# Get API token from environment variable
api_token = os.environ.get('DIGITALOCEAN_API_TOKEN')
if not api_token:
print("❌ Please set DIGITALOCEAN_API_TOKEN environment variable")
exit(1)
# Initialize manager
manager = DigitalOceanManager(api_token)
# Example: Create a droplet
print("🚀 Creating a new droplet...")
droplet = manager.create_droplet(
name="my-app-server",
region="nyc3", # New York 3
size="s-1vcpu-1gb", # $5/month
image="ubuntu-22-04-x64", # Ubuntu 22.04
tags=["web", "production"]
)
if droplet:
# Wait for droplet to be active
print("⏳ Waiting for droplet to be active...")
time.sleep(30)
# List all droplets
manager.list_droplets()
# Example: Get droplet info (replace with actual droplet ID)
# manager.get_droplet_info("your-droplet-id")
# Example: Create snapshot (replace with actual droplet ID)
# manager.create_snapshot("your-droplet-id", "backup-before-update")
# requirements.txt
"""
requests>=2.31.0
python-dotenv>=1.0.0
"""
💻 Kubernetes集群部署 yaml
🟡 intermediate
⭐⭐⭐
在DigitalOcean Kubernetes服务(DOKS)上部署和管理应用程序
⏱️ 30 min
🏷️ digitalocean, kubernetes, doks, deployment, containers
Prerequisites:
DigitalOcean account, Kubernetes knowledge, kubectl installed, doctl CLI
# DigitalOcean Kubernetes Deployment
# deployment.yaml - Complete application deployment on DOKS
apiVersion: v1
kind: Namespace
metadata:
name: myapp
labels:
name: myapp
environment: production
---
# ConfigMap for application configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: myapp
data:
NODE_ENV: "production"
PORT: "3000"
REDIS_HOST: "redis-service"
REDIS_PORT: "6379"
DB_HOST: "postgresql-service"
DB_PORT: "5432"
---
# Secret for sensitive data
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
namespace: myapp
type: Opaque
data:
# Base64 encoded values
# echo -n 'your-database-password' | base64
DB_PASSWORD: eW91ci1kYXRhYmFzZS1wYXNzd29yZA==
# echo -n 'your-jwt-secret' | base64
JWT_SECRET: eW91ci1qd3Qtc2VjcmV0
# echo -n 'your-api-key' | base64
API_KEY: eW91ci1hcGkta2V5
---
# Persistent Volume Claim for database storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: myapp
spec:
accessModes:
- ReadWriteOnce
storageClassName: do-block-storage
resources:
requests:
storage: 10Gi
---
# PostgreSQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
env:
- name: POSTGRES_DB
value: "myapp"
- name: POSTGRES_USER
value: "myappuser"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: DB_PASSWORD
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
# Redis Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "250m"
---
# Application Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: your-registry/myapp:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: myapp-config
- secretRef:
name: myapp-secrets
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
# Services
apiVersion: v1
kind: Service
metadata:
name: postgres-service
namespace: myapp
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: myapp
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
namespace: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 3000
type: ClusterIP
---
# Ingress for external access
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: myapp
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"
spec:
tls:
- hosts:
- yourdomain.com
secretName: myapp-tls
rules:
- host: yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
---
# Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
namespace: myapp
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
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
---
# Network Policy for security
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: myapp-netpol
namespace: myapp
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 3000
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
---
# PodDisruptionBudget for high availability
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myapp-pdb
namespace: myapp
spec:
minAvailable: 2
selector:
matchLabels:
app: myapp
# Additional Kubernetes manifests for DigitalOcean
# Load Balancer Service (if you need DO load balancer)
---
apiVersion: v1
kind: Service
metadata:
name: myapp-lb
namespace: myapp
annotations:
service.beta.kubernetes.io/do-loadbalancer-name: "myapp-loadbalancer"
service.beta.kubernetes.io/do-loadbalancer-protocol: "http"
service.beta.kubernetes.io/do-loadbalancer-port-forwarding-rules: "443:http"
service.beta.kubernetes.io/do-loadbalancer-http-ports: "80"
service.beta.kubernetes.io/do-loadbalancer-algorithm: "least_connections"
spec:
type: LoadBalancer
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 3000
- name: https
port: 443
targetPort: 3000
# doctl commands for cluster management
"""
# Create DigitalOcean Kubernetes cluster
doctl kubernetes cluster create myapp-cluster \
--region nyc3 \
--version 1.28.2-do.1 \
--node-pool "name=worker-pool;size=s-2vcpu-4gb;count=3" \
--auto-upgrade \
--enable-cluster-autoscaling \
--min-nodes=1 \
--max-nodes=5
# Get cluster credentials
doctl kubernetes cluster kubeconfig save myapp-cluster
# Create block storage for persistent volumes
doctl volume create postgres-storage --region nyc3 --size 10Gi
# Create domain for DNS management
doctl compute domain create yourdomain.com
# Create DNS records
doctl compute domain records create yourdomain.com --type A --name www --data 192.168.1.1
# Upload container registry
doctl registry repository create myapp
# Tag and push image
docker tag myapp:latest registry.digitalocean.com/your-username/myapp:latest
docker push registry.digitalocean.com/your-username/myapp:latest
"""
💻 Spaces对象存储 javascript
🟡 intermediate
⭐⭐⭐
在DigitalOcean Spaces中进行文件上传、下载和管理
⏱️ 25 min
🏷️ digitalocean, spaces, storage, s3, files
Prerequisites:
DigitalOcean account, Spaces bucket, Node.js knowledge
// DigitalOcean Spaces File Storage
// JavaScript/Node.js - spaces-manager.js
const AWS = require('aws-sdk');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
class SpacesManager {
constructor(accessKey, secretKey, region = 'nyc3', bucketName) {
// DigitalOcean Spaces uses S3-compatible API
this.s3 = new AWS.S3({
accessKeyId: accessKey,
secretAccessKey: secretKey,
region: region,
endpoint: new AWS.Endpoint(`https://${region}.digitaloceanspaces.com`)
});
this.bucketName = bucketName;
this.region = region;
}
async uploadFile(filePath, keyPrefix = '', metadata = {}) {
/** Upload a file to Spaces */
try {
const fileName = path.basename(filePath);
const key = keyPrefix ? `${keyPrefix}/${fileName}` : fileName;
// Read file
const fileContent = fs.readFileSync(filePath);
// Calculate file hash
const fileHash = crypto.createHash('md5').update(fileContent).digest('hex');
// Set content type based on file extension
const contentTypes = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.pdf': 'application/pdf',
'.txt': 'text/plain',
'.json': 'application/json',
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.zip': 'application/zip',
'.mp4': 'video/mp4',
'.mp3': 'audio/mpeg'
};
const ext = path.extname(fileName).toLowerCase();
const contentType = contentTypes[ext] || 'application/octet-stream';
const params = {
Bucket: this.bucketName,
Key: key,
Body: fileContent,
ContentType: contentType,
ACL: 'public-read', // Make file publicly accessible
Metadata: {
'original-name': fileName,
'upload-time': new Date().toISOString(),
'file-hash': fileHash,
...metadata
}
};
const result = await this.s3.upload(params).promise();
console.log(`✅ File uploaded successfully:`);
console.log(` Key: ${result.Key}`);
console.log(` URL: ${result.Location}`);
console.log(` Size: ${fs.statSync(filePath).size} bytes`);
console.log(` Hash: ${fileHash}`);
return {
key: result.Key,
url: result.Location,
size: result.ContentLength,
hash: fileHash
};
} catch (error) {
console.error(`❌ Error uploading file: ${error.message}`);
throw error;
}
}
async downloadFile(key, localPath) {
/** Download a file from Spaces */
try {
const params = {
Bucket: this.bucketName,
Key: key
};
const result = await this.s3.getObject(params).promise();
// Ensure directory exists
const dir = path.dirname(localPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Write file
fs.writeFileSync(localPath, result.Body);
console.log(`✅ File downloaded successfully:`);
console.log(` Key: ${key}`);
console.log(` Path: ${localPath}`);
console.log(` Size: ${result.ContentLength} bytes`);
return {
key: key,
path: localPath,
size: result.ContentLength,
metadata: result.Metadata
};
} catch (error) {
console.error(`❌ Error downloading file: ${error.message}`);
throw error;
}
}
async listFiles(prefix = '', maxKeys = 1000) {
/** List files in Spaces */
try {
const params = {
Bucket: this.bucketName,
Prefix: prefix,
MaxKeys: maxKeys
};
const result = await this.s3.listObjectsV2(params).promise();
console.log(`📋 Found ${result.Contents.length} file(s) in Spaces:`);
console.log("-" * 80);
const files = result.Contents.map(obj => {
const fileSize = this.formatFileSize(obj.Size);
const lastModified = new Date(obj.LastModified).toLocaleString();
console.log(`📄 ${obj.Key}`);
console.log(` Size: ${fileSize}`);
console.log(` Modified: ${lastModified}`);
console.log(` URL: https://${this.bucketName}.${this.region}.digitaloceanspaces.com/${obj.Key}`);
console.log();
return {
key: obj.Key,
size: obj.Size,
lastModified: obj.LastModified,
url: `https://${this.bucketName}.${this.region}.digitaloceanspaces.com/${obj.Key}`,
etag: obj.ETag
};
});
if (result.IsTruncated) {
console.log(`ℹ️ More files available. Use continuation token for next page.`);
}
return files;
} catch (error) {
console.error(`❌ Error listing files: ${error.message}`);
throw error;
}
}
async deleteFile(key) {
/** Delete a file from Spaces */
try {
const params = {
Bucket: this.bucketName,
Key: key
};
await this.s3.deleteObject(params).promise();
console.log(`🗑️ File deleted successfully: ${key}`);
return true;
} catch (error) {
console.error(`❌ Error deleting file: ${error.message}`);
throw error;
}
}
async generatePresignedUrl(key, expiresIn = 3600) {
/** Generate a presigned URL for temporary access */
try {
const params = {
Bucket: this.bucketName,
Key: key,
Expires: expiresIn
};
const url = await this.s3.getSignedUrlPromise('getObject', params);
console.log(`🔗 Presigned URL generated:`);
console.log(` Key: ${key}`);
console.log(` URL: ${url}`);
console.log(` Expires in: ${expiresIn} seconds`);
return url;
} catch (error) {
console.error(`❌ Error generating presigned URL: ${error.message}`);
throw error;
}
}
async createFolder(folderPath) {
/** Create a folder (actually just a placeholder object) */
try {
const key = folderPath.endsWith('/') ? folderPath : `${folderPath}/`;
const params = {
Bucket: this.bucketName,
Key: key,
Body: '',
ContentType: 'application/x-directory'
};
await this.s3.putObject(params).promise();
console.log(`📁 Folder created: ${key}`);
return key;
} catch (error) {
console.error(`❌ Error creating folder: ${error.message}`);
throw error;
}
}
async getFileInfo(key) {
/** Get detailed information about a file */
try {
const params = {
Bucket: this.bucketName,
Key: key
};
const result = await this.s3.headObject(params).promise();
console.log(`ℹ️ File Information for: ${key}`);
console.log("-" * 50);
console.log(`Size: ${this.formatFileSize(result.ContentLength)}`);
console.log(`Last Modified: ${new Date(result.LastModified).toLocaleString()}`);
console.log(`Content Type: ${result.ContentType}`);
console.log(`ETag: ${result.ETag}`);
console.log(`URL: https://${this.bucketName}.${this.region}.digitaloceanspaces.com/${key}`);
if (result.Metadata) {
console.log(`Metadata: ${JSON.stringify(result.Metadata, null, 2)}`);
}
return {
key: key,
size: result.ContentLength,
lastModified: result.LastModified,
contentType: result.ContentType,
etag: result.ETag,
metadata: result.Metadata
};
} catch (error) {
console.error(`❌ Error getting file info: ${error.message}`);
throw error;
}
}
formatFileSize(bytes) {
/** Format file size in human readable format */
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
async syncFolder(localFolder, spacesPrefix = '', excludePatterns = []) {
/** Sync a local folder to Spaces */
try {
console.log(`🔄 Starting sync from ${localFolder} to Spaces prefix ${spacesPrefix}...`);
let uploadedCount = 0;
let skippedCount = 0;
let errorCount = 0;
// Recursively walk through local folder
const walkDir = (dir, prefix = '') => {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
const stats = fs.statSync(filePath);
// Check if file should be excluded
if (excludePatterns.some(pattern => file.match(pattern))) {
console.log(`⏭️ Skipping excluded file: ${filePath}`);
skippedCount++;
return;
}
if (stats.isDirectory()) {
// Recurse into subdirectory
const subPrefix = prefix ? `${prefix}/${file}` : file;
walkDir(filePath, subPrefix);
} else {
// Upload file
const keyPrefix = spacesPrefix ? `${spacesPrefix}/${prefix}` : prefix;
try {
this.uploadFile(filePath, keyPrefix, {
'local-path': filePath,
'sync-time': new Date().toISOString()
});
uploadedCount++;
} catch (error) {
console.error(`❌ Error uploading ${filePath}: ${error.message}`);
errorCount++;
}
}
});
};
walkDir(localFolder);
console.log(`\n✅ Sync completed:`);
console.log(` Uploaded: ${uploadedCount} files`);
console.log(` Skipped: ${skippedCount} files`);
console.log(` Errors: ${errorCount} files`);
return {
uploaded: uploadedCount,
skipped: skippedCount,
errors: errorCount
};
} catch (error) {
console.error(`❌ Error syncing folder: ${error.message}`);
throw error;
}
}
}
// Example usage
async function main() {
// Load environment variables
require('dotenv').config();
// Initialize Spaces manager
const spacesManager = new SpacesManager(
process.env.SPACES_ACCESS_KEY,
process.env.SPACES_SECRET_KEY,
process.env.SPACES_REGION || 'nyc3',
process.env.SPACES_BUCKET_NAME
);
try {
// Example 1: Upload a file
console.log("📤 Uploading example file...");
await spacesManager.uploadFile('./example.txt', 'documents');
// Example 2: List files
console.log("\n📋 Listing files...");
await spacesManager.listFiles();
// Example 3: Create folder
console.log("\n📁 Creating folder...");
await spacesManager.createFolder('images/2024');
// Example 4: Generate presigned URL
console.log("\n🔗 Generating presigned URL...");
await spacesManager.generatePresignedUrl('documents/example.txt', 3600);
// Example 5: Sync folder (if folder exists)
if (fs.existsSync('./public')) {
console.log("\n🔄 Syncing public folder...");
await spacesManager.syncFolder('./public', 'assets', [
/.DS_Store/,
/Thumbs.db/,
/.git/
]);
}
} catch (error) {
console.error('❌ Error in main:', error);
}
}
// Export the class
module.exports = SpacesManager;
// Run example if called directly
if (require.main === module) {
main();
}
// package.json dependencies
/*
{
"dependencies": {
"aws-sdk": "^2.1500.0",
"dotenv": "^16.3.1"
}
}
*/
💻 App Platform部署 yaml
🔴 complex
⭐⭐⭐⭐
将容器化和静态应用程序部署到DigitalOcean App Platform
⏱️ 35 min
🏷️ digitalocean, app platform, deployment, containers, paas
Prerequisites:
DigitalOcean account, App Platform enabled, Docker knowledge, YAML configuration
# DigitalOcean App Platform Deployment
# app.yaml - Complete application deployment configuration
# Production configuration
name: my-production-app
region: nyc3
services:
- name: web
# Source code location
source_dir: /
# GitHub repository
github:
repo: your-username/your-app
branch: main
deploy_on_push: true
# Docker image configuration
image:
registry_type: DOCR
repository: your-username/myapp
tag: latest
# Build configuration
build_command: |
npm install --production
npm run build
# Run command
run_command: npm start
# HTTP port
http_port: 3000
# Instance count (auto-scaling)
instance_count: 2
instance_size_slug: professional-xs
# Environment variables
env:
- key: NODE_ENV
value: production
- key: PORT
value: "3000"
- key: DATABASE_URL
value: ${db.DATABASE_URL}
- key: REDIS_URL
value: ${redis.REDIS_URL}
- key: JWT_SECRET
value: ${_self.JWT_SECRET}
# Health check
health_check:
http_path: /health
port: 3000
initial_delay_seconds: 30
period_seconds: 10
timeout_seconds: 5
success_threshold: 3
failure_threshold: 5
# Resource limits
resource_limits:
memory: 512Mi
cpu: 500m
# Deployment strategy
deployment_strategy:
type: RollingUpdate
max_unavailable: 1
max_surge: 1
# Static site service
- name: docs
source_dir: ./docs
github:
repo: your-username/docs
branch: gh-pages
build_command: |
npm install
npm run build
output_dir: _site
http_port: 80
routes:
- path: /docs
preserve_path_prefix: true
resource_limits:
memory: 128Mi
cpu: 100m
# Workers/Cron jobs
jobs:
- name: daily-backup
source_dir: ./scripts
image:
registry_type: DOCKER_HUB
repository: your-username/backup-worker
tag: latest
run_command: node backup.js
schedule: "0 2 * * *" # Daily at 2 AM
kind: CRON
env:
- key: DATABASE_URL
value: ${db.DATABASE_URL}
- key: SPACES_ACCESS_KEY
value: ${_self.SPACES_ACCESS_KEY}
- key: SPACES_SECRET_KEY
value: ${_self.SPACES_SECRET_KEY}
resource_limits:
memory: 256Mi
cpu: 250m
# Databases
databases:
- name: db
engine: PG
version: "15"
size: db-s-1vcpu-2gb
# Connection details (automatically injected as environment variables)
# DATABASE_URL
# DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
# BACKUP_URL for restore
# Redis cache
- name: redis
engine: REDIS
version: "7"
size: redis-s-1vcpu-512mb
# Connection details (automatically injected as environment variables)
# REDIS_URL
# REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
# Domains
domains:
- domain: yourdomain.com
type: PRIMARY
zone: yourdomain.com
wildcard: true
# Monitoring and alerts
alerts:
- rule: DEPLOYMENT_FAILED
value: "1"
operator: GREATER_THAN
notification_emails:
- [email protected]
- rule: DOMAIN_FAILED
value: "1"
operator: GREATER_THAN
notification_emails:
- [email protected]
- rule: HIGH_ERROR_RATE
value: "90"
operator: GREATER_THAN
notification_emails:
- [email protected]
comparison: PERCENTAGE
# Development environment variant
# app.dev.yaml
---
name: my-app-dev
region: nyc3
services:
- name: web
source_dir: /
github:
repo: your-username/your-app
branch: develop
deploy_on_push: true
build_command: npm install && npm run build
run_command: npm run dev
http_port: 3000
instance_count: 1
instance_size_slug: basic-xxs
env:
- key: NODE_ENV
value: development
resource_limits:
memory: 256Mi
cpu: 100m
# Staging environment variant
# app.staging.yaml
---
name: my-app-staging
region: nyc3
services:
- name: web
source_dir: /
github:
repo: your-username/your-app
branch: staging
deploy_on_push: true
build_command: npm ci --production && npm run build
run_command: npm start
http_port: 3000
instance_count: 1
instance_size_slug: basic-xs
env:
- key: NODE_ENV
value: staging
- key: DATABASE_URL
value: ${db-staging.DATABASE_URL}
resource_limits:
memory: 512Mi
cpu: 250m
# Multi-service application (microservices)
# app-microservices.yaml
---
name: my-microservices-app
region: nyc3
services:
# API Gateway
- name: api-gateway
source_dir: ./gateway
github:
repo: your-username/microservices
branch: main
build_command: |
cd gateway
npm install --production
npm run build
run_command: |
cd gateway
npm start
http_port: 8080
instance_count: 2
instance_size_slug: basic-xs
routes:
- path: /
preserve_path_prefix: false
env:
- key: AUTH_SERVICE_URL
value: http://auth-service:3001
- key: USER_SERVICE_URL
value: http://user-service:3002
- key: PRODUCT_SERVICE_URL
value: http://product-service:3003
# Auth Service
- name: auth-service
source_dir: ./auth
github:
repo: your-username/microservices
branch: main
build_command: |
cd auth
npm install --production
npm run build
run_command: |
cd auth
npm start
http_port: 3001
instance_count: 1
instance_size_slug: basic-xxs
internal_ports:
- 3001
# User Service
- name: user-service
source_dir: ./user
github:
repo: your-username/microservices
branch: main
build_command: |
cd user
npm install --production
npm run build
run_command: |
cd user
npm start
http_port: 3002
instance_count: 1
instance_size_slug: basic-xxs
internal_ports:
- 3002
env:
- key: DATABASE_URL
value: ${user-db.DATABASE_URL}
# Product Service
- name: product-service
source_dir: ./product
github:
repo: your-username/microservices
branch: main
build_command: |
cd product
npm install --production
npm run build
run_command: |
cd product
npm start
http_port: 3003
instance_count: 2
instance_size_slug: basic-xs
internal_ports:
- 3003
env:
- key: DATABASE_URL
value: ${product-db.DATABASE_URL}
- key: REDIS_URL
value: ${cache.REDIS_URL}
# Databases for microservices
databases:
- name: user-db
engine: PG
version: "15"
size: db-s-1vcpu-1gb
- name: product-db
engine: PG
version: "15"
size: db-s-2vcpu-4gb
# Shared cache
- name: cache
engine: REDIS
version: "7"
size: redis-s-1vcpu-1gb
# CLI commands for App Platform
"""
# Install doctl
curl -sSL https://dl.digitalocean.com/doctl/install.sh | sh
# Authenticate
doctl auth init
# Create app from spec
doctl apps create --spec app.yaml
# Update app
doctl apps update <app-id> --spec app.yaml
# List apps
doctl apps list
# Get app details
doctl apps get <app-id>
# List deployments
doctl apps list-deployments <app-id>
# Create database
doctl databases create mydb --engine pg --version 15 --size db-s-1vcpu-2gb --region nyc3
# Create Redis
doctl databases create mycache --engine redis --version 7 --size redis-s-1vcpu-1gb --region nyc3
# Scale app
doctl apps update <app-id> --spec spec/production.yaml
# View logs
doctl apps logs <app-id> --follow
# Delete app
doctl apps delete <app-id>
"""