Skip to content

Commit

Permalink
feat(trait): builder pod resources config
Browse files Browse the repository at this point in the history
* Enabled resource configuration for builder pod
* Default resource configuration when Quarkus native build

Closes #4177
  • Loading branch information
squakez committed May 10, 2023
1 parent 13430f0 commit 73d5924
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 62 deletions.
4 changes: 2 additions & 2 deletions pkg/apis/camel/v1/build_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import (
type BuildSpec struct {
// The sequence of Build tasks to be performed as part of the Build execution.
Tasks []Task `json:"tasks,omitempty"`
// The strategy that should be used to perform the Build.
Strategy BuildStrategy `json:"strategy,omitempty"`
// The configuration that should be used to perform the Build.
Configuration BuildConfiguration `json:"configuration,omitempty"`
// The container image to be used to run the build.
ToolImage string `json:"toolImage,omitempty"`
// The namespace where to run the builder Pod (must be the same of the operator in charge of this Build reconciliation).
Expand Down
8 changes: 4 additions & 4 deletions pkg/apis/camel/v1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ const (
type BuildConfiguration struct {
// the strategy to adopt
Strategy BuildStrategy `property:"strategy" json:"strategy,omitempty"`
// The minimum amount of CPU required.
// The minimum amount of CPU required. Only used for `pod` strategy
RequestCPU string `property:"request-cpu" json:"requestCPU,omitempty"`
// The minimum amount of memory required.
// The minimum amount of memory required. Only used for `pod` strategy
RequestMemory string `property:"request-memory" json:"requestMemory,omitempty"`
// The maximum amount of CPU required.
// The maximum amount of CPU required. Only used for `pod` strategy
LimitCPU string `property:"limit-cpu" json:"limitCPU,omitempty"`
// The maximum amount of memory required.
// The maximum amount of memory required. Only used for `pod` strategy
LimitMemory string `property:"limit-memory" json:"limitMemory,omitempty"`
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/camel/v1/trait/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ type BuilderTrait struct {
Properties []string `property:"properties" json:"properties,omitempty"`
// The strategy to use, either `pod` or `routine` (default routine)
Strategy string `property:"strategy" json:"strategy,omitempty"`
// When using `pod` strategy, the minimum amount of CPU required by the pod builder.
RequestCPU string `property:"request-cpu" json:"requestCPU,omitempty"`
// When using `pod` strategy, the minimum amount of memory required by the pod builder.
RequestMemory string `property:"request-memory" json:"requestMemory,omitempty"`
// When using `pod` strategy, the maximum amount of CPU required by the pod builder.
LimitCPU string `property:"limit-cpu" json:"limitCPU,omitempty"`
// When using `pod` strategy, the maximum amount of memory required by the pod builder.
LimitMemory string `property:"limit-memory" json:"limitMemory,omitempty"`
}
4 changes: 2 additions & 2 deletions pkg/controller/build/build_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (r *reconcileBuild) Reconcile(ctx context.Context, request reconcile.Reques
maxRunningBuilds: instance.Spec.MaxRunningBuilds,
}

switch instance.Spec.Strategy {
switch instance.Spec.Configuration.Strategy {
case v1.BuildStrategyPod:
actions = []Action{
newInitializePodAction(r.reader),
Expand Down Expand Up @@ -209,7 +209,7 @@ func (r *reconcileBuild) Reconcile(ctx context.Context, request reconcile.Reques
return reconcile.Result{RequeueAfter: 5 * time.Second}, nil
}

if target.Spec.Strategy == v1.BuildStrategyPod &&
if target.Spec.Configuration.Strategy == v1.BuildStrategyPod &&
(target.Status.Phase == v1.BuildPhasePending || target.Status.Phase == v1.BuildPhaseRunning) {
// Requeue running Build to poll Pod and signal timeout
return reconcile.Result{RequeueAfter: 1 * time.Second}, nil
Expand Down
4 changes: 3 additions & 1 deletion pkg/controller/build/build_monitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ func newBuildWithLayoutInPhase(namespace string, name string, layout string, pha
},
},
Spec: v1.BuildSpec{
Strategy: v1.BuildStrategyRoutine,
Configuration: v1.BuildConfiguration{
Strategy: v1.BuildStrategyRoutine,
},
ToolImage: "camel:latest",
BuilderPodNamespace: "ns",
Tasks: []v1.Task{},
Expand Down
38 changes: 38 additions & 0 deletions pkg/controller/build/build_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,47 @@ func newBuildPod(ctx context.Context, c ctrl.Reader, build *v1.Build) (*corev1.P
pod.Spec.Containers = pod.Spec.InitContainers[len(pod.Spec.InitContainers)-1 : len(pod.Spec.InitContainers)]
pod.Spec.InitContainers = pod.Spec.InitContainers[:len(pod.Spec.InitContainers)-1]

if err := configureResources(build.Spec.Configuration, &pod.Spec.Containers[0]); err != nil {
return pod, err
}

return pod, nil
}

func configureResources(conf v1.BuildConfiguration, container *corev1.Container) error {
requestsList := container.Resources.Requests
limitsList := container.Resources.Limits
var err error
if requestsList == nil {
requestsList = make(corev1.ResourceList)
}
if limitsList == nil {
limitsList = make(corev1.ResourceList)
}

requestsList, err = kubernetes.ConfigureResource(conf.RequestCPU, requestsList, corev1.ResourceCPU)
if err != nil {
return err
}
requestsList, err = kubernetes.ConfigureResource(conf.RequestMemory, requestsList, corev1.ResourceMemory)
if err != nil {
return err
}
limitsList, err = kubernetes.ConfigureResource(conf.LimitCPU, limitsList, corev1.ResourceCPU)
if err != nil {
return err
}
limitsList, err = kubernetes.ConfigureResource(conf.LimitMemory, limitsList, corev1.ResourceMemory)
if err != nil {
return err
}

container.Resources.Requests = requestsList
container.Resources.Limits = limitsList

return nil
}

func deleteBuilderPod(ctx context.Context, c ctrl.Writer, build *v1.Build) error {
pod := corev1.Pod{
TypeMeta: metav1.TypeMeta{
Expand Down
11 changes: 4 additions & 7 deletions pkg/controller/integrationkit/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,10 @@ func (action *buildAction) handleBuildSubmitted(ctx context.Context, kit *v1.Int

// It has to be the same namespace as the operator as they must share a PVC
builderPodNamespace := platform.GetOperatorNamespace()
buildStrategy := env.Platform.Status.Build.BuildConfiguration.Strategy
if env.BuildConfiguration.Strategy != "" {
buildStrategy = env.BuildConfiguration.Strategy
}
buildConfig := env.Platform.Status.Build.BuildConfiguration

// nolint: contextcheck
if buildStrategy == v1.BuildStrategyPod {
if buildConfig.Strategy == v1.BuildStrategyPod {
// Pod strategy requires a PVC to exist. If it does not exist, we warn the user and fallback to Routine build strategy
if pvc, err := kubernetes.LookupPersistentVolumeClaim(env.Ctx, env.Client, builderPodNamespace, defaults.DefaultPVC); pvc != nil || err != nil {
err = platform.CreateBuilderServiceAccount(env.Ctx, env.Client, env.Platform)
Expand All @@ -124,7 +121,7 @@ func (action *buildAction) handleBuildSubmitted(ctx context.Context, kit *v1.Int
}
} else {
// Fallback to Routine strategy
buildStrategy = v1.BuildStrategyRoutine
buildConfig.Strategy = v1.BuildStrategyRoutine
Log.Info(`Warning: the operator was installed with an ephemeral storage, builder "pod" strategy is not supported: using "routine" build strategy as a fallback. We recommend to configure a PersistentVolumeClaim in order to be able to use "pod" builder strategy. Please consider that certain features such as Quarkus native require a "pod" builder strategy (hence a PVC) to work properly.`)
}
}
Expand All @@ -141,7 +138,7 @@ func (action *buildAction) handleBuildSubmitted(ctx context.Context, kit *v1.Int
Annotations: annotations,
},
Spec: v1.BuildSpec{
Strategy: buildStrategy,
Configuration: buildConfig,
ToolImage: env.CamelCatalog.Image,
BuilderPodNamespace: builderPodNamespace,
Tasks: env.BuildTasks,
Expand Down
25 changes: 21 additions & 4 deletions pkg/resources/resources.go

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions pkg/trait/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,19 @@ func (t *builderTrait) Apply(e *Environment) error {
}
}

if t.RequestCPU != "" {
e.BuildConfiguration.RequestCPU = t.RequestCPU
}
if t.RequestMemory != "" {
e.BuildConfiguration.RequestMemory = t.RequestMemory
}
if t.LimitCPU != "" {
e.BuildConfiguration.LimitCPU = t.LimitCPU
}
if t.LimitMemory != "" {
e.BuildConfiguration.LimitMemory = t.LimitMemory
}

return nil
}

Expand Down
65 changes: 25 additions & 40 deletions pkg/trait/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"

Expand Down Expand Up @@ -195,7 +194,7 @@ func (t *containerTrait) configureContainer(e *Environment) error {
e.Resources.Add(props)
}

t.configureResources(e, &container)
t.configureResources(&container)
if pointer.BoolDeref(t.Expose, false) {
t.configureService(e, &container)
}
Expand Down Expand Up @@ -303,50 +302,36 @@ func (t *containerTrait) configureService(e *Environment, container *corev1.Cont
service.Labels["camel.apache.org/service.type"] = v1.ServiceTypeUser
}

func (t *containerTrait) configureResources(_ *Environment, container *corev1.Container) {
// Requests
if container.Resources.Requests == nil {
container.Resources.Requests = make(corev1.ResourceList)
func (t *containerTrait) configureResources(container *corev1.Container) {
requestsList := container.Resources.Requests
limitsList := container.Resources.Limits
var err error
if requestsList == nil {
requestsList = make(corev1.ResourceList)
}

if t.RequestCPU != "" {
v, err := resource.ParseQuantity(t.RequestCPU)
if err != nil {
t.L.Error(err, "unable to parse quantity", "request-cpu", t.RequestCPU)
} else {
container.Resources.Requests[corev1.ResourceCPU] = v
}
}
if t.RequestMemory != "" {
v, err := resource.ParseQuantity(t.RequestMemory)
if err != nil {
t.L.Error(err, "unable to parse quantity", "request-memory", t.RequestMemory)
} else {
container.Resources.Requests[corev1.ResourceMemory] = v
}
if limitsList == nil {
limitsList = make(corev1.ResourceList)
}

// Limits
if container.Resources.Limits == nil {
container.Resources.Limits = make(corev1.ResourceList)
requestsList, err = kubernetes.ConfigureResource(t.RequestCPU, requestsList, corev1.ResourceCPU)
if err != nil {
t.L.Error(err, "unable to parse quantity", "request-cpu", t.RequestCPU)
}

if t.LimitCPU != "" {
v, err := resource.ParseQuantity(t.LimitCPU)
if err != nil {
t.L.Error(err, "unable to parse quantity", "limit-cpu", t.LimitCPU)
} else {
container.Resources.Limits[corev1.ResourceCPU] = v
}
requestsList, err = kubernetes.ConfigureResource(t.RequestMemory, requestsList, corev1.ResourceMemory)
if err != nil {
t.L.Error(err, "unable to parse quantity", "request-memory", t.RequestMemory)
}
if t.LimitMemory != "" {
v, err := resource.ParseQuantity(t.LimitMemory)
if err != nil {
t.L.Error(err, "unable to parse quantity", "limit-memory", t.LimitMemory)
} else {
container.Resources.Limits[corev1.ResourceMemory] = v
}
limitsList, err = kubernetes.ConfigureResource(t.LimitCPU, limitsList, corev1.ResourceCPU)
if err != nil {
t.L.Error(err, "unable to parse quantity", "limit-cpu", t.LimitCPU)
}
limitsList, err = kubernetes.ConfigureResource(t.LimitMemory, limitsList, corev1.ResourceMemory)
if err != nil {
t.L.Error(err, "unable to parse quantity", "limit-memory", t.LimitMemory)
}

container.Resources.Requests = requestsList
container.Resources.Limits = limitsList
}

func (t *containerTrait) configureCapabilities(e *Environment) {
Expand Down
5 changes: 3 additions & 2 deletions pkg/trait/quarkus.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,10 @@ func (t *quarkusTrait) Configure(e *Environment) (bool, error) {
func (t *quarkusTrait) Apply(e *Environment) error {
if t.hasKitNativeType() {
// Force the build to run in a separate Pod
t.L.Info("Quarkus Native requires a build pod strategy")
t.L.Info("This is a Quarkus native build: setting build configuration with build Pod strategy, 1 CPU core and 8 GiB memory. Make sure your cluster can handle it.")
e.BuildConfiguration.Strategy = v1.BuildStrategyPod
// TODO we may provide a set of sensible resource default values for the Pod spun off
e.BuildConfiguration.RequestCPU = "1000m"
e.BuildConfiguration.RequestMemory = "8Gi"
}

if e.IntegrationInPhase(v1.IntegrationPhaseBuildingKit) {
Expand Down
13 changes: 13 additions & 0 deletions pkg/util/kubernetes/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,16 @@ func NewPersistentVolumeClaim(ns, name, storageClassName, capacityStorage string
}
return &pvc
}

// ConfigureResource will set a resource on the specified resource list or returns an error.
func ConfigureResource(resourceQty string, list corev1.ResourceList, name corev1.ResourceName) (corev1.ResourceList, error) {
if resourceQty != "" {
v, err := resource.ParseQuantity(resourceQty)
if err != nil {
return list, err
}
list[name] = v
}

return list, nil
}
16 changes: 16 additions & 0 deletions pkg/util/kubernetes/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"

Check failure on line 25 in pkg/util/kubernetes/factory_test.go

View workflow job for this annotation

GitHub Actions / validate

ST1019: package "k8s.io/api/core/v1" is being imported more than once (stylecheck)
v1 "k8s.io/api/core/v1"

Check failure on line 26 in pkg/util/kubernetes/factory_test.go

View workflow job for this annotation

GitHub Actions / validate

ST1019(related information): other import of "k8s.io/api/core/v1" (stylecheck)
"k8s.io/apimachinery/pkg/api/resource"
)
Expand Down Expand Up @@ -170,3 +171,18 @@ func TestMissingResourceRequirements(t *testing.T) {
_, err := NewResourceRequirements(strings.Split(resReq, ","))
assert.NotNil(t, err)
}

func TestConfigureResources(t *testing.T) {
requestsList := make(corev1.ResourceList)
requestsList, err := ConfigureResource("500m", requestsList, corev1.ResourceCPU)
assert.Nil(t, err)
assert.Equal(t, "500m", requestsList.Cpu().String())
requestsList, err = ConfigureResource("5Gi", requestsList, corev1.ResourceMemory)
assert.Nil(t, err)
assert.Equal(t, "5Gi", requestsList.Memory().String())
requestsList, err = ConfigureResource("5ss", requestsList, corev1.ResourceCPU)
assert.NotNil(t, err)
// Assert previous values haven't changed
assert.Equal(t, "500m", requestsList.Cpu().String())
assert.Equal(t, "5Gi", requestsList.Memory().String())
}

0 comments on commit 73d5924

Please sign in to comment.