Skip to content

Commit

Permalink
Catch exceptions in k8s class, remove from annotation the .io, update…
Browse files Browse the repository at this point in the history
… Python 3.9.7, update Alpine
  • Loading branch information
dignajar committed Sep 16, 2021
1 parent 0ddf398 commit 22d502b
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 48 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.9.5-alpine3.13
FROM python:3.9.7-alpine3.14

ENV PYTHONUNBUFFERED=0

Expand Down
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,38 @@ kubectl get pods -n another
## Configuration
The following annotations for the deployments are valid (`metadata.annotations`).

- `another-autoscaler.io/stop-time`: Define the date and time when the replica of the deployment will be set to 0.
- `another-autoscaler.io/start-time` Define the date and time when the replica of the deployment will be set to 1.
- `another-autoscaler.io/restart-time:`: Define the date and time when the rollout restart will be peformerd to a deployment.
- `another-autoscaler.io/stop-replicas`: This is the number of replicas to set when Another Autoscaler scale down the deployment, by default is 0.
- `another-autoscaler.io/start-replicas`: This is the number of replicas to set when Another Autoscaler scale up the deployment, by default is 1.
- `another-autoscaler/stop-time`: Define the date and time when the replica of the deployment will be set to 0.
- `another-autoscaler/start-time` Define the date and time when the replica of the deployment will be set to 1.
- `another-autoscaler/restart-time:`: Define the date and time when the rollout restart will be peformerd to a deployment.
- `another-autoscaler/stop-replicas`: This is the number of replicas to set when Another Autoscaler scale down the deployment, by default is 0.
- `another-autoscaler/start-replicas`: This is the number of replicas to set when Another Autoscaler scale up the deployment, by default is 1.

## Examples

### Stop pods at 6pm every day:
```
another-autoscaler.io/stop-time: "00 18 * * *"
another-autoscaler/stop-time: "00 18 * * *"
```

### Start pods at 1pm every day:
```
another-autoscaler.io/start-time: "00 13 * * *"
another-autoscaler/start-time: "00 13 * * *"
```

### Start 3 pods at 2:30pm every day:
```
another-autoscaler.io/start-time: "30 14 * * *"
another-autoscaler.io/start-replicas: "3"
another-autoscaler/start-time: "30 14 * * *"
another-autoscaler/start-replicas: "3"
```

### Restart pods at 9:15am every day:
```
another-autoscaler.io/restart-time: "15 09 * * *"
another-autoscaler/restart-time: "15 09 * * *"
```

### Restart pods at 2:30am, only on Saturday and Sunday:
```
another-autoscaler.io/restart-time: "00 02 * * 0,6"
another-autoscaler/restart-time: "00 02 * * 0,6"
```

### Full example, how to start pods at 2pm and stop them at 3pm every day
Expand All @@ -80,10 +80,10 @@ metadata:
labels:
app: nginx
annotations:
another-autoscaler.io/start-time: "00 14 * * *"
another-autoscaler.io/start-replicas: "5"
another-autoscaler.io/stop-time: "00 15 * * *"
another-autoscaler.io/stop-replicas: "1"
another-autoscaler/start-time: "00 14 * * *"
another-autoscaler/start-replicas: "5"
another-autoscaler/stop-time: "00 15 * * *"
another-autoscaler/stop-replicas: "1"
spec:
replicas: 0
selector:
Expand Down
20 changes: 7 additions & 13 deletions files/aautoscaler.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __start__(self, namespace:str, deploy:dict, currentTime:datetime):
deployAnnotations = deploy.metadata.annotations
deployReplicas = deploy.spec.replicas

startAnnotation = 'another-autoscaler.io/start-time'
startAnnotation = 'another-autoscaler/start-time'
if startAnnotation in deployAnnotations:
self.logs.debug({'message': 'Start time detected.', 'namespace': namespace, 'deployment': deployName})
startTime = deployAnnotations[startAnnotation]
Expand All @@ -43,17 +43,14 @@ def __start__(self, namespace:str, deploy:dict, currentTime:datetime):

# start-replicas
startReplicas = 1
startReplicasAnnotation = 'another-autoscaler.io/start-replicas'
startReplicasAnnotation = 'another-autoscaler/start-replicas'
if startReplicasAnnotation in deployAnnotations:
self.logs.debug({'message': 'Replicas defined by the user for start.', 'namespace': namespace, 'deployment': deployName, 'startReplicas': deployAnnotations[startReplicasAnnotation]})
startReplicas = int(deployAnnotations[startReplicasAnnotation])

