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