--- apiVersion: apps/v1 kind: Deployment metadata: name: camera-tiler annotations: keel.sh/policy: force keel.sh/trigger: poll spec: revisionHistoryLimit: 0 replicas: 2 selector: matchLabels: &selectorLabels app.kubernetes.io/name: camtiler component: camera-tiler template: metadata: labels: *selectorLabels spec: serviceAccountName: camera-tiler containers: - name: camera-tiler image: harbor.k-space.ee/k-space/camera-tiler:latest securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 ports: - containerPort: 5001 name: "http" resources: requests: memory: "200Mi" cpu: "100m" limits: memory: "500Mi" cpu: "1" --- apiVersion: apps/v1 kind: Deployment metadata: name: log-viewer-frontend annotations: keel.sh/policy: force keel.sh/trigger: poll spec: revisionHistoryLimit: 0 replicas: 2 selector: matchLabels: &selectorLabels app.kubernetes.io/name: camtiler component: log-viewer-frontend template: metadata: labels: *selectorLabels spec: containers: - name: log-viewer-frontend image: harbor.k-space.ee/k-space/log-viewer-frontend:latest securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 resources: limits: memory: 50Mi requests: cpu: 1m memory: 20Mi volumeMounts: - name : nginx-cache mountPath: /var/cache/nginx/ - name : nginx-config mountPath: /var/config/nginx/ - name: var-run mountPath: /var/run/ volumes: - emptyDir: {} name: nginx-cache - emptyDir: {} name: nginx-config - emptyDir: {} name: var-run --- apiVersion: apps/v1 kind: Deployment metadata: name: log-viewer-backend annotations: keel.sh/policy: force keel.sh/trigger: poll spec: revisionHistoryLimit: 0 replicas: 2 selector: matchLabels: &selectorLabels app.kubernetes.io/name: camtiler component: log-viewer-backend template: metadata: labels: *selectorLabels spec: containers: - name: log-backend-backend image: harbor.k-space.ee/k-space/log-viewer:latest securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 env: - name: MONGO_URI valueFrom: secretKeyRef: name: mongodb-application-readwrite key: connectionString.standard - name: MINIO_BUCKET value: application - name: MINIO_HOSTNAME value: cams-s3.k-space.ee - name: MINIO_PORT value: "443" - name: MINIO_SCHEME value: "https" - name: MINIO_SECRET_KEY valueFrom: secretKeyRef: name: minio-secrets key: MINIO_ROOT_PASSWORD - name: MINIO_ACCESS_KEY valueFrom: secretKeyRef: name: minio-secrets key: MINIO_ROOT_USER --- apiVersion: v1 kind: Service metadata: name: log-viewer-frontend spec: type: ClusterIP selector: app.kubernetes.io/name: camtiler component: log-viewer-frontend ports: - protocol: TCP port: 3003 --- apiVersion: v1 kind: Service metadata: name: log-viewer-backend spec: type: ClusterIP selector: app.kubernetes.io/name: camtiler component: log-viewer-backend ports: - protocol: TCP port: 3002 --- apiVersion: v1 kind: Service metadata: name: camera-tiler labels: app.kubernetes.io/name: camtiler component: camera-tiler spec: type: ClusterIP selector: app.kubernetes.io/name: camtiler component: camera-tiler ports: - protocol: TCP port: 5001 --- apiVersion: v1 kind: ServiceAccount metadata: name: camera-tiler --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: camera-tiler rules: - apiGroups: - "" resources: - services verbs: - list --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: camera-tiler subjects: - kind: ServiceAccount name: camera-tiler apiGroup: "" roleRef: kind: Role name: camera-tiler apiGroup: "" --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: camtiler annotations: kubernetes.io/ingress.class: traefik # This tells Traefik this Ingress object is associated with the # https:// entrypoint # Global http:// to https:// redirect is enabled in # ../traefik/values.yml using `globalArguments` traefik.ingress.kubernetes.io/router.entrypoints: websecure # Following enables Authelia intercepting middleware # which makes sure user is authenticated and then # proceeds to inject Remote-User header for the application traefik.ingress.kubernetes.io/router.middlewares: traefik-sso@kubernetescrd traefik.ingress.kubernetes.io/router.tls: "true" # Following tells external-dns to add CNAME entry which makes # cams.k-space.ee point to same IP address as traefik.k-space.ee # The A record for traefik.k-space.ee is created via annotation # added in ../traefik/ingress.yml external-dns.alpha.kubernetes.io/target: traefik.k-space.ee spec: rules: - host: cams.k-space.ee http: paths: - pathType: Prefix path: "/tiled" backend: service: name: camera-tiler port: number: 5001 - pathType: Prefix path: "/events" backend: service: name: log-viewer-backend port: number: 3002 - pathType: Prefix path: "/" backend: service: name: log-viewer-frontend port: number: 3003 tls: - hosts: - "*.k-space.ee" --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: camera-motion-detect spec: podSelector: matchLabels: component: camera-motion-detect policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app.kubernetes.io/name: camtiler component: camera-tiler - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: prometheus-operator podSelector: matchLabels: app.kubernetes.io/name: prometheus egress: - to: - ipBlock: # Permit access to cameras outside the cluster cidr: 100.102.0.0/16 - to: - podSelector: matchLabels: app: mongodb-svc ports: - port: 27017 - to: - podSelector: matchLabels: app.kubernetes.io/name: minio ports: - port: 9000 --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: camera-tiler spec: podSelector: matchLabels: app.kubernetes.io/name: camtiler component: camera-tiler policyTypes: - Ingress - Egress egress: - to: - podSelector: matchLabels: component: camera-motion-detect ports: - port: 5000 ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: prometheus-operator podSelector: matchLabels: app.kubernetes.io/name: prometheus - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: traefik podSelector: matchLabels: app.kubernetes.io/name: traefik --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: log-viewer-backend spec: podSelector: matchLabels: app.kubernetes.io/name: camtiler component: log-viewer-backend policyTypes: - Ingress - Egress egress: - to: - podSelector: matchLabels: app: mongodb-svc - to: # Minio access via Traefik's public endpoint - namespaceSelector: matchLabels: kubernetes.io/metadata.name: traefik podSelector: matchLabels: app.kubernetes.io/name: traefik ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: traefik podSelector: matchLabels: app.kubernetes.io/name: traefik --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: log-viewer-frontend spec: podSelector: matchLabels: app.kubernetes.io/name: camtiler component: log-viewer-frontend policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: traefik podSelector: matchLabels: app.kubernetes.io/name: traefik --- 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"] --- apiVersion: codemowers.io/v1alpha1 kind: ClusterOperator metadata: name: camera spec: resource: group: k-space.ee version: v1alpha1 plural: cams secret: enabled: false services: - apiVersion: v1 kind: Service metadata: name: foobar labels: app.kubernetes.io/name: foobar component: camera-motion-detect spec: type: ClusterIP selector: app.kubernetes.io/name: foobar component: camera-motion-detect ports: - protocol: TCP port: 80 targetPort: 5000 deployments: - apiVersion: apps/v1 kind: Deployment metadata: name: camera-foobar # 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.kubernetes.io/name: foobar template: metadata: labels: app.kubernetes.io/name: foobar component: camera-motion-detect spec: containers: - name: camera-motion-detect image: harbor.k-space.ee/k-space/camera-motion-detect:latest starupProbe: httpGet: path: /healthz port: 5000 initialDelaySeconds: 2 periodSeconds: 180 timeoutSeconds: 60 readinessProbe: httpGet: path: /readyz port: 5000 initialDelaySeconds: 60 periodSeconds: 60 timeoutSeconds: 5 ports: - containerPort: 5000 name: "http" resources: requests: memory: "64Mi" cpu: "200m" limits: memory: "256Mi" cpu: "1" securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 command: - /app/camdetect.py - http://user@foobar.cam.k-space.ee:8080/?action=stream env: - name: SOURCE_NAME value: foobar - name: S3_BUCKET_NAME value: application - name: S3_ENDPOINT_URL value: http://minio - name: BASIC_AUTH_PASSWORD valueFrom: secretKeyRef: name: camera-secrets key: password - name: MONGO_URI valueFrom: secretKeyRef: name: mongodb-application-readwrite key: connectionString.standard - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: minio-secrets key: MINIO_ROOT_PASSWORD - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: minio-secrets key: MINIO_ROOT_USER # 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.kubernetes.io/name: foobar component: camera-motion-detect --- apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: name: camtiler spec: selector: {} podMetricsEndpoints: - port: http podTargetLabels: - app.kubernetes.io/name - component --- apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: cameras spec: groups: - name: cameras rules: - alert: CameraLost expr: rate(camtiler_frames_total{stage="downloaded"}[1m]) < 1 for: 2m labels: severity: warning annotations: summary: Camera feed stopped - alert: CameraServerRoomMotion expr: rate(camtiler_events_total{app_kubernetes_io_name="server-room"}[30m]) > 0 for: 1m labels: severity: warning annotations: summary: Motion was detected in server room - alert: CameraSlowUploads expr: camtiler_queue_frames{stage="upload"} > 10 for: 5m labels: severity: warning annotations: summary: Motion detect snapshots are piling up and not getting uploaded to S3 - alert: CameraSlowProcessing expr: camtiler_queue_frames{stage="download"} > 10 for: 5m labels: severity: warning annotations: summary: Motion detection processing pipeline is not keeping up with incoming frames --- apiVersion: k-space.ee/v1alpha1 kind: Camera metadata: name: workshop spec: target: http://user@workshop.cam.k-space.ee:8080/?action=stream secretRef: camera-secrets replicas: 2 --- apiVersion: k-space.ee/v1alpha1 kind: Camera metadata: name: server-room spec: target: http://user@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://user@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://user@chaos.cam.k-space.ee:8080/?action=stream secretRef: camera-secrets replicas: 2 --- apiVersion: k-space.ee/v1alpha1 kind: Camera metadata: name: cyber spec: target: http://user@cyber.cam.k-space.ee:8080/?action=stream secretRef: camera-secrets replicas: 2 --- apiVersion: k-space.ee/v1alpha1 kind: Camera metadata: name: kitchen spec: target: http://user@kitchen.cam.k-space.ee:8080/?action=stream secretRef: camera-secrets replicas: 2 --- apiVersion: k-space.ee/v1alpha1 kind: Camera metadata: name: back-door spec: target: http://user@back-door.cam.k-space.ee:8080/?action=stream secretRef: camera-secrets replicas: 2 --- apiVersion: k-space.ee/v1alpha1 kind: Camera metadata: name: ground-door spec: target: http://user@ground-door.cam.k-space.ee:8080/?action=stream secretRef: camera-secrets replicas: 2