Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI options to run metrics endpoint over HTTPS #2014

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ func main() {
options.AddFlags(fs)

if err = fs.Parse(args); err != nil {
panic(err)
klog.ErrorS(err, "Failed to parse options")
klog.FlushAndExit(klog.ExitFlushTimeout, 0)
}
if err = options.Validate(); err != nil {
klog.ErrorS(err, "Invalid options")
klog.FlushAndExit(klog.ExitFlushTimeout, 0)
}

err = logsapi.ValidateAndApply(c, featureGate)
Expand Down Expand Up @@ -130,7 +135,7 @@ func main() {

if options.HttpEndpoint != "" {
r := metrics.InitializeRecorder()
r.InitializeMetricsHandler(options.HttpEndpoint, "/metrics")
r.InitializeMetricsHandler(options.HttpEndpoint, "/metrics", options.MetricsCertFile, options.MetricsKeyFile)
}

cfg := metadata.MetadataServiceConfig{
Expand Down
2 changes: 2 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ There are a couple of driver options that can be passed as arguments when starti
|-----------------------------|---------------------------------------------------|-----------------------------------------------------|---------------------|
| endpoint | tcp://127.0.0.1:10000/ | unix:///var/lib/csi/sockets/pluginproxy/csi.sock | The socket on which the driver will listen for CSI RPCs|
| http-endpoint | :8080 | | The TCP network address where the HTTP server for metrics will listen (example: `:8080`). The default is empty string, which means the server is disabled.|
| metrics-cert-file | /metrics.crt | | The path to a certificate to use for serving the metrics server over HTTPS. If the certificate is signed by a certificate authority, this file should be the concatenation of the server's certificate, any intermediates, and the CA's certificate. If this is non-empty, `--http-endpoint` and `--metrics-key-file` MUST also be non-empty.|
| metrics-key-file | /metrics.key | | The path to a key to use for serving the metrics server over HTTPS. If this is non-empty, `--http-endpoint` and `--metrics-cert-file` MUST also be non-empty.|
| volume-attach-limit | 1,2,3 ... | -1 | Value for the maximum number of volumes attachable per node. If specified, the limit applies to all nodes. If not specified, the value is approximated from the instance type|
| extra-tags | key1=value1,key2=value2 | | Tags attached to each dynamically provisioned resource|
| k8s-tag-cluster-id | aws-cluster-id-1 | | ID of the Kubernetes cluster used for tagging provisioned EBS volumes|
Expand Down
23 changes: 21 additions & 2 deletions pkg/driver/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ type Options struct {
Endpoint string
// HttpEndpoint is the TCP network address where the HTTP server for metrics will listen
HttpEndpoint string
// MetricsCertFile is the location of the certificate for serving the metrics server over HTTPS
MetricsCertFile string
// MetricsKeyFile is the location of the key for serving the metrics server over HTTPS
MetricsKeyFile string
// EnableOtelTracing is a flag to enable opentelemetry tracing for the driver
EnableOtelTracing bool

Expand Down Expand Up @@ -83,6 +87,8 @@ func (o *Options) AddFlags(f *flag.FlagSet) {
// Server options
f.StringVar(&o.Endpoint, "endpoint", DefaultCSIEndpoint, "Endpoint for the CSI driver server")
f.StringVar(&o.HttpEndpoint, "http-endpoint", "", "The TCP network address where the HTTP server for metrics will listen (example: `:8080`). The default is empty string, which means the server is disabled.")
f.StringVar(&o.MetricsCertFile, "metrics-cert-file", "", "The path to a certificate to use for serving the metrics server over HTTPS. If the certificate is signed by a certificate authority, this file should be the concatenation of the server's certificate, any intermediates, and the CA's certificate. If this is non-empty, --http-endpoint and --metrics-key-file MUST also be non-empty.")
AndrewSirenko marked this conversation as resolved.
Show resolved Hide resolved
f.StringVar(&o.MetricsKeyFile, "metrics-key-file", "", "The path to a key to use for serving the metrics server over HTTPS. If this is non-empty, --http-endpoint and --metrics-cert-file MUST also be non-empty.")
f.BoolVar(&o.EnableOtelTracing, "enable-otel-tracing", false, "To enable opentelemetry tracing for the driver. The tracing is disabled by default. Configure the exporter endpoint with OTEL_EXPORTER_OTLP_ENDPOINT and other env variables, see https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration.")

// Controller options
Expand All @@ -104,8 +110,21 @@ func (o *Options) AddFlags(f *flag.FlagSet) {
}

func (o *Options) Validate() error {
if o.VolumeAttachLimit != -1 && o.ReservedVolumeAttachments != -1 {
return fmt.Errorf("only one of --volume-attach-limit and --reserved-volume-attachments may be specified")
if o.Mode == AllMode || o.Mode == NodeMode {
if o.VolumeAttachLimit != -1 && o.ReservedVolumeAttachments != -1 {
return fmt.Errorf("only one of --volume-attach-limit and --reserved-volume-attachments may be specified")
}
}

if o.MetricsCertFile != "" || o.MetricsKeyFile != "" {
if o.HttpEndpoint == "" {
return fmt.Errorf("--http-endpoint MUST be specififed when using the metrics server with HTTPS")
} else if o.MetricsCertFile == "" {
return fmt.Errorf("--metrics-cert-file MUST be specififed when using the metrics server with HTTPS")
} else if o.MetricsKeyFile == "" {
return fmt.Errorf("--metrics-key-file MUST be specififed when using the metrics server with HTTPS")
}
}

return nil
}
67 changes: 66 additions & 1 deletion pkg/driver/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ func TestAddFlags(t *testing.T) {
if err := f.Set("http-endpoint", ":8080"); err != nil {
t.Errorf("error setting http-endpoint: %v", err)
}
if err := f.Set("metrics-cert-file", "/https.crt"); err != nil {
t.Errorf("error setting metrics-cert-file: %v", err)
}
if err := f.Set("metrics-key-file", "/https.key"); err != nil {
t.Errorf("error setting metrics-key-file: %v", err)
}
if err := f.Set("enable-otel-tracing", "true"); err != nil {
t.Errorf("error setting enable-otel-tracing: %v", err)
}
Expand Down Expand Up @@ -89,7 +95,7 @@ func TestAddFlags(t *testing.T) {
}
}

func TestValidate(t *testing.T) {
func TestValidateAttachmentLimits(t *testing.T) {
tests := []struct {
name string
volumeAttachLimit int64
Expand Down Expand Up @@ -127,6 +133,7 @@ func TestValidate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &Options{
Mode: NodeMode,
VolumeAttachLimit: tt.volumeAttachLimit,
ReservedVolumeAttachments: tt.reservedAttachments,
}
Expand All @@ -142,3 +149,61 @@ func TestValidate(t *testing.T) {
})
}
}

func TestValidateMetricsHTTPS(t *testing.T) {
tests := []struct {
name string
httpEndpoint string
metricsCertFile string
metricsKeyFile string
expectError bool
}{
{
name: "disabled",
},
{
name: "only http",
httpEndpoint: ":8080",
},
{
name: "https with all",
httpEndpoint: ":443",
metricsCertFile: "/https.crt",
metricsKeyFile: "/https.key",
},
{
name: "https with endpoint missing",
metricsCertFile: "/https.crt",
metricsKeyFile: "/https.key",
expectError: true,
},
{
name: "https with cert missing",
httpEndpoint: ":443",
metricsKeyFile: "/https.key",
expectError: true,
},
{
name: "https with key missing",
httpEndpoint: ":443",
metricsCertFile: "/https.crt",
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &Options{
Mode: ControllerMode,
HttpEndpoint: tt.httpEndpoint,
MetricsCertFile: tt.metricsCertFile,
MetricsKeyFile: tt.metricsKeyFile,
}

err := o.Validate()
if (err != nil) != tt.expectError {
t.Errorf("Options.Validate() error = %v, wantErr %v", err, tt.expectError)
}
})
}
}
11 changes: 9 additions & 2 deletions pkg/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (m *metricRecorder) ObserveHistogram(name string, value float64, labels map
}

// InitializeMetricsHandler starts a new HTTP server to expose the metrics.
func (m *metricRecorder) InitializeMetricsHandler(address, path string) {
func (m *metricRecorder) InitializeMetricsHandler(address, path, certFile, keyFile string) {
if m == nil {
klog.InfoS("InitializeMetricsHandler: metric recorder is not initialized")
return
Expand All @@ -92,9 +92,16 @@ func (m *metricRecorder) InitializeMetricsHandler(address, path string) {
}

go func() {
var err error
klog.InfoS("Metric server listening", "address", address, "path", path)

if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
if certFile != "" {
err = server.ListenAndServeTLS(certFile, keyFile)
} else {
err = server.ListenAndServe()
}

if err != nil && err != http.ErrServerClosed {
klog.ErrorS(err, "Failed to start metric server", "address", address, "path", path)
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
}
Expand Down
Loading