Upgrade the operator to use Operator SDK v1.33.0 (#182)

* Move controller package inside internal directory

Based on the go/v4 project structure, the following changed:
- Pakcage `controllers` is now named `controller`
- Package `controller` now lives inside new `internal` directory

* Move main.go in cmd directory

Based on the new go/v4 project structure, `main.go` now lives in the `cmd` directory.

* Change package import in main.go

* Update go mod dependencies

Update the dependencies based on the versions obtained by creating a new operator project using `kubebuilder init --domain onepassword.com --plugins=go/v4`.

This is based on the migration steps provided to go from go/v3 to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4)

* Update vendor

* Adjust code for breaking changes from pkg update

sigs.k8s.io/controller-runtime package had breaking changes from v0.14.5 to v0.16.3. This commit brings the changes needed to achieve the same things using the new functionality avaialble.

* Adjust paths to connect yaml files

Since `main.go` is now in `cmd` directory, the paths to the files for deploying Connect have to be adjusted based on the new location `main.go` is executed from.

* Update files based on new structure and scaffolding

These changes are made based on the new project structure and scaffolding obtained when using the new go/v4 project structure.

These were done based on the migration steps mentioned when migrating to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4).

* Update config files

These updates are made based on the Kustomize v4 syntax.

This is part of the upgrate to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4)

* Update dependencies and GO version

* Update vendor

* Update Kubernetes tools versions

* Update operator version in Makefile

Now the version in the Makefile matches the version of the operator

* Update Operator SDK version in version.go

* Adjust generated deepcopy

It seems that the +build tag is no longer needed based on the latest generated scaffolding, therefore it's removed.

* Update copyright year

* Bring back missing changes from migration

Some customization in Makefile was lost during the migration process. Specifically, the namespace customization for `make deploy` command.

Also, we push changes to kustomization.yaml for making the deploy process smoother.

* Add RBAC perms for coordination.k8s.io

It seems that with the latest changes to Kubernetes and Kustomize, we need to add additional RBAC to the service account used so that it can properly access the `leases` resource.

* Optimize Dockerfile

Dockerfile had a step for caching dependencies (go mod download). However, this is already done by the vendor directory, which we include. Therefore, this step can be removed to make the image build time faster.
This commit is contained in:
Eduard Filip
2024-01-25 14:21:31 +01:00
committed by GitHub
parent 8fc852a4dd
commit f72e5243b0
1356 changed files with 86780 additions and 43671 deletions

View File

