Skip to content
Open
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
143 changes: 134 additions & 9 deletions cmd/machine-api-operator/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ package main

import (
"context"
"crypto/tls"
"errors"
"flag"
"fmt"
"net/http"
"os"
"reflect"
"strconv"
"sync"
"sync/atomic"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
coreclientsetv1 "k8s.io/client-go/kubernetes/typed/core/v1"
Expand All @@ -24,16 +29,21 @@ import (
"k8s.io/utils/clock"

osconfigv1 "github.com/openshift/api/config/v1"
osclientset "github.com/openshift/client-go/config/clientset/versioned"
utiltls "github.com/openshift/library-go/pkg/controllerruntime/tls"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/machine-api-operator/pkg/metrics"
"github.com/openshift/machine-api-operator/pkg/operator"
"github.com/openshift/machine-api-operator/pkg/util"
"github.com/openshift/machine-api-operator/pkg/version"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
// defaultMetricsPort is the default port to expose metrics.
defaultMetricsPort = 8080
defaultMetricsPort = 8443
metricsCertFile = "/etc/tls/private/tls.crt"
metricsKeyFile = "/etc/tls/private/tls.key"
)

var (
Expand Down Expand Up @@ -82,33 +92,50 @@ func runStartCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("error creating clients: %v", err)
}
stopCh := make(chan struct{})
leaderElectionCtx, leaderElectionCancel := context.WithCancel(context.Background())
var shutdownOnce sync.Once
var shuttingDown atomic.Bool
shutdown := func() {
shutdownOnce.Do(func() {
shuttingDown.Store(true)
close(stopCh)
leaderElectionCancel()
})
}

le := util.GetLeaderElectionConfig(cb.config, osconfigv1.LeaderElection{})

leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{
leaderelection.RunOrDie(leaderElectionCtx, leaderelection.LeaderElectionConfig{
Lock: CreateResourceLock(cb, componentNamespace, componentName),
RenewDeadline: le.RenewDeadline.Duration,
RetryPeriod: le.RetryPeriod.Duration,
LeaseDuration: le.LeaseDuration.Duration,
Callbacks: leaderelection.LeaderCallbacks{
OnStartedLeading: func(ctx context.Context) {
ctrlCtx := CreateControllerContext(cb, stopCh, componentNamespace)
if err := setupTLSProfileWatcher(ctrlCtx, shutdown); err != nil {
klog.Fatalf("Unable to set up TLS profile watcher: %v", err)
}
startControllersOrDie(ctrlCtx)
ctrlCtx.KubeNamespacedInformerFactory.Start(ctrlCtx.Stop)
ctrlCtx.ConfigInformerFactory.Start(ctrlCtx.Stop)
initMachineAPIInformers(ctrlCtx)
startMetricsCollectionAndServer(ctrlCtx)
close(ctrlCtx.InformersStarted)

select {}
<-stopCh
},
OnStoppedLeading: func() {
if shuttingDown.Load() {
klog.Info("Leader election stopped due to shutdown")
return
}
klog.Fatalf("Leader election lost")
},
},
ReleaseOnCancel: true,
})
panic("unreachable")
return nil
}

func initMachineAPIInformers(ctx *ControllerContext) {
Expand Down Expand Up @@ -196,16 +223,114 @@ func startMetricsCollectionAndServer(ctx *ControllerContext) {
metricsPort = v
}
klog.V(4).Info("Starting server to serve prometheus metrics")
go startHTTPMetricServer(fmt.Sprintf("localhost:%d", metricsPort))
tlsConfig, err := metricsTLSConfig(ctx)
if err != nil {
klog.Fatalf("Unable to configure metrics TLS: %v", err)
}
go startHTTPSMetricServer(fmt.Sprintf(":%d", metricsPort), tlsConfig)
}

func metricsTLSConfig(ctx *ControllerContext) (*tls.Config, error) {
scheme := runtime.NewScheme()
if err := osconfigv1.Install(scheme); err != nil {
return nil, fmt.Errorf("unable to add config.openshift.io scheme: %w", err)
}

k8sClient, err := client.New(ctx.ClientBuilder.config, client.Options{Scheme: scheme})
if err != nil {
return nil, fmt.Errorf("unable to create Kubernetes client: %w", err)
}

tlsSecurityProfileSpec, err := utiltls.FetchAPIServerTLSProfile(context.Background(), k8sClient)
if err != nil {
return nil, fmt.Errorf("unable to get TLS profile from API server: %w", err)
}

tlsConfigFn, unsupportedCiphers := utiltls.NewTLSConfigFromProfile(tlsSecurityProfileSpec)
if len(unsupportedCiphers) > 0 {
klog.Infof("TLS configuration contains unsupported ciphers that will be ignored: %v", unsupportedCiphers)
}

tlsConfig := &tls.Config{}
tlsConfigFn(tlsConfig)

return tlsConfig, nil
}

func setupTLSProfileWatcher(ctx *ControllerContext, shutdown func()) error {
configClient := ctx.ClientBuilder.OpenshiftClientOrDie("tls-profile-watcher")
initialProfile, err := fetchAPIServerTLSProfileSpec(context.Background(), configClient)
if err != nil {
return err
}

apiServerInformer := ctx.ConfigInformerFactory.Config().V1().APIServers().Informer()
_, err = apiServerInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
handleTLSProfileEvent(obj, initialProfile, shutdown)
},
UpdateFunc: func(_, newObj interface{}) {
handleTLSProfileEvent(newObj, initialProfile, shutdown)
},
})
if err != nil {
return fmt.Errorf("failed to add APIServer event handler: %w", err)
}

