Files
my-rook-config/pkg/operator/ceph/cluster/osd/key_rotation.go
T
Travis Nielsen 93adc5dd6b core: include namespace more consistently in logging
When writing to the operator log, where possible
the namespace of the resource on which the operator
is working will be logged as a standard prefix
to more easily troubleshoot what resource is being reconciled

Signed-off-by: Travis Nielsen <tnielsen@redhat.com>
2025-11-20 15:49:43 -07:00

304 lines
11 KiB
Go

/*
Copyright 2023 The Rook Authors. All rights reserved.
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 osd
import (
"fmt"
"path/filepath"
"github.com/pkg/errors"
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
kms "github.com/rook/rook/pkg/daemon/ceph/osd/kms"
"github.com/rook/rook/pkg/operator/ceph/controller"
"github.com/rook/rook/pkg/operator/k8sutil"
"github.com/rook/rook/pkg/util/log"
batch "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrl "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
const (
keyRotationCronJobAppName = "rook-ceph-osd-key-rotation"
keyRotationCronJobAppNameFmt = "rook-ceph-osd-key-rotation-%d"
)
// keyRotationCronJobName returns the name of the key rotation cron job for the given OSD ID.
func keyRotationCronJobName(osdID int) string {
return fmt.Sprintf(keyRotationCronJobAppNameFmt, osdID)
}
// applyKeyRotationPlacement applies the placement settings for the key rotation job
// so that it is scheduled on the same node as the OSD for which the key rotation is scheduled.
func applyKeyRotationPlacement(spec *v1.PodSpec, labels map[string]string) {
spec.TopologySpreadConstraints = nil
if spec.Affinity == nil {
spec.Affinity = &v1.Affinity{}
} else {
spec.Affinity.PodAntiAffinity = nil
}
spec.Affinity.PodAffinity = &v1.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchLabels: labels,
},
TopologyKey: k8sutil.LabelHostname(),
},
},
}
}
// getKeyRotationContainer returns the container spec for the key rotation job.
func (c *Cluster) getKeyRotationContainer(osdProps osdProperties, volumeMounts []v1.VolumeMount, devices []string) (v1.Container, error) {
envVars := c.getConfigEnvVars(osdProps, k8sutil.DataDir, true)
// enable debug logging
envVars = append(envVars, setDebugLogLevelEnvVar(true))
envVars = append(envVars, v1.EnvVar{Name: "ROOK_CEPH_VERSION", Value: c.clusterInfo.CephVersion.CephVersionFormatted()})
envVars = append(envVars, kms.ConfigToEnvVar(c.spec)...)
// run privileged always since we always mount /dev
privileged := true
runAsUser := int64(0)
runAsNonRoot := false
readOnlyRootFilesystem := false
args := []string{"key-management", "rotate-key", osdProps.pvc.ClaimName}
args = append(args, devices...)
osdProvisionContainer := v1.Container{
Args: args,
Name: keyRotationCronJobAppName,
Image: c.rookVersion,
ImagePullPolicy: controller.GetContainerImagePullPolicy(c.spec.CephVersion.ImagePullPolicy),
VolumeMounts: volumeMounts,
Env: envVars,
EnvFrom: getEnvFromSources(),
SecurityContext: &v1.SecurityContext{
Privileged: &privileged,
RunAsUser: &runAsUser,
RunAsNonRoot: &runAsNonRoot,
ReadOnlyRootFilesystem: &readOnlyRootFilesystem,
Capabilities: &v1.Capabilities{
Add: []v1.Capability{},
Drop: []v1.Capability{
"NET_RAW",
},
},
},
Resources: osdProps.resources,
}
return osdProvisionContainer, nil
}
// getKeyRotationPodTemplateSpec returns the pod template spec for the key rotation job.
func (c *Cluster) getKeyRotationPodTemplateSpec(osdProps osdProperties, osd OSDInfo, restart v1.RestartPolicy) (*v1.PodTemplateSpec, error) {
// create a volume on /dev so the pod can access devices on the host
devVolume := v1.Volume{Name: "devices", VolumeSource: v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: "/dev"}}}
udevVolume := v1.Volume{Name: "udev", VolumeSource: v1.VolumeSource{HostPath: &v1.HostPathVolumeSource{Path: "/run/udev"}}}
hostPathType := v1.HostPathDirectory
hostPath := filepath.Join(c.spec.DataDirHostPath, c.clusterInfo.Namespace, osdProps.pvc.ClaimName, fmt.Sprintf("ceph-%d", osd.ID))
hostPathVolume := v1.Volume{
Name: "bridge",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: hostPath,
Type: &hostPathType,
},
},
}
devicesBasePath := "/var/lib/ceph/osd/"
volumes := []v1.Volume{
udevVolume,
devVolume,
hostPathVolume,
}
volumeMounts := []v1.VolumeMount{
{Name: "devices", MountPath: "/dev"},
{Name: "udev", MountPath: "/run/udev"},
{Name: "bridge", MountPath: devicesBasePath},
}
devices := []string{encryptionBlockDestinationCopy(devicesBasePath, bluestoreBlockName)}
if osdProps.metadataPVC.ClaimName != "" {
devices = append(devices, encryptionBlockDestinationCopy(devicesBasePath, bluestoreMetadataName))
}
if osdProps.walPVC.ClaimName != "" {
devices = append(devices, encryptionBlockDestinationCopy(devicesBasePath, bluestoreWalName))
}
if c.spec.Security.KeyManagementService.IsVaultKMS() {
volumeTLS, volumeMountTLS := kms.VaultVolumeAndMount(c.spec.Security.KeyManagementService.ConnectionDetails, "")
volumes = append(volumes, volumeTLS)
volumeMounts = append(volumeMounts, volumeMountTLS)
}
keyRotationContainer, err := c.getKeyRotationContainer(osdProps, volumeMounts, devices)
if err != nil {
return nil, errors.Wrap(err, "failed to generate key rotation container")
}
podTemplateSpec := v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: keyRotationCronJobAppName,
Labels: map[string]string{
k8sutil.AppAttr: keyRotationCronJobAppName,
k8sutil.ClusterAttr: c.clusterInfo.Namespace,
},
Annotations: map[string]string{},
},
Spec: v1.PodSpec{
ServiceAccountName: serviceAccountName,
Containers: []v1.Container{
keyRotationContainer,
},
RestartPolicy: restart,
Volumes: volumes,
HostNetwork: c.spec.Network.IsHost(),
PriorityClassName: cephv1.GetOSDPriorityClassName(c.spec.PriorityClassNames),
SchedulerName: osdProps.schedulerName,
SecurityContext: &v1.PodSecurityContext{},
},
}
if c.spec.Network.IsHost() {
podTemplateSpec.Spec.DNSPolicy = v1.DNSClusterFirstWithHostNet
} else if c.spec.Network.IsMultus() {
if err := k8sutil.ApplyMultus(c.clusterInfo.Namespace, &c.spec.Network, &podTemplateSpec.ObjectMeta); err != nil {
return nil, err
}
}
cephv1.GetKeyRotationAnnotations(c.spec.Annotations).ApplyToObjectMeta(&podTemplateSpec.ObjectMeta)
cephv1.GetKeyRotationLabels(c.spec.Labels).ApplyToObjectMeta(&podTemplateSpec.ObjectMeta)
c.applyAllPlacementIfNeeded(&podTemplateSpec.Spec)
// apply storageClassDeviceSets.Placement
osdProps.placement.ApplyToPodSpec(&podTemplateSpec.Spec)
applyKeyRotationPlacement(&podTemplateSpec.Spec, c.getOSDLabels(osd, osdProps.crushHostname, osdProps.portable))
// cryptsetup synchronizes with udev on host through semaphore
podTemplateSpec.Spec.HostIPC = true
k8sutil.RemoveDuplicateEnvVars(&podTemplateSpec.Spec)
return &podTemplateSpec, nil
}
// makeKeyRotationCronJob creates a key rotation cron job for the given OSD.
func (c *Cluster) makeKeyRotationCronJob(pvcName string, osd OSDInfo, osdProps osdProperties) (*batch.CronJob, error) {
podSpec, err := c.getKeyRotationPodTemplateSpec(osdProps, osd, v1.RestartPolicyOnFailure)
if err != nil {
return nil, err
}
c.applyResourcesToAllContainers(&podSpec.Spec, cephv1.GetOSDResources(c.spec.Resources, osd.DeviceClass))
schedule := c.spec.Security.KeyRotation.Schedule
if schedule == "" {
// default to rotate keyrings weekly (default is in code since default in crds causes issues)
schedule = "@weekly"
}
cronJob := &batch.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: keyRotationCronJobName(osd.ID),
Namespace: c.clusterInfo.Namespace,
Labels: podSpec.Labels,
Annotations: podSpec.Annotations,
},
Spec: batch.CronJobSpec{
ConcurrencyPolicy: batch.ForbidConcurrent,
Schedule: schedule,
JobTemplate: batch.JobTemplateSpec{
Spec: batch.JobSpec{
Template: *podSpec,
},
},
},
}
return cronJob, nil
}
// reconcileKeyRotationCronJob reconciles the key rotation cron jobs for the OSDs.
func (c *Cluster) reconcileKeyRotationCronJob() error {
if !c.spec.Security.KeyRotation.Enabled {
listOpts := metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", k8sutil.AppAttr, keyRotationCronJobAppName)}
err := c.context.Clientset.BatchV1().
CronJobs(c.clusterInfo.Namespace).
DeleteCollection(c.clusterInfo.Context,
metav1.DeleteOptions{},
listOpts)
if err != nil {
if client.IgnoreNotFound(err) != nil {
return errors.Wrap(err, "failed to delete key rotation cron jobs")
}
return nil
}
log.NamespacedDebug(c.clusterInfo.Namespace, logger, "successfully deleted key rotation cron jobs")
return nil
}
// Get the list of OSDs backed by pvc.
osdListOpts := metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s,%s", k8sutil.AppAttr, AppName, OSDOverPVCLabelKey)}
deployments, err := c.context.Clientset.AppsV1().Deployments(c.clusterInfo.Namespace).List(c.clusterInfo.Context, osdListOpts)
if err != nil {
return errors.Wrap(err, "failed to query existing OSD deployments")
}
log.NamespacedDebug(c.clusterInfo.Namespace, logger, "found %d osd deployments", len(deployments.Items))
for i := range deployments.Items {
osdDep := deployments.Items[i]
osd, err := c.getOSDInfo(&osdDep)
if err != nil {
return errors.Wrapf(err, "failed to get osd info for osd %q", osdDep.Name)
}
pvcName := osdDep.Labels[OSDOverPVCLabelKey]
if pvcName == "" {
return errors.Errorf("pvc name label %q for osd %q is empty",
OSDOverPVCLabelKey, osdDep.Name)
}
osdProps, err := c.getOSDPropsForPVC(pvcName)
if err != nil {
return errors.Wrapf(err, "failed to generate config for osd %q", osdDep.Name)
}
if !osdProps.encrypted {
continue
}
log.NamespacedInfo(c.clusterInfo.Namespace, logger, "starting OSD key rotation cron job for osd %d", osd.ID)
cj, err := c.makeKeyRotationCronJob(pvcName, osd, osdProps)
if err != nil {
return errors.Wrap(err, "failed to make key rotation cron job")
}
err = ctrl.SetOwnerReference(&osdDep, cj, c.context.Client.Scheme())
if err != nil {
return errors.Wrapf(err, "failed to set controllerReference on cron job %q", cj.Name)
}
_, err = k8sutil.CreateOrUpdateCronJob(c.clusterInfo.Context, c.context.Clientset, cj)
if err != nil {
return errors.Wrapf(err, "failed to create or update key rotation cron job %q", cj.Name)
}
log.NamespacedInfo(c.clusterInfo.Namespace, logger, "started OSD key rotation cron job %q", cj.Name)
}
log.NamespacedInfo(c.clusterInfo.Namespace, logger, "successfully started OSD key rotation cron jobs")
return nil
}