@@ -18,8 +18,6 @@ package metrics
import (
"context"
"net/url"
"time"
"github.com/prometheus/client_golang/prometheus"
clientmetrics "k8s.io/client-go/tools/metrics"
@@ -29,70 +27,9 @@ import (
// that client-go registers metrics. We copy the names and formats
// from Kubernetes so that we match the core controllers.
// Metrics subsystem and all of the keys used by the rest client.
const (
RestClientSubsystem = "rest_client"
LatencyKey = "request_latency_seconds"
ResultKey = "requests_total"
)
var (
// client metrics.
// RequestLatency reports the request latency in seconds per verb/URL.
// Deprecated: This metric is deprecated for removal in a future release: using the URL as a
// dimension results in cardinality explosion for some consumers. It was deprecated upstream
// in k8s v1.14 and hidden in v1.17 via https://github.com/kubernetes/kubernetes/pull/83836.
// It is not registered by default. To register:
// import (
// clientmetrics "k8s.io/client-go/tools/metrics"
// clmetrics "sigs.k8s.io/controller-runtime/metrics"
// )
//
// func init() {
// clmetrics.Registry.MustRegister(clmetrics.RequestLatency)
// clientmetrics.Register(clientmetrics.RegisterOpts{
// RequestLatency: clmetrics.LatencyAdapter
// })
// }
RequestLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: RestClientSubsystem,
Name: LatencyKey,
Help: "Request latency in seconds. Broken down by verb and URL.",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 10),
}, []string{"verb", "url"})
// requestLatency is a Prometheus Histogram metric type partitioned by
// "verb", and "host" labels. It is used for the rest client latency metrics.
requestLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "rest_client_request_duration_seconds",
Help: "Request latency in seconds. Broken down by verb, and host.",
Buckets: []float64{0.005, 0.025, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 15.0, 30.0, 60.0},
},
[]string{"verb", "host"},
)
requestSize = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "rest_client_request_size_bytes",
Help: "Request size in bytes. Broken down by verb and host.",
// 64 bytes to 16MB
Buckets: []float64{64, 256, 512, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216},
},
[]string{"verb", "host"},
)
responseSize = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "rest_client_response_size_bytes",
Help: "Response size in bytes. Broken down by verb and host.",
// 64 bytes to 16MB
Buckets: []float64{64, 256, 512, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216},
},
[]string{"verb", "host"},
)
requestResult = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "rest_client_requests_total",
@@ -109,17 +46,11 @@ func init() {
// registerClientMetrics sets up the client latency metrics from client-go.
func registerClientMetrics() {
// register the metrics with our registry
Registry.MustRegister(requestLatency)
Registry.MustRegister(requestSize)
Registry.MustRegister(responseSize)
Registry.MustRegister(requestResult)
// register the metrics with client-go
clientmetrics.Register(clientmetrics.RegisterOpts{
RequestLatency: &LatencyAdapter{metric: requestLatency},
RequestSize: &sizeAdapter{metric: requestSize},
ResponseSize: &sizeAdapter{metric: responseSize},
RequestResult: &resultAdapter{metric: requestResult},
RequestResult: &resultAdapter{metric: requestResult},
})
}
@@ -131,24 +62,6 @@ func registerClientMetrics() {
// copied (more-or-less directly) from k8s.io/kubernetes setup code
// (which isn't anywhere in an easily-importable place).
// LatencyAdapter implements LatencyMetric.
type LatencyAdapter struct {
metric *prometheus.HistogramVec
}
// Observe increments the request latency metric for the given verb/URL.
func (l *LatencyAdapter) Observe(_ context.Context, verb string, u url.URL, latency time.Duration) {
l.metric.WithLabelValues(verb, u.String()).Observe(latency.Seconds())
}
type sizeAdapter struct {
metric *prometheus.HistogramVec
}
func (s *sizeAdapter) Observe(ctx context.Context, verb string, host string, size float64) {
s.metric.WithLabelValues(verb, host).Observe(size)
}
type resultAdapter struct {
metric *prometheus.CounterVec
}

View File

@@ -1,52 +0,0 @@
/*
Copyright 2018 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 metrics
import (
"fmt"
"net"
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
)
var log = logf.RuntimeLog.WithName("metrics")
// DefaultBindAddress sets the default bind address for the metrics listener
// The metrics is on by default.
var DefaultBindAddress = ":8080"
// NewListener creates a new TCP listener bound to the given address.
func NewListener(addr string) (net.Listener, error) {
if addr == "" {
// If the metrics bind address is empty, default to ":8080"
addr = DefaultBindAddress
}
// Add a case to disable metrics altogether
if addr == "0" {
return nil, nil
}
log.Info("Metrics server is starting to listen", "addr", addr)
ln, err := net.Listen("tcp", addr)
if err != nil {
er := fmt.Errorf("error listening on %s: %w", addr, err)
log.Error(er, "metrics server failed to listen. You may want to disable the metrics server or use another port if it is due to conflicts")
return nil, er
}
return ln, nil
}

View File

@@ -0,0 +1,26 @@
/*
Copyright 2023 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 server provides the metrics server implementation.
*/
package server
import (
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
)
var log = logf.RuntimeLog.WithName("metrics")

View File

@@ -0,0 +1,312 @@
/*
Copyright 2018 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 server
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"sync"
"time"
"github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus/promhttp"
"k8s.io/client-go/rest"
certutil "k8s.io/client-go/util/cert"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/internal/httpserver"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
const (
defaultMetricsEndpoint = "/metrics"
)
// DefaultBindAddress is the default bind address for the metrics server.
var DefaultBindAddress = ":8080"
// Server is a server that serves metrics.
type Server interface {
// NeedLeaderElection implements the LeaderElectionRunnable interface, which indicates
// the metrics server doesn't need leader election.
NeedLeaderElection() bool
// Start runs the server.
// It will install the metrics related resources depending on the server configuration.
Start(ctx context.Context) error
}
// Options are all available options for the metrics.Server
type Options struct {
// SecureServing enables serving metrics via https.
// Per default metrics will be served via http.
SecureServing bool
// BindAddress is the bind address for the metrics server.
// It will be defaulted to ":8080" if unspecified.
// Set this to "0" to disable the metrics server.
BindAddress string
// ExtraHandlers contains a map of handlers (by path) which will be added to the metrics server.
// This might be useful to register diagnostic endpoints e.g. pprof.
// Note that pprof endpoints are meant to be sensitive and shouldn't be exposed publicly.
// If the simple path -> handler mapping offered here is not enough, a new http
// server/listener should be added as Runnable to the manager via the Add method.
ExtraHandlers map[string]http.Handler
// FilterProvider provides a filter which is a func that is added around
// the metrics and the extra handlers on the metrics server.
// This can be e.g. used to enforce authentication and authorization on the handlers
// endpoint by setting this field to filters.WithAuthenticationAndAuthorization.
FilterProvider func(c *rest.Config, httpClient *http.Client) (Filter, error)
// CertDir is the directory that contains the server key and certificate. Defaults to
// <temp-dir>/k8s-metrics-server/serving-certs.
//
// Note: This option is only used when TLSOpts does not set GetCertificate.
// Note: If certificate or key doesn't exist a self-signed certificate will be used.
CertDir string
// CertName is the server certificate name. Defaults to tls.crt.
//
// Note: This option is only used when TLSOpts does not set GetCertificate.
// Note: If certificate or key doesn't exist a self-signed certificate will be used.
CertName string
// KeyName is the server key name. Defaults to tls.key.
//
// Note: This option is only used when TLSOpts does not set GetCertificate.
// Note: If certificate or key doesn't exist a self-signed certificate will be used.
KeyName string
// TLSOpts is used to allow configuring the TLS config used for the server.
// This also allows providing a certificate via GetCertificate.
TLSOpts []func(*tls.Config)
}
// Filter is a func that is added around metrics and extra handlers on the metrics server.
type Filter func(log logr.Logger, handler http.Handler) (http.Handler, error)
// NewServer constructs a new metrics.Server from the provided options.
func NewServer(o Options, config *rest.Config, httpClient *http.Client) (Server, error) {
o.setDefaults()
// Skip server creation if metrics are disabled.
if o.BindAddress == "0" {
return nil, nil
}
// Validate that ExtraHandlers is not overwriting the default /metrics endpoint.
if o.ExtraHandlers != nil {
if _, ok := o.ExtraHandlers[defaultMetricsEndpoint]; ok {
return nil, fmt.Errorf("overriding builtin %s endpoint is not allowed", defaultMetricsEndpoint)
}
}
// Create the metrics filter if a FilterProvider is set.
var metricsFilter Filter
if o.FilterProvider != nil {
var err error
metricsFilter, err = o.FilterProvider(config, httpClient)
if err != nil {
return nil, fmt.Errorf("filter provider failed to create filter for the metrics server: %w", err)
}
}
return &defaultServer{
metricsFilter: metricsFilter,
options: o,
}, nil
}
// defaultServer is the default implementation used for Server.
type defaultServer struct {
options Options
// metricsFilter is a filter which is added around
// the metrics and the extra handlers on the metrics server.
metricsFilter Filter
// mu protects access to the bindAddr field.
mu sync.RWMutex
// bindAddr is used to store the bindAddr after the listener has been created.
// This is used during testing to figure out the port that has been chosen randomly.
bindAddr string
}
// setDefaults does defaulting for the Server.
func (o *Options) setDefaults() {
if o.BindAddress == "" {
o.BindAddress = DefaultBindAddress
}
if len(o.CertDir) == 0 {
o.CertDir = filepath.Join(os.TempDir(), "k8s-metrics-server", "serving-certs")
}
if len(o.CertName) == 0 {
o.CertName = "tls.crt"
}
if len(o.KeyName) == 0 {
o.KeyName = "tls.key"
}
}
// NeedLeaderElection implements the LeaderElectionRunnable interface, which indicates
// the metrics server doesn't need leader election.
func (*defaultServer) NeedLeaderElection() bool {
return false
}
// Start runs the server.
// It will install the metrics related resources depend on the server configuration.
func (s *defaultServer) Start(ctx context.Context) error {
log.Info("Starting metrics server")
listener, err := s.createListener(ctx, log)
if err != nil {
return fmt.Errorf("failed to start metrics server: failed to create listener: %w", err)
}
// Storing bindAddr here so we can retrieve it during testing via GetBindAddr.
s.mu.Lock()
s.bindAddr = listener.Addr().String()
s.mu.Unlock()
mux := http.NewServeMux()
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{
ErrorHandling: promhttp.HTTPErrorOnError,
})
if s.metricsFilter != nil {
log := log.WithValues("path", defaultMetricsEndpoint)
var err error
handler, err = s.metricsFilter(log, handler)
if err != nil {
return fmt.Errorf("failed to start metrics server: failed to add metrics filter: %w", err)
}
}
// TODO(JoelSpeed): Use existing Kubernetes machinery for serving metrics
mux.Handle(defaultMetricsEndpoint, handler)
for path, extraHandler := range s.options.ExtraHandlers {
if s.metricsFilter != nil {
log := log.WithValues("path", path)
var err error
extraHandler, err = s.metricsFilter(log, extraHandler)
if err != nil {
return fmt.Errorf("failed to start metrics server: failed to add metrics filter to extra handler for path %s: %w", path, err)
}
}
mux.Handle(path, extraHandler)
}
log.Info("Serving metrics server", "bindAddress", s.options.BindAddress, "secure", s.options.SecureServing)
srv := httpserver.New(mux)
idleConnsClosed := make(chan struct{})
go func() {
<-ctx.Done()
log.Info("Shutting down metrics server with timeout of 1 minute")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
// Error from closing listeners, or context timeout
log.Error(err, "error shutting down the HTTP server")
}
close(idleConnsClosed)
}()
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
return err
}
<-idleConnsClosed
return nil
}
func (s *defaultServer) createListener(ctx context.Context, log logr.Logger) (net.Listener, error) {
if !s.options.SecureServing {
return net.Listen("tcp", s.options.BindAddress)
}
cfg := &tls.Config{ //nolint:gosec
NextProtos: []string{"h2"},
}
// fallback TLS config ready, will now mutate if passer wants full control over it
for _, op := range s.options.TLSOpts {
op(cfg)
}
if cfg.GetCertificate == nil {
certPath := filepath.Join(s.options.CertDir, s.options.CertName)
keyPath := filepath.Join(s.options.CertDir, s.options.KeyName)
_, certErr := os.Stat(certPath)
certExists := !os.IsNotExist(certErr)
_, keyErr := os.Stat(keyPath)
keyExists := !os.IsNotExist(keyErr)
if certExists && keyExists {
// Create the certificate watcher and
// set the config's GetCertificate on the TLSConfig
certWatcher, err := certwatcher.New(certPath, keyPath)
if err != nil {
return nil, err
}
cfg.GetCertificate = certWatcher.GetCertificate
go func() {
if err := certWatcher.Start(ctx); err != nil {
log.Error(err, "certificate watcher error")
}
}()
}
}
// If cfg.GetCertificate is still nil, i.e. we didn't configure a cert watcher, fallback to a self-signed certificate.
if cfg.GetCertificate == nil {
// Note: Using self-signed certificates here should be good enough. It's just important that we
// encrypt the communication. For example kube-controller-manager also uses a self-signed certificate
// for the metrics endpoint per default.
cert, key, err := certutil.GenerateSelfSignedCertKeyWithFixtures("localhost", []net.IP{{127, 0, 0, 1}}, nil, "")
if err != nil {
return nil, fmt.Errorf("failed to generate self-signed certificate for metrics server: %w", err)
}
keyPair, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, fmt.Errorf("failed to create self-signed key pair for metrics server: %w", err)
}
cfg.Certificates = []tls.Certificate{keyPair}
}
return tls.Listen("tcp", s.options.BindAddress, cfg)
}
func (s *defaultServer) GetBindAddr() string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.bindAddr
}