return nil
}

func fetchAPIServerTLSProfileSpec(ctx context.Context, configClient osclientset.Interface) (osconfigv1.TLSProfileSpec, error) {
apiServer, err := configClient.ConfigV1().APIServers().Get(ctx, utiltls.APIServerName, metav1.GetOptions{})
if err != nil {
return osconfigv1.TLSProfileSpec{}, fmt.Errorf("failed to get APIServer %q: %w", utiltls.APIServerName, err)
}

profile, err := utiltls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
if err != nil {
return osconfigv1.TLSProfileSpec{}, fmt.Errorf("failed to get TLS profile from APIServer %q: %w", utiltls.APIServerName, err)
}

return profile, nil
}

func handleTLSProfileEvent(obj interface{}, initialProfile osconfigv1.TLSProfileSpec, shutdown func()) {
apiServer, ok := obj.(*osconfigv1.APIServer)
if !ok {
return
}
if apiServer.Name != utiltls.APIServerName {
return
}

currentProfile, err := utiltls.GetTLSProfileSpec(apiServer.Spec.TLSSecurityProfile)
if err != nil {
klog.Errorf("Failed to get TLS profile from APIServer %q: %v", apiServer.Name, err)
return
}

if reflect.DeepEqual(initialProfile, currentProfile) {
klog.V(2).Info("TLS security profile unchanged")
return
}

klog.Infof("TLS security profile has changed, initiating a shutdown to pick up the new configuration: initialMinTLSVersion=%s currentMinTLSVersion=%s initialCiphers=%v currentCiphers=%v",
initialProfile.MinTLSVersion,
currentProfile.MinTLSVersion,
initialProfile.Ciphers,
currentProfile.Ciphers,
)
shutdown()
}

