diff --git a/docs/src/tutorials/cicd.md b/docs/src/tutorials/cicd.md new file mode 100644 index 00000000..1aff3077 --- /dev/null +++ b/docs/src/tutorials/cicd.md @@ -0,0 +1,575 @@ +# CI/CD Integration + +Integrate redisctl into your continuous integration and deployment pipelines. + +## Overview + +This tutorial covers: +- Automated database provisioning +- Environment promotion +- Testing with Redis +- GitOps workflows +- Blue-green deployments + +## GitHub Actions + +### Database Provisioning Workflow + +```yaml +# .github/workflows/provision-redis.yml +name: Provision Redis Database + +on: + workflow_dispatch: + inputs: + environment: + description: 'Environment to deploy to' + required: true + type: choice + options: + - development + - staging + - production + memory_gb: + description: 'Memory limit in GB' + required: true + default: '4' + +jobs: + provision: + runs-on: ubuntu-latest + environment: ${{ github.event.inputs.environment }} + + steps: + - uses: actions/checkout@v3 + + - name: Install redisctl + run: | + curl -L https://github.com/joshrotenberg/redisctl/releases/latest/download/redisctl-linux-amd64.tar.gz | tar xz + sudo mv redisctl /usr/local/bin/ + redisctl --version + + - name: Configure credentials + env: + REDIS_CLOUD_API_KEY: ${{ secrets.REDIS_CLOUD_API_KEY }} + REDIS_CLOUD_API_SECRET: ${{ secrets.REDIS_CLOUD_API_SECRET }} + run: | + redisctl profile set ci-${{ github.event.inputs.environment }} \ + --deployment cloud \ + --api-key "$REDIS_CLOUD_API_KEY" \ + --api-secret "$REDIS_CLOUD_API_SECRET" + + - name: Create database configuration + run: | + cat > database.json <> $GITHUB_OUTPUT + echo "endpoint=$ENDPOINT" >> $GITHUB_OUTPUT + echo "::add-mask::$PASSWORD" + echo "password=$PASSWORD" >> $GITHUB_OUTPUT + + - name: Update deployment configuration + run: | + # Update Kubernetes secret + kubectl create secret generic redis-${{ github.event.inputs.environment }} \ + --from-literal=endpoint=${{ steps.provision.outputs.endpoint }} \ + --from-literal=password=${{ steps.provision.outputs.password }} \ + --dry-run=client -o yaml | kubectl apply -f - + + - name: Run smoke tests + run: | + redis-cli -h ${{ steps.provision.outputs.endpoint }} \ + -a ${{ steps.provision.outputs.password }} \ + PING +``` + +### Environment Promotion + +```yaml +# .github/workflows/promote-environment.yml +name: Promote Database Configuration + +on: + workflow_dispatch: + inputs: + from_env: + description: 'Source environment' + required: true + type: choice + options: + - development + - staging + to_env: + description: 'Target environment' + required: true + type: choice + options: + - staging + - production + +jobs: + promote: + runs-on: ubuntu-latest + + steps: + - name: Export source configuration + run: | + SOURCE_DB=$(redisctl cloud database get \ + --subscription-id ${{ vars.SUBSCRIPTION_ID }} \ + --database-id ${{ vars[format('{0}_DATABASE_ID', inputs.from_env)] }} \ + --output json) + + # Extract configuration + echo "$SOURCE_DB" | jq '{ + memoryLimitInGb: .memoryLimitInGb, + throughputMeasurement: .throughputMeasurement, + modules: .modules, + alerts: .alerts, + dataEvictionPolicy: .dataEvictionPolicy, + dataPersistence: .dataPersistence + }' > config.json + + - name: Apply to target environment + run: | + redisctl cloud database update \ + --subscription-id ${{ vars.SUBSCRIPTION_ID }} \ + --database-id ${{ vars[format('{0}_DATABASE_ID', inputs.to_env)] }} \ + --data @config.json \ + --wait + + - name: Verify promotion + run: | + TARGET_CONFIG=$(redisctl cloud database get \ + --subscription-id ${{ vars.SUBSCRIPTION_ID }} \ + --database-id ${{ vars[format('{0}_DATABASE_ID', inputs.to_env)] }} \ + --output json) + + # Compare configurations + SOURCE_MEMORY=$(cat config.json | jq .memoryLimitInGb) + TARGET_MEMORY=$(echo "$TARGET_CONFIG" | jq .memoryLimitInGb) + + if [ "$SOURCE_MEMORY" != "$TARGET_MEMORY" ]; then + echo "Configuration mismatch!" + exit 1 + fi +``` + +## GitLab CI/CD + +### Pipeline Configuration + +```yaml +# .gitlab-ci.yml +stages: + - validate + - deploy + - test + - promote + +variables: + REDISCTL_VERSION: "latest" + +before_script: + - curl -L https://github.com/joshrotenberg/redisctl/releases/${REDISCTL_VERSION}/download/redisctl-linux-amd64.tar.gz | tar xz + - mv redisctl /usr/local/bin/ + - redisctl profile set gitlab --deployment cloud --api-key "$REDIS_API_KEY" --api-secret "$REDIS_API_SECRET" + +validate:config: + stage: validate + script: + - | + for file in configs/*.json; do + echo "Validating $file" + jq empty "$file" || exit 1 + done + +deploy:development: + stage: deploy + environment: development + script: + - | + redisctl cloud database update \ + --subscription-id "$DEV_SUBSCRIPTION_ID" \ + --database-id "$DEV_DATABASE_ID" \ + --data @configs/development.json \ + --wait + only: + - develop + +deploy:staging: + stage: deploy + environment: staging + script: + - | + redisctl cloud database update \ + --subscription-id "$STAGING_SUBSCRIPTION_ID" \ + --database-id "$STAGING_DATABASE_ID" \ + --data @configs/staging.json \ + --wait + only: + - main + +test:integration: + stage: test + script: + - | + # Get database endpoint + ENDPOINT=$(redisctl cloud database get \ + --subscription-id "$DEV_SUBSCRIPTION_ID" \ + --database-id "$DEV_DATABASE_ID" \ + -q "publicEndpoint") + + # Run tests + npm test -- --redis-endpoint="$ENDPOINT" + dependencies: + - deploy:development + +promote:to:production: + stage: promote + environment: production + when: manual + script: + - | + # Export staging config + redisctl cloud database get \ + --subscription-id "$STAGING_SUBSCRIPTION_ID" \ + --database-id "$STAGING_DATABASE_ID" \ + -o json > staging-config.json + + # Apply to production + redisctl cloud database update \ + --subscription-id "$PROD_SUBSCRIPTION_ID" \ + --database-id "$PROD_DATABASE_ID" \ + --data @staging-config.json \ + --wait + only: + - main +``` + +## Jenkins Pipeline + +### Jenkinsfile + +```groovy +// Jenkinsfile +pipeline { + agent any + + environment { + REDIS_CLOUD_API_KEY = credentials('redis-cloud-api-key') + REDIS_CLOUD_API_SECRET = credentials('redis-cloud-api-secret') + } + + stages { + stage('Setup') { + steps { + sh ''' + curl -L https://github.com/joshrotenberg/redisctl/releases/latest/download/redisctl-linux-amd64.tar.gz | tar xz + chmod +x redisctl + ./redisctl profile set jenkins \ + --deployment cloud \ + --api-key "$REDIS_CLOUD_API_KEY" \ + --api-secret "$REDIS_CLOUD_API_SECRET" + ''' + } + } + + stage('Provision Database') { + when { + expression { params.PROVISION_NEW == true } + } + steps { + script { + def dbConfig = readJSON file: 'database-config.json' + dbConfig.name = "${env.JOB_NAME}-${env.BUILD_NUMBER}" + + writeJSON file: 'temp-config.json', json: dbConfig + + def result = sh( + script: """ + ./redisctl cloud database create \ + --subscription-id ${params.SUBSCRIPTION_ID} \ + --data @temp-config.json \ + --wait \ + --output json + """, + returnStdout: true + ).trim() + + def db = readJSON text: result + env.DATABASE_ID = db.databaseId + env.DATABASE_ENDPOINT = db.publicEndpoint + } + } + } + + stage('Run Tests') { + steps { + sh ''' + export REDIS_ENDPOINT="${DATABASE_ENDPOINT}" + npm test + ''' + } + } + + stage('Cleanup') { + when { + expression { params.CLEANUP == true } + } + steps { + sh """ + ./redisctl cloud database delete \ + --subscription-id ${params.SUBSCRIPTION_ID} \ + --database-id ${env.DATABASE_ID} \ + --wait + """ + } + } + } + + post { + always { + cleanWs() + } + } +} +``` + +## ArgoCD GitOps + +### Application Manifest + +```yaml +# argocd/redis-app.yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: redis-databases + namespace: argocd +spec: + project: default + source: + repoURL: https://github.com/your-org/redis-config + targetRevision: HEAD + path: environments/production + destination: + server: https://kubernetes.default.svc + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + hooks: + - name: provision-redis + manifest: | + apiVersion: batch/v1 + kind: Job + metadata: + name: provision-redis + spec: + template: + spec: + containers: + - name: redisctl + image: redisctl/redisctl:latest + command: + - /bin/sh + - -c + - | + redisctl cloud database update \ + --subscription-id $SUBSCRIPTION_ID \ + --database-id $DATABASE_ID \ + --data @/config/database.json \ + --wait + volumeMounts: + - name: config + mountPath: /config + volumes: + - name: config + configMap: + name: redis-config +``` + +## Terraform Integration + +### Redis Resource Management + +```hcl +# terraform/redis.tf +terraform { + required_providers { + shell = { + source = "scottwinkler/shell" + version = "~> 1.7" + } + } +} + +resource "shell_script" "redis_database" { + lifecycle_commands { + create = <<-EOT + redisctl cloud database create \ + --subscription-id ${var.subscription_id} \ + --data '${jsonencode(var.database_config)}' \ + --wait \ + --output json + EOT + + read = <<-EOT + redisctl cloud database get \ + --subscription-id ${var.subscription_id} \ + --database-id $(cat database_id.txt) \ + --output json + EOT + + update = <<-EOT + redisctl cloud database update \ + --subscription-id ${var.subscription_id} \ + --database-id $(cat database_id.txt) \ + --data '${jsonencode(var.database_config)}' \ + --wait \ + --output json + EOT + + delete = <<-EOT + redisctl cloud database delete \ + --subscription-id ${var.subscription_id} \ + --database-id $(cat database_id.txt) \ + --wait + EOT + } + + environment = { + REDIS_CLOUD_API_KEY = var.redis_api_key + REDIS_CLOUD_API_SECRET = var.redis_api_secret + } +} + +output "redis_endpoint" { + value = jsondecode(shell_script.redis_database.output)["publicEndpoint"] +} + +output "redis_password" { + value = jsondecode(shell_script.redis_database.output)["password"] + sensitive = true +} +``` + +## Blue-Green Deployments + +### Deployment Script + +```bash +#!/bin/bash +# blue-green-deploy.sh + +set -euo pipefail + +# Configuration +SUBSCRIPTION_ID="${SUBSCRIPTION_ID}" +BLUE_DB_ID="${BLUE_DATABASE_ID}" +GREEN_DB_ID="${GREEN_DATABASE_ID}" +LOAD_BALANCER="${LOAD_BALANCER_NAME}" + +# Determine current active environment +CURRENT_ACTIVE=$(kubectl get service redis-active -o jsonpath='{.spec.selector.version}') +echo "Current active: $CURRENT_ACTIVE" + +if [ "$CURRENT_ACTIVE" = "blue" ]; then + TARGET="green" + TARGET_DB_ID="$GREEN_DB_ID" +else + TARGET="blue" + TARGET_DB_ID="$BLUE_DB_ID" +fi + +echo "Deploying to $TARGET environment (Database: $TARGET_DB_ID)" + +# Update target database +echo "Updating $TARGET database configuration..." +redisctl cloud database update \ + --subscription-id "$SUBSCRIPTION_ID" \ + --database-id "$TARGET_DB_ID" \ + --data @new-config.json \ + --wait + +# Run health checks +echo "Running health checks on $TARGET..." +ENDPOINT=$(redisctl cloud database get \ + --subscription-id "$SUBSCRIPTION_ID" \ + --database-id "$TARGET_DB_ID" \ + -q "publicEndpoint") + +for i in {1..10}; do + if redis-cli -h "$ENDPOINT" PING | grep -q PONG; then + echo "Health check passed" + break + fi + sleep 5 +done + +# Run smoke tests +echo "Running smoke tests..." +./run-smoke-tests.sh "$ENDPOINT" + +# Switch traffic +echo "Switching traffic to $TARGET..." +kubectl patch service redis-active \ + -p '{"spec":{"selector":{"version":"'$TARGET'"}}}' + +# Monitor for errors +echo "Monitoring for errors..." +sleep 30 + +ERROR_COUNT=$(kubectl logs -l app=redis,version=$TARGET --tail=100 | grep -c ERROR || true) +if [ "$ERROR_COUNT" -gt 0 ]; then + echo "Errors detected! Rolling back..." + kubectl patch service redis-active \ + -p '{"spec":{"selector":{"version":"'$CURRENT_ACTIVE'"}}}' + exit 1 +fi + +echo "Deployment successful! $TARGET is now active" +``` + +## Best Practices + +1. **Store credentials securely** - Use secrets management systems +2. **Use dedicated CI/CD profiles** - Don't reuse production credentials +3. **Implement rollback mechanisms** - Always have a way to revert +4. **Test in staging first** - Never deploy directly to production +5. **Monitor deployments** - Watch for errors during and after deployment +6. **Use infrastructure as code** - Version control your Redis configurations +7. **Implement approval gates** - Require manual approval for production +8. **Audit all changes** - Log who changed what and when +9. **Use immutable deployments** - Create new resources rather than updating +10. **Automate validation** - Test configurations before applying + +## Next Steps + +- [Managing Production Databases](./production-databases.md) +- [Disaster Recovery](./disaster-recovery.md) +- [Network Security](./network-security.md) \ No newline at end of file diff --git a/docs/src/tutorials/disaster-recovery.md b/docs/src/tutorials/disaster-recovery.md new file mode 100644 index 00000000..39e6b2bd --- /dev/null +++ b/docs/src/tutorials/disaster-recovery.md @@ -0,0 +1,13 @@ +# Disaster Recovery + +Learn how to prepare for and recover from disasters using redisctl. + +## Topics Covered + +- Backup strategies +- Recovery procedures +- Failover processes +- Data validation +- RTO/RPO planning + +*Full tutorial coming soon - see [Managing Production Databases](./production-databases.md) for backup examples in the meantime.* \ No newline at end of file diff --git a/docs/src/tutorials/monitoring.md b/docs/src/tutorials/monitoring.md new file mode 100644 index 00000000..e6094599 --- /dev/null +++ b/docs/src/tutorials/monitoring.md @@ -0,0 +1,607 @@ +# Setting Up Monitoring + +Learn how to monitor Redis Cloud and Enterprise deployments using redisctl with various monitoring stacks. + +## Overview + +Effective monitoring requires: +- Regular health checks +- Metric collection +- Alert configuration +- Dashboard visualization +- Log aggregation + +## Monitoring Architecture + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ redisctl │────▶│ Redis APIs │────▶│ Metrics │ +│ Scripts │ │ Cloud/Ent. │ │ Exporters │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ + ▼ +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Grafana │◀────│ Prometheus │◀────│ Format │ +│ Dashboards │ │ Storage │ │ Conversion │ +└─────────────┘ └──────────────┘ └─────────────┘ +``` + +## Basic Health Monitoring + +### Health Check Script + +Create a basic health monitor: + +```bash +#!/bin/bash +# health-check.sh + +set -euo pipefail + +# Configuration +PROFILE="${REDIS_PROFILE:-prod-cloud}" +CHECK_INTERVAL="${CHECK_INTERVAL:-60}" +ALERT_WEBHOOK="${ALERT_WEBHOOK}" + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" +} + +send_alert() { + local level=$1 + local message=$2 + + if [ -n "$ALERT_WEBHOOK" ]; then + curl -X POST "$ALERT_WEBHOOK" \ + -H 'Content-Type: application/json' \ + -d "{\"level\": \"$level\", \"message\": \"$message\"}" + fi + + case $level in + ERROR) echo -e "${RED}[ERROR]${NC} $message" ;; + WARNING) echo -e "${YELLOW}[WARN]${NC} $message" ;; + INFO) echo -e "${GREEN}[INFO]${NC} $message" ;; + esac +} + +check_databases() { + local subscription_id=$1 + + # Get all databases + local databases=$(redisctl --profile $PROFILE cloud database list \ + --subscription-id $subscription_id \ + -q "[].{id: databaseId, name: name, status: status}" 2>/dev/null) + + if [ -z "$databases" ]; then + send_alert "ERROR" "Failed to fetch databases for subscription $subscription_id" + return 1 + fi + + echo "$databases" | jq -c '.[]' | while read db; do + local id=$(echo $db | jq -r .id) + local name=$(echo $db | jq -r .name) + local status=$(echo $db | jq -r .status) + + if [ "$status" != "active" ]; then + send_alert "ERROR" "Database $name ($id) is not active: $status" + else + log "Database $name ($id) is healthy" + fi + done +} + +# Main monitoring loop +while true; do + log "Starting health check..." + + # Get all subscriptions + SUBSCRIPTIONS=$(redisctl --profile $PROFILE cloud subscription list \ + -q "[].id" 2>/dev/null | jq -r '.[]') + + for sub_id in $SUBSCRIPTIONS; do + check_databases $sub_id + done + + log "Health check complete. Sleeping for ${CHECK_INTERVAL}s..." + sleep $CHECK_INTERVAL +done +``` + +## Prometheus Integration + +### Metrics Exporter + +Create a Prometheus exporter for Redis metrics: + +```python +#!/usr/bin/env python3 +# redis_exporter.py + +import json +import subprocess +import time +from prometheus_client import start_http_server, Gauge, Counter +import os + +# Prometheus metrics +db_memory_used = Gauge('redis_memory_used_mb', 'Memory used in MB', ['database', 'subscription']) +db_memory_limit = Gauge('redis_memory_limit_gb', 'Memory limit in GB', ['database', 'subscription']) +db_connections = Gauge('redis_connections_used', 'Connections used', ['database', 'subscription']) +db_ops = Gauge('redis_operations_per_second', 'Operations per second', ['database', 'subscription']) +db_status = Gauge('redis_database_status', 'Database status (1=active, 0=inactive)', ['database', 'subscription']) + +def get_databases(profile, subscription_id): + """Fetch database list using redisctl""" + cmd = [ + 'redisctl', '--profile', profile, 'cloud', 'database', 'list', + '--subscription-id', str(subscription_id), '-o', 'json' + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return json.loads(result.stdout) + except Exception as e: + print(f"Error fetching databases: {e}") + return [] + +def get_database_details(profile, subscription_id, database_id): + """Fetch detailed database metrics""" + cmd = [ + 'redisctl', '--profile', profile, 'cloud', 'database', 'get', + '--subscription-id', str(subscription_id), + '--database-id', str(database_id), + '-o', 'json' + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return json.loads(result.stdout) + except Exception as e: + print(f"Error fetching database {database_id}: {e}") + return None + +def collect_metrics(): + """Collect metrics from all databases""" + profile = os.getenv('REDIS_PROFILE', 'prod-cloud') + subscriptions = os.getenv('REDIS_SUBSCRIPTIONS', '').split(',') + + for sub_id in subscriptions: + if not sub_id: + continue + + databases = get_databases(profile, sub_id) + + for db in databases: + db_id = db.get('databaseId') + db_name = db.get('name', f'db-{db_id}') + + # Get detailed metrics + details = get_database_details(profile, sub_id, db_id) + if not details: + continue + + # Update Prometheus metrics + labels = {'database': db_name, 'subscription': sub_id} + + db_memory_used.labels(**labels).set(details.get('memoryUsageInMB', 0)) + db_memory_limit.labels(**labels).set(details.get('memoryLimitInGB', 0)) + db_connections.labels(**labels).set(details.get('connectionsUsed', 0)) + + throughput = details.get('throughputMeasurement', {}) + db_ops.labels(**labels).set(throughput.get('value', 0)) + + status_value = 1 if details.get('status') == 'active' else 0 + db_status.labels(**labels).set(status_value) + + print(f"Updated metrics for {db_name}") + +def main(): + """Main exporter loop""" + port = int(os.getenv('EXPORTER_PORT', '9090')) + interval = int(os.getenv('SCRAPE_INTERVAL', '30')) + + # Start Prometheus HTTP server + start_http_server(port) + print(f"Exporter listening on port {port}") + + while True: + try: + collect_metrics() + except Exception as e: + print(f"Error collecting metrics: {e}") + + time.sleep(interval) + +if __name__ == '__main__': + main() +``` + +### Prometheus Configuration + +Configure Prometheus to scrape the exporter: + +```yaml +# prometheus.yml +global: + scrape_interval: 30s + evaluation_interval: 30s + +scrape_configs: + - job_name: 'redis-metrics' + static_configs: + - targets: ['localhost:9090'] + labels: + environment: 'production' + service: 'redis' + +# Alert rules +rule_files: + - 'redis_alerts.yml' +``` + +### Alert Rules + +Define Prometheus alert rules: + +```yaml +# redis_alerts.yml +groups: + - name: redis_alerts + interval: 30s + rules: + - alert: RedisHighMemoryUsage + expr: | + (redis_memory_used_mb / (redis_memory_limit_gb * 1024)) > 0.8 + for: 5m + labels: + severity: warning + annotations: + summary: "High memory usage on {{ $labels.database }}" + description: "Database {{ $labels.database }} is using {{ $value | humanizePercentage }} of available memory" + + - alert: RedisDatabaseDown + expr: redis_database_status == 0 + for: 2m + labels: + severity: critical + annotations: + summary: "Database {{ $labels.database }} is down" + description: "Database {{ $labels.database }} has been inactive for more than 2 minutes" + + - alert: RedisHighConnections + expr: redis_connections_used > 900 + for: 5m + labels: + severity: warning + annotations: + summary: "High connection count on {{ $labels.database }}" + description: "Database {{ $labels.database }} has {{ $value }} active connections" + + - alert: RedisLowThroughput + expr: redis_operations_per_second < 100 + for: 10m + labels: + severity: info + annotations: + summary: "Low throughput on {{ $labels.database }}" + description: "Database {{ $labels.database }} has only {{ $value }} ops/sec" +``` + +## Grafana Dashboards + +### Dashboard Configuration + +Create a comprehensive Grafana dashboard: + +```json +{ + "dashboard": { + "title": "Redis Production Monitoring", + "panels": [ + { + "title": "Database Status", + "type": "stat", + "targets": [ + { + "expr": "sum(redis_database_status)", + "legendFormat": "Active Databases" + } + ] + }, + { + "title": "Memory Usage", + "type": "graph", + "targets": [ + { + "expr": "redis_memory_used_mb", + "legendFormat": "{{ database }}" + } + ] + }, + { + "title": "Operations/Second", + "type": "graph", + "targets": [ + { + "expr": "redis_operations_per_second", + "legendFormat": "{{ database }}" + } + ] + }, + { + "title": "Connection Count", + "type": "graph", + "targets": [ + { + "expr": "redis_connections_used", + "legendFormat": "{{ database }}" + } + ] + } + ] + } +} +``` + +## Log Monitoring + +### Centralized Logging with ELK + +Ship Redis logs to Elasticsearch: + +```bash +#!/bin/bash +# ship-logs.sh + +# For Redis Enterprise +redisctl enterprise logs list \ + --profile prod-enterprise \ + --output json | \ + jq -c '.[] | { + "@timestamp": .time, + "level": .severity, + "message": .message, + "node": .node_uid, + "component": .component + }' | \ + while read log; do + curl -X POST "http://elasticsearch:9200/redis-logs/_doc" \ + -H 'Content-Type: application/json' \ + -d "$log" + done +``` + +### Logstash Configuration + +Process logs with Logstash: + +```ruby +# logstash.conf +input { + exec { + command => "redisctl enterprise logs list --output json" + interval => 60 + codec => "json" + } +} + +filter { + date { + match => [ "time", "ISO8601" ] + target => "@timestamp" + } + + mutate { + add_field => { "environment" => "production" } + } + + if [severity] == "error" { + mutate { + add_tag => [ "alert" ] + } + } +} + +output { + elasticsearch { + hosts => ["localhost:9200"] + index => "redis-logs-%{+YYYY.MM.dd}" + } + + if "alert" in [tags] { + email { + to => "ops-team@example.com" + subject => "Redis Error Alert" + body => "Error detected: %{message}" + } + } +} +``` + +## Alerting Integration + +### Slack Notifications + +Send alerts to Slack: + +```bash +#!/bin/bash +# slack-alert.sh + +send_slack_alert() { + local level=$1 + local message=$2 + local webhook_url="${SLACK_WEBHOOK_URL}" + + local color="good" + case $level in + ERROR) color="danger" ;; + WARNING) color="warning" ;; + esac + + curl -X POST "$webhook_url" \ + -H 'Content-Type: application/json' \ + -d "{ + \"attachments\": [{ + \"color\": \"$color\", + \"title\": \"Redis Alert: $level\", + \"text\": \"$message\", + \"footer\": \"redisctl monitoring\", + \"ts\": $(date +%s) + }] + }" +} + +# Monitor and alert +while true; do + STATUS=$(redisctl cloud database get \ + --subscription-id 123456 \ + --database-id 789 \ + -q "status") + + if [ "$STATUS" != "active" ]; then + send_slack_alert "ERROR" "Database 789 is $STATUS" + fi + + sleep 60 +done +``` + +### PagerDuty Integration + +Integrate with PagerDuty for critical alerts: + +```python +#!/usr/bin/env python3 +# pagerduty_alert.py + +import pdpyras +import subprocess +import json +import os + +def check_redis_health(): + """Check Redis database health""" + cmd = [ + 'redisctl', 'cloud', 'database', 'list', + '--subscription-id', os.getenv('SUBSCRIPTION_ID'), + '-o', 'json' + ] + + result = subprocess.run(cmd, capture_output=True, text=True) + databases = json.loads(result.stdout) + + alerts = [] + for db in databases: + if db['status'] != 'active': + alerts.append({ + 'database': db['name'], + 'status': db['status'], + 'id': db['databaseId'] + }) + + return alerts + +def send_pagerduty_alert(session, alerts): + """Send alert to PagerDuty""" + for alert in alerts: + session.trigger_incident( + summary=f"Redis database {alert['database']} is {alert['status']}", + source="redisctl-monitoring", + severity="error", + custom_details=alert + ) + +def main(): + api_key = os.getenv('PAGERDUTY_API_KEY') + session = pdpyras.APISession(api_key) + + alerts = check_redis_health() + if alerts: + send_pagerduty_alert(session, alerts) + +if __name__ == '__main__': + main() +``` + +## Custom Metrics Collection + +### Performance Baseline + +Establish performance baselines: + +```bash +#!/bin/bash +# baseline.sh + +# Collect baseline metrics for 24 hours +DURATION=86400 +INTERVAL=60 +OUTPUT="baseline_$(date +%Y%m%d).csv" + +echo "timestamp,database,ops,latency,memory,cpu" > $OUTPUT + +END=$(($(date +%s) + DURATION)) +while [ $(date +%s) -lt $END ]; do + TIMESTAMP=$(date +%s) + + redisctl cloud database get \ + --subscription-id 123456 \ + --database-id 789 \ + -o json | \ + jq -r "\"$TIMESTAMP,prod-db,\(.throughputMeasurement.value),\(.latency),\(.memoryUsageInMB),\(.cpuUsagePercentage)\"" \ + >> $OUTPUT + + sleep $INTERVAL +done + +# Analyze baseline +echo "Baseline collection complete. Analyzing..." +python3 analyze_baseline.py $OUTPUT +``` + +## Automation with Cron + +Schedule monitoring tasks: + +```bash +# crontab -e + +# Health check every 5 minutes +*/5 * * * * /opt/monitoring/health-check.sh + +# Collect metrics every minute +* * * * * /opt/monitoring/collect-metrics.sh + +# Daily report +0 8 * * * /opt/monitoring/daily-report.sh + +# Weekly capacity planning +0 0 * * 0 /opt/monitoring/capacity-planning.sh + +# Backup monitoring config +0 2 * * * /opt/monitoring/backup-monitoring.sh +``` + +## Best Practices + +1. **Monitor proactively** - Set up alerts before issues occur +2. **Use multiple data sources** - Combine metrics, logs, and traces +3. **Set appropriate thresholds** - Avoid alert fatigue +4. **Automate responses** - Use runbooks for common issues +5. **Track trends** - Look for patterns over time +6. **Test alert paths** - Ensure alerts reach the right people +7. **Document procedures** - Have clear escalation paths +8. **Review regularly** - Update monitoring as systems evolve + +## Next Steps + +- [Disaster Recovery](./disaster-recovery.md) +- [Network Security](./network-security.md) +- [CI/CD Integration](./cicd.md) \ No newline at end of file diff --git a/docs/src/tutorials/network-security.md b/docs/src/tutorials/network-security.md new file mode 100644 index 00000000..3654ca3f --- /dev/null +++ b/docs/src/tutorials/network-security.md @@ -0,0 +1,13 @@ +# Network Security + +Secure your Redis deployments with proper network configuration. + +## Topics Covered + +- VPC peering setup +- Private endpoints +- Security groups +- SSL/TLS configuration +- Access control + +*Full tutorial coming soon - see [Network Connectivity](../cloud/connectivity.md) for basic setup.* \ No newline at end of file diff --git a/docs/src/tutorials/production-databases.md b/docs/src/tutorials/production-databases.md new file mode 100644 index 00000000..e84b8b54 --- /dev/null +++ b/docs/src/tutorials/production-databases.md @@ -0,0 +1,480 @@ +# Managing Production Databases + +This tutorial covers best practices for managing Redis databases in production using redisctl. + +## Prerequisites + +- redisctl installed and configured +- Appropriate API credentials with production access +- Understanding of Redis concepts (memory, persistence, replication) + +## Setting Up Production Profiles + +First, create separate profiles for different environments: + +```bash +# Development environment +redisctl profile set dev-cloud \ + --deployment cloud \ + --api-key "$DEV_API_KEY" \ + --api-secret "$DEV_API_SECRET" + +# Staging environment +redisctl profile set staging-cloud \ + --deployment cloud \ + --api-key "$STAGING_API_KEY" \ + --api-secret "$STAGING_API_SECRET" + +# Production environment +redisctl profile set prod-cloud \ + --deployment cloud \ + --api-key "$PROD_API_KEY" \ + --api-secret "$PROD_API_SECRET" + +# Set production as default +redisctl profile default prod-cloud +``` + +## Creating a Production Database + +### Step 1: Prepare Database Configuration + +Create a production database configuration file: + +```json +{ + "name": "prod-cache-01", + "memoryLimitInGb": 16, + "protocol": "redis", + "port": 10000, + "throughputMeasurement": { + "by": "operations-per-second", + "value": 100000 + }, + "replication": true, + "dataPersistence": "aof-every-1-second", + "dataEvictionPolicy": "allkeys-lru", + "modules": [ + { + "name": "RedisJSON" + }, + { + "name": "RediSearch" + } + ], + "alerts": [ + { + "name": "dataset-size", + "value": 80 + }, + { + "name": "throughput-higher-than", + "value": 90000 + }, + { + "name": "throughput-lower-than", + "value": 1000 + }, + { + "name": "latency", + "value": 5 + } + ], + "backup": { + "interval": 6, + "enabled": true + }, + "clustering": { + "enabled": true, + "shardCount": 3 + } +} +``` + +### Step 2: Create the Database + +```bash +# Create database and wait for completion +redisctl cloud database create \ + --subscription-id 123456 \ + --data @prod-database.json \ + --wait \ + --wait-timeout 900 + +# Verify creation +redisctl cloud database list --subscription-id 123456 -o table +``` + +### Step 3: Configure Network Access + +Set up VPC peering for secure access: + +```bash +# Create VPC peering +redisctl cloud connectivity create-vpc \ + --subscription-id 123456 \ + --data @vpc-peering.json \ + --wait + +# Verify connection +redisctl cloud connectivity list-vpc --subscription-id 123456 +``` + +## Monitoring Production Databases + +### Health Checks + +Create a monitoring script: + +```bash +#!/bin/bash +# monitor-redis.sh + +PROFILE="prod-cloud" +SUBSCRIPTION_ID="123456" + +# Check all databases +DATABASES=$(redisctl --profile $PROFILE cloud database list \ + --subscription-id $SUBSCRIPTION_ID \ + -q "[].{id: databaseId, name: name, status: status}") + +echo "$DATABASES" | jq -c '.[]' | while read db; do + ID=$(echo $db | jq -r .id) + NAME=$(echo $db | jq -r .name) + STATUS=$(echo $db | jq -r .status) + + if [ "$STATUS" != "active" ]; then + echo "ALERT: Database $NAME ($ID) is not active: $STATUS" + # Send alert (PagerDuty, Slack, etc.) + fi +done + +# Check memory usage +for db_id in $(echo "$DATABASES" | jq -r '.[].id'); do + DB_INFO=$(redisctl --profile $PROFILE cloud database get \ + --subscription-id $SUBSCRIPTION_ID \ + --database-id $db_id) + + MEMORY_USED=$(echo $DB_INFO | jq -r .memoryUsageInMB) + MEMORY_LIMIT=$(echo $DB_INFO | jq -r .memoryLimitInGB) + MEMORY_LIMIT_MB=$((MEMORY_LIMIT * 1024)) + USAGE_PERCENT=$((MEMORY_USED * 100 / MEMORY_LIMIT_MB)) + + if [ $USAGE_PERCENT -gt 80 ]; then + echo "WARNING: Database $db_id memory usage at ${USAGE_PERCENT}%" + fi +done +``` + +### Performance Metrics + +Track key performance indicators: + +```bash +# Get database metrics +redisctl cloud database get \ + --subscription-id 123456 \ + --database-id 789 \ + -q "{ + name: name, + ops: throughputMeasurement.value, + connections: connectionsUsed, + memory: memoryUsageInMB, + evicted: evictedObjects + }" + +# Monitor over time +while true; do + redisctl cloud database get \ + --subscription-id 123456 \ + --database-id 789 \ + -q "throughputMeasurement.value" >> ops.log + sleep 60 +done +``` + +## Scaling Operations + +### Vertical Scaling (Resize) + +```bash +# Increase memory limit +redisctl cloud database update \ + --subscription-id 123456 \ + --database-id 789 \ + --data '{"memoryLimitInGb": 32}' \ + --wait + +# Increase throughput +redisctl cloud database update \ + --subscription-id 123456 \ + --database-id 789 \ + --data '{ + "throughputMeasurement": { + "by": "operations-per-second", + "value": 200000 + } + }' \ + --wait +``` + +### Horizontal Scaling (Sharding) + +For Redis Enterprise: + +```bash +# Add shards +redisctl enterprise database update \ + --database-id 1 \ + --data '{"shardCount": 5}' \ + --wait +``` + +## Backup and Recovery + +### Automated Backups + +Configure backup schedule: + +```bash +# Enable backups every 4 hours +redisctl cloud database update \ + --subscription-id 123456 \ + --database-id 789 \ + --data '{ + "backup": { + "enabled": true, + "interval": 4 + } + }' +``` + +### Manual Backups + +```bash +# Create manual backup before maintenance +redisctl cloud database backup \ + --subscription-id 123456 \ + --database-id 789 \ + --wait + +# List available backups +redisctl cloud database list-backups \ + --subscription-id 123456 \ + --database-id 789 +``` + +### Restore from Backup + +```bash +# Prepare import configuration +cat > import.json <