- Add gRPC health check support to Consul registry - Services are gRPC-only, not HTTP - Consul was trying HTTP health checks which failed - Now uses gRPC health checks via grpc.health.v1.Health service - Update HealthCheckConfig to support both HTTP and gRPC - Add GRPC field for gRPC service name - Add UseGRPC flag to choose health check type - Default to gRPC for services (use_grpc: true in config) - Fix service address registration in Docker - Services now register with Docker service name (e.g., auth-service) - Allows Consul to reach services via Docker network DNS - Falls back to localhost for local development - Update default.yaml to enable gRPC health checks - Set use_grpc: true - Set grpc: grpc.health.v1.Health This fixes services being deregistered from Consul due to failed HTTP health checks. Services will now pass gRPC health checks.
238 lines
6.0 KiB
Go
238 lines
6.0 KiB
Go
// Package main provides the entry point for the Auth Service.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.dcentral.systems/toolz/goplt/internal/client"
|
|
"git.dcentral.systems/toolz/goplt/internal/di"
|
|
healthpkg "git.dcentral.systems/toolz/goplt/internal/health"
|
|
"git.dcentral.systems/toolz/goplt/internal/infra/database"
|
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
"git.dcentral.systems/toolz/goplt/pkg/registry"
|
|
"git.dcentral.systems/toolz/goplt/pkg/services"
|
|
"go.uber.org/fx"
|
|
"go.uber.org/zap"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
// grpcServerWrapper wraps the gRPC server for lifecycle management.
|
|
type grpcServerWrapper struct {
|
|
server *grpc.Server
|
|
listener net.Listener
|
|
port int
|
|
logger logger.Logger
|
|
}
|
|
|
|
func (s *grpcServerWrapper) Start() error {
|
|
s.logger.Info("Starting Auth Service gRPC server",
|
|
zap.Int("port", s.port),
|
|
zap.String("addr", s.listener.Addr().String()),
|
|
)
|
|
|
|
errChan := make(chan error, 1)
|
|
go func() {
|
|
if err := s.server.Serve(s.listener); err != nil {
|
|
errChan <- err
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case err := <-errChan:
|
|
return fmt.Errorf("gRPC server failed to start: %w", err)
|
|
case <-time.After(100 * time.Millisecond):
|
|
s.logger.Info("Auth Service gRPC server started successfully",
|
|
zap.Int("port", s.port),
|
|
)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
|
|
s.logger.Info("Stopping Auth Service gRPC server")
|
|
|
|
stopped := make(chan struct{})
|
|
go func() {
|
|
s.server.GracefulStop()
|
|
close(stopped)
|
|
}()
|
|
|
|
select {
|
|
case <-stopped:
|
|
s.logger.Info("Auth Service gRPC server stopped gracefully")
|
|
return nil
|
|
case <-ctx.Done():
|
|
s.logger.Warn("Auth Service gRPC server stop timeout, forcing stop")
|
|
s.server.Stop()
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
|
|
func (s *grpcServerWrapper) Port() int {
|
|
return s.port
|
|
}
|
|
|
|
func main() {
|
|
// Create DI container
|
|
// Note: CoreModule() is automatically included by NewContainer()
|
|
container := di.NewContainer(
|
|
// Database for auth service (auth schema)
|
|
fx.Provide(func(cfg config.ConfigProvider, log logger.Logger) (*database.Client, error) {
|
|
dsn := cfg.GetString("database.dsn")
|
|
if dsn == "" {
|
|
return nil, fmt.Errorf("database.dsn is required")
|
|
}
|
|
client, err := database.NewClientWithSchema(dsn, "auth")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Run migrations
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if err := client.Migrate(ctx); err != nil {
|
|
log.Warn("Failed to run migrations",
|
|
zap.Error(err),
|
|
)
|
|
} else {
|
|
log.Info("Database migrations completed for auth service")
|
|
}
|
|
|
|
return client, nil
|
|
}),
|
|
|
|
// Register database health checker with existing health registry
|
|
fx.Invoke(func(registry *healthpkg.Registry, db *database.Client) {
|
|
registry.Register("database", healthpkg.NewDatabaseChecker(db))
|
|
}),
|
|
|
|
// Identity Service client
|
|
fx.Provide(func(factory *client.ServiceClientFactory) (services.IdentityServiceClient, error) {
|
|
return factory.GetIdentityClient()
|
|
}),
|
|
|
|
// Provide auth service and gRPC server (defined in auth_service_fx.go)
|
|
provideAuthService(),
|
|
|
|
// Lifecycle hooks
|
|
fx.Invoke(registerLifecycle),
|
|
)
|
|
|
|
// Create root context
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Handle signals
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
|
|
// Start the application
|
|
if err := container.Start(ctx); err != nil {
|
|
log := logger.GetGlobalLogger()
|
|
if log != nil {
|
|
log.Error("Failed to start Auth Service",
|
|
logger.Error(err),
|
|
)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "Failed to start Auth Service: %v\n", err)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Wait for interrupt signal
|
|
<-sigChan
|
|
fmt.Println("\nShutting down Auth Service...")
|
|
|
|
// Create shutdown context with timeout
|
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer shutdownCancel()
|
|
|
|
// Stop the application
|
|
if err := container.Stop(shutdownCtx); err != nil {
|
|
log := logger.GetGlobalLogger()
|
|
if log != nil {
|
|
log.Error("Error during Auth Service shutdown",
|
|
logger.Error(err),
|
|
)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("Auth Service stopped successfully")
|
|
}
|
|
|
|
// registerLifecycle registers lifecycle hooks for the service.
|
|
func registerLifecycle(
|
|
lc fx.Lifecycle,
|
|
grpcServer *grpcServerWrapper,
|
|
serviceRegistry registry.ServiceRegistry,
|
|
cfg config.ConfigProvider,
|
|
log logger.Logger,
|
|
) {
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(ctx context.Context) error {
|
|
// Start gRPC server
|
|
if err := grpcServer.Start(); err != nil {
|
|
return fmt.Errorf("failed to start gRPC server: %w", err)
|
|
}
|
|
|
|
// Register with service registry
|
|
serviceID := fmt.Sprintf("auth-service-%d", time.Now().Unix())
|
|
host := cfg.GetString("services.auth.host")
|
|
if host == "" {
|
|
// In Docker, use service name for Consul to reach the service
|
|
// For local development, use localhost
|
|
if os.Getenv("ENVIRONMENT") == "production" || os.Getenv("DOCKER") == "true" {
|
|
host = "auth-service" // Docker service name
|
|
} else {
|
|
host = "localhost"
|
|
}
|
|
}
|
|
port := grpcServer.Port()
|
|
|
|
instance := ®istry.ServiceInstance{
|
|
ID: serviceID,
|
|
Name: "auth-service",
|
|
Address: host,
|
|
Port: port,
|
|
Tags: []string{"grpc", "auth"},
|
|
Metadata: map[string]string{
|
|
"version": "1.0.0",
|
|
"protocol": "grpc",
|
|
},
|
|
}
|
|
|
|
if err := serviceRegistry.Register(ctx, instance); err != nil {
|
|
log.Warn("Failed to register with service registry",
|
|
zap.Error(err),
|
|
)
|
|
} else {
|
|
log.Info("Registered Auth Service with service registry",
|
|
zap.String("service_id", serviceID),
|
|
zap.String("name", instance.Name),
|
|
zap.Int("port", port),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
OnStop: func(ctx context.Context) error {
|
|
// Stop gRPC server
|
|
if err := grpcServer.Stop(ctx); err != nil {
|
|
return fmt.Errorf("failed to stop gRPC server: %w", err)
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
}
|