Chapter 06

Monitoring, Logging & Backup

Health checks, logging strategies, backup/restore cho PostgreSQL và Redis

Health Checks

Health checks cho Docker biết container có đang hoạt động bình thường không. Nếu health check fail, Docker có thể tự restart container:

Docker Engine
Chạy health check
Test Command
curl / pg_isready
Healthy
Exit code 0
hoặc
Unhealthy
Exit code 1

Health check trong docker-compose.yml

YAML
# ── ASP.NET API ──
backend:
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    interval: 30s      # Kiểm tra mỗi 30 giây
    timeout: 10s       # Timeout sau 10 giây
    retries: 3         # Sau 3 lần fail → unhealthy
    start_period: 40s  # Chờ 40s khi khởi động (warm-up)

# ── PostgreSQL ──
postgres:
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
    interval: 10s
    timeout: 5s
    retries: 5

# ── Redis ──
redis:
  healthcheck:
    test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
    interval: 10s
    timeout: 5s
    retries: 5

ASP.NET Health Endpoint

C#
// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Đăng ký health checks
builder.Services.AddHealthChecks()
    .AddNpgSql(
        builder.Configuration.GetConnectionString("DefaultConnection")!,
        name: "postgresql",
        tags: new[] { "db", "ready" })
    .AddRedis(
        builder.Configuration["Redis:ConnectionString"]!,
        name: "redis",
        tags: new[] { "cache", "ready" });

var app = builder.Build();

// Map health endpoint
app.MapHealthChecks("/health", new HealthCheckOptions
{
    // Trả về chi tiết từng dependency
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

// Endpoint đơn giản chỉ check app alive
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false  // Không check dependencies
});
Tách /health (readiness — bao gồm database, cache) và /health/live (liveness — chỉ check app sống). Docker health check nên dùng /health. Load balancer dùng /health/live để tránh false negative khi database tạm chậm.

Logging

Docker tự capture stdout/stderr của mỗi container. Các lệnh xem logs:

Bash
# Xem logs tất cả services
docker compose logs

# Follow logs real-time
docker compose logs -f

# Logs của một service cụ thể, 100 dòng cuối
docker compose logs -f --tail=100 backend

# Logs với timestamp
docker compose logs -t backend

# Logs trong khoảng thời gian
docker compose logs --since="2024-01-15T10:00:00" backend
docker compose logs --since="1h" backend  # 1 giờ gần nhất

Cấu hình Log Rotation

Mặc định Docker không giới hạn log size — logs có thể chiếm hết disk space:

YAML
# docker-compose.yml — Thêm vào mỗi service
backend:
  logging:
    driver: "json-file"
    options:
      max-size: "10m"    # Max 10 MB per log file
      max-file: "3"     # Giữ tối đa 3 files (30 MB total)
Nếu không cấu hình log rotation, một service log nhiều (như Nginx access log) có thể chiếm hết disk space trong vài tuần. Luôn set max-sizemax-file cho production.

Hoặc cấu hình global cho toàn bộ Docker daemon:

JSON
// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

ASP.NET Logging cho Production

Cấu hình logging level phù hợp cho production (giảm noise, giữ thông tin quan trọng):

JSON
// appsettings.Production.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Error",
      "MyApp": "Information"
    },
    "Console": {
      "FormatterName": "json"
    }
  }
}
Dùng JSON formatter cho production logs. Structured logging (JSON) dễ parse bằng tools (Grafana Loki, ELK stack) hơn nhiều so với plain text. Namespace MyApp giữ ở Information để log business logic, framework giữ ở Warning để giảm noise.

Backup & Restore

PostgreSQL Backup

Bash
# ── Manual backup ──
docker compose exec postgres pg_dump \
  -U myapp_user \
  -d myapp_db \
  --format=custom \
  --compress=9 \
  > backup_$(date +%Y%m%d_%H%M%S).dump

# ── Restore từ backup ──
docker compose exec -T postgres pg_restore \
  -U myapp_user \
  -d myapp_db \
  --clean --if-exists \
  < backup_20240115_100000.dump

# ── Automated daily backup (cron) ──
cat > /home/$USER/backup-db.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/home/$USER/backups"
KEEP_DAYS=7

mkdir -p $BACKUP_DIR
cd /home/$USER/my-app

# Tạo backup
docker compose exec -T postgres pg_dump \
  -U myapp_user -d myapp_db \
  --format=custom --compress=9 \
  > "$BACKUP_DIR/db_$(date +%Y%m%d_%H%M%S).dump"

# Xóa backup cũ hơn 7 ngày
find $BACKUP_DIR -name "db_*.dump" -mtime +$KEEP_DAYS -delete

echo "[$(date)] Backup completed"
EOF

chmod +x /home/$USER/backup-db.sh

# Chạy mỗi ngày lúc 2:00 AM
(crontab -l; echo "0 2 * * * /home/$USER/backup-db.sh >> /var/log/db-backup.log 2>&1") | crontab -
Backup vô nghĩa nếu bạn chưa bao giờ test restore. Sau khi setup backup, hãy thử restore vào một database test để đảm bảo backup file hoạt động. Lên lịch test restore ít nhất mỗi tháng một lần.

Redis Backup

RDB Snapshot (mặc định) Simple +

Redis tự tạo RDB snapshot theo interval mặc định (sau 3600 giây nếu có ít nhất 1 thay đổi).

Backup: Copy file dump.rdb từ volume:

Bash
# Trigger save thủ công
docker compose exec redis redis-cli -a $REDIS_PASSWORD BGSAVE

# Copy RDB file ra ngoài
docker cp $(docker compose ps -q redis):/data/dump.rdb ./backups/redis_backup.rdb

Restore: Stop Redis, copy dump.rdb vào volume, start lại.

AOF (Append-Only File) Durable +

AOF log mọi write operation, cho phép khôi phục gần như 100% dữ liệu:

YAML
redis:
  command: >
    redis-server
    --requirepass ${REDIS_PASSWORD}
    --appendonly yes
    --appendfsync everysec

--appendfsync everysec sync mỗi giây — cân bằng giữa durability và performance. Tối đa mất 1 giây dữ liệu khi crash.

Monitoring cơ bản

Các lệnh giám sát tài nguyên của Docker containers:

Bash
# Real-time resource usage (CPU, Memory, Network I/O)
docker stats

# Resource usage của compose stack
docker compose stats

# Disk usage tổng quan
docker system df

# Chi tiết disk usage
docker system df -v

# Xem health status
docker compose ps
# Output: NAME    STATUS                    PORTS
#         backend Up 2 hours (healthy)      8080/tcp
#         postgres Up 2 hours (healthy)     5432/tcp
Cho production nghiêm túc, cân nhắc thêm monitoring stack: Prometheus (thu thập metrics) + Grafana (dashboard) + Loki (log aggregation). Chúng đều chạy được dưới dạng Docker containers và thêm vào docker-compose.yml.

Uptime Check đơn giản

Script kiểm tra hệ thống còn hoạt động và gửi alert nếu down:

Bash
cat > /home/$USER/check-health.sh << 'EOF'
#!/bin/bash
URL="https://app.example.com/api/health"
WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 $URL)

if [ "$HTTP_CODE" != "200" ]; then
  curl -s -X POST $WEBHOOK \
    -H "Content-Type: application/json" \
    -d "{\"text\": \":red_circle: App DOWN! Health check returned $HTTP_CODE\"}"
fi
EOF

chmod +x /home/$USER/check-health.sh

# Chạy mỗi 5 phút
(crontab -l; echo "*/5 * * * * /home/$USER/check-health.sh") | crontab -