if deployReplicas != startReplicas:
self.logs.info({'message': 'Deployment set to start.', 'namespace': namespace, 'deployment': deployName, 'startTime': str(startTime), 'availableReplicas': deploy.status.available_replicas, 'startReplicas': str(startReplicas)})
try:
self.k8s.setReplicas(namespace, deployName, startReplicas)
except:
self.logs.error({'message': 'There was an error increasing the replicas, don\'t worry, we\'ll try again.'})
self.k8s.setReplicas(namespace, deployName, startReplicas)

def __stop__(self, namespace:str, deploy:dict, currentTime:datetime):
'''
Expand All @@ -63,7 +60,7 @@ def __stop__(self, namespace:str, deploy:dict, currentTime:datetime):
deployAnnotations = deploy.metadata.annotations
deployReplicas = deploy.spec.replicas

stopAnnotation = 'another-autoscaler.io/stop-time'
stopAnnotation = 'another-autoscaler/stop-time'
if stopAnnotation in deployAnnotations:
self.logs.debug({'message': 'Stop time detected.', 'namespace': namespace, 'deployment': deployName})
stopTime = deployAnnotations[stopAnnotation]
Expand All @@ -73,17 +70,14 @@ def __stop__(self, namespace:str, deploy:dict, currentTime:datetime):

# stop-replicas
stopReplicas = 0
stopReplicasAnnotation = 'another-autoscaler.io/stop-replicas'
stopReplicasAnnotation = 'another-autoscaler/stop-replicas'
if stopReplicasAnnotation in deployAnnotations:
self.logs.debug({'message': 'Replicas defined by the user for stop.', 'namespace': namespace, 'deployment': deployName, 'stopReplicas': deployAnnotations[stopReplicasAnnotation]})
stopReplicas = int(deployAnnotations[stopReplicasAnnotation])

if deployReplicas != stopReplicas:
self.logs.info({'message': 'Deployment set to stop.', 'namespace': namespace, 'deployment': deployName, 'stopTime': str(stopTime), 'availableReplicas': deploy.status.available_replicas, 'stopReplicas': str(stopReplicas)})
try:
self.k8s.setReplicas(namespace, deployName, stopReplicas)
except:
self.logs.error({'message': 'There was an error decreasing the replicas, don\'t worry, we\'ll try again.'})
self.k8s.setReplicas(namespace, deployName, stopReplicas)

def __restart__(self, namespace:str, deploy:dict, currentTime:datetime):
'''
Expand All @@ -92,7 +86,7 @@ def __restart__(self, namespace:str, deploy:dict, currentTime:datetime):
deployName = deploy.metadata.name
deployAnnotations = deploy.metadata.annotations

restartAnnotation = 'another-autoscaler.io/restart-time'
restartAnnotation = 'another-autoscaler/restart-time'
if restartAnnotation in deployAnnotations:
self.logs.debug({'message': 'Restart time detected.', 'namespace': namespace, 'deployment': deployName})
restartTime = deployAnnotations[restartAnnotation]
Expand Down
79 changes: 60 additions & 19 deletions files/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import pytz
import urllib3
from kubernetes import client, config
from kubernetes.client.rest import ApiException
from logs import Logs

class K8s:

Expand All @@ -28,40 +30,59 @@ def getNamespaces(self) -> list:
'''
Returns a list of namespaces.
'''
response = self.CoreV1Api.list_namespace()
return response.items
try:
response = self.CoreV1Api.list_namespace()
return response.items
except ApiException as e:
self.logs.error({'message': 'Exception when calling CoreV1Api.list_namespace', 'exception': e})
return []

def getDeployments(self, namespace:str, labelSelector:str=False) -> list:
'''
Returns all deployments from a namespace.
Label selector should be an string "app=kube-web-view".
'''
if labelSelector:
response = self.AppsV1Api.list_namespaced_deployment(namespace=namespace, label_selector=labelSelector)
else:
response = self.AppsV1Api.list_namespaced_deployment(namespace=namespace)
return response.items
try:
if labelSelector:
response = self.AppsV1Api.list_namespaced_deployment(namespace=namespace, label_selector=labelSelector)
else:
response = self.AppsV1Api.list_namespaced_deployment(namespace=namespace)
return response.items
except ApiException as e:
self.logs.error({'message': 'Exception when calling AppsV1Api.list_namespaced_deployment', 'exception': e})
return []

