fix(instrumented_http): migrate to own http instrumenter (#5650)

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
This commit is contained in:
Ivan Ka 2025-07-18 18:16:25 +01:00 committed by GitHub
parent c4db11af1b
commit 48760e653b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 200 additions and 34 deletions

1
go.mod
View File

@ -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
View File

@ -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
View 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
View 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))
})
}
}

View File

@ -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),
}

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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
}