Course Navigation
← Back to Course OverviewAll Lessons
✓
Introduction and Database Fundamentals ✓
Building the Core Data Structure ✓
Concurrency and Thread Safety ✓
Append-Only Log (Write-Ahead Log) ✓
SSTables and LSM Trees ✓
Compaction and Optimization ✓
TCP Server and Protocol Design ✓
Client Library and Advanced Networking ✓
Transactions and ACID Properties ✓
Replication and High Availability ✓
Monitoring, Metrics, and Observability ✓
Performance Optimization and Tuning 13
Configuration and Deployment 14
Security and Production Hardening 15
Final Project and Beyond Current Lesson
13 of 15
Progress 87%
Configuration and Deployment
Learning Objectives
- • Master configuration management with YAML, TOML, and environment variables
- • Containerize applications with Docker and Docker Compose
- • Deploy to Kubernetes with proper resource management
- • Implement backup and recovery strategies
- • Design production-ready deployment pipelines
- • Handle graceful shutdowns and rolling upgrades
Lesson 13.1: Configuration Management
Configuration File Formats
YAML Configuration
# config.yaml
server:
host: "0.0.0.0"
port: 6379
max_connections: 10000
database:
memtable_size: 67108864 # 64MB
shard_count: 16
wal_sync_interval: 100
replication:
mode: "semi-sync"
min_replicas: 1
timeout_ms: 5000
cluster:
nodes:
- "node1:6379"
- "node2:6379"
- "node3:6379"
monitoring:
metrics_port: 9090
health_port: 8080 TOML Configuration
# config.toml
[server]
host = "0.0.0.0"
port = 6379
max_connections = 10000
[database]
memtable_size = 67108864
shard_count = 16
wal_sync_interval = 100
[replication]
mode = "semi-sync"
min_replicas = 1
timeout_ms = 5000
[[cluster.nodes]]
address = "node1:6379"
[[cluster.nodes]]
address = "node2:6379" Loading Configuration
package config
import (
"fmt"
"os"
"github.com/spf13/viper"
)
type Config struct {
Server struct {
Host string
Port int
MaxConnections int
}
Database struct {
MemtableSize int64
ShardCount int
WalSyncInterval int
}
Replication struct {
Mode string
MinReplicas int
TimeoutMs int
}
Cluster struct {
Nodes []string
}
Monitoring struct {
MetricsPort int
HealthPort int
}
}
// LoadConfig loads configuration from file
func LoadConfig(filepath string) (*Config, error) {
viper.SetConfigFile(filepath)
viper.SetConfigType("yaml")
// Read config file
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("error reading config: %w", err)
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("error unmarshaling config: %w", err)
}
return &cfg, nil
}
// LoadConfigWithDefaults loads with defaults
func LoadConfigWithDefaults() *Config {
viper.SetDefault("server.host", "0.0.0.0")
viper.SetDefault("server.port", 6379)
viper.SetDefault("server.max_connections", 10000)
viper.SetDefault("database.memtable_size", 67108864)
viper.SetDefault("database.shard_count", 16)
cfg, _ := LoadConfig("config.yaml")
return cfg
} Environment Variables
// Override config with environment variables
func LoadConfigFromEnv() *Config {
viper.AutomaticEnv()
viper.SetEnvPrefix("KVDB")
// Usage: KVDB_SERVER_PORT=7000
cfg, _ := LoadConfig("config.yaml")
return cfg
}
// Example environment variables:
// KVDB_SERVER_HOST=0.0.0.0
// KVDB_SERVER_PORT=6379
// KVDB_DATABASE_SHARD_COUNT=32
// KVDB_REPLICATION_MODE=sync Configuration Validation
// ValidateConfig checks configuration for errors
func (c *Config) Validate() error {
if c.Server.Port <= 0 || c.Server.Port > 65535 {
return fmt.Errorf("invalid port: %d", c.Server.Port)
}
if c.Database.MemtableSize <= 0 {
return fmt.Errorf("invalid memtable size: %d", c.Database.MemtableSize)
}
if c.Database.ShardCount <= 0 || c.Database.ShardCount > 256 {
return fmt.Errorf("invalid shard count: %d", c.Database.ShardCount)
}
if c.Replication.Mode != "async" && c.Replication.Mode != "sync" && c.Replication.Mode != "semi-sync" {
return fmt.Errorf("invalid replication mode: %s", c.Replication.Mode)
}
if len(c.Cluster.Nodes) == 0 {
return fmt.Errorf("no cluster nodes configured")
}
return nil
} Lesson 13.2: Docker Containerization
Dockerfile
# Multi-stage build for small image
FROM golang:1.21-alpine AS builder
WORKDIR /build
# Copy go mod files
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -o kvdb ./cmd/server
# Final stage
FROM alpine:3.18
# Install runtime dependencies
RUN apk add --no-cache ca-certificates tzdata
# Create app user
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
WORKDIR /app
# Copy binary from builder
COPY --from=builder /build/kvdb .
# Copy config
COPY config.yaml .
# Change ownership
RUN chown -R app:app /app
# Switch to non-root user
USER app
# Expose ports
EXPOSE 6379 9090 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/healthz || exit 1
# Run server
CMD ["./kvdb", "-config", "config.yaml"] Docker Compose for Local Development
# docker-compose.yml
version: '3.8'
services:
kvdb-1:
build: .
container_name: kvdb-1
ports:
- "6379:6379"
- "9090:9090"
- "8080:8080"
environment:
- KVDB_SERVER_HOST=0.0.0.0
- KVDB_SERVER_PORT=6379
- KVDB_CLUSTER_NODES=kvdb-1:6379,kvdb-2:6379,kvdb-3:6379
volumes:
- kvdb-1-data:/data
- ./config.yaml:/app/config.yaml
networks:
- kvdb-network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/healthz"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
kvdb-2:
build: .
container_name: kvdb-2
ports:
- "6380:6379"
- "9091:9090"
- "8081:8080"
environment:
- KVDB_SERVER_HOST=0.0.0.0
- KVDB_CLUSTER_NODES=kvdb-1:6379,kvdb-2:6379,kvdb-3:6379
volumes:
- kvdb-2-data:/data
- ./config.yaml:/app/config.yaml
networks:
- kvdb-network
depends_on:
- kvdb-1
kvdb-3:
build: .
container_name: kvdb-3
ports:
- "6381:6379"
- "9092:9090"
- "8082:8080"
environment:
- KVDB_SERVER_HOST=0.0.0.0
- KVDB_CLUSTER_NODES=kvdb-1:6379,kvdb-2:6379,kvdb-3:6379
volumes:
- kvdb-3-data:/data
- ./config.yaml:/app/config.yaml
networks:
- kvdb-network
depends_on:
- kvdb-1
volumes:
kvdb-1-data:
kvdb-2-data:
kvdb-3-data:
networks:
kvdb-network:
driver: bridge Building and Running
# Build Docker image
docker build -t kvdb:latest .
# Run container
docker run -p 6379:6379 -p 9090:9090 -p 8080:8080 kvdb:latest
# Run with custom config
docker run -p 6379:6379 \
-v $(pwd)/config.yaml:/app/config.yaml \
kvdb:latest ./kvdb -config /app/config.yaml
# Local cluster with Docker Compose
docker-compose up -d
# View logs
docker-compose logs -f kvdb-1
# Stop cluster
docker-compose down Lesson 13.3: Kubernetes Deployment
Deployment Manifest
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kvdb
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: kvdb
template:
metadata:
labels:
app: kvdb
spec:
containers:
- name: kvdb
image: kvdb:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
name: server
- containerPort: 9090
name: metrics
- containerPort: 8080
name: health
env:
- name: KVDB_SERVER_HOST
value: "0.0.0.0"
- name: KVDB_SERVER_PORT
value: "6379"
- name: KVDB_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: KVDB_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
httpGet:
path: /healthz
port: health
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: health
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
volumeMounts:
- name: data
mountPath: /data
- name: config
mountPath: /app/config.yaml
subPath: config.yaml
volumes:
- name: data
emptyDir:
- name: config
configMap:
name: kvdb-config Service Manifest
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: kvdb
namespace: default
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
protocol: TCP
name: server
- port: 9090
targetPort: 9090
protocol: TCP
name: metrics
selector:
app: kvdb
---
apiVersion: v1
kind: Service
metadata:
name: kvdb-headless
namespace: default
spec:
type: ClusterIP
clusterIP: None
ports:
- port: 6379
targetPort: 6379
selector:
app: kvdb ConfigMap
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kvdb-config
data:
config.yaml: |
server:
host: "0.0.0.0"
port: 6379
max_connections: 10000
database:
memtable_size: 67108864
shard_count: 16
wal_sync_interval: 100
replication:
mode: "semi-sync"
min_replicas: 1
timeout_ms: 5000
cluster:
nodes:
- "kvdb-0:6379"
- "kvdb-1:6379"
- "kvdb-2:6379" Deploy to Kubernetes
# Create namespace
kubectl create namespace kvdb
# Apply ConfigMap
kubectl apply -f configmap.yaml
# Apply Deployment
kubectl apply -f deployment.yaml
# Apply Service
kubectl apply -f service.yaml
# Check deployment status
kubectl get deployment kvdb
# View pods
kubectl get pods -l app=kvdb
# View logs
kubectl logs deployment/kvdb -f
# Port forward for testing
kubectl port-forward svc/kvdb 6379:6379
# Scale replicas
kubectl scale deployment kvdb --replicas=5
# Delete deployment
kubectl delete deployment kvdb Lesson 13.4: Backup and Recovery
Backup Strategy
// BackupManager handles database backups
type BackupManager struct {
backupDir string
store Store
logger *zap.Logger
}
// CreateBackup creates point-in-time backup
func (bm *BackupManager) CreateBackup(name string) error {
backupPath := filepath.Join(bm.backupDir, name)
if err := os.MkdirAll(backupPath, 0755); err != nil {
return err
}
// Get snapshot from store
snapshot, err := bm.store.GetSnapshot()
if err != nil {
return err
}
defer snapshot.Release()
// Write snapshot to backup directory
iter := snapshot.Iterator()
defer iter.Close()
backupFile := filepath.Join(backupPath, "data.backup")
f, err := os.Create(backupFile)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
for iter.Next() {
entry := map[string]interface{
"key": string(iter.Key()),
"value": string(iter.Value()),
}
encoder.Encode(entry)
}
bm.logger.Info("backup created", zap.String("path", backupPath))
return nil
}
// RestoreBackup restores database from backup
func (bm *BackupManager) RestoreBackup(name string) error {
backupPath := filepath.Join(bm.backupDir, name)
backupFile := filepath.Join(backupPath, "data.backup")
f, err := os.Open(backupFile)
if err != nil {
return err
}
defer f.Close()
decoder := json.NewDecoder(f)
for {
var entry map[string]interface
if err := decoder.Decode(&entry); err != nil {
if err == io.EOF {
break
}
return err
}
key := []byte(entry["key"].(string))
value := []byte(entry["value"].(string))
bm.store.Put(context.Background(), key, value)
}
bm.logger.Info("backup restored", zap.String("path", backupPath))
return nil
}
// ListBackups lists all available backups
func (bm *BackupManager) ListBackups() ([]string, error) {
entries, err := os.ReadDir(bm.backupDir)
if err != nil {
return nil, err
}
var backups []string
for _, entry := range entries {
if entry.IsDir() {
backups = append(backups, entry.Name())
}
}
return backups, nil
}
// DeleteBackup deletes a backup
func (bm *BackupManager) DeleteBackup(name string) error {
backupPath := filepath.Join(bm.backupDir, name)
return os.RemoveAll(backupPath)
} Scheduled Backups
// ScheduledBackupManager handles periodic backups
type ScheduledBackupManager struct {
backupMgr *BackupManager
interval time.Duration
maxBackups int
ticker *time.Ticker
done chan struct
}
// Start begins scheduled backups
func (sbm *ScheduledBackupManager) Start() {
sbm.ticker = time.NewTicker(sbm.interval)
go func() {
for {
select {
case <-sbm.ticker.C:
// Create backup with timestamp
name := fmt.Sprintf("backup-%d", time.Now().Unix())
if err := sbm.backupMgr.CreateBackup(name); err != nil {
log.Printf("backup failed: %v", err)
continue
}
// Clean old backups
sbm.cleanOldBackups()
case <-sbm.done:
return
}
}
}()
}
// cleanOldBackups removes old backups
func (sbm *ScheduledBackupManager) cleanOldBackups() {
backups, _ := sbm.backupMgr.ListBackups()
if len(backups) > sbm.maxBackups {
// Sort by name (timestamp)
sort.Strings(backups)
// Delete oldest
for i := 0; i < len(backups)-sbm.maxBackups; i++ {
sbm.backupMgr.DeleteBackup(backups[i])
}
}
}
// Stop stops scheduled backups
func (sbm *ScheduledBackupManager) Stop() {
sbm.ticker.Stop()
close(sbm.done)
} Lab 13.1: Production Deployment
Objective
Build a complete production deployment pipeline with configuration management, containerization, orchestration, and backup strategies.
Requirements
- • Configuration Management: YAML/TOML configs with validation
- • Docker Containerization: Multi-stage builds, health checks
- • Kubernetes Deployment: Deployments, Services, ConfigMaps
- • Backup System: Point-in-time backups, scheduled backups
- • Monitoring: Health checks, metrics, logging
- • CI/CD Pipeline: Automated testing and deployment
Starter Code
// TODO: Implement configuration management
func LoadConfig(filepath string) (*Config, error) {
// Load YAML/TOML configuration
// Validate configuration
// Return parsed config
}
// TODO: Implement Docker health checks
func (s *Server) HealthCheck() bool {
// Check database connectivity
// Check replication status
// Check resource usage
return true
}
// TODO: Implement backup system
func (s *Server) CreateBackup() error {
// Create point-in-time backup
// Compress backup data
// Store backup metadata
return nil
}
// TODO: Implement graceful shutdown
func (s *Server) GracefulShutdown(timeout time.Duration) error {
// Stop accepting new connections
// Wait for active connections to finish
// Clean up resources
return nil
} Test Template
func TestConfiguration(t *testing.T) {
cfg, err := LoadConfig("test-config.yaml")
assert.NoError(t, err)
assert.NotNil(t, cfg)
// Test configuration validation
err = cfg.Validate()
assert.NoError(t, err)
// Test environment variable override
os.Setenv("KVDB_SERVER_PORT", "7000")
cfg, _ = LoadConfigFromEnv()
assert.Equal(t, 7000, cfg.Server.Port)
}
func TestDockerHealthCheck(t *testing.T) {
server := NewServer()
// Test health check
healthy := server.HealthCheck()
assert.True(t, healthy)
}
func TestBackupRestore(t *testing.T) {
store := NewStore()
backupMgr := NewBackupManager(store)
// Create test data
store.Put([]byte("key1"), []byte("value1"))
store.Put([]byte("key2"), []byte("value2"))
// Create backup
err := backupMgr.CreateBackup("test-backup")
assert.NoError(t, err)
// Clear store
store.Clear()
// Restore backup
err = backupMgr.RestoreBackup("test-backup")
assert.NoError(t, err)
// Verify data restored
value, _ := store.Get([]byte("key1"))
assert.Equal(t, "value1", string(value))
} Acceptance Criteria
- ✅ Configuration loads from YAML/TOML files
- ✅ Environment variables override config
- ✅ Docker image builds and runs correctly
- ✅ Kubernetes deployment works
- ✅ Health checks respond correctly
- ✅ Backup and restore functionality works
- ✅ Graceful shutdown handles connections
- ✅ Monitoring and logging integrated
- ✅ > 90% code coverage
- ✅ All tests pass
Summary: Week 13 Complete
By completing Week 13, you've learned and implemented:
1. Configuration Management
- • YAML and TOML configuration files
- • Environment variable overrides
- • Configuration validation
- • Default value handling
2. Docker Containerization
- • Multi-stage Docker builds
- • Docker Compose for local development
- • Health checks and monitoring
- • Security best practices
3. Kubernetes Deployment
- • Deployment and Service manifests
- • ConfigMaps for configuration
- • Resource limits and requests
- • Health and readiness probes
4. Backup and Recovery
- • Point-in-time backups
- • Scheduled backup management
- • Backup restoration
- • Backup cleanup strategies
Key Skills Mastered:
- ✅ Manage configuration with multiple formats
- ✅ Containerize applications with Docker
- ✅ Deploy to Kubernetes clusters
- ✅ Implement backup and recovery strategies
- ✅ Design production deployment pipelines
- ✅ Handle graceful shutdowns and upgrades
Ready for Week 14?
Next week we'll focus on security hardening, authentication, authorization, and production security best practices.
Continue to Week 14: Security and Hardening →