Production
Ce guide couvre les modèles essentiels pour l’exécution des services Goa en production, y compris l’observabilité, la sécurité et les modèles de déploiement courants.
Observabilité
Les systèmes distribués modernes nécessitent une observabilité complète. Goa recommande Clue, construit sur OpenTelemetry, pour l’observabilité. Cette section couvre les modèles communs ; pour une documentation complète de l’API, voir Clue Documentation.
Les trois piliers
- Traçage distribué : Suivre les demandes à travers votre système
- Mesures : Mesurer le comportement et les performances du système
- Journaux : Enregistrer des événements et des erreurs spécifiques
Configuration de Clue
import (
"goa.design/clue/clue"
"goa.design/clue/log"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
)
func main() {
// 1. Create logger
format := log.FormatJSON
if log.IsTerminal() {
format = log.FormatTerminal
}
ctx := log.Context(context.Background(),
log.WithFormat(format),
log.WithFunc(log.Span))
// 2. Configure OpenTelemetry exporters
spanExporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint(*collectorAddr),
otlptracegrpc.WithTLSCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf(ctx, err, "failed to initialize tracing")
}
metricExporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint(*collectorAddr),
otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf(ctx, err, "failed to initialize metrics")
}
// 3. Initialize Clue
cfg, err := clue.NewConfig(ctx,
genservice.ServiceName,
genservice.APIVersion,
metricExporter,
spanExporter)
clue.ConfigureOpenTelemetry(ctx, cfg)
}
Traçage distribué
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
func (s *Service) CreateOrder(ctx context.Context, order *Order) error {
// Start a span
ctx, span := otel.Tracer("service").Start(ctx, "create_order")
defer span.End()
// Add attributes
span.SetAttributes(
attribute.String("order.id", order.ID),
attribute.Float64("order.amount", order.Amount))
if err := s.processOrder(ctx, order); err != nil {
span.RecordError(err)
return err
}
return nil
}
Métriques
import (
"go.opentelemetry.io/otel/metric"
)
type Service struct {
orderCounter metric.Int64Counter
orderLatency metric.Float64Histogram
}
func NewService(meter metric.Meter) *Service {
counter, _ := meter.Int64Counter("orders_total",
metric.WithDescription("Total number of orders"))
latency, _ := meter.Float64Histogram("order_latency_seconds",
metric.WithDescription("Order processing latency"))
return &Service{
orderCounter: counter,
orderLatency: latency,
}
}
func (s *Service) CreateOrder(ctx context.Context, order *Order) error {
start := time.Now()
defer func() {
s.orderLatency.Record(ctx, time.Since(start).Seconds())
}()
s.orderCounter.Add(ctx, 1, attribute.String("type", order.Type))
// ...
}
Journalisation
import "goa.design/clue/log"
func (s *Service) CreateOrder(ctx context.Context, order *Order) error {
log.Info(ctx, "processing order",
log.KV{"order_id", order.ID},
log.KV{"amount", order.Amount})
if err := s.processOrder(ctx, order); err != nil {
log.Error(ctx, err, "failed to process order",
log.KV{"order_id", order.ID})
return err
}
return nil
}
Contrôles de santé
import "goa.design/clue/health"
func main() {
// Create health checker
checker := health.NewChecker(
health.NewPinger("database", dbHealthAddr),
health.NewPinger("cache", cacheHealthAddr),
)
// Mount health endpoint
http.Handle("/healthz", log.HTTP(ctx)(health.Handler(checker)))
}
Service complet observable
func main() {
ctx := log.Context(context.Background(), log.WithFormat(log.FormatJSON))
// Initialize OpenTelemetry
cfg, _ := clue.NewConfig(ctx, serviceName, version, metricExporter, spanExporter)
clue.ConfigureOpenTelemetry(ctx, cfg)
// Create service with middleware
svc := NewService()
endpoints := genservice.NewEndpoints(svc)
endpoints.Use(debug.LogPayloads())
endpoints.Use(log.Endpoint)
// Set up HTTP with observability middleware
mux := goahttp.NewMuxer()
mux.Use(otelhttp.NewMiddleware(serviceName))
mux.Use(debug.HTTP())
mux.Use(log.HTTP(ctx))
// Mount debug endpoints
debug.MountDebugLogEnabler(debug.Adapt(mux))
debug.MountPprofHandlers(debug.Adapt(mux))
// Mount health checks
http.Handle("/healthz", health.Handler(health.NewChecker(...)))
// Start server
server := &http.Server{Addr: ":8080", Handler: mux}
server.ListenAndServe()
}
Sécurité
Goa offre de solides dispositifs de sécurité par le biais de sa DSL.
Schémas de sécurité
Authentification de base
var BasicAuth = BasicAuthSecurity("basic", func() {
Description("Basic authentication using username and password")
})
Authentification par clé API
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("Secures endpoint by requiring an API key")
})
Authentification par JWT
var JWTAuth = JWTSecurity("jwt", func() {
Description("Secures endpoint by requiring a valid JWT token")
Scope("api:read", "Read access to API")
Scope("api:write", "Write access to API")
})
Authentification OAuth2
var OAuth2 = OAuth2Security("oauth2", func() {
Description("OAuth2 authentication")
AuthorizationCodeFlow("/authorize", "/token", "/refresh")
Scope("api:write", "Write access")
Scope("api:read", "Read access")
})
Application de la sécurité
La sécurité peut être appliquée au niveau de l’API, du service ou de la méthode :
// API level - default for all endpoints
var _ = API("myapi", func() {
Security(BasicAuth)
})
// Service level - override API default
var _ = Service("users", func() {
Security(APIKeyAuth)
Method("list", func() {
// Uses service-level APIKeyAuth
Payload(func() {
APIKey("api_key", "key", String)
Required("key")
})
})
Method("admin", func() {
// Override with JWT for this method
Security(JWTAuth)
Payload(func() {
Token("token", String)
Required("token")
})
})
Method("public", func() {
// No security for this method
NoSecurity()
})
})
Meilleures pratiques en matière de sécurité
- **Toujours utiliser HTTPS en production
- Définir la sécurité au niveau de l’API pour des valeurs par défaut cohérentes
- Utiliser
NoSecurity()explicitement pour les points d’extrémité publics - Mettre en place une limitation de débit pour l’authentification par clé d’API
- Utiliser une expiration de jeton appropriée pour les jetons JWT
- Effectuer une rotation régulière des secrets et des clés
- Enregistrer et surveiller les échecs d’authentification
- Valider toutes les entrées même pour les demandes authentifiées
Modèles communs
Arrêt en douceur
func main() {
ctx, cancel := context.WithCancel(context.Background())
// Create server
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// Start server in goroutine
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Errorf(ctx, err, "server error")
}
}()
// Wait for interrupt signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
// Graceful shutdown with timeout
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer shutdownCancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Errorf(ctx, err, "shutdown error")
}
cancel()
wg.Wait()
}
Gestion de la configuration
type Config struct {
HTTPAddr string `env:"HTTP_ADDR" default:":8080"`
GRPCAddr string `env:"GRPC_ADDR" default:":8081"`
DatabaseURL string `env:"DATABASE_URL" required:"true"`
LogLevel string `env:"LOG_LEVEL" default:"info"`
ReadTimeout time.Duration `env:"READ_TIMEOUT" default:"10s"`
WriteTimeout time.Duration `env:"WRITE_TIMEOUT" default:"30s"`
}
func main() {
var cfg Config
if err := envconfig.Process("", &cfg); err != nil {
log.Fatal(err)
}
server := &http.Server{
Addr: cfg.HTTPAddr,
Handler: mux,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
}
}
Délais d’attente du serveur
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 120 * time.Second,
MaxHeaderBytes: 1 << 20, // 1MB
}
Résumé
Les services Goa prêts à la production devraient inclure :
- Observabilité : Traçage, métriques, journalisation et contrôles de santé
- **Sécurité Authentification et autorisation appropriées
- Résilience : Arrêt progressif, délais d’attente et gestion des erreurs
- Configuration : Gestion de la configuration basée sur l’environnement
- Surveillance : Points d’extrémité de débogage et capacités de profilage
Ces modèles garantissent la fiabilité, la sécurité et la maintenance de vos services dans les environnements de production.
Voir aussi
- Clue Documentation - Boîte à outils d’observabilité complète avec référence API détaillée
- DSL Reference : Security - Définitions des schémas de sécurité
- Guide de gestion des erreurs - Modèles de gestion des erreurs et meilleures pratiques
- Intercepteurs - Modèles d’intergiciels et d’intercepteurs