Skip to content

Kata installation using RHCOS image layer #416

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

Merged
merged 1 commit into from
Jul 8, 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
7 changes: 3 additions & 4 deletions config/samples/featuregates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ metadata:
name: osc-feature-gates
namespace: openshift-sandboxed-containers-operator
data:
# timeTravel allows navigating through cluster states across time.
# It is useful for scenarios where you want to view historical data or
# predict future states based on current trends. Default is "false".
# timeTravel: "false"
# layeredImageDeployment allows deploying Kata using RHCOS layered image
# This feature gate needs a ConfigMap named layered-image-deploy-cm
layeredImageDeployment: "false"
9 changes: 9 additions & 0 deletions config/samples/layered-image-deploy-cm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
data:
osImageURL: "quay.io/openshift_sandboxed_containers/kata-ocp415:tdx"
kernelArguments: "kvm_intel.tdx=1"

kind: ConfigMap
metadata:
name: layered-image-deploy-cm
namespace: openshift-sandboxed-containers-operator
13 changes: 12 additions & 1 deletion controllers/fg_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
const (
FgConfigMapName = "osc-feature-gates"
ConfidentialFeatureGate = "confidential"
LayeredImageDeployment = "layeredImageDeployment"
)

var DefaultFeatureGates = map[string]bool{
ConfidentialFeatureGate: false,
LayeredImageDeployment: false,
}

type FeatureGateStatus struct {
Expand Down Expand Up @@ -95,6 +97,15 @@ func (r *KataConfigOpenShiftReconciler) processFeatureGates() error {
}
}

return err
// Check layered Image deployment FG
if IsEnabled(fgStatus, LayeredImageDeployment) {
r.Log.Info("Feature gate is enabled", "featuregate", LayeredImageDeployment)
// Perform the necessary actions
return r.handleLayeredImageDeploymentFeature(Enabled)
} else {
r.Log.Info("Feature gate is disabled", "featuregate", LayeredImageDeployment)
// Perform the necessary actions
return r.handleLayeredImageDeploymentFeature(Disabled)
}

}
164 changes: 164 additions & 0 deletions controllers/layered_image_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package controllers

import (
"context"
"encoding/json"
"fmt"
"strings"

ignTypes "github.com/coreos/ignition/v2/config/v3_2/types"
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)

const (
LayeredImageDeployCm = "layered-image-deploy-cm"
image_mc_name = "50-enable-sandboxed-containers-image"
)

// Process the LayeredImageDeployment feature gate (FG)
// This method will be called by the processFeatureGates method during the beginning of the reconcile loop
// The method will check for any existing MachineConfig related to KataConfig and return to the caller
// by setting r.ImgMc to the image MachineConfig if present. This will allow the remainder of the reconcile loop
// to use the image MachineConfig as needed.
// If neither extension or image MachineConfig exists, and layeredImageDeployment feature is enabled,
// then this method will create the image MachineConfig from the fg ConfigMap and set r.ImgMc to the
// newly created image MachineConfig.
// If layeredImageDeployment feature is disabled, then the method will reset r.ImgMc to nil
// The key design aspect of this FG is that it has effect only during the creation of the KataConfig.
// After creation of the KataConfig this FG has no effect.
func (r *KataConfigOpenShiftReconciler) handleLayeredImageDeploymentFeature(state FeatureGateState) error {

// Check if MachineConfig exists and return the same without changing anything
mc, err := r.getExistingMachineConfig()
if err != nil {
r.Log.Info("Error in getting existing MachineConfig", "err", err)
return err
}

if mc != nil {
r.Log.Info("MachineConfig is already present. No changes will be done")
// If the MachineConfig is imageMachineConfig, then set r.ImgMc to the same
if mc.Name == image_mc_name {
r.ImgMc = mc
}
return nil
}

if state == Enabled {
r.Log.Info("LayeredImageDeployment feature is enabled")

cm := &corev1.ConfigMap{}
err := r.Client.Get(context.Background(), types.NamespacedName{
Name: LayeredImageDeployCm,
Namespace: OperatorNamespace,
}, cm)
if err != nil {
r.Log.Info("Error in retrieving LayeredImageDeployment ConfigMap", "err", err)
return err
}

// Set the ImgMc here
r.ImgMc, err = r.createMachineConfigFromConfigMap(cm)
if err != nil {
r.Log.Info("Error in creating MachineConfig for LayeredImageDeployment from ConfigMap", "err", err)
return err
}

} else {
r.Log.Info("LayeredImageDeployment feature is disabled. Resetting ImgMc")
// Reset ImgMc
r.ImgMc = nil

}

return nil
}

