Skip to content

Commit

Permalink
api: add /api/debug/health for operator
Browse files Browse the repository at this point in the history
Signed-off-by: xhe <xw897002528@gmail.com>
  • Loading branch information
xhebox committed Jan 12, 2023
1 parent 1072617 commit 682703f
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 66 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ DEBUG ?=
DOCKERPREFIX ?=
BUILD_TAGS ?=
LDFLAGS ?=
BUILDFLAGS := $(BUILDFLAGS) -gcflags '$(GCFLAGS)' -ldflags '$(LDFLAGS) -X main.Version=$(VERSION) -X main.Commit=$(COMMIT)' -tags '$(BUILD_TAGS)'
BUILDFLAGS ?= -gcflags '$(GCFLAGS)' -ldflags '$(LDFLAGS) -X main.Version=$(VERSION) -X main.Commit=$(COMMIT)' -tags '$(BUILD_TAGS)'
ifneq ("$(DEBUG)", "")
BUILDFLAGS += -race
endif
Expand Down Expand Up @@ -67,7 +67,7 @@ test: gocovmerge
go tool cover -html=.cover -o .cover.html

docker:
docker build -t "$(DOCKERPREFIX)tiproxy:$(IMAGE_TAG)" --build-arg 'GOPROXY=$(shell go env GOPROXY),BUILDFLAGS=$(BUILDFLAGS),' -f docker/Dockerfile .
docker build -t "$(DOCKERPREFIX)tiproxy:$(IMAGE_TAG)" --build-arg "GOPROXY=$(shell go env GOPROXY)" --build-arg "VERSION=$(VERSION)" --build-arg "COMMIT=$(COMMIT)" -f docker/Dockerfile .

docker-release:
docker buildx build --platform linux/amd64,linux/arm64 --push -t "$(DOCKERPREFIX)tiproxy:$(IMAGE_TAG)" --build-arg 'GOPROXY=$(shell go env GOPROXY),BUILDFLAGS=$(BUILDFLAGS),' -f docker/Dockerfile .
docker buildx build --platform linux/amd64,linux/arm64 --push -t "$(DOCKERPREFIX)tiproxy:$(IMAGE_TAG)" --build-arg "GOPROXY=$(shell go env GOPROXY)" --build-arg "VERSION=$(VERSION)" --build-arg "COMMIT=$(COMMIT)" -f docker/Dockerfile .
9 changes: 5 additions & 4 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
FROM alpine:edge as builder

ADD https://raw.githubusercontent.com/njhallett/apk-fastest-mirror/main/apk-fastest-mirror.sh /
ADD https://raw.fastgit.org/njhallett/apk-fastest-mirror/main/apk-fastest-mirror.sh /
RUN sed -i 's/https/http/g' /apk-fastest-mirror.sh && sh /apk-fastest-mirror.sh -t 50 && apk add --no-cache --progress git make go
ARG BUILDFLAGS
ARG VERSION
ARG COMMIT
ARG GOPROXY
ADD . /proxy
RUN export BUILDFLAGS=${BUILDFLAGS} && export GOPROXY=${GOPROXY} && cd /proxy && go mod download -x
RUN export BUILDFLAGS=${BUILDFLAGS} && export GOPROXY=${GOPROXY} && cd /proxy && make cmd
RUN export GOPROXY=${GOPROXY} && cd /proxy && go mod download -x
RUN export VERSION=${VERSION} && export COMMIT=${COMMIT} && export GOPROXY=${GOPROXY} && cd /proxy && make cmd

FROM alpine:latest

Expand Down
43 changes: 43 additions & 0 deletions lib/cli/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2022 PingCAP, 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 cli

import (
"github.com/spf13/cobra"
"net/http"
)

const (
healthPrefix = "/api/debug/health"
)

func GetHealthCmd(ctx *Context) *cobra.Command {
rootCmd := &cobra.Command{
Use: "health",
Short: "",
}

rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
resp, err := doRequest(cmd.Context(), ctx, http.MethodGet, healthPrefix, nil)
if err != nil {
return err
}

cmd.Println(resp)
return nil
}

