Skip to content

Commit

Permalink
init code
Browse files Browse the repository at this point in the history
  • Loading branch information
voxmaster committed May 7, 2022
1 parent 8e98372 commit 09c0566
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git
.gitlab-ci.yml
.vscode
37 changes: 37 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
stages:
- main-build

variables:
GIT_DEPTH: "1"
CONTAINER_REGISTRY: voxsoft/kube-synchro-scale

Build:
stage: main-build
image: ezkrg/buildx:latest
tags:
- docker
services:
- docker:dind
script:
- docker login -u $CONTAINER_REGISTRY_USER -p $CONTAINER_REGISTRY_PASSWORD
- docker buildx create --name mybuilder --use
- docker buildx build --platform linux/arm/v7,linux/arm64/v8,linux/amd64 --push
-t ${CONTAINER_REGISTRY}:${CI_COMMIT_BRANCH} .
except:
- tags

Release:
stage: main-build
image: ezkrg/buildx:latest
tags:
- docker
services:
- docker:dind
script:
- docker login -u $CONTAINER_REGISTRY_USER -p $CONTAINER_REGISTRY_PASSWORD
- docker buildx create --name mybuilder --use
- docker buildx build --platform linux/arm/v7,linux/arm64/v8,linux/amd64 --push
-t ${CONTAINER_REGISTRY}:${CI_COMMIT_TAG}
-t ${CONTAINER_REGISTRY}:latest .
only:
- tags
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.9-slim
LABEL maintainer="Oleksii Marchenko <[email protected]>"

WORKDIR /usr/src/app
COPY . .
RUN pip3 install -r requirements.txt
CMD python3 main.py
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# kube-synchro-scale
Publish metrics that allows you to scale 2 Kubernetes Deployments almost synchronously using HPA

## How does it works
HTTP server gives you an `synchro_scale_coefficient` metric on `/metrics` URL path which represent next formula: `Source Deploy Replicas / Target Deploy Replicas`

## Configuration
Use environment variables to configure the application

| Environment variable name | Description | Default value |
|:--------------------------|:------------------------|:---------------|
|`METRIC_PORT` | Metrics port | `9102` |
|`METRIC_PATH` | Metrics URL path | `/metrics` |
|`DEPLOY_SOURCE_NAME` |Deployment name to be in sync with | |
|`DEPLOY_SOURCE_NAMESPACE` |Deployment namespace to be in sync with| |
|`DEPLOY_TARGET_NAME` |Deployment name to be synced | |
|`DEPLOY_TARGET_NAMESPACE` |Deployment namespace to be synced | |

## Required permissions
- List and Get Deployments

## Example usage
Minimal setup (Permissions and prometheus configuration not included)
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: targetapp
spec:
selector:
matchLabels:
app: targetapp
template:
metadata:
labels:
app: targetapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: /metrics
prometheus.io/port: '9102'
spec:
containers:
- name: kube-synchro-scale
image: voxsoft/kube-synchro-scale:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 9102
protocol: TCP
env:
- name: DEPLOY_SOURCE_NAME
value: sourceapp
- name: DEPLOY_SOURCE_NAMESPACE
value: default
- name: DEPLOY_TARGET_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: DEPLOY_TARGET_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace

---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: scale-targetapp-based-on-sourceapp-replicas
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: targetapp
minReplicas: 1
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: synchro_scale_coefficient
target:
type: AverageValue
averageValue: 1000m
```
Other examples [./examples](./examples)
111 changes: 111 additions & 0 deletions examples/complex.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube-synchro-scale
namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kube-synchro-scale
namespace: default
rules:
- apiGroups: [ "apps" ]
resources: [ "deployments" ]
verbs: ["list", "get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kube-synchro-scale
namespace: default
subjects:
- kind: ServiceAccount
name: kube-synchro-scale
namespace: default
roleRef:
kind: Role
name: kube-synchro-scale
apiGroup: rbac.authorization.k8s.io

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: targetapp
namespace: default
spec:
selector:
matchLabels:
app: targetapp
template:
metadata:
labels:
app: targetapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: /metrics
prometheus.io/port: '9102'
spec:
automountServiceAccountToken: true
serviceAccountName: kube-synchro-scale
containers:
# - Your other containers
- name: kube-synchro-scale
image: voxsoft/kube-synchro-scale:develop
imagePullPolicy: Always
resources: {}
ports:
- containerPort: 9102
protocol: TCP
env:
- name: DEPLOY_SOURCE_NAME
value: sourceapp
- name: DEPLOY_SOURCE_NAMESPACE
value: default
- name: DEPLOY_TARGET_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: DEPLOY_TARGET_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace

---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: scale-targetapp-based-on-sourceapp-replicas
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: targetapp
minReplicas: 1
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: synchro_scale_coefficient
target:
type: AverageValue
averageValue: 1000m
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 15
35 changes: 35 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from kubernetes import client, config, watch
from http.server import BaseHTTPRequestHandler, HTTPServer
from os import environ

metric_port = environ.get('METRIC_PORT', '9102')
metric_path = environ.get('METRIC_PATH', '/metrics')
deploy_source_name = environ.get('DEPLOY_SOURCE_NAME')
deploy_source_namespace = environ.get('DEPLOY_SOURCE_NAMESPACE')
deploy_target_name = environ.get('DEPLOY_TARGET_NAME')
deploy_target_namespace = environ.get('DEPLOY_TARGET_NAMESPACE')

def metric_value():
try:
config.load_incluster_config()
except:
print("ERROR: Incluster config load failed. Loading local kube config. If you see this in cluster check Pod permissions")
config.load_kube_config()
v1 = client.AppsV1Api()
deploy_source = v1.list_namespaced_deployment(deploy_source_namespace, field_selector = f"metadata.name={deploy_source_name}")
deploy_target = v1.list_namespaced_deployment(deploy_target_namespace, field_selector = f"metadata.name={deploy_target_name}")
return deploy_source.items[0].status.replicas / deploy_target.items[0].status.replicas

class handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == metric_path:
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
message = "synchro_scale_coefficient" + " " + str(metric_value())
self.wfile.write(bytes(message, "utf8"))
else:
self.send_response(404)

with HTTPServer(('', int(metric_port)), handler) as server:
server.serve_forever()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kubernetes

0 comments on commit 09c0566

Please sign in to comment.