func startHTTPMetricServer(metricsPort string) {
func startHTTPSMetricServer(metricsAddr string, tlsConfig *tls.Config) {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())

server := &http.Server{
Addr: metricsPort,
Handler: mux,
Addr: metricsAddr,
Handler: mux,
TLSConfig: tlsConfig,
}
klog.Fatal(server.ListenAndServe())
klog.Fatal(server.ListenAndServeTLS(metricsCertFile, metricsKeyFile))
}
49 changes: 26 additions & 23 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ replace (
github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20251001123353-fd5b1fb35db1
k8s.io/apiserver => github.com/openshift/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20251015171918-61114aa5a292 // openshift kubernetes has very old copy of k8s.io/kubernetes/pkg/kubelet/server/server.go
k8s.io/kubelet => github.com/openshift/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20251015171918-61114aa5a292 // openshift kubernetes has very old copy of k8s.io/kubernetes/cmd/kubelet/app/options/options.go
k8s.io/kubernetes => github.com/openshift/kubernetes v1.30.1-0.20251027205255-4e0347881cbd
k8s.io/kubernetes => /home/rmanak/work-git/kubernetes
)

// TODO: remove once https://github.com/openshift/library-go/pull/2082 is merged
replace github.com/openshift/library-go => github.com/damdo/library-go v0.0.0-20260120133104-12bddc18ba38

require (
github.com/blang/semver v3.5.1+incompatible
github.com/go-logr/logr v1.4.3
Expand All @@ -29,15 +32,15 @@ require (
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/vmware/govmomi v0.52.0
golang.org/x/net v0.46.0
golang.org/x/net v0.47.0
golang.org/x/time v0.14.0
gopkg.in/gcfg.v1 v1.2.3 // indirect
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/apiserver v0.34.1
k8s.io/client-go v0.34.1
k8s.io/api v0.34.3
k8s.io/apimachinery v0.34.3
k8s.io/apiserver v0.34.3
k8s.io/client-go v0.34.3
k8s.io/cloud-provider-vsphere v1.32.2
k8s.io/component-base v0.34.1
k8s.io/component-base v0.34.3
k8s.io/cri-client v0.34.1 // indirect
k8s.io/csi-translation-lib v0.34.1 // indirect
k8s.io/dynamic-resource-allocation v0.34.1 // indirect
Expand All @@ -49,7 +52,7 @@ require (
k8s.io/sample-apiserver v0.34.1 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/cluster-api v1.11.3
sigs.k8s.io/controller-runtime v0.22.3
sigs.k8s.io/controller-runtime v0.22.5
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240923090159-236e448db12c
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96
sigs.k8s.io/yaml v1.6.0
Expand All @@ -61,6 +64,7 @@ require (
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
4d63.com/gochecknoglobals v0.2.2 // indirect
cel.dev/expr v0.24.0 // indirect
cyphar.com/go-pathrs v0.2.1 // indirect
github.com/4meepo/tagalign v1.4.2 // indirect
github.com/Abirdcfly/dupword v0.1.3 // indirect
github.com/Antonboom/errname v1.0.0 // indirect
Expand Down Expand Up @@ -112,9 +116,9 @@ require (
github.com/containerd/ttrpc v1.2.6 // indirect
github.com/containerd/typeurl/v2 v2.2.2 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
github.com/curioswitch/go-reassign v0.3.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
Expand Down Expand Up @@ -237,12 +241,11 @@ require (
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.19.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/cgroups v0.0.3 // indirect
github.com/opencontainers/cgroups v0.0.6 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.5 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/opencontainers/selinux v1.11.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/opencontainers/selinux v1.13.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand Down Expand Up @@ -321,16 +324,16 @@ require (
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
Expand All @@ -346,14 +349,14 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.6.1 // indirect
k8s.io/apiextensions-apiserver v0.34.1 // indirect
k8s.io/apiextensions-apiserver v0.34.3 // indirect
k8s.io/cli-runtime v0.34.1 // indirect
k8s.io/cloud-provider v0.32.0 // indirect
k8s.io/cluster-bootstrap v0.33.3 // indirect
k8s.io/component-helpers v0.34.1 // indirect
k8s.io/controller-manager v0.32.1 // indirect
k8s.io/cri-api v0.34.1 // indirect
k8s.io/kms v0.34.1 // indirect
k8s.io/kms v0.34.3 // indirect
k8s.io/kube-aggregator v0.34.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/kubelet v0.34.1 // indirect
Expand Down
Loading