controlplanecomponent

package
v0.1.56 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2025 License: Apache-2.0 Imports: 33 Imported by: 0

README

Adding new ControlPlane Component

Creating a manifests directory

Every component needs to create a directory under /control-plane-operator/controllers/hostedcontrolplane/v2/assets to host its manifests, the name of the directory is your component's name

mkdir /control-plane-operator/controllers/hostedcontrolplane/v2/assets/my-component
Creating Workload manifest

Create a new file named deployment.yaml containing the Deployment's manifest of your component

cat <<EOF > /control-plane-operator/controllers/hostedcontrolplane/v2/assets/my-component/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-component
spec:
  selector:
    matchLabels:
      app: my-component
  template:
    metadata:
      labels:
        app: my-component
    spec:
      containers:
      - name: my-component
        image: image-key
EOF

Note that the container image image-key will be replaced with the image value corresponding to that key from the release payload if that key exist, and kept as is otherwise.

!!! note

If your workload is a StatefulSet, create a filed named `statefulset.yaml` containing the StatefulSet's manifest instead!
Creating other resource manifests

Place any other required resource under the same directory, such as configMaps, secrets, roles, etc. All resource manifests will be automatically picked up and deployed.

To create a configMap for example:

cat <<EOF > /control-plane-operator/controllers/hostedcontrolplane/v2/assets/my-component/my-config.yaml
apiVersion: v1
data:
  config.yaml: "test-config"
kind: ConfigMap
metadata:
  name: my-config
EOF

Defining your component

Create a new directory for your component's code under /control-plane-operator/controllers/hostedcontrolplane/v2

mkdir /control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent

Create a new go file

touch /control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go
Implementing the ComponentOptions interface
// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

import (
    component "github.com/openshift/hypershift/support/controlplane-component"
)

var _ component.ComponentOptions = &MyComponent{}

type MyComponent struct {
}

// Specify whether this component serves requests outside its node.
func (m *MyComponent) IsRequestServing() bool {
	return false
}

// Specify whether this component's workload(pods) should be spread across availability zones
func (m *MyComponent) MultiZoneSpread() bool {
	return false
}

// Specify whether this component requires access to the kube-apiserver of the cluster where the workload is running
func (m *MyComponent) NeedsManagementKASAccess() bool {
	return false
}
Creating a ControlPlaneComponent instance
// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

const (
    // This is the name of your manifests directory.
    ComponentName = "my-component"
)

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
		Build()
}

!!! note

use `component.NewStatefulSetComponent()` instead if your components's workload is a StatefulSet.

Registering your component

Finally your component needs to be be registered in /control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go inside registerComponents() method

// control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go

import (
    mycomponentv2 "github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent"
)

func (r *HostedControlPlaneReconciler) registerComponents() {
	r.components = append(r.components,
        // other components  
        ...
		mycomponentv2.NewComponent(),
	)
}

Customizing your component

Some components require dynamic additional config, arguments, env variables, etc. based on the HostedControlPlane spec. You can adapt your manifests dynamically by defining adapt functions with the following signature func(WorkloadContext, resourceType) error

To adapt your deployment, first define your adapt function:

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

import (
    "github.com/openshift/hypershift/support/util"
    component "github.com/openshift/hypershift/support/controlplane-component"
)

func adaptDeployment(cpContext component.WorkloadContext, deployment *appsv1.Deployment) error {
    // for example, append a new arg to a container
    util.UpdateContainer("container-name", deployment.Spec.Template.Spec.Containers, func(c *corev1.Container) {
		c.Args = append(c.Args,
			"--platform-type", string(cpContext.HCP.Spec.Platform.Type),
		)
    })
}

Then register your adapt function when creating the ControlPlaneComponent instance

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
        WithAdaptFunction(adaptDeployment).
		Build()
}

Similarly to adapt any other manifest, define your adapt function then register it using .WithManifestAdapter("manifest-name.yaml", component.WithAdaptFunction(func)):

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
        WithAdaptFunction(adaptDeployment).
        WithManifestAdapter(
			"my-config.yaml",
			component.WithAdaptFunction(adaptMyConfig),
		).
		Build()
}

