From 84b597478f3314e6263250c7bd6eb4e006d1d51f Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Tue, 25 Jul 2023 15:55:29 +0200 Subject: [PATCH] Add ping command to server to allow container healthchecks (#2030) Fixes: https://github.com/woodpecker-ci/woodpecker/issues/1943 Note: Kubernetes ignores the container `HEALTHCHECK` by default. --------- Co-authored-by: 6543 <6543@obermui.de> --- cmd/server/health.go | 63 +++++++++++++++++++++++ cmd/server/main.go | 7 +++ docker/Dockerfile.server.alpine.multiarch | 1 + docker/Dockerfile.server.multiarch | 1 + 4 files changed, 72 insertions(+) create mode 100644 cmd/server/health.go diff --git a/cmd/server/health.go b/cmd/server/health.go new file mode 100644 index 000000000..73ce37479 --- /dev/null +++ b/cmd/server/health.go @@ -0,0 +1,63 @@ +// Copyright 2023 Woodpecker Authors +// Copyright 2018 Drone.IO Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net/http" + "strings" + "time" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" +) + +const pingTimeout = 1 * time.Second + +// handles pinging the endpoint and returns an error if the +// server is in an unhealthy state. +func pinger(c *cli.Context) error { + scheme := "http" + serverAddr := c.String("server-addr") + if strings.HasPrefix(serverAddr, ":") { + // this seems sufficient according to https://pkg.go.dev/net#Dial + serverAddr = "localhost" + serverAddr + } + + // if woodpecker do ssl on it's own + if c.String("server-cert") != "" || c.Bool("lets-encrypt") { + scheme = "https" + } + + // create the health url + healthURL := fmt.Sprintf("%s://%s/healthz", scheme, serverAddr) + log.Trace().Msgf("try to ping with url '%s'", healthURL) + + // ask server if all is healthy + client := http.Client{Timeout: pingTimeout} + resp, err := client.Get(healthURL) + if err != nil { + if strings.Contains(err.Error(), "deadline exceeded") { + return fmt.Errorf("ping timeout reached after %s", pingTimeout) + } + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("server returned non-200 status code") + } + return nil +} diff --git a/cmd/server/main.go b/cmd/server/main.go index bbb1f0ba3..fa2683b58 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -31,6 +31,13 @@ func main() { app.Version = version.String() app.Usage = "woodpecker server" app.Action = run + app.Commands = []*cli.Command{ + { + Name: "ping", + Usage: "ping the server", + Action: pinger, + }, + } app.Flags = flags setupSwaggerStaticConfig() diff --git a/docker/Dockerfile.server.alpine.multiarch b/docker/Dockerfile.server.alpine.multiarch index dcea144b3..d1835a55a 100644 --- a/docker/Dockerfile.server.alpine.multiarch +++ b/docker/Dockerfile.server.alpine.multiarch @@ -11,4 +11,5 @@ EXPOSE 8000 9000 80 443 COPY dist/server/${TARGETOS}/${TARGETARCH}/woodpecker-server /bin/ +HEALTHCHECK CMD ["/bin/woodpecker-server", "ping"] ENTRYPOINT ["/bin/woodpecker-server"] diff --git a/docker/Dockerfile.server.multiarch b/docker/Dockerfile.server.multiarch index 51def0fb0..d79012861 100644 --- a/docker/Dockerfile.server.multiarch +++ b/docker/Dockerfile.server.multiarch @@ -14,4 +14,5 @@ COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certifica # copy server binary COPY dist/server/${TARGETOS}/${TARGETARCH}/woodpecker-server /bin/ +HEALTHCHECK CMD ["/bin/woodpecker-server", "ping"] ENTRYPOINT ["/bin/woodpecker-server"]