Deployment Strategy - yuzvak/flashsale-service GitHub Wiki
Deployment Strategy
Overview
The flash sale service is containerized using Docker with a multi-stage build process and includes comprehensive orchestration for development, testing, and production environments.
Docker Configuration
Multi-Stage Dockerfile
# Build stage
FROM golang:1.24-alpine AS builder
WORKDIR /app
# Install dependencies
RUN apk add --no-cache git
# Copy dependency files
COPY go.mod ./
COPY go.sum ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o flashsale ./cmd/server
# Final stage
FROM alpine:latest
WORKDIR /app
# Install runtime dependencies
RUN apk --no-cache add ca-certificates tzdata
# Copy binary from builder stage
COPY --from=builder /app/flashsale /app/
# Copy migrations
COPY --from=builder /app/migrations /app/migrations
# Copy config file
COPY --from=builder /app/config.json /app/
# Expose port
EXPOSE 8080
# Run the application
CMD ["/app/flashsale"]
Build Optimizations:
- Multi-stage build reduces final image size
- Static binary compilation (CGO_ENABLED=0)
- Alpine Linux base for security and size
- Essential runtime dependencies only
Docker Compose Orchestration
Core Services
services:
app:
build: .
ports:
- "${SERVER_PORT:-8080}:${SERVER_PORT:-8080}"
environment:
- SERVER_HOST=${SERVER_HOST:-0.0.0.0}
- SERVER_PORT=${SERVER_PORT:-8080}
- DB_HOST=${DB_HOST:-postgres}
- DB_PORT=${DB_PORT:-5432}
- DB_USER=${DB_USER:-postgres}
- DB_PASSWORD=${DB_PASSWORD:-postgres}
- DB_NAME=${DB_NAME:-flashsale}
- DB_SSLMODE=${DB_SSLMODE:-disable}
- REDIS_HOST=${REDIS_HOST:-redis}
- REDIS_PORT=${REDIS_PORT:-6379}
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
- REDIS_DB=${REDIS_DB:-0}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- flashsale-network
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=${DB_USER:-postgres}
- POSTGRES_PASSWORD=${DB_PASSWORD:-postgres}
- POSTGRES_DB=${DB_NAME:-flashsale}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d
networks:
- flashsale-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
interval: 30s
timeout: 10s
retries: 3
ports:
- "5432:5432"
redis:
image: redis:7-alpine
command: redis-server --requirepass "${REDIS_PASSWORD:-}"
volumes:
- redis-data:/data
networks:
- flashsale-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
ports:
- "6379:6379"
volumes:
postgres-data:
redis-data:
networks:
flashsale-network:
driver: bridge
Configuration Management
Environment Variables
# Server Configuration
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=your_secure_password_here
DB_NAME=flashsale
DB_SSLMODE=disable
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password_here
REDIS_DB=0
JSON Configuration
{
"server": {
"host": "0.0.0.0",
"port": 8080
},
"database": {
"host": "postgres",
"port": 5432,
"user": "postgres",
"password": "postgres",
"dbname": "flashsale",
"sslmode": "disable",
"migrations_path": "migrations"
},
"redis": {
"host": "redis",
"port": 6379,
"password": "",
"db": 0
}
}
Configuration Loading
func LoadConfig(path string) (*Config, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var config Config
decoder := json.NewDecoder(file)
if err := decoder.Decode(&config); err != nil {
return nil, err
}
return &config, nil
}
Database Migration Strategy
Automated Migration Runner
func RunMigrations(cfg config.DatabaseConfig) error {
db, err := sql.Open("postgres", connStr)
if err != nil {
return err
}
defer db.Close()
// Create migrations tracking table
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS migrations (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return err
}
// Get applied migrations
rows, err := db.Query("SELECT name FROM migrations")
if err != nil {
return err
}
defer rows.Close()
appliedMigrations := make(map[string]bool)
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return err
}
appliedMigrations[name] = true
}
// Apply new migrations
files, err := os.ReadDir(cfg.MigrationsPath)
if err != nil {
return err
}
var migrations []string
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".up.sql") {
migrations = append(migrations, file.Name())
}
}
sort.Strings(migrations)
for _, migration := range migrations {
if appliedMigrations[migration] {
continue
}
filePath := filepath.Join(cfg.MigrationsPath, migration)
content, err := os.ReadFile(filePath)
if err != nil {
return err
}
tx, err := db.Begin()
if err != nil {
return err
}
_, err = tx.Exec(string(content))
if err != nil {
tx.Rollback()
return err
}
_, err = tx.Exec("INSERT INTO migrations (name) VALUES ($1)", migration)
if err != nil {
tx.Rollback()
return err
}
if err := tx.Commit(); err != nil {
return err
}
fmt.Printf("Applied migration: %s\n", migration)
}
return nil
}
Health Checks & Readiness
Health Check Endpoint
type HealthData struct {
ServicesStatus ServicesStatus `json:"services_status"`
Uptime string `json:"uptime"`
Memory MemoryMetrics `json:"memory"`
Goroutines int `json:"goroutines"`
}
func (h *HealthHandler) HandleHealth() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
dbStatus := "UP"
if err := h.db.Ping(); err != nil {
dbStatus = "DOWN"
}
redisStatus := "UP"
if err := h.redis.Ping(r.Context()).Err(); err != nil {
redisStatus = "DOWN"
}
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
data := HealthData{
ServicesStatus: ServicesStatus{
App: "UP",
Database: dbStatus,
Redis: redisStatus,
},
Uptime: time.Since(h.startTime).String(),
Memory: MemoryMetrics{
Alloc: mem.Alloc,
TotalAlloc: mem.TotalAlloc,
Sys: mem.Sys,
NumGC: mem.NumGC,
},
Goroutines: runtime.NumGoroutine(),
}
response.WriteSuccess(w, data)
}
}
Docker Health Checks
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
interval: 30s
timeout: 10s
retries: 3
Monitoring Stack Deployment
Prometheus & Grafana
# monitoring/docker-compose.yml
services:
prometheus:
image: prom/prometheus:v2.30.3
container_name: prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
ports:
- "9090:9090"
restart: unless-stopped
networks:
- monitoring
grafana:
image: grafana/grafana:8.2.2
container_name: grafana
volumes:
- ./grafana-dashboards:/etc/grafana/provisioning/dashboards
- ./grafana-datasources:/etc/grafana/provisioning/datasources
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
ports:
- "3000:3000"
restart: unless-stopped
networks:
- monitoring
depends_on:
- prometheus
Build & Deployment Automation
Makefile Commands
# Build commands
build:
go build -o flashsale ./cmd/server
docker-build:
docker build -t flashsale-service .
# Deployment commands
docker-run:
docker-compose up -d
docker-stop:
docker-compose down
docker-logs:
docker-compose logs -f
# Monitoring
run-monitoring:
cd monitoring && docker-compose up -d
# Load testing
load-test:
go run ./scripts/load-testing/run_test_load.go
realistic-test:
go run ./scripts/load-testing/run_test_realistic_load.go
Application Startup Sequence
Main Application Bootstrap
func main() {
configPath := flag.String("config", "config.json", "Path to configuration file")
flag.Parse()
log := logger.NewLogger()
log.Info("Starting Flash Sale Service")
// Load configuration
cfg, err := config.LoadConfig(*configPath)
if err != nil {
log.Fatal("Failed to load configuration", "error", err)
}
// Database connection
db, err := postgres.NewConnection(cfg.Database)
if err != nil {
log.Fatal("Failed to connect to database", "error", err)
}
defer db.Close()
// Run migrations
if err := postgres.RunMigrations(cfg.Database); err != nil {
log.Fatal("Failed to run migrations", "error", err)
}
// Redis connection
redisClient, err := redis.NewConnection(cfg.Redis)
if err != nil {
log.Fatal("Failed to connect to Redis", "error", err)
}
defer redisClient.Close()
// Start metrics collection
dbMetricsCollector := monitoring.NewDBMetricsCollector(db.GetDB())
dbMetricsCollector.StartCollecting(context.Background(), 30*time.Second)
// Initialize repositories and services
saleRepo := postgres.NewSaleRepository(db)
saleScheduler := scheduler.NewSaleScheduler(saleRepo, log, 10000)
// Start HTTP server
httpServer := server.NewServer(cfg, db.GetDB(), redisClient, log)
// Graceful shutdown handling
serverCtx, serverStopCtx := context.WithCancel(context.Background())
go saleScheduler.Start(serverCtx)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
<-sigChan
shutdownCtx, _ := context.WithTimeout(serverCtx, 30*time.Second)
log.Info("Shutting down server...")
saleScheduler.Stop()
if err := httpServer.Shutdown(shutdownCtx); err != nil {
log.Error("Server shutdown error", "error", err)
}
serverStopCtx()
}()
log.Info("Server starting", "address", fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port))
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("Server failed", "error", err)
}
<-serverCtx.Done()
log.Info("Server stopped")
}
Security Considerations
Container Security
- Non-root user in containers
- Read-only filesystem where possible
- Minimal attack surface with Alpine Linux
- Regular security updates for base images
Network Security
- Internal Docker networks for service communication
- Exposed ports only where necessary
- Environment variable injection for secrets
- No hardcoded credentials in images
Data Security
- TLS connections for database (configurable)
- Redis AUTH when required
- Input validation and sanitization
- SQL injection prevention with prepared statements
Production Deployment Checklist
- Environment variables configured
- Database migrations applied
- Health checks responding
- Monitoring stack deployed
- Log aggregation configured
- Backup strategy implemented
- Load testing completed
- Security scan passed
- Performance benchmarks met