Skip to content

Commit

Permalink
feat(slimfaas): scale down statefulset
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaume-chervet committed Jan 4, 2024
1 parent 3321afa commit fe2da23
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 42 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
![SlimFaas.png](https://github.com/AxaFrance/SlimFaas/blob/main/documentation/SlimFaas.png)

Why use SlimFaas ?
- Scale to 0 after a period of inactivity
- Scale to 0 after a period of inactivity (work with deployment and statefulset)
- Synchronous HTTP calls
- Asynchronous HTTP calls
- Allows you to limit the number of parallel HTTP requests for each underlying function
Expand Down Expand Up @@ -33,6 +33,8 @@ kubectl apply -f deployment-slimfaas.yml
# Install three instances of fibonacci functions
# fibonacci1, fibonacci2 and fibonacci3
kubectl apply -f deployment-functions.yml
# Install MySql
kubectl apply -f deployment-mysql.yml
# to run Single Page webapp demo (optional)
docker run -p 8000:8000 --rm axaguildev/fibonacci-webapp:latest
```
Expand Down Expand Up @@ -319,11 +321,10 @@ Why .NET ?

## Videos

- French : https://www.youtube.com/watch?v=Lvd6FCuCZPI
- French : https://www.youtube.com/watch?v=Lvd6FCuCZPI
- English: https://www.youtube.com/watch?hxRfvJhWW1w

## What Next ?

1. Different scale down mode depending from configuration and current hour
2. Allow to scale down also statefulset pods
3. Scale up dynamically from SlimFaas
2. Scale up dynamically from SlimFaas
75 changes: 75 additions & 0 deletions demo/deployment-mysql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: kubernetes.io/basic-auth
stringData:
password: "test1234"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
annotations:
SlimFaas/Function: "true"
SlimFaas/ReplicasMin: "0"
SlimFaas/ReplicasAtStart: "1"
SlimFaas/ReplicasStartAsSoonAsOneFunctionRetrieveARequest: "true"
SlimFaas/TimeoutSecondBeforeSetReplicasMin: "60"
spec:
containers:
- image: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
resources:
limits:
cpu: 600m
memory: 1024Mi
requests:
cpu: 300m
memory: 512Mi
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
---
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql
121 changes: 88 additions & 33 deletions src/SlimFaas/Kubernetes/KubernetesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@

namespace SlimFaas.Kubernetes;

public record ReplicaRequest(string Deployment, string Namespace, int Replicas);
public enum PodType
{
Deployment,
StatefulSet
}

public record ReplicaRequest(string Deployment, string Namespace, int Replicas, PodType PodType);

public record SlimFaasDeploymentInformation(int Replicas, IList<PodInformation> Pods);

Expand All @@ -15,7 +21,7 @@ public record DeploymentsInformations(IList<DeploymentInformation> Functions, Sl
public record DeploymentInformation(string Deployment, string Namespace, IList<PodInformation> Pods, int Replicas,
int ReplicasAtStart = 1, int ReplicasMin = 0, int TimeoutSecondBeforeSetReplicasMin = 300,
int NumberParallelRequest = 10,
bool ReplicasStartAsSoonAsOneFunctionRetrieveARequest = false);
bool ReplicasStartAsSoonAsOneFunctionRetrieveARequest = false, PodType PodType = PodType.Deployment);

public record PodInformation(string Name, bool? Started, bool? Ready, string Ip, string DeploymentName);

Expand Down Expand Up @@ -48,10 +54,20 @@ public KubernetesService(ILogger<KubernetesService> logger, bool useKubeConfig)
{
try
{
using k8s.Kubernetes client = new k8s.Kubernetes(_k8SConfig);
using k8s.Kubernetes client = new(_k8SConfig);
string patchString = $"{{\"spec\": {{\"replicas\": {request.Replicas}}}}}";
V1Patch patch = new V1Patch(patchString, V1Patch.PatchType.MergePatch);
await client.PatchNamespacedDeploymentScaleAsync(patch, request.Deployment, request.Namespace);
V1Patch patch = new(patchString, V1Patch.PatchType.MergePatch);
switch (request.PodType)
{
case PodType.Deployment:
await client.PatchNamespacedDeploymentScaleAsync(patch, request.Deployment, request.Namespace);
break;
case PodType.StatefulSet:
await client.PatchNamespacedStatefulSetScaleAsync(patch, request.Deployment, request.Namespace);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
catch (HttpOperationException e)
{
Expand All @@ -68,7 +84,7 @@ public async Task<DeploymentsInformations> ListFunctionsAsync(string kubeNamespa
try
{
IList<DeploymentInformation>? deploymentInformationList = new List<DeploymentInformation>();
using k8s.Kubernetes client = new k8s.Kubernetes(_k8SConfig);
using k8s.Kubernetes client = new(_k8SConfig);
Task<V1DeploymentList>? deploymentListTask = client.ListNamespacedDeploymentAsync(kubeNamespace);
Task<V1PodList>? podListTask = client.ListNamespacedPodAsync(kubeNamespace);
Task<V1StatefulSetList>? statefulSetListTask = client.ListNamespacedStatefulSetAsync(kubeNamespace);
Expand All @@ -85,33 +101,8 @@ public async Task<DeploymentsInformations> ListFunctionsAsync(string kubeNamespa
podList.Where(p => p.Name.StartsWith(deploymentListItem.Metadata.Name)).ToList()))
.FirstOrDefault();

foreach (V1Deployment? deploymentListItem in deploymentList.Items)
{
IDictionary<string, string>? annotations = deploymentListItem.Spec.Template.Metadata.Annotations;
if (annotations == null || !annotations.ContainsKey(Function) ||
annotations[Function].ToLower() != "true")
{
continue;
}

DeploymentInformation deploymentInformation = new DeploymentInformation(
deploymentListItem.Metadata.Name,
kubeNamespace,
podList.Where(p => p.DeploymentName == deploymentListItem.Metadata.Name).ToList(),
deploymentListItem.Spec.Replicas ?? 0,
annotations.ContainsKey(ReplicasAtStart)
? int.Parse(annotations[ReplicasAtStart])
: 1, annotations.ContainsKey(ReplicasMin)
? int.Parse(annotations[ReplicasMin])
: 1, annotations.ContainsKey(TimeoutSecondBeforeSetReplicasMin)
? int.Parse(annotations[TimeoutSecondBeforeSetReplicasMin])
: 300, annotations.ContainsKey(NumberParallelRequest)
? int.Parse(annotations[NumberParallelRequest])
: 10, annotations.ContainsKey(
ReplicasStartAsSoonAsOneFunctionRetrieveARequest) &&
annotations[ReplicasStartAsSoonAsOneFunctionRetrieveARequest].ToLower() == "true");
deploymentInformationList.Add(deploymentInformation);
}
AddDeployments(kubeNamespace, deploymentList, podList, deploymentInformationList);
AddStatefulSets(kubeNamespace, statefulSetList, podList, deploymentInformationList);

return new DeploymentsInformations(deploymentInformationList,
slimFaasDeploymentInformation ?? new SlimFaasDeploymentInformation(1, new List<PodInformation>()));
Expand All @@ -124,6 +115,70 @@ public async Task<DeploymentsInformations> ListFunctionsAsync(string kubeNamespa
}
}

private static void AddDeployments(string kubeNamespace, V1DeploymentList deploymentList, IEnumerable<PodInformation> podList,
IList<DeploymentInformation> deploymentInformationList)
{
foreach (V1Deployment? deploymentListItem in deploymentList.Items)
{
IDictionary<string, string>? annotations = deploymentListItem.Spec.Template.Metadata.Annotations;
if (annotations == null || !annotations.ContainsKey(Function) ||
annotations[Function].ToLower() != "true")
{
continue;
}

DeploymentInformation deploymentInformation = new(
deploymentListItem.Metadata.Name,
kubeNamespace,
podList.Where(p => p.DeploymentName == deploymentListItem.Metadata.Name).ToList(),
deploymentListItem.Spec.Replicas ?? 0,
annotations.ContainsKey(ReplicasAtStart)
? int.Parse(annotations[ReplicasAtStart])
: 1, annotations.ContainsKey(ReplicasMin)
? int.Parse(annotations[ReplicasMin])
: 1, annotations.ContainsKey(TimeoutSecondBeforeSetReplicasMin)
? int.Parse(annotations[TimeoutSecondBeforeSetReplicasMin])
: 300, annotations.ContainsKey(NumberParallelRequest)
? int.Parse(annotations[NumberParallelRequest])
: 10, annotations.ContainsKey(
ReplicasStartAsSoonAsOneFunctionRetrieveARequest) &&
annotations[ReplicasStartAsSoonAsOneFunctionRetrieveARequest].ToLower() == "true");
deploymentInformationList.Add(deploymentInformation);
}
}

private static void AddStatefulSets(string kubeNamespace, V1StatefulSetList deploymentList, IEnumerable<PodInformation> podList,
IList<DeploymentInformation> deploymentInformationList)
{
foreach (V1StatefulSet? deploymentListItem in deploymentList.Items)
{
IDictionary<string, string>? annotations = deploymentListItem.Spec.Template.Metadata.Annotations;
if (annotations == null || !annotations.ContainsKey(Function) ||
annotations[Function].ToLower() != "true")
{
continue;
}

DeploymentInformation deploymentInformation = new(
deploymentListItem.Metadata.Name,
kubeNamespace,
podList.Where(p => p.DeploymentName == deploymentListItem.Metadata.Name).ToList(),
deploymentListItem.Spec.Replicas ?? 0,
annotations.ContainsKey(ReplicasAtStart)
? int.Parse(annotations[ReplicasAtStart])
: 1, annotations.ContainsKey(ReplicasMin)
? int.Parse(annotations[ReplicasMin])
: 1, annotations.ContainsKey(TimeoutSecondBeforeSetReplicasMin)
? int.Parse(annotations[TimeoutSecondBeforeSetReplicasMin])
: 300, annotations.ContainsKey(NumberParallelRequest)
? int.Parse(annotations[NumberParallelRequest])
: 10, annotations.ContainsKey(
ReplicasStartAsSoonAsOneFunctionRetrieveARequest) &&
annotations[ReplicasStartAsSoonAsOneFunctionRetrieveARequest].ToLower() == "true", PodType.StatefulSet);
deploymentInformationList.Add(deploymentInformation);
}
}

private static IEnumerable<PodInformation> MapPodInformations(V1PodList v1PodList)
{
foreach (V1Pod? item in v1PodList.Items)
Expand Down
8 changes: 5 additions & 3 deletions src/SlimFaas/ReplicasService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public async Task CheckScaleAsync(string kubeNamespace)
Task<ReplicaRequest?> task = kubernetesService.ScaleAsync(new ReplicaRequest(
Replicas: deploymentInformation.ReplicasMin,
Deployment: deploymentInformation.Deployment,
Namespace: kubeNamespace
Namespace: kubeNamespace,
PodType: deploymentInformation.PodType
));

tasks.Add(task);
Expand All @@ -94,7 +95,8 @@ public async Task CheckScaleAsync(string kubeNamespace)
Task<ReplicaRequest?> task = kubernetesService.ScaleAsync(new ReplicaRequest(
Replicas: deploymentInformation.ReplicasAtStart,
Deployment: deploymentInformation.Deployment,
Namespace: kubeNamespace
Namespace: kubeNamespace,
PodType: deploymentInformation.PodType
));

tasks.Add(task);
Expand All @@ -106,7 +108,7 @@ public async Task CheckScaleAsync(string kubeNamespace)
return;
}

List<DeploymentInformation> updatedFunctions = new List<DeploymentInformation>();
List<DeploymentInformation> updatedFunctions = new();
ReplicaRequest?[] replicaRequests = await Task.WhenAll(tasks);
foreach (DeploymentInformation function in Deployments.Functions)
{
Expand Down
4 changes: 2 additions & 2 deletions tests/SlimFaas.Tests/ReplicasScaleWorkerShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ public async Task ScaleFunctionUpAndDown(DeploymentsInformations deploymentsInfo
masterService.Setup(ms => ms.IsMaster).Returns(true);
kubernetesService.Setup(k => k.ListFunctionsAsync(It.IsAny<string>())).ReturnsAsync(deploymentsInformations);

ReplicaRequest scaleRequestFibonacci1 = new ReplicaRequest("fibonacci1", "default", 0);
ReplicaRequest scaleRequestFibonacci1 = new ReplicaRequest("fibonacci1", "default", 0, PodType.Deployment);
kubernetesService.Setup(k => k.ScaleAsync(scaleRequestFibonacci1)).ReturnsAsync(scaleRequestFibonacci1);
ReplicaRequest scaleRequestFibonacci2 = new ReplicaRequest("fibonacci2", "default", 1);
ReplicaRequest scaleRequestFibonacci2 = new ReplicaRequest("fibonacci2", "default", 1, PodType.Deployment);
kubernetesService.Setup(k => k.ScaleAsync(scaleRequestFibonacci2)).ReturnsAsync(scaleRequestFibonacci2);
await replicasService.SyncDeploymentsAsync("default");

Expand Down

0 comments on commit fe2da23

Please sign in to comment.