Skip to content

Commit 2ce7841

Browse files
authored
feat: Add replicas field to Function CRD for configurable deployment scaling (#202)
### Motivation Currently, Function deployments are hardcoded to use 1 replica, which limits the ability to scale functions based on workload requirements. This change adds a configurable `replicas` field to the Function CRD, allowing users to specify the desired number of replicas for their function deployments. This enables horizontal scaling of functions to handle increased load and improve availability. ### Modifications - **Function CRD Enhancement**: Added `replicas` field to `FunctionSpec` with validation and default value of 1 - **Controller Logic**: Updated `FunctionReconciler` to use the specified replicas when creating deployments - **CRD Generation**: Updated generated deepcopy functions and CRD YAML files to include the new field - **Testing**: Added comprehensive test cases to verify replica configuration works correctly, including: - Custom replica specification - Replica updates - Default replica behavior when not specified - **Gitignore Updates**: Added macOS-specific ignore patterns (`.DS_Store` and `._*` files) to prevent accidental commits The implementation maintains backward compatibility by defaulting to 1 replica when the field is not specified, ensuring existing function deployments continue to work without modification.
1 parent 0f86cc0 commit 2ce7841

File tree

8 files changed

+137
-1
lines changed

8 files changed

+137
-1
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,7 @@ bin/
110110

111111
benchmark/*.pprof
112112

113-
operator/vendor/
113+
operator/vendor/
114+
115+
._*
116+
**/.DS_Store

operator/api/v1alpha1/function_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ type FunctionSpec struct {
3737
// Module name
3838
// +kubebuilder:validation:Required
3939
Module string `json:"module"`
40+
// Number of replicas for the function deployment
41+
// +kubebuilder:validation:Optional
42+
// +kubebuilder:default=1
43+
Replicas *int32 `json:"replicas,omitempty"`
4044
// +kubebuilder:validation:Optional
4145
SubscriptionName string `json:"subscriptionName,omitempty"`
4246
// List of sources

operator/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

operator/config/crd/bases/fs.functionstream.github.io_functions.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ spec:
5656
package:
5757
description: Package name
5858
type: string
59+
replicas:
60+
default: 1
61+
description: Number of replicas for the function deployment
62+
format: int32
63+
type: integer
5964
requestSource:
6065
description: Request source
6166
properties:

operator/deploy/chart/templates/crd/fs.functionstream.github.io_functions.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ spec:
6262
package:
6363
description: Package name
6464
type: string
65+
replicas:
66+
default: 1
67+
description: Number of replicas for the function deployment
68+
format: int32
69+
type: integer
6570
requestSource:
6671
description: Request source
6772
properties:

operator/internal/controller/function_controller.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ func (r *FunctionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
118118
// 5. Build Deployment
119119
deployName := fmt.Sprintf("function-%s", fn.Name)
120120
var replicas int32 = 1
121+
if fn.Spec.Replicas != nil {
122+
replicas = *fn.Spec.Replicas
123+
}
121124
labels := map[string]string{
122125
"function": fn.Name,
123126
}

operator/internal/controller/function_controller_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,112 @@ var _ = Describe("Function Controller", func() {
581581
Expect(deploy2.Spec.Template.Spec.Containers[0].Image).To(Equal("nginx:latest"))
582582
})
583583

584+
It("should use the specified replicas from FunctionSpec", func() {
585+
By("creating a Function with custom replicas")
586+
controllerReconciler := &FunctionReconciler{
587+
Client: k8sClient,
588+
Scheme: k8sClient.Scheme(),
589+
Config: Config{
590+
PulsarServiceURL: "pulsar://test-broker:6650",
591+
PulsarAuthPlugin: "org.apache.pulsar.client.impl.auth.AuthenticationToken",
592+
PulsarAuthParams: "token:my-token",
593+
},
594+
}
595+
596+
// Create a Package resource first
597+
pkg := &fsv1alpha1.Package{
598+
ObjectMeta: metav1.ObjectMeta{
599+
Name: "test-pkg-replicas",
600+
Namespace: "default",
601+
},
602+
Spec: fsv1alpha1.PackageSpec{
603+
DisplayName: "Test Package Replicas",
604+
Description: "desc",
605+
FunctionType: fsv1alpha1.FunctionType{
606+
Cloud: &fsv1alpha1.CloudType{Image: "busybox:latest"},
607+
},
608+
Modules: map[string]fsv1alpha1.Module{},
609+
},
610+
}
611+
Expect(k8sClient.Create(ctx, pkg)).To(Succeed())
612+
613+
// Create a Function with custom replicas
614+
customReplicas := int32(3)
615+
fn := &fsv1alpha1.Function{
616+
ObjectMeta: metav1.ObjectMeta{
617+
Name: "test-fn-replicas",
618+
Namespace: "default",
619+
},
620+
Spec: fsv1alpha1.FunctionSpec{
621+
Package: "test-pkg-replicas",
622+
Module: "mod",
623+
Replicas: &customReplicas,
624+
SubscriptionName: "sub",
625+
Sink: &fsv1alpha1.SinkSpec{Pulsar: &fsv1alpha1.PulsarSinkSpec{Topic: "out"}},
626+
RequestSource: &fsv1alpha1.SourceSpec{Pulsar: &fsv1alpha1.PulsarSourceSpec{Topic: "in"}},
627+
},
628+
}
629+
Expect(k8sClient.Create(ctx, fn)).To(Succeed())
630+
631+
// Reconcile the Function
632+
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
633+
NamespacedName: types.NamespacedName{Name: fn.Name, Namespace: fn.Namespace},
634+
})
635+
Expect(err).NotTo(HaveOccurred())
636+
637+
// Check that the Deployment has the correct number of replicas
638+
deployName := "function-" + fn.Name
639+
deploy := &appsv1.Deployment{}
640+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: deployName, Namespace: fn.Namespace}, deploy)).To(Succeed())
641+
Expect(deploy.Spec.Replicas).NotTo(BeNil())
642+
Expect(*deploy.Spec.Replicas).To(Equal(int32(3)))
643+
644+
// Test updating replicas
645+
newReplicas := int32(5)
646+
patch := client.MergeFrom(fn.DeepCopy())
647+
fn.Spec.Replicas = &newReplicas
648+
Expect(k8sClient.Patch(ctx, fn, patch)).To(Succeed())
649+
650+
// Reconcile again
651+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
652+
NamespacedName: types.NamespacedName{Name: fn.Name, Namespace: fn.Namespace},
653+
})
654+
Expect(err).NotTo(HaveOccurred())
655+
656+
// Verify the deployment was updated with new replicas
657+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: deployName, Namespace: fn.Namespace}, deploy)).To(Succeed())
658+
Expect(*deploy.Spec.Replicas).To(Equal(int32(5)))
659+
660+
// Test default replicas when not specified
661+
fnDefault := &fsv1alpha1.Function{
662+
ObjectMeta: metav1.ObjectMeta{
663+
Name: "test-fn-default-replicas",
664+
Namespace: "default",
665+
},
666+
Spec: fsv1alpha1.FunctionSpec{
667+
Package: "test-pkg-replicas",
668+
Module: "mod",
669+
SubscriptionName: "sub-default",
670+
Sink: &fsv1alpha1.SinkSpec{Pulsar: &fsv1alpha1.PulsarSinkSpec{Topic: "out-default"}},
671+
RequestSource: &fsv1alpha1.SourceSpec{Pulsar: &fsv1alpha1.PulsarSourceSpec{Topic: "in-default"}},
672+
},
673+
}
674+
Expect(k8sClient.Create(ctx, fnDefault)).To(Succeed())
675+
676+
// Reconcile the Function with default replicas
677+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
678+
NamespacedName: types.NamespacedName{Name: fnDefault.Name, Namespace: fnDefault.Namespace},
679+
})
680+
Expect(err).NotTo(HaveOccurred())
681+
682+
// Check that the Deployment has default replicas (1)
683+
deployDefaultName := "function-" + fnDefault.Name
684+
deployDefault := &appsv1.Deployment{}
685+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: deployDefaultName, Namespace: fnDefault.Namespace}, deployDefault)).To(Succeed())
686+
Expect(deployDefault.Spec.Replicas).NotTo(BeNil())
687+
Expect(*deployDefault.Spec.Replicas).To(Equal(int32(1)))
688+
})
689+
584690
It("should handle Package updates in different namespaces", func() {
585691
By("creating Functions and Packages in different namespaces")
586692
controllerReconciler := &FunctionReconciler{

operator/scripts/deploy.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ spec:
8383
package:
8484
description: Package name
8585
type: string
86+
replicas:
87+
default: 1
88+
description: Number of replicas for the function deployment
89+
format: int32
90+
type: integer
8691
requestSource:
8792
description: Request source
8893
properties:

0 commit comments

Comments
 (0)