mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 09:06:58 +02:00
fix(instrumented_http): migrate to own http instrumenter (#5650)
Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
This commit is contained in:
parent
c4db11af1b
commit
48760e653b
1
go.mod
1
go.mod
@ -38,7 +38,6 @@ require (
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/linki/instrumented_http v0.3.0
|
||||
github.com/linode/linodego v1.53.0
|
||||
github.com/maxatome/go-testdeep v1.14.0
|
||||
github.com/miekg/dns v1.1.67
|
||||
|
2
go.sum
2
go.sum
@ -676,8 +676,6 @@ github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/linki/instrumented_http v0.3.0 h1:dsN92+mXpfZtjJraartcQ99jnuw7fqsnPDjr85ma2dA=
|
||||
github.com/linki/instrumented_http v0.3.0/go.mod h1:pjYbItoegfuVi2GUOMhEqzvm/SJKuEL3H0tc8QRLRFk=
|
||||
github.com/linode/linodego v1.53.0 h1:UWr7bUUVMtcfsuapC+6blm6+jJLPd7Tf9MZUpdOERnI=
|
||||
github.com/linode/linodego v1.53.0/go.mod h1:bI949fZaVchjWyKIA08hNyvAcV6BAS+PM2op3p7PAWA=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
|
97
pkg/http/http.go
Normal file
97
pkg/http/http.go
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// ref: https://github.com/linki/instrumented_http/blob/master/client.go
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
requestDuration = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "request_duration_seconds",
|
||||
Help: "The HTTP request latencies in seconds.",
|
||||
Subsystem: "http",
|
||||
ConstLabels: prometheus.Labels{"handler": "instrumented_http"},
|
||||
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
||||
},
|
||||
[]string{"scheme", "host", "path", "method", "status"},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(requestDuration)
|
||||
}
|
||||
|
||||
type CustomRoundTripper struct {
|
||||
next http.RoundTripper
|
||||
}
|
||||
|
||||
// CancelRequest is a no-op to satisfy interfaces that require it.
|
||||
// https://github.com/kubernetes/client-go/blob/34f52c14eaed7e50c845cc14e85e1c4c91e77470/transport/transport.go#L346
|
||||
func (r *CustomRoundTripper) CancelRequest(_ *http.Request) {
|
||||
}
|
||||
|
||||
func (r *CustomRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
start := time.Now()
|
||||
resp, err := r.next.RoundTrip(req)
|
||||
|
||||
status := ""
|
||||
if resp != nil {
|
||||
status = fmt.Sprintf("%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
labels := prometheus.Labels{
|
||||
"scheme": req.URL.Scheme,
|
||||
"host": req.URL.Host,
|
||||
"path": pathProcessor(req.URL.Path),
|
||||
"method": req.Method,
|
||||
"status": status,
|
||||
}
|
||||
requestDuration.With(labels).Observe(time.Since(start).Seconds())
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func NewInstrumentedClient(next *http.Client) *http.Client {
|
||||
if next == nil {
|
||||
next = http.DefaultClient
|
||||
}
|
||||
|
||||
next.Transport = NewInstrumentedTransport(next.Transport)
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
func NewInstrumentedTransport(next http.RoundTripper) http.RoundTripper {
|
||||
if next == nil {
|
||||
next = http.DefaultTransport
|
||||
}
|
||||
|
||||
return &CustomRoundTripper{next: next}
|
||||
}
|
||||
|
||||
func pathProcessor(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
return parts[len(parts)-1]
|
||||
}
|
81
pkg/http/http_test.go
Normal file
81
pkg/http/http_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
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 http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type dummyTransport struct{}
|
||||
|
||||
func (d *dummyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return nil, fmt.Errorf("dummy error")
|
||||
}
|
||||
|
||||
func TestNewInstrumentedTransport(t *testing.T) {
|
||||
dt := &dummyTransport{}
|
||||
rt := NewInstrumentedTransport(dt)
|
||||
crt, ok := rt.(*CustomRoundTripper)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, dt, crt.next)
|
||||
|
||||
// Should default to http.DefaultTransport if nil
|
||||
rt2 := NewInstrumentedTransport(nil)
|
||||
crt2, ok := rt2.(*CustomRoundTripper)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, http.DefaultTransport, crt2.next)
|
||||
}
|
||||
|
||||
func TestNewInstrumentedClient(t *testing.T) {
|
||||
client := &http.Client{Transport: &dummyTransport{}}
|
||||
result := NewInstrumentedClient(client)
|
||||
require.Equal(t, client, result)
|
||||
_, ok := result.Transport.(*CustomRoundTripper)
|
||||
require.True(t, ok)
|
||||
|
||||
// Should default to http.DefaultClient if nil
|
||||
result2 := NewInstrumentedClient(nil)
|
||||
require.Equal(t, http.DefaultClient, result2)
|
||||
_, ok = result2.Transport.(*CustomRoundTripper)
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
func TestPathProcessor(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"/foo/bar", "bar"},
|
||||
{"/foo/", ""},
|
||||
{"/", ""},
|
||||
{"", ""},
|
||||
{"/foo/bar/baz", "baz"},
|
||||
{"foo/bar", "bar"},
|
||||
{"foo", "foo"},
|
||||
{"foo/", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
require.Equal(t, tt.expected, pathProcessor(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
@ -20,16 +20,16 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
stscredsv2 "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/linki/instrumented_http"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
|
||||
|
||||
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||
)
|
||||
|
||||
@ -84,12 +84,7 @@ func newV2Config(awsConfig AWSSessionConfig) (awsv2.Config, error) {
|
||||
config.WithRetryer(func() awsv2.Retryer {
|
||||
return retry.AddWithMaxAttempts(retry.NewStandard(), awsConfig.APIRetries)
|
||||
}),
|
||||
config.WithHTTPClient(instrumented_http.NewClient(&http.Client{}, &instrumented_http.Callbacks{
|
||||
PathProcessor: func(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
return parts[len(parts)-1]
|
||||
},
|
||||
})),
|
||||
config.WithHTTPClient(extdnshttp.NewInstrumentedClient(&http.Client{})),
|
||||
config.WithSharedConfigProfile(awsConfig.Profile),
|
||||
}
|
||||
|
||||
|
@ -20,17 +20,17 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/linki/instrumented_http"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2/google"
|
||||
dns "google.golang.org/api/dns/v1"
|
||||
googleapi "google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/option"
|
||||
|
||||
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
@ -131,12 +131,7 @@ func NewGoogleProvider(ctx context.Context, project string, domainFilter *endpoi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcloud = instrumented_http.NewClient(gcloud, &instrumented_http.Callbacks{
|
||||
PathProcessor: func(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
return parts[len(parts)-1]
|
||||
},
|
||||
})
|
||||
gcloud = extdnshttp.NewInstrumentedClient(gcloud)
|
||||
|
||||
dnsClient, err := dns.NewService(ctx, option.WithHTTPClient(gcloud))
|
||||
if err != nil {
|
||||
|
@ -28,10 +28,11 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/linki/instrumented_http"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
@ -71,7 +72,8 @@ func newPiholeClient(cfg PiholeConfig) (piholeAPI, error) {
|
||||
},
|
||||
},
|
||||
}
|
||||
cl := instrumented_http.NewClient(httpClient, &instrumented_http.Callbacks{})
|
||||
|
||||
cl := extdnshttp.NewInstrumentedClient(httpClient)
|
||||
|
||||
p := &piholeClient{
|
||||
cfg: cfg,
|
||||
|
@ -30,9 +30,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/linki/instrumented_http"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
@ -65,7 +66,7 @@ func newPiholeClientV6(cfg PiholeConfig) (piholeAPI, error) {
|
||||
},
|
||||
}
|
||||
|
||||
cl := instrumented_http.NewClient(httpClient, &instrumented_http.Callbacks{})
|
||||
cl := extdnshttp.NewInstrumentedClient(httpClient)
|
||||
|
||||
p := &piholeClientV6{
|
||||
cfg: cfg,
|
||||
@ -164,17 +165,17 @@ func (p *piholeClientV6) listRecords(ctx context.Context, rtype string) ([]*endp
|
||||
DNSName, Target = recs[1], recs[0]
|
||||
switch rtype {
|
||||
case endpoint.RecordTypeA:
|
||||
//PiHole return A and AAAA records. Filter to only keep the A records
|
||||
// PiHole return A and AAAA records. Filter to only keep the A records
|
||||
if !isValidIPv4(Target) {
|
||||
continue
|
||||
}
|
||||
case endpoint.RecordTypeAAAA:
|
||||
//PiHole return A and AAAA records. Filter to only keep the AAAA records
|
||||
// PiHole return A and AAAA records. Filter to only keep the AAAA records
|
||||
if !isValidIPv6(Target) {
|
||||
continue
|
||||
}
|
||||
case endpoint.RecordTypeCNAME:
|
||||
//PiHole return only CNAME records.
|
||||
// PiHole return only CNAME records.
|
||||
// CNAME format is DNSName,target, ttl?
|
||||
DNSName, Target = recs[0], recs[1]
|
||||
if len(recs) == 3 { // TTL is present
|
||||
|
@ -22,12 +22,11 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudfoundry-community/go-cfclient"
|
||||
"github.com/linki/instrumented_http"
|
||||
openshift "github.com/openshift/client-go/route/clientset/versioned"
|
||||
log "github.com/sirupsen/logrus"
|
||||
istioclient "istio.io/client-go/pkg/clientset/versioned"
|
||||
@ -38,6 +37,8 @@ import (
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
|
||||
|
||||
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
|
||||
|
||||
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||
)
|
||||
|
||||
@ -623,14 +624,11 @@ func instrumentedRESTConfig(kubeConfig, apiServerURL string, requestTimeout time
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
|
||||
return instrumented_http.NewTransport(rt, &instrumented_http.Callbacks{
|
||||
PathProcessor: func(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
return parts[len(parts)-1]
|
||||
},
|
||||
})
|
||||
return extdnshttp.NewInstrumentedTransport(rt)
|
||||
}
|
||||
|
||||
config.Timeout = requestTimeout
|
||||
return config, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user