fleet docker

This commit is contained in:
Ravi Prasad
2025-05-01 00:16:24 -05:00
parent 3a87b594de
commit 9d97283f82
10 changed files with 2543 additions and 40 deletions
+3
View File
@@ -0,0 +1,3 @@
.git
.idea
*.log
+30
View File
@@ -0,0 +1,30 @@
# STEP 1 - build with CGO enabled
FROM golang:1.22-bullseye AS builder
ENV CGO_ENABLED=1
ENV GOOS=linux
ENV GOARCH=arm64
WORKDIR /app
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . .
RUN go build -o /nba_go main.go
# STEP 2 - final image
FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
# Create data directory
RUN mkdir -p /app/data
COPY --from=builder /nba_go /nba_go
EXPOSE 5000
CMD ["/nba_go"]
+1 -1
View File
@@ -8,7 +8,7 @@ import (
)
func InitDB() *gorm.DB {
db, err := gorm.Open(sqlite.Open("/Users/ravi/code/projects/NBA_Go/data/nba.db"), &gorm.Config{})
db, err := gorm.Open(sqlite.Open("/app/data/nba.db"), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect database")
}
+85
View File
@@ -0,0 +1,85 @@
version: '3.8'
services:
db-init:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./data:/app/data
command: "/nba_go import-data"
api1:
volumes:
- ./data:/app/data
build: .
ports:
- "5001:5000"
restart: always
depends_on:
db-init:
condition: service_completed_successfully
networks:
- api.network
# Add the same depends_on configuration for api2 and api3
api2:
volumes:
- ./data:/app/data
build: .
ports:
- "5002:5000"
restart: always
depends_on:
db-init:
condition: service_completed_successfully
networks:
- api.network
api3:
volumes:
- ./data:/app/data
build: .
ports:
- "5003:5000"
restart: always
depends_on:
db-init:
condition: service_completed_successfully
networks:
- api.network
# Nginx load balancer
nginx:
image: nginx:stable
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api1
- api2
- api3
ports:
- "8080:8080"
networks:
- api.network
# Prometheus for metrics
prometheus:
image: prom/prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
networks:
- api.network
# Grafana for dashboards
grafana:
image: grafana/grafana
ports:
- "3001:3000"
networks:
- api.network
networks:
api.network:
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/nprasad2077/NBA_Go
go 1.24.2
go 1.20
require (
github.com/KyleBanks/depth v1.2.1 // indirect
+47 -34
View File
@@ -2,56 +2,69 @@ package main
import (
"log"
"os"
"time"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2"
"github.com/nprasad2077/NBA_Go/config"
"github.com/nprasad2077/NBA_Go/routes"
"github.com/nprasad2077/NBA_Go/config"
"github.com/nprasad2077/NBA_Go/routes"
"github.com/nprasad2077/NBA_Go/services"
_"github.com/nprasad2077/NBA_Go/docs" // swag docs
fiberswagger "github.com/swaggo/fiber-swagger"
fiberswagger "github.com/swaggo/fiber-swagger"
"gorm.io/gorm"
)
func main() {
app := fiber.New()
// importData handles fetching and storing all player stats
func importData(db *gorm.DB) {
log.Println("Starting data import process...")
// Import advanced stats
for season := 2020; season <= 2025; season++ {
if err := services.FetchAndStorePlayerAdvancedStats(db, season); err != nil {
log.Printf("Fetch failed for player advanced season %d: %v\n", season, err)
} else {
log.Printf("Fetch successful for player advanced season %d\n", season)
}
time.Sleep(1100 * time.Millisecond) // optional delay
}
log.Printf("Player advanced import completed")
// Import total stats
for season := 2020; season <= 2025; season++ {
if err := services.FetchAndStorePlayerTotalStats(db, season); err != nil {
log.Printf("Fetch failed for player totals season %d: %v\n", season, err)
} else {
log.Printf("Fetch successful for player totals season %d\n", season)
}
time.Sleep(1000 * time.Millisecond) // optional delay
}
log.Printf("Player totals import completed")
log.Println("Data import process completed successfully!")
}
func main() {
// Check if we're running in import-data mode
if len(os.Args) > 1 && os.Args[1] == "import-data" {
db := config.InitDB()
importData(db)
return // Exit after import is complete
}
// Regular API server mode
app := fiber.New()
app.Use(logger.New())
db := config.InitDB()
// Automatically fetch player stats on startup
go func() {
for season := 1993; season <= 2025; season++ {
if err := services.FetchAndStorePlayerAdvancedStats(db, season); err != nil {
log.Printf("Fetch failed for player advanced season %d: %v\n", season, err)
} else {
log.Printf("Fetch successful for player advanced season %d\n", season)
}
time.Sleep(1100 * time.Millisecond) // optional delay
}
log.Printf("player advanced Import Success")
}()
go func() {
for season := 1993; season <= 2025; season++ {
if err := services.FetchAndStorePlayerTotalStats(db, season); err != nil {
log.Printf("Fetch failed for player totals season %d: %v\n", season, err)
} else {
log.Printf("Fetch successful for player totals season %d\n", season)
}
time.Sleep(1000 * time.Millisecond) // optional delay
}
log.Printf("player totals Import Success")
}()
// No automatic data fetching in API server mode
// Only the db-init container will handle data imports
routes.RegisterPlayerAdvancedRoutes(app, db)
routes.RegisterPlayerTotalRoutes(app, db)
app.Get("/swagger/*", fiberswagger.WrapHandler)
app.Listen(":3001")
log.Println("Starting API server on port 5000...")
app.Listen(":5000")
}
+32
View File
@@ -0,0 +1,32 @@
user nginx;
events {
worker_connections 1024;
}
http {
upstream go_backend {
server api1:5000;
server api2:5000;
server api3:5000;
}
server {
listen 8080;
access_log /dev/stdout;
error_log /dev/stderr;
location / {
proxy_pass http://go_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
global:
scrape_interval: 5s
scrape_configs:
- job_name: 'nba_go_api'
metrics_path: /metrics
static_configs:
- targets: ['nginx:8080']
+132
View File
@@ -0,0 +1,132 @@
package main
import (
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
"log"
"flag"
"os"
)
// Response struct to track which server handled the request
type Response struct {
StatusCode int
Body string
Duration time.Duration
Error error
}
func main() {
// Define command line flags
numRequests := flag.Int("n", 100, "Number of requests to send")
concurrency := flag.Int("c", 10, "Number of concurrent requests")
endpoint := flag.String("url", "http://localhost:8080/api/player/advanced", "API endpoint to test")
logFile := flag.String("log", "loadtest.log", "Log file path")
flag.Parse()
// Setup logging
f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Error opening log file: %v", err)
}
defer f.Close()
log.SetOutput(f)
fmt.Printf("Starting load test with %d requests, %d concurrent\n", *numRequests, *concurrency)
fmt.Printf("Testing endpoint: %s\n", *endpoint)
// Channel to collect results
results := make(chan Response, *numRequests)
// Use a WaitGroup to manage concurrency
var wg sync.WaitGroup
// Semaphore to limit concurrency
sem := make(chan bool, *concurrency)
// Start the timer
startTime := time.Now()
// Launch goroutines for requests
for i := 0; i < *numRequests; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Acquire semaphore
sem <- true
defer func() { <-sem }()
// Make the request
start := time.Now()
resp, err := http.Get(*endpoint)
duration := time.Since(start)
result := Response{
Duration: duration,
Error: err,
}
if err != nil {
log.Printf("Request %d failed: %v", id, err)
results <- result
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Failed to read response body: %v", err)
result.Error = err
results <- result
return
}
result.StatusCode = resp.StatusCode
result.Body = string(body)
results <- result
// Log request details
log.Printf("Request %d: Status=%d, Time=%v",
id, resp.StatusCode, duration)
}(i)
}
// Close the results channel when all requests are done
go func() {
wg.Wait()
close(results)
}()
// Process results
var successCount, errorCount int
var totalDuration time.Duration
for result := range results {
if result.Error != nil {
errorCount++
} else if result.StatusCode == 200 {
successCount++
totalDuration += result.Duration
} else {
errorCount++
}
}
// Calculate statistics
totalTime := time.Since(startTime)
avgDuration := totalDuration / time.Duration(successCount)
requestsPerSecond := float64(*numRequests) / totalTime.Seconds()
// Print summary
fmt.Printf("\nLoad Test Summary:\n")
fmt.Printf("Total Requests: %d\n", *numRequests)
fmt.Printf("Successful Requests: %d\n", successCount)
fmt.Printf("Failed Requests: %d\n", errorCount)
fmt.Printf("Total Time: %v\n", totalTime)
fmt.Printf("Average Response Time: %v\n", avgDuration)
fmt.Printf("Requests Per Second: %.2f\n", requestsPerSecond)
}
+2200
View File
File diff suppressed because it is too large Load Diff