def getDeployment(self, namespace:str, deploymentName:str):
'''
Returns a particular deployment.
'''
return self.AppsV1Api.read_namespaced_deployment(namespace=namespace, name=deploymentName)
try:
return self.AppsV1Api.read_namespaced_deployment(namespace=namespace, name=deploymentName)
except ApiException as e:
self.logs.error({'message': 'Exception when calling AppsV1Api.read_namespaced_deployment', 'exception': e})
return []

def getPods(self, namespace:str, labelSelector:str, limit:int=1) -> list:
'''
Returns a list of pods for the label selector.
Label selector should be an string "app=kube-web-view".
'''
response = self.CoreV1Api.list_namespaced_pod(namespace=namespace, label_selector=labelSelector, limit=limit)
return response.items
try:
response = self.CoreV1Api.list_namespaced_pod(namespace=namespace, label_selector=labelSelector, limit=limit)
return response.items
except ApiException as e:
self.logs.error({'message': 'Exception when calling CoreV1Api.list_namespaced_pod', 'exception': e})
return []

def getPodsByDeployment(self, namespace:str, deploymentName:str, limit:int=1) -> list:
'''
Returns a list of pods related to a deployment.
'''
deploy = self.getDeployment(namespace, deploymentName)
matchLabels = deploy.spec.selector.match_labels
deployment = self.getDeployment(namespace, deploymentName)
if not deployment:
return []

matchLabels = deployment.spec.selector.match_labels
labelSelector = ''
for key, value in matchLabels.items():
labelSelector += key+'='+value+','
Expand All @@ -73,34 +94,54 @@ def deleteAllPods(self, namespace:str, labelSelector:str):
Delete all pods from a namespace filter by label selector.
'''
deployments = self.getDeployments(namespace=namespace, labelSelector=labelSelector)
if not deployments:
return False

deployment = deployments[0]
for labelKey, labelValue in deployment.spec.selector.match_labels.items():
pods = self.getPods(namespace, labelKey+'='+labelValue)
for pod in pods:
self.deletePod(namespace=namespace, podName=pod.metadata.name)
return True

def setReplicas(self, namespace:str, deploymentName:str, replicas:int):
def setReplicas(self, namespace:str, deploymentName:str, replicas:int) -> bool:
'''
Set the number of replicas of a deployment.
'''
currentScale = self.AppsV1Api.read_namespaced_deployment_scale(namespace=namespace, name=deploymentName)
currentScale.spec.replicas = replicas
self.AppsV1Api.replace_namespaced_deployment_scale(namespace=namespace, name=deploymentName, body=currentScale)
try:
currentScale = self.AppsV1Api.read_namespaced_deployment_scale(namespace=namespace, name=deploymentName)
currentScale.spec.replicas = replicas
self.AppsV1Api.replace_namespaced_deployment_scale(namespace=namespace, name=deploymentName, body=currentScale)
return True
except ApiException as e:
self.logs.error({'message': 'Exception when calling AppsV1Api.read_namespaced_deployment_scale', 'exception': e})
return False

def getReplicas(self, namespace:str, deploymentName:str):
'''
Returns the number of replicas of a deployment.
'''
return self.AppsV1Api.read_namespaced_deployment_scale(namespace=namespace, name=deploymentName)
try:
return self.AppsV1Api.read_namespaced_deployment_scale(namespace=namespace, name=deploymentName)
except ApiException as e:
self.logs.error({'message': 'Exception when calling AppsV1Api.read_namespaced_deployment_scale', 'exception': e})
return False

def rolloutDeployment(self, namespace:str, deploymentName:str):
def rolloutDeployment(self, namespace:str, deploymentName:str) -> bool:
'''
Execute a rollout restart deployment.
'''
deploymentManifest = self.getDeployment(namespace, deploymentName)
if not deploymentManifest:
return False

deploymentManifest.spec.template.metadata.annotations = {"kubectl.kubernetes.io/restartedAt": datetime.datetime.utcnow().replace(tzinfo=pytz.UTC).isoformat()}
self.AppsV1Api.replace_namespaced_deployment(namespace=namespace, name=deploymentName, body=deploymentManifest)
try:
self.AppsV1Api.replace_namespaced_deployment(namespace=namespace, name=deploymentName, body=deploymentManifest)
return True
except ApiException as e:
self.logs.error({'message': 'Exception when calling AppsV1Api.replace_namespaced_deployment', 'exception': e})
return False

def getIngress(self, namespace, ingressName):
response = self.ExtensionsV1beta1Api.read_namespaced_ingress(namespace=namespace, name=ingressName)
Expand Down

0 comments on commit 22d502b

Please sign in to comment.