Merge github.com:traefik/traefik

This commit is contained in:
baalajimaestro 2022-11-12 16:53:37 +05:30
commit 52c628acd8
Signed by: baalajimaestro
GPG key ID: F93C394FE9BBAFD5
6 changed files with 2763 additions and 2168 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -16,3 +16,7 @@ feature by feature, of how the configuration looked like in v2, and how it now l
## IPWhiteList ## IPWhiteList
In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration. In v3, we renamed the `IPWhiteList` middleware to `IPAllowList` without changing anything to the configuration.
## gRPC Metrics
In v3, the reported status code for gRPC requests is now the value of the `Grpc-Status` header.

View file

@ -13,6 +13,8 @@ Traefik supports these metrics backends:
- [Prometheus](./prometheus.md) - [Prometheus](./prometheus.md)
- [StatsD](./statsd.md) - [StatsD](./statsd.md)
Traefik Proxy hosts an official Grafana dashboard for both [on-premises](https://grafana.com/grafana/dashboards/17346) and [Kubernetes](https://grafana.com/grafana/dashboards/17347) deployments.
## Global Metrics ## Global Metrics
| Metric | Type | Description | | Metric | Type | Description |

View file

@ -16,10 +16,13 @@ import (
"github.com/traefik/traefik/v2/pkg/middlewares/capture" "github.com/traefik/traefik/v2/pkg/middlewares/capture"
"github.com/traefik/traefik/v2/pkg/middlewares/retry" "github.com/traefik/traefik/v2/pkg/middlewares/retry"
traefiktls "github.com/traefik/traefik/v2/pkg/tls" traefiktls "github.com/traefik/traefik/v2/pkg/tls"
"google.golang.org/grpc/codes"
) )
const ( const (
protoHTTP = "http" protoHTTP = "http"
protoGRPC = "grpc"
protoGRPCWeb = "grpc-web"
protoSSE = "sse" protoSSE = "sse"
protoWebsocket = "websocket" protoWebsocket = "websocket"
typeName = "Metrics" typeName = "Metrics"
@ -109,9 +112,12 @@ func WrapServiceHandler(ctx context.Context, registry metrics.Registry, serviceN
} }
func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
proto := getRequestProtocol(req)
var labels []string var labels []string
labels = append(labels, m.baseLabels...) labels = append(labels, m.baseLabels...)
labels = append(labels, "method", getMethod(req), "protocol", getRequestProtocol(req)) labels = append(labels, "method", getMethod(req))
labels = append(labels, "protocol", proto)
openConnsGauge := m.openConnsGauge.With(labels...) openConnsGauge := m.openConnsGauge.With(labels...)
openConnsGauge.Add(1) openConnsGauge.Add(1)
@ -145,7 +151,12 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request)
start := time.Now() start := time.Now()
next.ServeHTTP(rw, req) next.ServeHTTP(rw, req)
labels = append(labels, "code", strconv.Itoa(capt.StatusCode())) code := capt.StatusCode()
if proto == protoGRPC || proto == protoGRPCWeb {
code = grpcStatusCode(rw)
}
labels = append(labels, "code", strconv.Itoa(code))
m.reqDurationHistogram.With(labels...).ObserveFromStart(start) m.reqDurationHistogram.With(labels...).ObserveFromStart(start)
m.reqsCounter.With(labels...).Add(1) m.reqsCounter.With(labels...).Add(1)
m.respsBytesCounter.With(labels...).Add(float64(capt.ResponseSize())) m.respsBytesCounter.With(labels...).Add(float64(capt.ResponseSize()))
@ -158,6 +169,10 @@ func getRequestProtocol(req *http.Request) string {
return protoWebsocket return protoWebsocket
case isSSERequest(req): case isSSERequest(req):
return protoSSE return protoSSE
case isGRPCWebRequest(req):
return protoGRPCWeb
case isGRPCRequest(req):
return protoGRPC
default: default:
return protoHTTP return protoHTTP
} }
@ -173,6 +188,27 @@ func isSSERequest(req *http.Request) bool {
return containsHeader(req, "Accept", "text/event-stream") return containsHeader(req, "Accept", "text/event-stream")
} }
// isGRPCWebRequest determines if the specified HTTP request is a gRPC-Web request.
func isGRPCWebRequest(req *http.Request) bool {
return strings.HasPrefix(req.Header.Get("Content-Type"), "application/grpc-web")
}
// isGRPCRequest determines if the specified HTTP request is a gRPC request.
func isGRPCRequest(req *http.Request) bool {
return strings.HasPrefix(req.Header.Get("Content-Type"), "application/grpc")
}
// grpcStatusCode parses and returns the gRPC status code from the Grpc-Status header.
func grpcStatusCode(rw http.ResponseWriter) int {
code := codes.Unknown
if status := rw.Header().Get("Grpc-Status"); status != "" {
if err := code.UnmarshalJSON([]byte(status)); err != nil {
return int(code)
}
}
return int(code)
}
func containsHeader(req *http.Request, name, value string) bool { func containsHeader(req *http.Request, name, value string) bool {
items := strings.Split(req.Header.Get(name), ",") items := strings.Split(req.Header.Get(name), ",")
for _, item := range items { for _, item := range items {

View file

@ -9,6 +9,7 @@ import (
"github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
) )
// CollectingCounter is a metrics.Counter implementation that enables access to the CounterValue and LastLabelValues. // CollectingCounter is a metrics.Counter implementation that enables access to the CounterValue and LastLabelValues.
@ -129,3 +130,102 @@ func Test_getMethod(t *testing.T) {
}) })
} }
} }
func Test_getRequestProtocol(t *testing.T) {
testCases := []struct {
desc string
headers http.Header
expected string
}{
{
desc: "default",
expected: protoHTTP,
},
{
desc: "websocket",
headers: http.Header{
"Connection": []string{"upgrade"},
"Upgrade": []string{"websocket"},
},
expected: protoWebsocket,
},
{
desc: "SSE",
headers: http.Header{
"Accept": []string{"text/event-stream"},
},
expected: protoSSE,
},
{
desc: "grpc web",
headers: http.Header{
"Content-Type": []string{"application/grpc-web-text"},
},
expected: protoGRPCWeb,
},
{
desc: "grpc",
headers: http.Header{
"Content-Type": []string{"application/grpc-text"},
},
expected: protoGRPC,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest(http.MethodGet, "https://localhost", http.NoBody)
req.Header = test.headers
protocol := getRequestProtocol(req)
assert.Equal(t, test.expected, protocol)
})
}
}
func Test_grpcStatusCode(t *testing.T) {
testCases := []struct {
desc string
status string
expected codes.Code
}{
{
desc: "invalid number",
status: "foo",
expected: codes.Unknown,
},
{
desc: "number",
status: "1",
expected: codes.Canceled,
},
{
desc: "invalid string",
status: `"foo"`,
expected: codes.Unknown,
},
{
desc: "string",
status: `"OK"`,
expected: codes.OK,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rw := httptest.NewRecorder()
rw.Header().Set("Grpc-Status", test.status)
code := grpcStatusCode(rw)
assert.EqualValues(t, test.expected, code)
})
}
}