func adaptMyConfig(cpContext component.WorkloadContext, config *corev1.ConfigMap) error {
    config.Data["config.yaml"] = "dynamic-content"
}
Predicates

If your component has any prerequisites or depends on external resources to exist, you can define a predicate to block the creation/reconciliation of your component until your conditions are met.

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

func myPredicate(cpContext component.WorkloadContext) (bool, error) {
    // reconcile only if platform is AWS
    if cpContext.HCP.Spec.Platform.Type == "AWS" {
        return true
    }
    return false
}

Then register your predicate function when creating the ControlPlaneComponent instance

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
        WithPredicate(myPredicate).
		Build()
}

Similarly to define predicate for any specific manifest, define your predicate function then register it using .WithManifestAdapter("manifest-name.yaml", component.WithPredicate(func)):

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
        WithManifestAdapter(
			"my-config.yaml",
            component.WithAdaptFunction(adaptMyConfig),
			component.WithPredicate(myConfigPredicate),
		).
		Build()
}

func myConfigPredicate(cpContext component.WorkloadContext) bool {
    if _, exists := cpContext.HCP.Annotations["disable_my_config"]; exists {
        return false
    }
    return true
}
Rollout on ConfigMap/Secrets changes

If your workload(Deployment/StatefulSet) requires a rollout if a configMap or Secret data is changed, you can configure your component to watch those resources as follows:

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
        RolloutOnConfigMapChange("my-config1", ...).
        RolloutOnSecretChange("my-secret", ...).
		Build()
}

This will trigger a rollout of your Deployment/StatefulSet on any change to my-config configMap or my-secret Secret.

Dependencies

If your component depends on other components being Available before it can start reconciliation, you can define your Dependencies as a list of components' names as follows:

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

import (
    oapiv2 "github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/v2/oapi"
)

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
        WithDependencies(oapiv2.ComponentName, "other-component"). // most components depend on openshift-api-server(oapi) component.
		Build()
}
Tokens

If your component requires access to the cloud or kube-apiserver. It requires a serviceAccount token in the guest cluster. This can be provided by the token-minter sidecar container which you can automatically inject into your deployment as follows:

// control-plane-operator/controllers/hostedcontrolplane/v2/mycomponent/component.go

func NewComponent() component.ControlPlaneComponent {
	return component.NewDeploymentComponent(ComponentName, &MyComponent{}).
        InjectTokenMinterContainer(component.TokenMinterContainerOptions{
			TokenType:               component.Cloud, // mint token for cloud access, possible values: Cloud, KubeAPIServerToken or CloudAndAPIServerToken.
			ServiceAccountName:      "my-serivceaccount",
			ServiceAccountNameSpace: "my-serivceaccount-namespace",
		}).
		Build()
}

See [controlPlaneWorkloadBuilder](/support/controlplane-component/builder.go) for all available options

Documentation

Index

Constants

View Source
const (
	ServiceAccountKubeconfigVolumeName = "service-account-kubeconfig"
)

Variables

This section is empty.

Functions

func AdaptPodDisruptionBudget added in v0.1.51

func AdaptPodDisruptionBudget() option

func DisableIfAnnotationExist

func DisableIfAnnotationExist(annotation string) option

DisableIfAnnotationExist is a helper predicate for the common use case of disabling a resource when an annotation exists.

func EnableForPlatform added in v0.1.51

func EnableForPlatform(platform hyperv1.PlatformType) option

EnableForPlatform is a helper predicate for the common use case of only enabling a resource for a specific platform.

func NewCronJobComponent added in v0.1.56

func NewCronJobComponent(name string, opts ComponentOptions) *controlPlaneWorkloadBuilder[*batchv1.CronJob]

func NewDeploymentComponent

func NewDeploymentComponent(name string, opts ComponentOptions) *controlPlaneWorkloadBuilder[*appsv1.Deployment]

func NewStatefulSetComponent

func NewStatefulSetComponent(name string, opts ComponentOptions) *controlPlaneWorkloadBuilder[*appsv1.StatefulSet]

