Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add /health-check #3

Merged
merged 2 commits into from
Oct 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions cmd/mono/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -34,7 +34,7 @@ type embeddedService interface {
//nolint:gochecknoglobals // Main.
var (
embeddedServices = []embeddedService{
&metrics.Service{},
&mono.Service{},
&example.Service{},
}

Expand Down
4 changes: 2 additions & 2 deletions env.sh.dist
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export mono_nats_addr_port="4222"
# <OTHER> - 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}"
Expand Down
17 changes: 6 additions & 11 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}

Expand Down
10 changes: 5 additions & 5 deletions ms/example/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion ms/example/internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions ms/mono/health-check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mono

import "net/http"

func (s *Service) serveHealthCheck(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("OK"))
}
2 changes: 1 addition & 1 deletion ms/metrics/metrics.go → ms/mono/metrics.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package metrics
package mono

import (
"runtime"
Expand Down
31 changes: 19 additions & 12 deletions ms/metrics/service.go → ms/mono/service.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Package metrics provides embedded microservice.
package metrics
// Package mono provides embedded microservice.
package mono

import (
"context"
"net/http"
"regexp"
"strconv"

Expand All @@ -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()
Expand All @@ -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 {
Expand All @@ -53,28 +55,33 @@ 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)
}
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")
}
9 changes: 7 additions & 2 deletions pkg/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down