return rootCmd
}
1 change: 1 addition & 0 deletions lib/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,6 @@ func GetRootCmd(tlsConfig *tls.Config) *cobra.Command {

rootCmd.AddCommand(GetNamespaceCmd(ctx))
rootCmd.AddCommand(GetConfigCmd(ctx))
rootCmd.AddCommand(GetHealthCmd(ctx))
return rootCmd
}
2 changes: 1 addition & 1 deletion pkg/server/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"go.uber.org/zap"
)

func Register(group *gin.RouterGroup, cfg config.API, logger *zap.Logger, nsmgr *mgrns.NamespaceManager, cfgmgr *mgrcfg.ConfigManager) {
func register(group *gin.RouterGroup, cfg config.API, logger *zap.Logger, nsmgr *mgrns.NamespaceManager, cfgmgr *mgrcfg.ConfigManager) {
{
adminGroup := group.Group("admin")
if cfg.EnableBasicAuth {
Expand Down
5 changes: 5 additions & 0 deletions pkg/server/api/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type debugHttpHandler struct {
nsmgr *mgrns.NamespaceManager
}

func (h *debugHttpHandler) Health(c *gin.Context) {
c.JSON(http.StatusOK, "")
}

func (h *debugHttpHandler) Redirect(c *gin.Context) {
errs := h.nsmgr.RedirectConnections()
if len(errs) != 0 {
Expand All @@ -48,5 +52,6 @@ func (h *debugHttpHandler) Redirect(c *gin.Context) {
func registerDebug(group *gin.RouterGroup, logger *zap.Logger, nsmgr *mgrns.NamespaceManager) {
handler := &debugHttpHandler{logger, nsmgr}
group.POST("/redirect", handler.Redirect)
group.GET("/health", handler.Health)
pprof.RouteRegister(group, "/pprof")
}
112 changes: 112 additions & 0 deletions pkg/server/api/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2023 PingCAP, 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 api

import (
"crypto/tls"
"net"
"net/http"
"time"

ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"github.com/pingcap/TiProxy/lib/config"
"github.com/pingcap/TiProxy/lib/util/errors"
"github.com/pingcap/TiProxy/lib/util/waitgroup"
mgrcrt "github.com/pingcap/TiProxy/pkg/manager/cert"
mgrcfg "github.com/pingcap/TiProxy/pkg/manager/config"
mgrns "github.com/pingcap/TiProxy/pkg/manager/namespace"
"go.uber.org/atomic"
"go.uber.org/ratelimit"
"go.uber.org/zap"
)

const (
// DefAPILimit is the global API limit per second.
DefAPILimit = 100
// DefConnTimeout is used as timeout duration in the HTTP server.
DefConnTimeout = 30 * time.Second
)

type HTTPHandler interface {
RegisterHTTP(c *gin.Engine) error
}

type HTTPServer struct {
listener net.Listener
wg waitgroup.WaitGroup
}

func NewHTTPServer(cfg config.API, lg *zap.Logger,
nsmgr *mgrns.NamespaceManager, cfgmgr *mgrcfg.ConfigManager,
crtmgr *mgrcrt.CertManager, handler HTTPHandler,
ready *atomic.Bool) (*HTTPServer, error) {
h := &HTTPServer{}

var err error
h.listener, err = net.Listen("tcp", cfg.Addr)
if err != nil {
return nil, err
}

gin.SetMode(gin.ReleaseMode)
engine := gin.New()
limit := ratelimit.New(DefAPILimit)
engine.Use(
gin.Recovery(),
ginzap.GinzapWithConfig(lg.Named("gin"), &ginzap.Config{
UTC: true,
SkipPaths: []string{
"/api/debug/health",
},
}),
func(c *gin.Context) {
_ = limit.Take()
if !ready.Load() {
c.Abort()
c.JSON(http.StatusInternalServerError, "service not ready")
}
},
)

register(engine.Group("/api"), cfg, lg, nsmgr, cfgmgr)

if tlscfg := crtmgr.ServerTLS(); tlscfg != nil {
h.listener = tls.NewListener(h.listener, tlscfg)
}

if handler != nil {
if err := handler.RegisterHTTP(engine); err != nil {
return nil, errors.WithStack(err)
}
}

h.wg.Run(func() {
hsrv := http.Server{
Handler: engine.Handler(),
ReadHeaderTimeout: DefConnTimeout,
IdleTimeout: DefConnTimeout,
}
lg.Info("HTTP closed", zap.Error(hsrv.Serve(h.listener)))
})

return h, nil
}

func (h *HTTPServer) Close() error {
err := h.listener.Close()
h.wg.Wait()
return err
}
64 changes: 6 additions & 58 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,8 @@ package server

import (
"context"
"crypto/tls"
"net"
"net/http"
"time"

ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"github.com/pingcap/TiProxy/lib/config"
"github.com/pingcap/TiProxy/lib/util/errors"
"github.com/pingcap/TiProxy/lib/util/waitgroup"
Expand All @@ -38,17 +33,9 @@ import (
"github.com/pingcap/TiProxy/pkg/server/api"
clientv3 "go.etcd.io/etcd/client/v3"
"go.uber.org/atomic"
"go.uber.org/ratelimit"
"go.uber.org/zap"
)

const (
// DefAPILimit is the global API limit per second.
DefAPILimit = 100
// DefConnTimeout is used as timeout duration in the HTTP server.
DefConnTimeout = 30 * time.Second
)

type Server struct {
wg waitgroup.WaitGroup
// managers
Expand All @@ -61,7 +48,7 @@ type Server struct {
// HTTP client
Http *http.Client
// HTTP server
HTTPListener net.Listener
HTTPServer *api.HTTPServer
// L7 proxy
Proxy *proxy.SQLServer
}
Expand Down Expand Up @@ -102,48 +89,9 @@ func NewServer(ctx context.Context, sctx *sctx.Context) (srv *Server, err error)
return
}

// setup gin
{
slogger := lg.Named("gin")
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
limit := ratelimit.New(DefAPILimit)
engine.Use(
gin.Recovery(),
ginzap.Ginzap(slogger, "", true),
func(c *gin.Context) {
_ = limit.Take()
if !ready.Load() {
c.Abort()
c.JSON(http.StatusInternalServerError, "service not ready")
}
},
)

api.Register(engine.Group("/api"), cfg.API, lg.Named("api"), srv.NamespaceManager, srv.ConfigManager)

srv.HTTPListener, err = net.Listen("tcp", cfg.API.Addr)
if err != nil {
return nil, err
}
if tlscfg := srv.CertManager.ServerTLS(); tlscfg != nil {
srv.HTTPListener = tls.NewListener(srv.HTTPListener, tlscfg)
}

if handler != nil {
if err := handler.RegisterHTTP(engine); err != nil {
return nil, errors.WithStack(err)
}
}

srv.wg.Run(func() {
hsrv := http.Server{
Handler: engine.Handler(),
ReadHeaderTimeout: DefConnTimeout,
IdleTimeout: DefConnTimeout,
}
slogger.Info("HTTP closed", zap.Error(hsrv.Serve(srv.HTTPListener)))
})
// setup http
if srv.HTTPServer, err = api.NewHTTPServer(cfg.API, lg.Named("api"), srv.NamespaceManager, srv.ConfigManager, srv.CertManager, handler, ready); err != nil {
return
}

// general cluster HTTP client
Expand Down Expand Up @@ -217,8 +165,8 @@ func (s *Server) Close() error {
if s.Proxy != nil {
errs = append(errs, s.Proxy.Close())
}
if s.HTTPListener != nil {
s.HTTPListener.Close()
if s.HTTPServer != nil {
s.HTTPServer.Close()
}
if s.NamespaceManager != nil {
errs = append(errs, s.NamespaceManager.Close())
Expand Down

0 comments on commit 682703f

Please sign in to comment.