mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-12 15:52:53 +02:00
envoy: Enable zipkin tracing (#737)
- Update envoy bootstrap config to protobufs - Reorganize tracing config to avoid cyclic import - Push down zipkin config to Envoy - Update tracing options to provide sample rate
This commit is contained in:
parent
38c1b5ec65
commit
3e17befff7
13 changed files with 434 additions and 120 deletions
|
@ -34,6 +34,9 @@ const DisableHeaderKey = "disable"
|
|||
// gRPC server, or is used for healthchecks (authorize only service)
|
||||
const DefaultAlternativeAddr = ":5443"
|
||||
|
||||
// EnvoyAdminURL indicates where the envoy control plane is listening
|
||||
var EnvoyAdminURL = &url.URL{Host: "localhost:9901", Scheme: "http"}
|
||||
|
||||
// Options are the global environmental flags used to set up pomerium's services.
|
||||
// Use NewXXXOptions() methods for a safely initialized data structure.
|
||||
type Options struct {
|
||||
|
@ -171,8 +174,8 @@ type Options struct {
|
|||
MetricsAddr string `mapstructure:"metrics_address" yaml:"metrics_address,omitempty"`
|
||||
|
||||
// Tracing shared settings
|
||||
TracingProvider string `mapstructure:"tracing_provider" yaml:"tracing_provider,omitempty"`
|
||||
TracingDebug bool `mapstructure:"tracing_debug" yaml:"tracing_debug,omitempty"`
|
||||
TracingProvider string `mapstructure:"tracing_provider" yaml:"tracing_provider,omitempty"`
|
||||
TracingSampleRate float64 `mapstructure:"tracing_sample_rate" yaml:"tracing_sample_rate,omitempty"`
|
||||
|
||||
// Jaeger
|
||||
//
|
||||
|
@ -272,6 +275,7 @@ var defaultOptions = Options{
|
|||
CacheStore: "autocache",
|
||||
AuthenticateCallbackPath: "/oauth2/callback",
|
||||
AutoCertFolder: dataDir(),
|
||||
TracingSampleRate: 0.0001,
|
||||
}
|
||||
|
||||
// NewDefaultOptions returns a copy the default options. It's the caller's
|
||||
|
|
|
@ -206,7 +206,7 @@ func Test_Checksum(t *testing.T) {
|
|||
func TestOptionsFromViper(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := []cmp.Option{
|
||||
cmpopts.IgnoreFields(Options{}, "CacheStore", "CookieSecret", "GRPCInsecure", "GRPCAddr", "CacheURLString", "CacheURL", "AuthorizeURL", "AuthorizeURLString", "DefaultUpstreamTimeout", "CookieExpire", "Services", "Addr", "RefreshCooldown", "LogLevel", "KeyFile", "CertFile", "SharedKey", "ReadTimeout", "IdleTimeout", "GRPCClientTimeout", "GRPCClientDNSRoundRobin"),
|
||||
cmpopts.IgnoreFields(Options{}, "CacheStore", "CookieSecret", "GRPCInsecure", "GRPCAddr", "CacheURLString", "CacheURL", "AuthorizeURL", "AuthorizeURLString", "DefaultUpstreamTimeout", "CookieExpire", "Services", "Addr", "RefreshCooldown", "LogLevel", "KeyFile", "CertFile", "SharedKey", "ReadTimeout", "IdleTimeout", "GRPCClientTimeout", "GRPCClientDNSRoundRobin", "TracingSampleRate"),
|
||||
cmpopts.IgnoreFields(Policy{}, "Source", "Destination"),
|
||||
cmpOptIgnoreUnexported,
|
||||
}
|
||||
|
|
80
config/trace.go
Normal file
80
config/trace.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// JaegerTracingProviderName is the name of the tracing provider Jaeger.
|
||||
JaegerTracingProviderName = "jaeger"
|
||||
// ZipkinTracingProviderName is the name of the tracing provider Zipkin.
|
||||
ZipkinTracingProviderName = "zipkin"
|
||||
)
|
||||
|
||||
// TracingOptions contains the configurations settings for a http server.
|
||||
type TracingOptions struct {
|
||||
// Shared
|
||||
Provider string
|
||||
Service string
|
||||
Debug bool
|
||||
|
||||
// Jaeger
|
||||
|
||||
// CollectorEndpoint is the full url to the Jaeger HTTP Thrift collector.
|
||||
// For example, http://localhost:14268/api/traces
|
||||
JaegerCollectorEndpoint *url.URL
|
||||
// AgentEndpoint instructs exporter to send spans to jaeger-agent at this address.
|
||||
// For example, localhost:6831.
|
||||
JaegerAgentEndpoint string
|
||||
|
||||
// Zipkin
|
||||
|
||||
// ZipkinEndpoint configures the zipkin collector URI
|
||||
// Example: http://zipkin:9411/api/v2/spans
|
||||
ZipkinEndpoint *url.URL
|
||||
|
||||
// SampleRate is percentage of requests which are sampled
|
||||
SampleRate float64
|
||||
}
|
||||
|
||||
// NewTracingOptions builds a new TracingOptions from core Options
|
||||
func NewTracingOptions(o *Options) (*TracingOptions, error) {
|
||||
tracingOpts := TracingOptions{
|
||||
Provider: o.TracingProvider,
|
||||
Service: o.Services,
|
||||
JaegerAgentEndpoint: o.TracingJaegerAgentEndpoint,
|
||||
SampleRate: o.TracingSampleRate,
|
||||
}
|
||||
|
||||
switch o.TracingProvider {
|
||||
case JaegerTracingProviderName:
|
||||
if o.TracingJaegerCollectorEndpoint != "" {
|
||||
jaegerCollectorEndpoint, err := urlutil.ParseAndValidateURL(o.TracingJaegerCollectorEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config: invalid jaeger endpoint url: %w", err)
|
||||
}
|
||||
tracingOpts.JaegerCollectorEndpoint = jaegerCollectorEndpoint
|
||||
tracingOpts.JaegerAgentEndpoint = o.TracingJaegerAgentEndpoint
|
||||
}
|
||||
case ZipkinTracingProviderName:
|
||||
zipkinEndpoint, err := urlutil.ParseAndValidateURL(o.ZipkinEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config: invalid zipkin endpoint url: %w", err)
|
||||
}
|
||||
tracingOpts.ZipkinEndpoint = zipkinEndpoint
|
||||
case "":
|
||||
default:
|
||||
return nil, fmt.Errorf("config: provider %s unknown", o.TracingProvider)
|
||||
}
|
||||
|
||||
return &tracingOpts, nil
|
||||
|
||||
}
|
||||
|
||||
// Enabled indicates whether tracing is enabled on a given TracingOptions
|
||||
func (t *TracingOptions) Enabled() bool {
|
||||
return t.Provider != ""
|
||||
}
|
79
config/trace_test.go
Normal file
79
config/trace_test.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_NewTracingOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *Options
|
||||
want *TracingOptions
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"jaeger_good",
|
||||
&Options{TracingProvider: "jaeger", TracingJaegerAgentEndpoint: "foo", TracingJaegerCollectorEndpoint: "http://foo"},
|
||||
&TracingOptions{Provider: "jaeger", JaegerAgentEndpoint: "foo", JaegerCollectorEndpoint: &url.URL{Scheme: "http", Host: "foo"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"jaeger_bad",
|
||||
&Options{TracingProvider: "jaeger", TracingJaegerAgentEndpoint: "foo", TracingJaegerCollectorEndpoint: "badurl"},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"zipkin_good",
|
||||
&Options{TracingProvider: "zipkin", ZipkinEndpoint: "https://foo/api/v1/spans"},
|
||||
&TracingOptions{Provider: "zipkin", ZipkinEndpoint: &url.URL{Scheme: "https", Host: "foo", Path: "/api/v1/spans"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"zipkin_bad",
|
||||
&Options{TracingProvider: "zipkin", ZipkinEndpoint: "notaurl"},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"noprovider",
|
||||
&Options{},
|
||||
&TracingOptions{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"fakeprovider",
|
||||
&Options{TracingProvider: "fake"},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewTracingOptions(tt.opts)
|
||||
assert.NotEqual(t, err == nil, tt.wantErr, "unexpected error value")
|
||||
assert.Empty(t, cmp.Diff(tt.want, got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_TracingEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *TracingOptions
|
||||
want bool
|
||||
}{
|
||||
{"enabled", &TracingOptions{Provider: "zipkin"}, true},
|
||||
{"not enabled", &TracingOptions{}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, tt.opts.Enabled(), "unexpected tracing state")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -449,10 +449,10 @@ Each unit work is called a Span in a trace. Spans include metadata about the wor
|
|||
|
||||
#### Shared Tracing Settings
|
||||
|
||||
| Config Key | Description | Required |
|
||||
| :--------------- | :---------------------------------------------------------------- | -------- |
|
||||
| tracing_provider | The name of the tracing provider. (e.g. jaeger, zipkin) | ✅ |
|
||||
| tracing_debug | Will disable [sampling](https://opencensus.io/tracing/sampling/). | ❌ |
|
||||
| Config Key | Description | Required |
|
||||
| :------------------ | :------------------------------------------------------------------------------------ | -------- |
|
||||
| tracing_provider | The name of the tracing provider. (e.g. jaeger, zipkin) | ✅ |
|
||||
| tracing_sample_rate | Percentage of requests to sample in decimal notation. Default is `0.0001`, or `.01%` | ❌ |
|
||||
|
||||
#### Jaeger (partial)
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ description: >-
|
|||
|
||||
### Tracing
|
||||
|
||||
Jaeger tracing support is no longer end-to-end in the proxy service. We recommend updating to the Zipkin provider for proper tracing support. Jaeger will continue to work but will not have coverage in the data plane.
|
||||
- Jaeger tracing support is no longer end-to-end in the proxy service. We recommend updating to the Zipkin provider for proper tracing support. Jaeger will continue to work but will not have coverage in the data plane.
|
||||
- Option `tracing_debug` is no longer supported. Use `tracing_sampling_rate` instead. [Details](https://www.pomerium.io/configuration/#shared-tracing-settings).
|
||||
|
||||
# Since 0.7.0
|
||||
|
||||
|
|
|
@ -62,9 +62,9 @@ func Run(ctx context.Context, configFile string) error {
|
|||
_, httpPort, _ := net.SplitHostPort(controlPlane.HTTPListener.Addr().String())
|
||||
|
||||
// create envoy server
|
||||
envoyServer, err := envoy.NewServer(grpcPort, httpPort)
|
||||
envoyServer, err := envoy.NewServer(opt, grpcPort, httpPort)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating envoy server")
|
||||
return fmt.Errorf("error creating envoy server: %w", err)
|
||||
}
|
||||
|
||||
// add services
|
||||
|
@ -165,7 +165,7 @@ func setupCache(opt *config.Options, controlPlane *controlplane.Server) error {
|
|||
|
||||
func setupMetrics(ctx context.Context, opt *config.Options) error {
|
||||
if opt.MetricsAddr != "" {
|
||||
handler, err := metrics.PrometheusHandler()
|
||||
handler, err := metrics.PrometheusHandler(config.EnvoyAdminURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -203,16 +203,12 @@ func setupProxy(opt *config.Options, controlPlane *controlplane.Server) error {
|
|||
}
|
||||
|
||||
func setupTracing(ctx context.Context, opt *config.Options) error {
|
||||
if opt.TracingProvider != "" {
|
||||
tracingOpts := &trace.TracingOptions{
|
||||
Provider: opt.TracingProvider,
|
||||
Service: opt.Services,
|
||||
Debug: opt.TracingDebug,
|
||||
JaegerAgentEndpoint: opt.TracingJaegerAgentEndpoint,
|
||||
JaegerCollectorEndpoint: opt.TracingJaegerCollectorEndpoint,
|
||||
ZipkinEndpoint: opt.ZipkinEndpoint,
|
||||
}
|
||||
exporter, err := trace.RegisterTracing(tracingOpts)
|
||||
traceOpts, err := config.NewTracingOptions(opt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up tracing: %w", err)
|
||||
}
|
||||
if traceOpts.Enabled() {
|
||||
exporter, err := trace.RegisterTracing(traceOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -217,6 +217,9 @@ func (srv *Server) buildMainHTTPConnectionManagerFilter(options *config.Options,
|
|||
MaxStreamDuration: maxStreamDuration,
|
||||
},
|
||||
RequestTimeout: ptypes.DurationProto(options.ReadTimeout),
|
||||
Tracing: &envoy_http_connection_manager.HttpConnectionManager_Tracing{
|
||||
RandomSampling: &envoy_type_v3.Percent{Value: options.TracingSampleRate * 100},
|
||||
},
|
||||
})
|
||||
|
||||
return &envoy_config_listener_v3.Filter{
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
package envoy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
|
@ -12,19 +14,26 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
envoy_config_bootstrap_v3 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
|
||||
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_config_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
||||
envoy_config_trace_v3 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v3"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/natefinch/atomic"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
workingDirectoryName = ".pomerium-envoy"
|
||||
configFileName = "envoy-config.yaml"
|
||||
// EnvoyAdminURL indicates where the envoy control plane is listening
|
||||
EnvoyAdminURL = "http://localhost:9901"
|
||||
)
|
||||
|
||||
// A Server is a pomerium proxy implemented via envoy.
|
||||
|
@ -33,10 +42,11 @@ type Server struct {
|
|||
cmd *exec.Cmd
|
||||
|
||||
grpcPort, httpPort string
|
||||
opts *config.Options
|
||||
}
|
||||
|
||||
// NewServer creates a new server with traffic routed by envoy.
|
||||
func NewServer(grpcPort, httpPort string) (*Server, error) {
|
||||
func NewServer(opts *config.Options, grpcPort, httpPort string) (*Server, error) {
|
||||
wd := filepath.Join(os.TempDir(), workingDirectoryName)
|
||||
err := os.MkdirAll(wd, 0755)
|
||||
if err != nil {
|
||||
|
@ -47,6 +57,7 @@ func NewServer(grpcPort, httpPort string) (*Server, error) {
|
|||
wd: wd,
|
||||
grpcPort: grpcPort,
|
||||
httpPort: httpPort,
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
err = srv.writeConfig()
|
||||
|
@ -96,40 +107,206 @@ func (srv *Server) Run(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (srv *Server) writeConfig() error {
|
||||
return atomic.WriteFile(filepath.Join(srv.wd, configFileName), strings.NewReader(`
|
||||
node:
|
||||
id: pomerium-envoy
|
||||
cluster: pomerium-envoy
|
||||
confBytes, err := srv.buildBootstrapConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
admin:
|
||||
access_log_path: /tmp/admin_access.log
|
||||
address:
|
||||
socket_address: { address: 127.0.0.1, port_value: 9901 }
|
||||
cfgPath := filepath.Join(srv.wd, configFileName)
|
||||
log.WithLevel(zerolog.DebugLevel).Str("service", "envoy").Str("location", cfgPath).Msg("wrote config file to location")
|
||||
|
||||
dynamic_resources:
|
||||
cds_config:
|
||||
ads: {}
|
||||
resource_api_version: V3
|
||||
lds_config:
|
||||
ads: {}
|
||||
resource_api_version: V3
|
||||
ads_config:
|
||||
api_type: GRPC
|
||||
transport_api_version: V3
|
||||
grpc_services:
|
||||
- envoy_grpc:
|
||||
cluster_name: pomerium-control-plane-grpc
|
||||
static_resources:
|
||||
clusters:
|
||||
- name: pomerium-control-plane-grpc
|
||||
connect_timeout: { seconds: 5 }
|
||||
type: STATIC
|
||||
hosts:
|
||||
- socket_address:
|
||||
address: 127.0.0.1
|
||||
port_value: `+srv.grpcPort+`
|
||||
http2_protocol_options: {}
|
||||
`))
|
||||
return atomic.WriteFile(cfgPath, bytes.NewReader(confBytes))
|
||||
}
|
||||
|
||||
func (srv *Server) buildBootstrapConfig() ([]byte, error) {
|
||||
|
||||
nodeCfg := &envoy_config_core_v3.Node{
|
||||
Id: "proxy",
|
||||
Cluster: "proxy",
|
||||
}
|
||||
|
||||
adminCfg := &envoy_config_bootstrap_v3.Admin{
|
||||
AccessLogPath: "/tmp/admin_access.log",
|
||||
Address: &envoy_config_core_v3.Address{
|
||||
Address: &envoy_config_core_v3.Address_SocketAddress{
|
||||
SocketAddress: &envoy_config_core_v3.SocketAddress{
|
||||
Address: "127.0.0.1",
|
||||
PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{
|
||||
PortValue: 9901,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
dynamicCfg := &envoy_config_bootstrap_v3.Bootstrap_DynamicResources{
|
||||
AdsConfig: &envoy_config_core_v3.ApiConfigSource{
|
||||
ApiType: envoy_config_core_v3.ApiConfigSource_ApiType(envoy_config_core_v3.ApiConfigSource_ApiType_value["GRPC"]),
|
||||
TransportApiVersion: envoy_config_core_v3.ApiVersion_V3,
|
||||
GrpcServices: []*envoy_config_core_v3.GrpcService{
|
||||
{
|
||||
TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{
|
||||
EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{
|
||||
ClusterName: "pomerium-control-plane-grpc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
LdsConfig: &envoy_config_core_v3.ConfigSource{
|
||||
ResourceApiVersion: envoy_config_core_v3.ApiVersion_V3,
|
||||
ConfigSourceSpecifier: &envoy_config_core_v3.ConfigSource_Ads{},
|
||||
},
|
||||
CdsConfig: &envoy_config_core_v3.ConfigSource{
|
||||
ResourceApiVersion: envoy_config_core_v3.ApiVersion_V3,
|
||||
ConfigSourceSpecifier: &envoy_config_core_v3.ConfigSource_Ads{},
|
||||
},
|
||||
}
|
||||
|
||||
controlPlanePort, err := strconv.Atoi(srv.grpcPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid control plane port: %w", err)
|
||||
}
|
||||
|
||||
controlPlaneEndpoint := &envoy_config_endpoint_v3.LbEndpoint_Endpoint{
|
||||
Endpoint: &envoy_config_endpoint_v3.Endpoint{
|
||||
Address: &envoy_config_core_v3.Address{
|
||||
Address: &envoy_config_core_v3.Address_SocketAddress{
|
||||
SocketAddress: &envoy_config_core_v3.SocketAddress{
|
||||
Address: "127.0.0.1",
|
||||
PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{
|
||||
PortValue: uint32(controlPlanePort),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
controlPlaneCluster := &envoy_config_cluster_v3.Cluster{
|
||||
Name: "pomerium-control-plane-grpc",
|
||||
ConnectTimeout: &durationpb.Duration{
|
||||
Seconds: 5,
|
||||
},
|
||||
ClusterDiscoveryType: &envoy_config_cluster_v3.Cluster_Type{
|
||||
Type: envoy_config_cluster_v3.Cluster_STATIC,
|
||||
},
|
||||
LbPolicy: envoy_config_cluster_v3.Cluster_ROUND_ROBIN,
|
||||
LoadAssignment: &envoy_config_endpoint_v3.ClusterLoadAssignment{
|
||||
ClusterName: "pomerium-control-plane-grpc",
|
||||
Endpoints: []*envoy_config_endpoint_v3.LocalityLbEndpoints{
|
||||
{
|
||||
LbEndpoints: []*envoy_config_endpoint_v3.LbEndpoint{
|
||||
{
|
||||
HostIdentifier: controlPlaneEndpoint,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Http2ProtocolOptions: &envoy_config_core_v3.Http2ProtocolOptions{},
|
||||
}
|
||||
|
||||
staticCfg := &envoy_config_bootstrap_v3.Bootstrap_StaticResources{
|
||||
Clusters: []*envoy_config_cluster_v3.Cluster{
|
||||
controlPlaneCluster,
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &envoy_config_bootstrap_v3.Bootstrap{
|
||||
Node: nodeCfg,
|
||||
Admin: adminCfg,
|
||||
DynamicResources: dynamicCfg,
|
||||
StaticResources: staticCfg,
|
||||
}
|
||||
|
||||
traceOpts, err := config.NewTracingOptions(srv.opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tracing config: %w", err)
|
||||
}
|
||||
|
||||
if err := srv.addTraceConfig(traceOpts, cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to add tracing config: %w", err)
|
||||
}
|
||||
|
||||
jsonBytes, err := protojson.Marshal(proto.MessageV2(cfg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonBytes, nil
|
||||
}
|
||||
|
||||
func (srv *Server) addTraceConfig(traceOpts *config.TracingOptions, bootCfg *envoy_config_bootstrap_v3.Bootstrap) error {
|
||||
|
||||
if !traceOpts.Enabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
zipkinPort, err := strconv.Atoi(traceOpts.ZipkinEndpoint.Port())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port number: %w", err)
|
||||
}
|
||||
zipkinAddress := traceOpts.ZipkinEndpoint.Hostname()
|
||||
const zipkinClusterName = "zipkin"
|
||||
|
||||
zipkinEndpoint := &envoy_config_endpoint_v3.LbEndpoint_Endpoint{
|
||||
Endpoint: &envoy_config_endpoint_v3.Endpoint{
|
||||
Address: &envoy_config_core_v3.Address{
|
||||
Address: &envoy_config_core_v3.Address_SocketAddress{
|
||||
SocketAddress: &envoy_config_core_v3.SocketAddress{
|
||||
Address: zipkinAddress,
|
||||
PortSpecifier: &envoy_config_core_v3.SocketAddress_PortValue{
|
||||
PortValue: uint32(zipkinPort),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
zipkinCluster := &envoy_config_cluster_v3.Cluster{
|
||||
Name: zipkinClusterName,
|
||||
ConnectTimeout: &durationpb.Duration{
|
||||
Seconds: 10,
|
||||
},
|
||||
ClusterDiscoveryType: &envoy_config_cluster_v3.Cluster_Type{
|
||||
Type: envoy_config_cluster_v3.Cluster_STATIC,
|
||||
},
|
||||
LbPolicy: envoy_config_cluster_v3.Cluster_ROUND_ROBIN,
|
||||
LoadAssignment: &envoy_config_endpoint_v3.ClusterLoadAssignment{
|
||||
ClusterName: zipkinClusterName,
|
||||
Endpoints: []*envoy_config_endpoint_v3.LocalityLbEndpoints{
|
||||
{
|
||||
LbEndpoints: []*envoy_config_endpoint_v3.LbEndpoint{
|
||||
{
|
||||
HostIdentifier: zipkinEndpoint,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tracingTC, _ := ptypes.MarshalAny(
|
||||
&envoy_config_trace_v3.ZipkinConfig{
|
||||
CollectorCluster: zipkinClusterName,
|
||||
CollectorEndpoint: traceOpts.ZipkinEndpoint.Path,
|
||||
CollectorEndpointVersion: envoy_config_trace_v3.ZipkinConfig_HTTP_JSON,
|
||||
},
|
||||
)
|
||||
|
||||
tracingCfg := &envoy_config_trace_v3.Tracing{
|
||||
Http: &envoy_config_trace_v3.Tracing_Http{
|
||||
Name: "envoy.tracers.zipkin",
|
||||
ConfigType: &envoy_config_trace_v3.Tracing_Http_TypedConfig{
|
||||
TypedConfig: tracingTC,
|
||||
},
|
||||
},
|
||||
}
|
||||
bootCfg.StaticResources.Clusters = append(bootCfg.StaticResources.Clusters, zipkinCluster)
|
||||
bootCfg.Tracing = tracingCfg
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) handleLogs(stdout io.ReadCloser) {
|
||||
|
|
|
@ -10,16 +10,12 @@ import (
|
|||
prom "github.com/prometheus/client_golang/prometheus"
|
||||
"go.opencensus.io/stats/view"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/envoy"
|
||||
log "github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
var envoyURL = envoy.EnvoyAdminURL
|
||||
|
||||
// PrometheusHandler creates an exporter that exports stats to Prometheus
|
||||
// and returns a handler suitable for exporting metrics.
|
||||
func PrometheusHandler() (http.Handler, error) {
|
||||
func PrometheusHandler(envoyURL *url.URL) (http.Handler, error) {
|
||||
if err := registerDefaultViews(); err != nil {
|
||||
return nil, fmt.Errorf("telemetry/metrics: failed registering views")
|
||||
}
|
||||
|
@ -35,7 +31,7 @@ func PrometheusHandler() (http.Handler, error) {
|
|||
view.RegisterExporter(exporter)
|
||||
mux := http.NewServeMux()
|
||||
|
||||
envoyMetricsURL, err := urlutil.ParseAndValidateURL(fmt.Sprintf("%s/stats/prometheus", envoyURL))
|
||||
envoyMetricsURL, err := envoyURL.Parse("/stats/prometheus")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("telemetry/metrics: invalid proxy URL: %w", err)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
@ -27,8 +28,8 @@ envoy_server_initialization_time_ms_bucket{le="1000"} 1
|
|||
}
|
||||
}
|
||||
|
||||
func getMetrics(t *testing.T) []byte {
|
||||
h, err := PrometheusHandler()
|
||||
func getMetrics(t *testing.T, envoyURL *url.URL) []byte {
|
||||
h, err := PrometheusHandler(envoyURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -48,7 +49,7 @@ func getMetrics(t *testing.T) []byte {
|
|||
func Test_PrometheusHandler(t *testing.T) {
|
||||
|
||||
t.Run("no envoy", func(t *testing.T) {
|
||||
b := getMetrics(t)
|
||||
b := getMetrics(t, &url.URL{})
|
||||
|
||||
if m, _ := regexp.Match(`(?m)^# HELP .*`, b); !m {
|
||||
t.Errorf("Metrics endpoint did not contain any help messages: %s", b)
|
||||
|
@ -57,8 +58,8 @@ func Test_PrometheusHandler(t *testing.T) {
|
|||
|
||||
t.Run("with envoy", func(t *testing.T) {
|
||||
fakeEnvoyMetricsServer := httptest.NewServer(newEnvoyMetricsHandler())
|
||||
envoyURL = fakeEnvoyMetricsServer.URL
|
||||
b := getMetrics(t)
|
||||
envoyURL, _ := url.Parse(fakeEnvoyMetricsServer.URL)
|
||||
b := getMetrics(t, envoyURL)
|
||||
|
||||
if m, _ := regexp.Match(`(?m)^go_.*`, b); !m {
|
||||
t.Errorf("Metrics endpoint did not contain internal metrics: %s", b)
|
||||
|
|
|
@ -10,47 +10,18 @@ import (
|
|||
zipkinHTTP "github.com/openzipkin/zipkin-go/reporter/http"
|
||||
"go.opencensus.io/trace"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// JaegerTracingProviderName is the name of the tracing provider Jaeger.
|
||||
JaegerTracingProviderName = "jaeger"
|
||||
// ZipkinTracingProviderName is the name of the tracing provider Zipkin.
|
||||
ZipkinTracingProviderName = "zipkin"
|
||||
)
|
||||
|
||||
// TracingOptions contains the configurations settings for a http server.
|
||||
type TracingOptions struct {
|
||||
// Shared
|
||||
Provider string
|
||||
Service string
|
||||
Debug bool
|
||||
|
||||
// Jaeger
|
||||
|
||||
// CollectorEndpoint is the full url to the Jaeger HTTP Thrift collector.
|
||||
// For example, http://localhost:14268/api/traces
|
||||
JaegerCollectorEndpoint string `mapstructure:"tracing_jaeger_collector_endpoint"`
|
||||
// AgentEndpoint instructs exporter to send spans to jaeger-agent at this address.
|
||||
// For example, localhost:6831.
|
||||
JaegerAgentEndpoint string `mapstructure:"tracing_jaeger_agent_endpoint"`
|
||||
|
||||
// Zipkin
|
||||
|
||||
// ZipkinEndpoint configures the zipkin collector URI
|
||||
// Example: http://zipkin:9411/api/v2/spans
|
||||
ZipkinEndpoint string `mapstructure:"tracing_zipkin_endpoint"`
|
||||
}
|
||||
|
||||
// RegisterTracing creates a new trace exporter from TracingOptions.
|
||||
func RegisterTracing(opts *TracingOptions) (trace.Exporter, error) {
|
||||
func RegisterTracing(opts *config.TracingOptions) (trace.Exporter, error) {
|
||||
var exporter trace.Exporter
|
||||
var err error
|
||||
switch opts.Provider {
|
||||
case JaegerTracingProviderName:
|
||||
case config.JaegerTracingProviderName:
|
||||
exporter, err = registerJaeger(opts)
|
||||
case ZipkinTracingProviderName:
|
||||
case config.ZipkinTracingProviderName:
|
||||
exporter, err = registerZipkin(opts)
|
||||
default:
|
||||
return nil, fmt.Errorf("telemetry/trace: provider %s unknown", opts.Provider)
|
||||
|
@ -58,10 +29,8 @@ func RegisterTracing(opts *TracingOptions) (trace.Exporter, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.Debug {
|
||||
log.Debug().Msg("telemetry/trace: debug on, sample everything")
|
||||
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
|
||||
}
|
||||
trace.ApplyConfig(trace.Config{DefaultSampler: trace.ProbabilitySampler(opts.SampleRate)})
|
||||
|
||||
log.Debug().Interface("Opts", opts).Msg("telemetry/trace: exporter created")
|
||||
return exporter, nil
|
||||
}
|
||||
|
@ -71,13 +40,15 @@ func UnregisterTracing(exporter trace.Exporter) {
|
|||
trace.UnregisterExporter(exporter)
|
||||
}
|
||||
|
||||
func registerJaeger(opts *TracingOptions) (trace.Exporter, error) {
|
||||
jex, err := jaeger.NewExporter(
|
||||
jaeger.Options{
|
||||
AgentEndpoint: opts.JaegerAgentEndpoint,
|
||||
CollectorEndpoint: opts.JaegerCollectorEndpoint,
|
||||
ServiceName: opts.Service,
|
||||
})
|
||||
func registerJaeger(opts *config.TracingOptions) (trace.Exporter, error) {
|
||||
jOpts := jaeger.Options{
|
||||
ServiceName: opts.Service,
|
||||
AgentEndpoint: opts.JaegerAgentEndpoint,
|
||||
}
|
||||
if opts.JaegerCollectorEndpoint != nil {
|
||||
jOpts.CollectorEndpoint = opts.JaegerCollectorEndpoint.String()
|
||||
}
|
||||
jex, err := jaeger.NewExporter(jOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -85,13 +56,13 @@ func registerJaeger(opts *TracingOptions) (trace.Exporter, error) {
|
|||
return jex, nil
|
||||
}
|
||||
|
||||
func registerZipkin(opts *TracingOptions) (trace.Exporter, error) {
|
||||
func registerZipkin(opts *config.TracingOptions) (trace.Exporter, error) {
|
||||
localEndpoint, err := zipkin.NewEndpoint(opts.Service, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("telemetry/trace: could not create local endpoint: %w", err)
|
||||
}
|
||||
|
||||
reporter := zipkinHTTP.NewReporter(opts.ZipkinEndpoint)
|
||||
reporter := zipkinHTTP.NewReporter(opts.ZipkinEndpoint.String())
|
||||
|
||||
exporter := ocZipkin.NewExporter(reporter, localEndpoint)
|
||||
trace.RegisterExporter(exporter)
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
package trace
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
)
|
||||
|
||||
func TestRegisterTracing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *TracingOptions
|
||||
opts *config.TracingOptions
|
||||
wantErr bool
|
||||
}{
|
||||
{"jaeger", &TracingOptions{JaegerAgentEndpoint: "localhost:6831", Service: "all", Provider: "jaeger"}, false},
|
||||
{"jaeger with debug", &TracingOptions{JaegerAgentEndpoint: "localhost:6831", Service: "all", Provider: "jaeger", Debug: true}, false},
|
||||
{"jaeger no endpoint", &TracingOptions{JaegerAgentEndpoint: "", Service: "all", Provider: "jaeger"}, true},
|
||||
{"unknown provider", &TracingOptions{JaegerAgentEndpoint: "localhost:0", Service: "all", Provider: "Lucius Cornelius Sulla"}, true},
|
||||
{"jaeger", &config.TracingOptions{JaegerAgentEndpoint: "localhost:6831", Service: "all", Provider: "jaeger"}, false},
|
||||
{"jaeger with debug", &config.TracingOptions{JaegerAgentEndpoint: "localhost:6831", Service: "all", Provider: "jaeger", Debug: true}, false},
|
||||
{"jaeger no endpoint", &config.TracingOptions{JaegerAgentEndpoint: "", Service: "all", Provider: "jaeger"}, true},
|
||||
{"unknown provider", &config.TracingOptions{JaegerAgentEndpoint: "localhost:0", Service: "all", Provider: "Lucius Cornelius Sulla"}, true},
|
||||
{"zipkin with debug", &config.TracingOptions{ZipkinEndpoint: &url.URL{Host: "localhost"}, Service: "all", Provider: "zipkin", Debug: true}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue