Initial commit
This commit is contained in:
commit
ad7f7acdca
2
.drone.yml
Normal file
2
.drone.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
kind: template
|
||||||
|
load: docker.yaml
|
5
Dockerfile
Normal file
5
Dockerfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
FROM harbor.k-space.ee/k-space/microservice-base
|
||||||
|
ADD camera-operator.py /camera-operator.py
|
||||||
|
ADD camera-deployment.yml camera-service.yml /config/
|
||||||
|
WORKDIR /config
|
||||||
|
ENTRYPOINT /camera-operator.py
|
94
camera-deployment.yml
Normal file
94
camera-deployment.yml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: foobar
|
||||||
|
labels: {}
|
||||||
|
|
||||||
|
# Make sure keel.sh pulls updates for this deployment
|
||||||
|
annotations:
|
||||||
|
keel.sh/policy: force
|
||||||
|
keel.sh/trigger: poll
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
# Make sure we do not congest the network during rollout
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 0
|
||||||
|
maxUnavailable: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: foobar
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: 'true'
|
||||||
|
prometheus.io/port: '5000'
|
||||||
|
labels:
|
||||||
|
app: foobar
|
||||||
|
component: camdetect
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: camdetect
|
||||||
|
image: harbor.k-space.ee/k-space/camera-motion-detect:latest
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "250m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1000
|
||||||
|
command:
|
||||||
|
- /app/camdetect.py
|
||||||
|
env:
|
||||||
|
- name: SOURCE_NAME
|
||||||
|
value: foobar
|
||||||
|
- name: S3_ENDPOINT_URL
|
||||||
|
value: http://minio:9000
|
||||||
|
- name: MJPEGSTREAMER_CREDENTIALS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: application-secrets
|
||||||
|
key: MJPEGSTREAMER_CREDENTIALS
|
||||||
|
- name: MONGO_URI
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mongodb-application-application
|
||||||
|
key: connectionString.standard
|
||||||
|
- name: AWS_SECRET_ACCESS_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: minio-secret
|
||||||
|
key: accesskey
|
||||||
|
- name: AWS_ACCESS_KEY_ID
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: minio-secret
|
||||||
|
key: secretkey
|
||||||
|
|
||||||
|
# Make sure 2+ pods of same camera are scheduled on different hosts
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- foobar
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
|
||||||
|
# Make sure camera deployments are spread over workers
|
||||||
|
topologySpreadConstraints:
|
||||||
|
- maxSkew: 1
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
whenUnsatisfiable: DoNotSchedule
|
||||||
|
labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: foobar
|
||||||
|
component: camdetect
|
87
camera-operator.py
Normal file
87
camera-operator.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
import yaml
|
||||||
|
from kubernetes_asyncio.client.api_client import ApiClient
|
||||||
|
from kubernetes_asyncio import client, config
|
||||||
|
from os import path
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
LABEL_MANAGED_BY = "camera-operator"
|
||||||
|
with open("camera-service.yml") as stream:
|
||||||
|
SERVICE_BODY = stream.read()
|
||||||
|
with open("camera-deployment.yml") as stream:
|
||||||
|
DEPLOYMENT_BODY = stream.read()
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
|
||||||
|
await config.load_kube_config()
|
||||||
|
async with ApiClient() as api:
|
||||||
|
|
||||||
|
v1 = client.CoreV1Api(api)
|
||||||
|
apps_api = client.AppsV1Api()
|
||||||
|
|
||||||
|
print("Listing namespaces")
|
||||||
|
ret = await v1.list_namespace()
|
||||||
|
api_instance = client.CustomObjectsApi(api)
|
||||||
|
now = str(time())
|
||||||
|
|
||||||
|
for i in ret.items:
|
||||||
|
try:
|
||||||
|
resp = await api_instance.list_namespaced_custom_object(
|
||||||
|
"k-space.ee", "v1alpha1", i.metadata.name, "cams")
|
||||||
|
except client.exceptions.ApiException:
|
||||||
|
continue
|
||||||
|
for item in resp["items"]:
|
||||||
|
target = item["spec"]["target"]
|
||||||
|
secret_ref = item["spec"].get("secretRef")
|
||||||
|
replicas = item["spec"].get("replicas")
|
||||||
|
|
||||||
|
print("Applying", target)
|
||||||
|
name = "camera-%s" % item["metadata"]["name"]
|
||||||
|
|
||||||
|
# Generate Deployment
|
||||||
|
body = yaml.safe_load(DEPLOYMENT_BODY.replace("foobar", name))
|
||||||
|
body["metadata"]["labels"] ["app.kubernetes.io/managed-by"] = LABEL_MANAGED_BY
|
||||||
|
body["metadata"]["labels"] ["modified"] = now
|
||||||
|
body["spec"]["template"]["spec"]["containers"][0]["args"] = [target]
|
||||||
|
if replicas:
|
||||||
|
body["spec"]["replicas"] = replicas
|
||||||
|
try:
|
||||||
|
await apps_api.replace_namespaced_deployment(
|
||||||
|
name = name, body = body, namespace=i.metadata.name)
|
||||||
|
print("Updated deployment %s/%s" % (i.metadata.name, name))
|
||||||
|
except client.exceptions.ApiException as e:
|
||||||
|
await apps_api.create_namespaced_deployment(
|
||||||
|
body = body, namespace=i.metadata.name)
|
||||||
|
print("Created deployment %s/%s" % (i.metadata.name, name))
|
||||||
|
|
||||||
|
# Generate Service
|
||||||
|
body = yaml.safe_load(SERVICE_BODY.replace("foobar", name))
|
||||||
|
body["metadata"]["labels"] ["app.kubernetes.io/managed-by"] = LABEL_MANAGED_BY
|
||||||
|
body["metadata"]["labels"] ["modified"] = now
|
||||||
|
try:
|
||||||
|
await v1.replace_namespaced_service(
|
||||||
|
name = name, body = body, namespace=i.metadata.name)
|
||||||
|
print("Updated service %s/%s" % (i.metadata.name, name))
|
||||||
|
except client.exceptions.ApiException as e:
|
||||||
|
await v1.create_namespaced_service(
|
||||||
|
body = body, namespace=i.metadata.name)
|
||||||
|
print("Created service %s/%s" % (i.metadata.name, name))
|
||||||
|
|
||||||
|
deployments = await apps_api.list_deployment_for_all_namespaces()
|
||||||
|
for dep in deployments.items:
|
||||||
|
if not dep.metadata.labels:
|
||||||
|
continue
|
||||||
|
if dep.metadata.labels.get("app.kubernetes.io/managed-by") != LABEL_MANAGED_BY:
|
||||||
|
continue
|
||||||
|
if dep.metadata.labels.get("modified") == now:
|
||||||
|
continue
|
||||||
|
print("Removing deployment: %s/%s" % (dep.metadata.namespace, dep.metadata.name))
|
||||||
|
await apps_api.delete_namespaced_deployment(name=dep.metadata.name, namespace=dep.metadata.namespace)
|
||||||
|
print("Done")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
loop.close()
|
||||||
|
|
15
camera-service.yml
Normal file
15
camera-service.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: foobar
|
||||||
|
labels:
|
||||||
|
component: camdetect
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: foobar
|
||||||
|
component: camdetect
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: 5000
|
67
cameras.yml
Normal file
67
cameras.yml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: workshop
|
||||||
|
spec:
|
||||||
|
target: http://workshop.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: server-room
|
||||||
|
spec:
|
||||||
|
target: http://server-room.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
replicas: 2
|
||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: printer
|
||||||
|
spec:
|
||||||
|
target: http://printer.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
replicas: 2
|
||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: chaos
|
||||||
|
spec:
|
||||||
|
target: http://chaos.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: cyber
|
||||||
|
spec:
|
||||||
|
target: http://cyber.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: kitchen
|
||||||
|
spec:
|
||||||
|
target: http://kitchen.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: back-door
|
||||||
|
spec:
|
||||||
|
target: http://back-door.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
---
|
||||||
|
apiVersion: k-space.ee/v1alpha1
|
||||||
|
kind: Camera
|
||||||
|
metadata:
|
||||||
|
name: ground-door
|
||||||
|
spec:
|
||||||
|
target: http://ground-door.cam.k-space.ee:8080/?action=stream
|
||||||
|
secretRef: camera-secrets
|
||||||
|
|
72
crd.yaml
Normal file
72
crd.yaml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: cams.k-space.ee
|
||||||
|
spec:
|
||||||
|
group: k-space.ee
|
||||||
|
names:
|
||||||
|
plural: cams
|
||||||
|
singular: cam
|
||||||
|
kind: Camera
|
||||||
|
shortNames:
|
||||||
|
- cam
|
||||||
|
scope: Namespaced
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
roi:
|
||||||
|
type: object
|
||||||
|
description: Region of interest for this camera
|
||||||
|
properties:
|
||||||
|
threshold:
|
||||||
|
type: integer
|
||||||
|
description: Percentage of pixels changed within ROI to
|
||||||
|
consider whole frame to have motion detected.
|
||||||
|
Defaults to 5.
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
description: Whether motion detection is enabled for this
|
||||||
|
camera. Defaults to false.
|
||||||
|
left:
|
||||||
|
type: integer
|
||||||
|
description: Left boundary of ROI as
|
||||||
|
percentage of the width of a frame.
|
||||||
|
By default 0.
|
||||||
|
right:
|
||||||
|
type: integer
|
||||||
|
description: Right boundary of ROI as
|
||||||
|
percentage of the width of a frame.
|
||||||
|
By default 100.
|
||||||
|
top:
|
||||||
|
type: integer
|
||||||
|
description: Top boundary of ROI as
|
||||||
|
percentage of the height of a frame
|
||||||
|
By deafault 0.
|
||||||
|
bottom:
|
||||||
|
type: integer
|
||||||
|
description: Bottom boundary of ROI as
|
||||||
|
percentage of the height of a frame.
|
||||||
|
By default 100.
|
||||||
|
secretRef:
|
||||||
|
type: string
|
||||||
|
description: Secret that contains authentication credentials
|
||||||
|
target:
|
||||||
|
type: string
|
||||||
|
description: URL of the video feed stream
|
||||||
|
replicas:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 2
|
||||||
|
description: For highly available deployment set this to 2 or
|
||||||
|
higher. Make sure you also run Mongo and Minio in HA
|
||||||
|
configurations
|
||||||
|
required: ["target"]
|
||||||
|
required: ["spec"]
|
Reference in New Issue
Block a user