func (r *KataConfigOpenShiftReconciler) getExistingMachineConfig() (*mcfgv1.MachineConfig, error) {
r.Log.Info("Getting any existing MachineConfigs related to KataConfig")

// Retrieve the existing MachineConfig for Kata - either extension or image
// Check for label "app":r.kataConfig.Name
// and name "50-enable-sandboxed-containers-extension" or name "50-enable-sandboxed-containers-image"
mcList := &mcfgv1.MachineConfigList{}
err := r.Client.List(context.Background(), mcList)
if err != nil {
r.Log.Info("Error in listing MachineConfigs", "err", err)
return nil, err
}

for _, mc := range mcList.Items {
if mc.Labels["app"] == r.kataConfig.Name &&
(mc.Name == extension_mc_name || mc.Name == image_mc_name) {
return &mc, nil
}
}

r.Log.Info("No existing MachineConfigs related to KataConfig found")

return nil, nil
}

// Method to create a new MachineConfig object from configMap data
// The configMap data will have two keys: "osImageURL" and "kernelArgs"
func (r *KataConfigOpenShiftReconciler) createMachineConfigFromConfigMap(cm *corev1.ConfigMap) (*mcfgv1.MachineConfig, error) {

// Get the osImageURL from the ConfigMap
// osImageURL is mandatory for creating a MachineConfig
osImageURL, exists := cm.Data["osImageURL"]
if !exists {
return nil, fmt.Errorf("osImageURL not found in ConfigMap")
}

ic := ignTypes.Config{
Ignition: ignTypes.Ignition{
Version: "3.2.0",
},
}

icb, err := json.Marshal(ic)
if err != nil {
return nil, err
}
mc := &mcfgv1.MachineConfig{
TypeMeta: metav1.TypeMeta{
APIVersion: "machineconfiguration.openshift.io/v1",
Kind: "MachineConfig",
},
ObjectMeta: metav1.ObjectMeta{
Name: image_mc_name,
Namespace: OperatorNamespace,
},
Spec: mcfgv1.MachineConfigSpec{
Config: runtime.RawExtension{
Raw: icb,
},
OSImageURL: osImageURL,
},
}

if kernelArguments, ok := cm.Data["kernelArguments"]; ok {
// Parse the kernel arguments and set them in the MachineConfig
// Note that in the configmap the kernel arguments are stored as a single string
// eg. "a=b c=d ..." and we need to split them into individual arguments
// eg ["a=b", "c=d", ...]
// Split the kernel arguments string into individual arguments
mc.Spec.KernelArguments = strings.Fields(kernelArguments)
}

// Set the required labels
mcp, err := r.getMcpName()
if err != nil {
return nil, err
}
mc.Labels = map[string]string{
"machineconfiguration.openshift.io/role": mcp,
"app": r.kataConfig.Name,
}

return mc, nil
}
39 changes: 26 additions & 13 deletions controllers/openshift_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type KataConfigOpenShiftReconciler struct {
Scheme *runtime.Scheme

kataConfig *kataconfigurationv1.KataConfig

ImgMc *mcfgv1.MachineConfig
}

