diff --git a/Dockerfile b/Dockerfile index ec2d119..974c03c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,9 @@ LABEL org.opencontainers.image.source="https://github.com/powerman/go-monolith-e WORKDIR /app +HEALTHCHECK --interval=30s --timeout=5s \ + CMD wget -q -O - http://$HOSTNAME:17000/health-check || exit 1 + COPY . . ENTRYPOINT [ "bin/mono" ] diff --git a/README.md b/README.md index df43d58..9afbb53 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,6 @@ $ ./bin/mono serve ## TODO - [ ] Update srv/jsonrpc2 to use HTTP mux and CORS middleware. -- [ ] Add /health-check. - [ ] Add gRPC service example. - [ ] Add OpenAPI service example. - [ ] Add NATS/STAN publish/subscribe example. diff --git a/cmd/mono/main.go b/cmd/mono/main.go index 176b940..3525dfe 100644 --- a/cmd/mono/main.go +++ b/cmd/mono/main.go @@ -16,7 +16,7 @@ import ( "github.com/powerman/go-monolith-example/internal/config" "github.com/powerman/go-monolith-example/ms/example" - "github.com/powerman/go-monolith-example/ms/metrics" + "github.com/powerman/go-monolith-example/ms/mono" "github.com/powerman/go-monolith-example/pkg/cobrax" "github.com/powerman/go-monolith-example/pkg/concurrent" "github.com/powerman/go-monolith-example/pkg/def" @@ -34,7 +34,7 @@ type embeddedService interface { //nolint:gochecknoglobals // Main. var ( embeddedServices = []embeddedService{ - &metrics.Service{}, + &mono.Service{}, &example.Service{}, } diff --git a/env.sh.dist b/env.sh.dist index a0bf722..eb5ae3d 100644 --- a/env.sh.dist +++ b/env.sh.dist @@ -22,8 +22,8 @@ export mono_nats_addr_port="4222" # - used by 3rd-party libraries/frameworks/tools export GO_TEST_TIME_FACTOR="1.0" # Increase if tests fail because of slow CPU. export MONO_EXAMPLE_ADDR_PORT="17001" -export MONO_EXAMPLE_METRICS_ADDR_PORT="16001" -export MONO_METRICS_ADDR_PORT="16000" +export MONO_EXAMPLE_METRICS_ADDR_PORT="17002" +export MONO_MONO_ADDR_PORT="17000" export MONO_X_MYSQL_ADDR_HOST="localhost" export MONO_X_MYSQL_ADDR_PORT="${mono_mysql_addr_port}" export MONO_X_NATS_ADDR_URLS="nats://localhost:${mono_nats_addr_port}" diff --git a/internal/config/config.go b/internal/config/config.go index 5c6db55..2c0687d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,33 +20,28 @@ const EnvPrefix = "MONO_" // Shared contains configurable values shared by microservices. type Shared struct { + AddrHost appcfg.NotEmptyString `env:"ADDR_HOST"` + AddrHostInt appcfg.NotEmptyString `env:"ADDR_HOST_INT"` ExampleAddrPort appcfg.Port `env:"EXAMPLE_ADDR_PORT"` ExampleMetricsAddrPort appcfg.Port `env:"EXAMPLE_METRICS_ADDR_PORT"` - MonoAddrHost appcfg.NotEmptyString `env:"MONO_ADDR_HOST"` - MonoMetricsAddrHost appcfg.NotEmptyString `env:"MONO_METRICS_ADDR_HOST"` XMySQLAddrHost appcfg.NotEmptyString `env:"X_MYSQL_ADDR_HOST"` XMySQLAddrPort appcfg.Port `env:"X_MYSQL_ADDR_PORT"` XNATSAddrUrls appcfg.NotEmptyString `env:"X_NATS_ADDR_URLS"` XSTANClusterID appcfg.NotEmptyString `env:"X_STAN_CLUSTER_ID"` } -// Default ports for metrics. -const ( - MetricsPort = 16000 + iota // XXX Used in ms/metrics/service.go. - ExampleMetricsPort -) - // Default ports. const ( - _ = 17000 + iota + MonoPort = 17000 + iota ExamplePort + ExampleMetricsPort ) var shared = &Shared{ //nolint:gochecknoglobals // Config is global anyway. + AddrHost: appcfg.MustNotEmptyString(def.Hostname), + AddrHostInt: appcfg.MustNotEmptyString(def.Hostname), ExampleAddrPort: appcfg.MustPort(strconv.Itoa(ExamplePort)), ExampleMetricsAddrPort: appcfg.MustPort(strconv.Itoa(ExampleMetricsPort)), - MonoAddrHost: appcfg.MustNotEmptyString(def.Hostname), - MonoMetricsAddrHost: appcfg.MustNotEmptyString(def.Hostname), XMySQLAddrPort: appcfg.MustPort("3306"), } diff --git a/ms/example/internal/config/config.go b/ms/example/internal/config/config.go index 5693cb8..e90e4d4 100644 --- a/ms/example/internal/config/config.go +++ b/ms/example/internal/config/config.go @@ -81,10 +81,10 @@ func Init(sharedCfg *SharedCfg, flagsets FlagSets) error { appcfg.AddPFlag(fs.Serve, &own.MySQLName, pfx+"mysql.dbname", "MySQL database name") appcfg.AddPFlag(fs.Serve, &shared.XNATSAddrUrls, "nats.urls", "URLs to connect to NATS (separated by comma)") appcfg.AddPFlag(fs.Serve, &shared.XSTANClusterID, "stan.cluster_id", "STAN cluster ID") - appcfg.AddPFlag(fs.Serve, &shared.MonoMetricsAddrHost, "metrics.host", "host to serve Prometheus metrics") - appcfg.AddPFlag(fs.Serve, &shared.ExampleMetricsAddrPort, pfx+"metrics.port", "port to serve Prometheus metrics") - appcfg.AddPFlag(fs.Serve, &shared.MonoAddrHost, "host", "host to serve") + appcfg.AddPFlag(fs.Serve, &shared.AddrHost, "host", "host to serve") + appcfg.AddPFlag(fs.Serve, &shared.AddrHostInt, "host-int", "internal host to serve") appcfg.AddPFlag(fs.Serve, &shared.ExampleAddrPort, pfx+"port", "port to serve") + appcfg.AddPFlag(fs.Serve, &shared.ExampleMetricsAddrPort, pfx+"metrics.port", "port to serve Prometheus metrics") return nil } @@ -113,8 +113,8 @@ func GetServe() (c *ServeConfig, err error) { MySQLGooseDir: own.GooseDir.Value(&err), NATSURLs: shared.XNATSAddrUrls.Value(&err), STANClusterID: shared.XSTANClusterID.Value(&err), - Addr: netx.NewAddr(shared.MonoAddrHost.Value(&err), shared.ExampleAddrPort.Value(&err)), - MetricsAddr: netx.NewAddr(shared.MonoMetricsAddrHost.Value(&err), shared.ExampleMetricsAddrPort.Value(&err)), + Addr: netx.NewAddr(shared.AddrHost.Value(&err), shared.ExampleAddrPort.Value(&err)), + MetricsAddr: netx.NewAddr(shared.AddrHostInt.Value(&err), shared.ExampleMetricsAddrPort.Value(&err)), } if err != nil { return nil, appcfg.WrapPErr(err, fs.Serve, own, shared) diff --git a/ms/example/internal/config/config_test.go b/ms/example/internal/config/config_test.go index bc6fdee..18d67d2 100644 --- a/ms/example/internal/config/config_test.go +++ b/ms/example/internal/config/config_test.go @@ -68,8 +68,8 @@ func Test(t *testing.T) { "--nats.urls=nats://nats4:4222", "--stan.cluster_id=cluster4", "--host=host4", + "--host-int=metrics4", "--example.port=8004", - "--metrics.host=metrics4", "--example.metrics.port=4", ) t.Nil(err) diff --git a/ms/mono/health-check.go b/ms/mono/health-check.go new file mode 100644 index 0000000..0d06d95 --- /dev/null +++ b/ms/mono/health-check.go @@ -0,0 +1,7 @@ +package mono + +import "net/http" + +func (s *Service) serveHealthCheck(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("OK")) +} diff --git a/ms/metrics/metrics.go b/ms/mono/metrics.go similarity index 97% rename from ms/metrics/metrics.go rename to ms/mono/metrics.go index 19c6033..5d82618 100644 --- a/ms/metrics/metrics.go +++ b/ms/mono/metrics.go @@ -1,4 +1,4 @@ -package metrics +package mono import ( "runtime" diff --git a/ms/metrics/service.go b/ms/mono/service.go similarity index 68% rename from ms/metrics/service.go rename to ms/mono/service.go index a7de57b..dcc32f2 100644 --- a/ms/metrics/service.go +++ b/ms/mono/service.go @@ -1,8 +1,9 @@ -// Package metrics provides embedded microservice. -package metrics +// Package mono provides embedded microservice. +package mono import ( "context" + "net/http" "regexp" "strconv" @@ -27,9 +28,9 @@ var ( fs *pflag.FlagSet shared *config.Shared own = &struct { - MetricsPort appcfg.Port `env:"METRICS_ADDR_PORT"` + Port appcfg.Port `env:"MONO_ADDR_PORT"` }{ - MetricsPort: appcfg.MustPort(strconv.Itoa(config.MetricsPort)), + Port: appcfg.MustPort(strconv.Itoa(config.MonoPort)), } reg = prometheus.NewPedanticRegistry() @@ -38,12 +39,13 @@ var ( // Service implements main.embeddedService interface. type Service struct { cfg struct { - metricsAddr netx.Addr + addr netx.Addr } + mux *http.ServeMux } // Name implements main.embeddedService interface. -func (s *Service) Name() string { return "metrics" } +func (s *Service) Name() string { return "mono" } // Init implements main.embeddedService interface. func (s *Service) Init(sharedCfg *config.Shared, _, serveCmd *cobra.Command) error { @@ -53,21 +55,26 @@ func (s *Service) Init(sharedCfg *config.Shared, _, serveCmd *cobra.Command) err fs, shared = serveCmd.Flags(), sharedCfg fromEnv := appcfg.NewFromEnv(config.EnvPrefix) err := appcfg.ProvideStruct(own, fromEnv) - appcfg.AddPFlag(fs, &shared.MonoMetricsAddrHost, "metrics.host", "host to serve Prometheus metrics") - appcfg.AddPFlag(fs, &own.MetricsPort, "metrics.port", "port to serve Prometheus metrics") + pfx := s.Name() + "." + appcfg.AddPFlag(fs, &shared.AddrHostInt, "host-int", "internal host to serve") + appcfg.AddPFlag(fs, &own.Port, pfx+"port", "port to serve monolith introspection") return err } // RunServe implements main.embeddedService interface. func (s *Service) RunServe(_, ctxShutdown Ctx, shutdown func()) (err error) { log := structlog.FromContext(ctxShutdown, nil) - s.cfg.metricsAddr = netx.NewAddr(shared.MonoMetricsAddrHost.Value(&err), own.MetricsPort.Value(&err)) + s.cfg.addr = netx.NewAddr(shared.AddrHostInt.Value(&err), own.Port.Value(&err)) if err != nil { return log.Err("failed to get config", "err", appcfg.WrapPErr(err, fs, shared, own)) } + s.mux = http.NewServeMux() + serve.HandleMetrics(s.mux, reg) + s.mux.Handle("/health-check", http.HandlerFunc(s.serveHealthCheck)) + err = concurrent.Serve(ctxShutdown, shutdown, - s.serveMetrics, + s.serveHTTP, ) if err != nil { return log.Err("failed to serve", "err", err) @@ -75,6 +82,6 @@ func (s *Service) RunServe(_, ctxShutdown Ctx, shutdown func()) (err error) { return nil } -func (s *Service) serveMetrics(ctx Ctx) error { - return serve.Metrics(ctx, s.cfg.metricsAddr, reg) +func (s *Service) serveHTTP(ctx Ctx) error { + return serve.HTTP(ctx, s.cfg.addr, s.mux, "monolith introspection") } diff --git a/pkg/serve/serve.go b/pkg/serve/serve.go index f770fd7..7ad5b02 100644 --- a/pkg/serve/serve.go +++ b/pkg/serve/serve.go @@ -48,12 +48,17 @@ func HTTP(ctx Ctx, addr netx.Addr, handler http.Handler, service string) error { // Metrics starts HTTP server on addr path /metrics using reg as // prometheus handler. func Metrics(ctx Ctx, addr netx.Addr, reg *prometheus.Registry) error { - handler := promhttp.InstrumentMetricHandler(reg, promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) mux := http.NewServeMux() - mux.Handle("/metrics", handler) + HandleMetrics(mux, reg) return HTTP(ctx, addr, mux, "Prometheus metrics") } +// HandleMetrics adds reg's prometheus handler on /metrics at mux. +func HandleMetrics(mux *http.ServeMux, reg *prometheus.Registry) { + handler := promhttp.InstrumentMetricHandler(reg, promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) + mux.Handle("/metrics", handler) +} + // RPC starts HTTP server on addr path /rpc using rcvr as JSON-RPC 2.0 // handler. func RPC(ctx Ctx, addr netx.Addr, rcvr interface{}) error {