func WithAdaptFunction

func WithAdaptFunction[T client.Object](adapt func(cpContext WorkloadContext, resource T) error) option

func WithPredicate

func WithPredicate(predicate Predicate) option

Types

type ComponentOptions

type ComponentOptions interface {
	IsRequestServing() bool
	MultiZoneSpread() bool
	NeedsManagementKASAccess() bool
}

type ControlPlaneComponent

type ControlPlaneComponent interface {
	NamedComponent
	Reconcile(cpContext ControlPlaneContext) error
}

type ControlPlaneContext

type ControlPlaneContext struct {
	context.Context

	// CreateOrUpdateProviderV2 knows how to create/update manifest based resources.
	upsert.CreateOrUpdateProviderV2
	// Client knows how to perform CRUD operations on Kubernetes objects in the HCP namespace.
	Client client.Client
	// HCP is the HostedControlPlane object
	HCP *hyperv1.HostedControlPlane
	// ReleaseImageProvider contains the version and component images related to control-plane release image.
	ReleaseImageProvider imageprovider.ReleaseImageProvider
	// UserReleaseImageProvider contains the version and component images related to data-plane release image.
	UserReleaseImageProvider imageprovider.ReleaseImageProvider
	// ImageMetadataProvider returns metadata for a given release image using the given pull secret.
	ImageMetadataProvider util.ImageMetadataProvider

	// InfraStatus contains all the information about the Hosted cluster's infra services.
	InfraStatus infra.InfrastructureStatus
	// SetDefaultSecurityContext is used to configure Security Context for containers.
	SetDefaultSecurityContext bool
	// EnableCIDebugOutput enable extra debug logs.
	EnableCIDebugOutput bool
	// MetricsSet specifies which metrics to use in the service/pod-monitors.
	MetricsSet metrics.MetricsSet

	// This is needed for the generic unit test, so we can always generate a fixture for the components deployment/statefulset.
	SkipPredicate bool
}

type HTTPSOptions added in v0.1.52

type HTTPSOptions struct {
	// KonnectivityHost is the host name of the Konnectivity server proxy.
	KonnectivityHost string
	// KonnectivityPort is the port of the Konnectivity server proxy.
	KonnectivityPort uint32
	// The port that https proxy should serve on.
	ServingPort uint32
	// ConnectDirectlyToCloudAPIs specifies whether cloud APIs should be bypassed
	// by the proxy. This is used by the ingress operator to be able to create DNS records
	// before worker nodes are present in the cluster.
	// See https://github.com/openshift/hypershift/pull/1601
	ConnectDirectlyToCloudAPIs *bool
}

type KonnectivityContainerOptions

type KonnectivityContainerOptions struct {
	Mode ProxyMode
	// defaults to 'kubeconfig'
	KubeconfingVolumeName string

	HTTPSOptions  HTTPSOptions
	Socks5Options Socks5Options
}

type NamedComponent

type NamedComponent interface {
	Name() string
}

type Predicate

type Predicate func(cpContext WorkloadContext) bool

type ProxyMode

type ProxyMode string
const (
	Socks5 ProxyMode = "socks5"
	HTTPS  ProxyMode = "https"

	// Dual mode will inject 2 konnectivity containers, one using HTTPS mode and the other using Socks5 mode.
	Dual ProxyMode = "dual"
)

type ServiceAccountKubeConfigOpts added in v0.1.55

type ServiceAccountKubeConfigOpts struct {
	Name, Namespace, MountPath, ContainerName string
}

type Socks5Options added in v0.1.52