const (
Expand Down Expand Up @@ -494,6 +496,12 @@ func getExtensionName() string {
func (r *KataConfigOpenShiftReconciler) newMCForCR(machinePool string) (*mcfgv1.MachineConfig, error) {
r.Log.Info("Creating MachineConfig for Custom Resource")

if r.ImgMc != nil {
r.Log.Info("Image based MachineConfig", "MachineConfig", r.ImgMc)
return r.ImgMc, nil
}

// Create extension MachineConfig
ic := ignTypes.Config{
Ignition: ignTypes.Ignition{
Version: "3.2.0",
Expand Down Expand Up @@ -528,6 +536,8 @@ func (r *KataConfigOpenShiftReconciler) newMCForCR(machinePool string) (*mcfgv1.
},
}

r.Log.Info("Extension based MachineConfig", "MachineConfig", mc)

return &mc, nil
}

Expand Down Expand Up @@ -843,6 +853,8 @@ func (r *KataConfigOpenShiftReconciler) processKataConfigDeleteRequest() (ctrl.R
err = r.Client.Get(context.TODO(), types.NamespacedName{Name: mc.Name}, mc)
if err != nil && k8serrors.IsNotFound(err) {
isMcDeleted = true
// Reset ImgMc
r.ImgMc = nil
} else if err != nil {
return ctrl.Result{}, err
}
Expand Down Expand Up @@ -1053,7 +1065,7 @@ func (r *KataConfigOpenShiftReconciler) processKataConfigInstallRequest() (ctrl.
r.Log.Info("SCNodeRole is: " + machinePool)
}

wasMcJustCreated, err := r.createExtensionMc(machinePool)
wasMcJustCreated, err := r.createMc(machinePool)
if err != nil {
return ctrl.Result{Requeue: true}, nil
}
Expand Down Expand Up @@ -1281,24 +1293,24 @@ func (r *KataConfigOpenShiftReconciler) processKataConfigInstallRequest() (ctrl.
// If the first return value is 'true' it means that the MC was just created
// by this call, 'false' means that it's already existed. As usual, the first
// return value is only valid if the second one is nil.
func (r *KataConfigOpenShiftReconciler) createExtensionMc(machinePool string) (bool, error) {
func (r *KataConfigOpenShiftReconciler) createMc(machinePool string) (bool, error) {

// In case we're returning an error we want to make it explicit that
// the first return value is "not care". Unfortunately golang seems
// to lack syntax for creating an expression with default bool value
// hence this work-around.
var dummy bool

/* Create Machine Config object to enable sandboxed containers RHCOS extension */
mc := &mcfgv1.MachineConfig{}
err := r.Client.Get(context.TODO(), types.NamespacedName{Name: extension_mc_name}, mc)
if err != nil && (k8serrors.IsNotFound(err) || k8serrors.IsGone(err)) {
/* Create Machine Config object to install sandboxed containers */

r.Log.Info("creating RHCOS extension MachineConfig")
mc, err = r.newMCForCR(machinePool)
if err != nil {
return dummy, err
}
r.Log.Info("creating RHCOS MachineConfig")
mc, err := r.newMCForCR(machinePool)
if err != nil {
return dummy, err
}

err = r.Client.Get(context.TODO(), types.NamespacedName{Name: mc.Name}, mc)
if err != nil && (k8serrors.IsNotFound(err) || k8serrors.IsGone(err)) {

err = r.Client.Create(context.TODO(), mc)
if err != nil {
Expand All @@ -1308,12 +1320,13 @@ func (r *KataConfigOpenShiftReconciler) createExtensionMc(machinePool string) (b
r.Log.Info("MachineConfig successfully created", "mc.Name", mc.Name)
return true, nil
} else if err != nil {
r.Log.Info("failed to retrieve extension MachineConfig", "err", err)
r.Log.Info("failed to retrieve MachineConfig", "err", err)
return dummy, err
} else {
r.Log.Info("extension MachineConfig already exists")
r.Log.Info("MachineConfig already exists")
return false, nil
}

}

func (r *KataConfigOpenShiftReconciler) makeReconcileRequest() reconcile.Request {
Expand Down