Skip to content

Commit 068038d

Browse files
committed
[health] add internal healthcheck command
1 parent 35e685f commit 068038d

File tree

5 files changed

+127
-10
lines changed

5 files changed

+127
-10
lines changed

Dockerfile.goreleaser

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ARG TARGETPLATFORM
44
ARG BINARY_NAME
55

66
# Install certificates and timezone data
7-
RUN apk add --no-cache ca-certificates tzdata curl
7+
RUN apk add --no-cache ca-certificates tzdata
88

99
# Set the Current Working Directory inside the container
1010
WORKDIR /app
@@ -21,7 +21,7 @@ USER guest
2121
ENTRYPOINT ["/docker-entrypoint.sh"]
2222

2323
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
24-
CMD curl -fs http://localhost:3000/health/live
24+
CMD [ "/app/app", "health" ]
2525

2626
CMD [ "/app/app" ]
2727

build/package/Dockerfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ FROM alpine:3 AS prod
3333

3434
WORKDIR /app
3535

36-
RUN apk add --no-cache tzdata \
37-
curl
36+
RUN apk add --no-cache tzdata
3837

3938
COPY scripts/docker-entrypoint.sh /docker-entrypoint.sh
4039

@@ -48,6 +47,6 @@ USER guest
4847
ENTRYPOINT ["/docker-entrypoint.sh"]
4948

5049
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
51-
CMD curl -fs http://localhost:3000/health/live
50+
CMD [ "/app/app", "health" ]
5251

5352
CMD [ "/app/app" ]

cmd/sms-gateway/main.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package main
33
import (
44
"os"
55

6+
"github.com/android-sms-gateway/server/internal/health"
67
smsgateway "github.com/android-sms-gateway/server/internal/sms-gateway"
78
"github.com/android-sms-gateway/server/internal/worker"
89
)
910

1011
const (
1112
cmdWorker = "worker"
13+
cmdHealth = "health"
1214
)
1315

1416
// @securitydefinitions.basic ApiAuth
@@ -53,13 +55,18 @@ const (
5355
func main() {
5456
args := os.Args[1:]
5557
cmd := "start"
56-
if len(args) > 0 && args[0] == cmdWorker {
57-
cmd = cmdWorker
58+
if len(args) > 0 {
59+
cmd = args[0]
5860
}
5961

60-
if cmd == cmdWorker {
62+
switch cmd {
63+
case cmdHealth:
64+
health.Run()
65+
return
66+
case cmdWorker:
6167
worker.Run()
62-
} else {
63-
smsgateway.Run()
68+
return
6469
}
70+
71+
smsgateway.Run()
6572
}

internal/health/app.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package health
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/android-sms-gateway/server/internal/config"
8+
"github.com/go-core-fx/logger"
9+
"go.uber.org/fx"
10+
)
11+
12+
func Run() {
13+
fx.New(
14+
fx.StartTimeout(time.Second),
15+
logger.Module(),
16+
logger.WithFxDefaultLogger(),
17+
config.Module(),
18+
module(),
19+
).Run()
20+
}
21+
22+
func module() fx.Option {
23+
return fx.Module(
24+
"health",
25+
fx.Provide(NewChecker),
26+
fx.Invoke(func(lc fx.Lifecycle, checker *Checker) {
27+
lc.Append(fx.Hook{
28+
OnStart: func(ctx context.Context) error {
29+
return checker.Execute(ctx)
30+
},
31+
OnStop: func(_ context.Context) error {
32+
return nil
33+
},
34+
})
35+
}),
36+
)
37+
}

internal/health/checker.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package health
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"io"
8+
httpclient "net/http"
9+
"time"
10+
11+
"github.com/capcom6/go-infra-fx/http"
12+
"go.uber.org/fx"
13+
"go.uber.org/zap"
14+
)
15+
16+
var ErrNotHealthy = errors.New("not healthy")
17+
18+
type Checker struct {
19+
config http.Config
20+
21+
shutdowner fx.Shutdowner
22+
logger *zap.Logger
23+
}
24+
25+
func NewChecker(config http.Config, shutdowner fx.Shutdowner, logger *zap.Logger) *Checker {
26+
return &Checker{
27+
config: config,
28+
shutdowner: shutdowner,
29+
logger: logger,
30+
}
31+
}
32+
33+
func (c *Checker) Execute(ctx context.Context) error {
34+
ctx, cancel := context.WithTimeout(ctx, time.Second)
35+
defer cancel()
36+
37+
client := httpclient.DefaultClient
38+
39+
req, err := httpclient.NewRequestWithContext(
40+
ctx,
41+
httpclient.MethodGet,
42+
"http://"+c.config.Listen+"/health/live",
43+
nil,
44+
)
45+
if err != nil {
46+
return fmt.Errorf("failed to create request: %w", err)
47+
}
48+
49+
res, err := client.Do(req)
50+
if err != nil {
51+
return fmt.Errorf("failed to send request: %w", err)
52+
}
53+
defer res.Body.Close()
54+
55+
body, err := io.ReadAll(res.Body)
56+
if err != nil {
57+
return fmt.Errorf("failed to read response body: %w", err)
58+
}
59+
60+
c.logger.Info(string(body))
61+
62+
if res.StatusCode >= httpclient.StatusBadRequest {
63+
c.logger.Error("health check failed", zap.Int("status", res.StatusCode), zap.String("body", string(body)))
64+
return fmt.Errorf("%w: health check failed: %s", ErrNotHealthy, string(body))
65+
}
66+
67+
c.logger.Info("health check passed", zap.Int("status", res.StatusCode))
68+
69+
if shErr := c.shutdowner.Shutdown(); shErr != nil {
70+
c.logger.Error("failed to shutdown", zap.Error(shErr))
71+
}
72+
73+
return nil
74+
}

0 commit comments

Comments
 (0)