- 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.
231 lines
5.8 KiB
Go
231 lines
5.8 KiB
Go
// Package main provides the entry point for the Identity Service.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"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"
|
|
"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 Identity 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("Identity Service gRPC server started successfully",
|
|
zap.Int("port", s.port),
|
|
)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *grpcServerWrapper) Stop(ctx context.Context) error {
|
|
s.logger.Info("Stopping Identity Service gRPC server")
|
|
|
|
stopped := make(chan struct{})
|
|
go func() {
|
|
s.server.GracefulStop()
|
|
close(stopped)
|
|
}()
|
|
|
|
select {
|
|
case <-stopped:
|
|
s.logger.Info("Identity Service gRPC server stopped gracefully")
|
|
return nil
|
|
case <-ctx.Done():
|
|
s.logger.Warn("Identity 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 identity service (identity 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, "identity")
|
|
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 identity 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))
|
|
}),
|
|
|
|
// Provide identity service and gRPC server (defined in identity_service_fx.go)
|
|
provideIdentityService(),
|
|
|
|
// 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 Identity Service",
|
|
logger.Error(err),
|
|
)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "Failed to start Identity Service: %v\n", err)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Wait for interrupt signal
|
|
<-sigChan
|
|
fmt.Println("\nShutting down Identity 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 Identity Service shutdown",
|
|
logger.Error(err),
|
|
)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("Identity 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("identity-service-%d", time.Now().Unix())
|
|
host := cfg.GetString("services.identity.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 = "identity-service" // Docker service name
|
|
} else {
|
|
host = "localhost"
|
|
}
|
|
}
|
|
port := grpcServer.Port()
|
|
|
|
instance := ®istry.ServiceInstance{
|
|
ID: serviceID,
|
|
Name: "identity-service",
|
|
Address: host,
|
|
Port: port,
|
|
Tags: []string{"grpc", "identity"},
|
|
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 Identity 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
|
|
},
|
|
})
|
|
}
|