diff --git a/api/v1alpha2/minicluster_types.go b/api/v1alpha2/minicluster_types.go index d5f1abf9..33c73622 100644 --- a/api/v1alpha2/minicluster_types.go +++ b/api/v1alpha2/minicluster_types.go @@ -188,6 +188,10 @@ type PodSpec struct { // +optional ServiceAccountName string `json:"serviceAccountName,omitempty"` + // PodSecurityContext + // +optional + SecurityContext PodSecurityContext `json:"securityContext,omitempty"` + // RuntimeClassName for the pod // +optional RuntimeClassName string `json:"runtimeClassName,omitempty"` @@ -592,6 +596,13 @@ type SecurityContext struct { AddCapabilities []string `json:"addCapabilities,omitempty"` } +type PodSecurityContext struct { + + // Sysctls + // +optional + Sysctls map[string]string `json:"sysctls,omitempty"` +} + type LifeCycle struct { // +optional diff --git a/api/v1alpha2/swagger.json b/api/v1alpha2/swagger.json index f7562fba..98d839af 100644 --- a/api/v1alpha2/swagger.json +++ b/api/v1alpha2/swagger.json @@ -742,6 +742,19 @@ } } }, + "PodSecurityContext": { + "type": "object", + "properties": { + "sysctls": { + "description": "Sysctls", + "type": "object", + "additionalProperties": { + "type": "string", + "default": "" + } + } + } + }, "PodSpec": { "description": "PodSpec controlls variables for the cluster pod", "type": "object", @@ -793,6 +806,11 @@ "description": "Scheduler name for the pod", "type": "string" }, + "securityContext": { + "description": "PodSecurityContext", + "default": {}, + "$ref": "#/definitions/PodSecurityContext" + }, "serviceAccountName": { "description": "Service account name for the pod", "type": "string" diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index 161e1b28..a10a27f2 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -448,6 +448,28 @@ func (in *Network) DeepCopy() *Network { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PodSecurityContext) DeepCopyInto(out *PodSecurityContext) { + *out = *in + if in.Sysctls != nil { + in, out := &in.Sysctls, &out.Sysctls + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSecurityContext. +func (in *PodSecurityContext) DeepCopy() *PodSecurityContext { + if in == nil { + return nil + } + out := new(PodSecurityContext) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodSpec) DeepCopyInto(out *PodSpec) { *out = *in @@ -465,6 +487,7 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { (*out)[key] = val } } + in.SecurityContext.DeepCopyInto(&out.SecurityContext) if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = make(map[string]string, len(*in)) diff --git a/api/v1alpha2/zz_generated.openapi.go b/api/v1alpha2/zz_generated.openapi.go index 3a564bad..71f80f49 100644 --- a/api/v1alpha2/zz_generated.openapi.go +++ b/api/v1alpha2/zz_generated.openapi.go @@ -41,6 +41,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/flux-framework/flux-operator/api/v1alpha2.MiniClusterStatus": schema_flux_framework_flux_operator_api_v1alpha2_MiniClusterStatus(ref), "github.com/flux-framework/flux-operator/api/v1alpha2.MiniClusterUser": schema_flux_framework_flux_operator_api_v1alpha2_MiniClusterUser(ref), "github.com/flux-framework/flux-operator/api/v1alpha2.Network": schema_flux_framework_flux_operator_api_v1alpha2_Network(ref), + "github.com/flux-framework/flux-operator/api/v1alpha2.PodSecurityContext": schema_flux_framework_flux_operator_api_v1alpha2_PodSecurityContext(ref), "github.com/flux-framework/flux-operator/api/v1alpha2.PodSpec": schema_flux_framework_flux_operator_api_v1alpha2_PodSpec(ref), "github.com/flux-framework/flux-operator/api/v1alpha2.Secret": schema_flux_framework_flux_operator_api_v1alpha2_Secret(ref), "github.com/flux-framework/flux-operator/api/v1alpha2.SecurityContext": schema_flux_framework_flux_operator_api_v1alpha2_SecurityContext(ref), @@ -1312,6 +1313,34 @@ func schema_flux_framework_flux_operator_api_v1alpha2_Network(ref common.Referen } } +func schema_flux_framework_flux_operator_api_v1alpha2_PodSecurityContext(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "sysctls": { + SchemaProps: spec.SchemaProps{ + Description: "Sysctls", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + func schema_flux_framework_flux_operator_api_v1alpha2_PodSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1365,6 +1394,13 @@ func schema_flux_framework_flux_operator_api_v1alpha2_PodSpec(ref common.Referen Format: "", }, }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "PodSecurityContext", + Default: map[string]interface{}{}, + Ref: ref("github.com/flux-framework/flux-operator/api/v1alpha2.PodSecurityContext"), + }, + }, "runtimeClassName": { SchemaProps: spec.SchemaProps{ Description: "RuntimeClassName for the pod", @@ -1420,7 +1456,7 @@ func schema_flux_framework_flux_operator_api_v1alpha2_PodSpec(ref common.Referen }, }, Dependencies: []string{ - "k8s.io/apimachinery/pkg/util/intstr.IntOrString"}, + "github.com/flux-framework/flux-operator/api/v1alpha2.PodSecurityContext", "k8s.io/apimachinery/pkg/util/intstr.IntOrString"}, } } diff --git a/chart/templates/minicluster-crd.yaml b/chart/templates/minicluster-crd.yaml index e6ada969..c3cc9a5a 100644 --- a/chart/templates/minicluster-crd.yaml +++ b/chart/templates/minicluster-crd.yaml @@ -567,6 +567,15 @@ spec: schedulerName: description: Scheduler name for the pod type: string + securityContext: + description: PodSecurityContext + properties: + sysctls: + additionalProperties: + type: string + description: Sysctls + type: object + type: object serviceAccountName: description: Service account name for the pod type: string diff --git a/config/crd/bases/flux-framework.org_miniclusters.yaml b/config/crd/bases/flux-framework.org_miniclusters.yaml index aa1687ba..357da52a 100644 --- a/config/crd/bases/flux-framework.org_miniclusters.yaml +++ b/config/crd/bases/flux-framework.org_miniclusters.yaml @@ -570,6 +570,15 @@ spec: schedulerName: description: Scheduler name for the pod type: string + securityContext: + description: PodSecurityContext + properties: + sysctls: + additionalProperties: + type: string + description: Sysctls + type: object + type: object serviceAccountName: description: Service account name for the pod type: string diff --git a/controllers/flux/containers.go b/controllers/flux/containers.go index 7d8051d3..4f07ae5d 100644 --- a/controllers/flux/containers.go +++ b/controllers/flux/containers.go @@ -137,6 +137,7 @@ func getContainers( Add: addCaps, }, } + newContainer := corev1.Container{ // Call this the driver container, number 0 diff --git a/controllers/flux/job.go b/controllers/flux/job.go index 6c48c730..05943aa8 100644 --- a/controllers/flux/job.go +++ b/controllers/flux/job.go @@ -73,6 +73,7 @@ func NewMiniClusterJob(cluster *api.MiniCluster) (*batchv1.Job, error) { RestartPolicy: corev1.RestartPolicyOnFailure, NodeSelector: cluster.Spec.Pod.NodeSelector, SchedulerName: cluster.Spec.Pod.SchedulerName, + SecurityContext: getPodSecurityContext(cluster), }, }, }, diff --git a/controllers/flux/pods.go b/controllers/flux/pods.go index 4a0ff9d3..aa83dd28 100644 --- a/controllers/flux/pods.go +++ b/controllers/flux/pods.go @@ -36,6 +36,22 @@ func getPodLabels(cluster *api.MiniCluster) map[string]string { return podLabels } +// getPodSecurityContext is shared for the pod and service pods +func getPodSecurityContext(cluster *api.MiniCluster) *corev1.PodSecurityContext { + sysctls := []corev1.Sysctl{} + for key, value := range cluster.Spec.Pod.SecurityContext.Sysctls { + newSysctls := corev1.Sysctl{ + Name: key, + Value: value, + } + sysctls = append(sysctls, newSysctls) + } + securityContext := &corev1.PodSecurityContext{ + Sysctls: sysctls, + } + return securityContext +} + // ensure service containers are running, currently in one pod func (r *MiniClusterReconciler) ensureServicePod( ctx context.Context, @@ -139,6 +155,7 @@ func (r *MiniClusterReconciler) newServicePod( ServiceAccountName: cluster.Spec.Pod.ServiceAccountName, AutomountServiceAccountToken: &cluster.Spec.Pod.AutomountServiceAccountToken, NodeSelector: cluster.Spec.Pod.NodeSelector, + SecurityContext: getPodSecurityContext(cluster), }, } diff --git a/docs/getting_started/custom-resource-definition.md b/docs/getting_started/custom-resource-definition.md index e861ff48..f0dac2fe 100644 --- a/docs/getting_started/custom-resource-definition.md +++ b/docs/getting_started/custom-resource-definition.md @@ -639,6 +639,39 @@ be adjusted if needed. Variables and attributes for each pod in the Indexed job. +#### securityContext + +Currently, we just support setting sysctls. The following section: + +```yaml +pod: + securityContext: + sysctls: + "net.core.somaxconn": "4096" +``` + +Would map to this for the minicluster pod: + +```yaml +pod: + securityContext: + sysctls: + - name: net.core.somaxconn + value: "4096" +``` + +Note that you need to also make changes to the kubeletConfiguration to allowUnsafeSysctls. + +```yaml +kind: KubeletConfiguration +apiVersion: kubelet.config.k8s.io/v1beta1 +failSwapOn: false +featureGates: + KubeletInUserNamespace: true +allowedUnsafeSysctls: +- "net.core*" +``` + #### labels To add custom labels for your pods (in the indexed job), add a set of key value pairs (strings) to a "labels" section: diff --git a/examples/dist/flux-operator-arm.yaml b/examples/dist/flux-operator-arm.yaml index fb32c61f..45aa18e3 100644 --- a/examples/dist/flux-operator-arm.yaml +++ b/examples/dist/flux-operator-arm.yaml @@ -576,6 +576,15 @@ spec: schedulerName: description: Scheduler name for the pod type: string + securityContext: + description: PodSecurityContext + properties: + sysctls: + additionalProperties: + type: string + description: Sysctls + type: object + type: object serviceAccountName: description: Service account name for the pod type: string diff --git a/examples/dist/flux-operator.yaml b/examples/dist/flux-operator.yaml index 99e97407..dcb23ab7 100644 --- a/examples/dist/flux-operator.yaml +++ b/examples/dist/flux-operator.yaml @@ -576,6 +576,15 @@ spec: schedulerName: description: Scheduler name for the pod type: string + securityContext: + description: PodSecurityContext + properties: + sysctls: + additionalProperties: + type: string + description: Sysctls + type: object + type: object serviceAccountName: description: Service account name for the pod type: string diff --git a/sdk/python/v1alpha2/docs/PodSecurityContext.md b/sdk/python/v1alpha2/docs/PodSecurityContext.md new file mode 100644 index 00000000..a31d00ca --- /dev/null +++ b/sdk/python/v1alpha2/docs/PodSecurityContext.md @@ -0,0 +1,29 @@ +# PodSecurityContext + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**sysctls** | **Dict[str, str]** | Sysctls | [optional] + +## Example + +```python +from fluxoperator.models.pod_security_context import PodSecurityContext + +# TODO update the JSON string below +json = "{}" +# create an instance of PodSecurityContext from a JSON string +pod_security_context_instance = PodSecurityContext.from_json(json) +# print the JSON string representation of the object +print(PodSecurityContext.to_json()) + +# convert the object into a dict +pod_security_context_dict = pod_security_context_instance.to_dict() +# create an instance of PodSecurityContext from a dict +pod_security_context_from_dict = PodSecurityContext.from_dict(pod_security_context_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/sdk/python/v1alpha2/docs/PodSpec.md b/sdk/python/v1alpha2/docs/PodSpec.md index ff9c546f..6f2aacc6 100644 --- a/sdk/python/v1alpha2/docs/PodSpec.md +++ b/sdk/python/v1alpha2/docs/PodSpec.md @@ -14,6 +14,7 @@ Name | Type | Description | Notes **restart_policy** | **str** | Restart Policy | [optional] **runtime_class_name** | **str** | RuntimeClassName for the pod | [optional] **scheduler_name** | **str** | Scheduler name for the pod | [optional] +**security_context** | [**PodSecurityContext**](PodSecurityContext.md) | | [optional] **service_account_name** | **str** | Service account name for the pod | [optional] ## Example diff --git a/sdk/python/v1alpha2/fluxoperator/__init__.py b/sdk/python/v1alpha2/fluxoperator/__init__.py index 589d73b0..8060372a 100644 --- a/sdk/python/v1alpha2/fluxoperator/__init__.py +++ b/sdk/python/v1alpha2/fluxoperator/__init__.py @@ -49,6 +49,7 @@ from fluxoperator.models.mini_cluster_status import MiniClusterStatus from fluxoperator.models.mini_cluster_user import MiniClusterUser from fluxoperator.models.network import Network +from fluxoperator.models.pod_security_context import PodSecurityContext from fluxoperator.models.pod_spec import PodSpec from fluxoperator.models.secret import Secret from fluxoperator.models.security_context import SecurityContext diff --git a/sdk/python/v1alpha2/fluxoperator/models/__init__.py b/sdk/python/v1alpha2/fluxoperator/models/__init__.py index 62fe08c0..815c049e 100644 --- a/sdk/python/v1alpha2/fluxoperator/models/__init__.py +++ b/sdk/python/v1alpha2/fluxoperator/models/__init__.py @@ -33,6 +33,7 @@ from fluxoperator.models.mini_cluster_status import MiniClusterStatus from fluxoperator.models.mini_cluster_user import MiniClusterUser from fluxoperator.models.network import Network +from fluxoperator.models.pod_security_context import PodSecurityContext from fluxoperator.models.pod_spec import PodSpec from fluxoperator.models.secret import Secret from fluxoperator.models.security_context import SecurityContext diff --git a/sdk/python/v1alpha2/fluxoperator/models/pod_security_context.py b/sdk/python/v1alpha2/fluxoperator/models/pod_security_context.py new file mode 100644 index 00000000..dde35229 --- /dev/null +++ b/sdk/python/v1alpha2/fluxoperator/models/pod_security_context.py @@ -0,0 +1,87 @@ +# coding: utf-8 + +""" + fluxoperator + + Python SDK for Flux-Operator + + The version of the OpenAPI document: v1alpha2 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self + +class PodSecurityContext(BaseModel): + """ + PodSecurityContext + """ # noqa: E501 + sysctls: Optional[Dict[str, StrictStr]] = Field(default=None, description="Sysctls") + __properties: ClassVar[List[str]] = ["sysctls"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of PodSecurityContext from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of PodSecurityContext from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "sysctls": obj.get("sysctls") + }) + return _obj + + diff --git a/sdk/python/v1alpha2/fluxoperator/models/pod_spec.py b/sdk/python/v1alpha2/fluxoperator/models/pod_spec.py index bdd10c14..fc92cbe7 100644 --- a/sdk/python/v1alpha2/fluxoperator/models/pod_spec.py +++ b/sdk/python/v1alpha2/fluxoperator/models/pod_spec.py @@ -19,6 +19,7 @@ from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr from typing import Any, ClassVar, Dict, List, Optional +from fluxoperator.models.pod_security_context import PodSecurityContext from typing import Optional, Set from typing_extensions import Self @@ -34,8 +35,9 @@ class PodSpec(BaseModel): restart_policy: Optional[StrictStr] = Field(default=None, description="Restart Policy", alias="restartPolicy") runtime_class_name: Optional[StrictStr] = Field(default=None, description="RuntimeClassName for the pod", alias="runtimeClassName") scheduler_name: Optional[StrictStr] = Field(default=None, description="Scheduler name for the pod", alias="schedulerName") + security_context: Optional[PodSecurityContext] = Field(default=None, alias="securityContext") service_account_name: Optional[StrictStr] = Field(default=None, description="Service account name for the pod", alias="serviceAccountName") - __properties: ClassVar[List[str]] = ["annotations", "automountServiceAccountToken", "labels", "nodeSelector", "resources", "restartPolicy", "runtimeClassName", "schedulerName", "serviceAccountName"] + __properties: ClassVar[List[str]] = ["annotations", "automountServiceAccountToken", "labels", "nodeSelector", "resources", "restartPolicy", "runtimeClassName", "schedulerName", "securityContext", "serviceAccountName"] model_config = ConfigDict( populate_by_name=True, @@ -83,6 +85,9 @@ def to_dict(self) -> Dict[str, Any]: if self.resources[_key]: _field_dict[_key] = self.resources[_key].to_dict() _dict['resources'] = _field_dict + # override the default output from pydantic by calling `to_dict()` of security_context + if self.security_context: + _dict['securityContext'] = self.security_context.to_dict() return _dict @classmethod @@ -108,6 +113,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "restartPolicy": obj.get("restartPolicy"), "runtimeClassName": obj.get("runtimeClassName"), "schedulerName": obj.get("schedulerName"), + "securityContext": PodSecurityContext.from_dict(obj["securityContext"]) if obj.get("securityContext") is not None else None, "serviceAccountName": obj.get("serviceAccountName") }) return _obj diff --git a/sdk/python/v1alpha2/test/test_pod_security_context.py b/sdk/python/v1alpha2/test/test_pod_security_context.py new file mode 100644 index 00000000..e926fb15 --- /dev/null +++ b/sdk/python/v1alpha2/test/test_pod_security_context.py @@ -0,0 +1,53 @@ +# coding: utf-8 + +""" + fluxoperator + + Python SDK for Flux-Operator + + The version of the OpenAPI document: v1alpha2 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import unittest + +from fluxoperator.models.pod_security_context import PodSecurityContext + +class TestPodSecurityContext(unittest.TestCase): + """PodSecurityContext unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def make_instance(self, include_optional) -> PodSecurityContext: + """Test PodSecurityContext + include_optional is a boolean, when False only required + params are included, when True both required and + optional params are included """ + # uncomment below to create an instance of `PodSecurityContext` + """ + model = PodSecurityContext() + if include_optional: + return PodSecurityContext( + sysctls = { + 'key' : '' + } + ) + else: + return PodSecurityContext( + ) + """ + + def testPodSecurityContext(self): + """Test PodSecurityContext""" + # inst_req_only = self.make_instance(include_optional=False) + # inst_req_and_optional = self.make_instance(include_optional=True) + +if __name__ == '__main__': + unittest.main()