type Socks5Options struct {
	// KonnectivityHost is the host name of the Konnectivity server proxy.
	KonnectivityHost string
	// KonnectivityPort is the port of the Konnectivity server proxy.
	KonnectivityPort uint32
	// The port that socks5 proxy should serve on.
	ServingPort uint32
	// ConnectDirectlyToCloudAPIs specifies whether cloud APIs should be bypassed
	// by the proxy. This is used by the ingress operator to be able to create DNS records
	// before worker nodes are present in the cluster.
	// See https://github.com/openshift/hypershift/pull/1601
	ConnectDirectlyToCloudAPIs *bool
	// ResolveFromManagementClusterDNS tells the dialer to fallback to the management
	// cluster's DNS (and direct dialer) initially until the konnectivity tunnel is available.
	// Once the konnectivity tunnel is available, it no longer falls back on the management
	// cluster. This is used by the OAuth server to allow quicker initialization of identity
	// providers while worker nodes have not joined.
	// See https://github.com/openshift/hypershift/pull/2261
	ResolveFromManagementClusterDNS *bool
	// ResolveFromGuestClusterDNS tells the dialer to resolve names using the guest
	// cluster's coreDNS service. Used by oauth and ingress operator.
	ResolveFromGuestClusterDNS *bool
	// DisableResolver disables any name resolution by the resolver. This is used by the CNO.
	// See https://github.com/openshift/hypershift/pull/3986
	DisableResolver *bool
}

type TokenMinterContainerOptions added in v0.1.56

type TokenMinterContainerOptions struct {
	// TokenType defines the token purpose, either to grant cloud access, kube-apiserver access to both.
	TokenType TokenType
	// ServiceAccountName is the name of the service account for which to mint a token.
	ServiceAccountName string
	// ServiceAccountNameSpace is the namespace of the service account for which to mint a token.
	ServiceAccountNameSpace string

	// KubeconfingVolumeName is the volume name which contains the kubeconfig to use mint the token in the target cluster.
	// defaults to 'kubeconfig'
	KubeconfingVolumeName string
	// OneShot, if true, will cause the token-minter container to exit after minting the token.
	OneShot bool
}

TokenMinterContainerOptions defines the options for token-minter sidecar container which mints ServiceAccount tokens in the tenant cluster for the given named service account, and then make it available for the main container with a volume mount.

type TokenType added in v0.1.56

type TokenType string
const (
	CloudToken         TokenType = "cloud"
	KubeAPIServerToken TokenType = "apiserver"

	// CloudAndAPIServerToken will inject 2 token-minter containers, one using minting a token for cloud and the other minting a token for kube-apiserver access.
	CloudAndAPIServerToken TokenType = "cloud-and-apiserver"
)

type WorkloadContext added in v0.1.53

type WorkloadContext struct {
	context.Context

	// reader client, as workloads should not be creating resources.
	Client                   client.Reader
	HCP                      *hyperv1.HostedControlPlane
	ReleaseImageProvider     imageprovider.ReleaseImageProvider
	UserReleaseImageProvider imageprovider.ReleaseImageProvider
	ImageMetadataProvider    util.ImageMetadataProvider

	InfraStatus               infra.InfrastructureStatus
	SetDefaultSecurityContext bool
	EnableCIDebugOutput       bool
	MetricsSet                metrics.MetricsSet
}

type WorkloadProvider added in v0.1.56

type WorkloadProvider[T client.Object] interface {
	// NewObject returns a new object of the generic type. This is useful when getting/deleting the workload.
	NewObject() T
	// LoadManifest know how to load the correct workload manifest and return a workload object of the correct type.
	LoadManifest(componentName string) (T, error)

	// PodTemplateSpec knows how to extract corev1.PodTemplateSpec field from the given workload object.
	PodTemplateSpec(object T) *corev1.PodTemplateSpec
	// PodTemplateSpec knows how to extract replicas field from the given workload object.
	Replicas(object T) *int32
	// ApplyOptionsTo knows how to apply the given deploymentConfig options to the given workload object.
	// TODO(Mulham): remove all usage of deploymentConfig in cpov2 and remove this function eventually.
	ApplyOptionsTo(cpContext ControlPlaneContext, object T, oldObject T, deploymentConfig *config.DeploymentConfig)

	// IsReady returns the status, reason and message describing the readiness status of the workload object.
	IsReady(object T) (status metav1.ConditionStatus, reason string, message string)
	// IsProgressing returns the status, reason and message describing the progressing status of the workload object.
	IsProgressing(object T) (status metav1.ConditionStatus, reason string, message string)
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL