diff --git a/README.md b/README.md index 7942f43..a328787 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ The repository is split into three top-level areas. Within each, there is one di │ ├── minio-aistor/ # S3-compatible object storage │ ├── n8n/ # Workflow automation │ ├── netbootxyz/ # Network boot service +│ ├── netbox/ # IPAM and DCIM source of truth │ ├── ollama/ # Local LLM runner │ ├── openwebui/ # Web UI for AI models │ ├── outline/ # Knowledge base / wiki @@ -105,6 +106,7 @@ The repository is split into three top-level areas. Within each, there is one di - **Technitium**: DNS server with web interface - **Azure DDNS Python**: Dynamic DNS updater for Azure DNS - **NetbootXYZ**: Network boot service for OS installation +- **NetBox**: IPAM and DCIM source of truth for the network - **VictoriaMetrics**: Metrics collection and monitoring stack ### External Service Integration diff --git a/applications/netbox/housekeeping/cronjob.yaml b/applications/netbox/housekeeping/cronjob.yaml new file mode 100644 index 0000000..f468d15 --- /dev/null +++ b/applications/netbox/housekeeping/cronjob.yaml @@ -0,0 +1,37 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + namespace: netbox + name: netbox-housekeeping +spec: + schedule: "15 3 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 2 + template: + spec: + restartPolicy: OnFailure + securityContext: + runAsUser: 999 + runAsGroup: 0 + fsGroup: 0 + containers: + - name: netbox-housekeeping + image: netboxcommunity/netbox:v4.6.8-5.0.2 + args: + - /opt/netbox/venv/bin/python + - /opt/netbox/netbox/manage.py + - housekeeping + resources: + requests: + memory: "128Mi" + cpu: "50m" + limits: + memory: "512Mi" + cpu: "500m" + envFrom: + - secretRef: + name: netbox-app-secrets diff --git a/applications/netbox/netbox-httproute.yaml b/applications/netbox/netbox-httproute.yaml new file mode 100644 index 0000000..565027a --- /dev/null +++ b/applications/netbox/netbox-httproute.yaml @@ -0,0 +1,46 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: netbox-route + namespace: netbox +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-internal + namespace: envoy-gateway + sectionName: netbox + hostnames: + - "netbox.jsme.be" + rules: + - matches: + - path: + type: PathPrefix + value: / + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + set: + - name: X-Forwarded-Host + value: "netbox.jsme.be" + - name: X-Forwarded-Proto + value: https + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: Strict-Transport-Security + value: "max-age=31536000; includeSubDomains" + - name: X-Content-Type-Options + value: nosniff + - name: X-Frame-Options + value: SAMEORIGIN + - name: Referrer-Policy + value: strict-origin-when-cross-origin + - name: Permissions-Policy + value: "camera=(), microphone=(), geolocation=(), payment=()" + backendRefs: + - name: netbox + port: 8080 + kind: Service + group: "" + weight: 1 diff --git a/applications/netbox/netbox-namespace.yaml b/applications/netbox/netbox-namespace.yaml new file mode 100644 index 0000000..1a63822 --- /dev/null +++ b/applications/netbox/netbox-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: netbox diff --git a/applications/netbox/netbox/deployment.yaml b/applications/netbox/netbox/deployment.yaml new file mode 100644 index 0000000..b8e567f --- /dev/null +++ b/applications/netbox/netbox/deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: netbox + name: netbox +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: netbox + template: + metadata: + labels: + app: netbox + spec: + restartPolicy: Always + terminationGracePeriodSeconds: 60 + securityContext: + runAsUser: 999 + runAsGroup: 0 + fsGroup: 0 + containers: + - name: netbox + image: netboxcommunity/netbox:v4.6.8-5.0.2 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "2000m" + ports: + - containerPort: 8080 + envFrom: + - secretRef: + name: netbox-app-secrets + volumeMounts: + - name: netbox-data + mountPath: /opt/netbox/netbox/media + subPath: media + - name: netbox-data + mountPath: /opt/netbox/netbox/reports + subPath: reports + - name: netbox-data + mountPath: /opt/netbox/netbox/scripts + subPath: scripts + startupProbe: + httpGet: + path: /login/ + port: 8080 + periodSeconds: 10 + # Generous: the entrypoint runs migrations before granian binds. + failureThreshold: 60 + readinessProbe: + httpGet: + path: /login/ + port: 8080 + periodSeconds: 15 + livenessProbe: + httpGet: + path: /login/ + port: 8080 + periodSeconds: 30 + failureThreshold: 5 + volumes: + - name: netbox-data + persistentVolumeClaim: + claimName: netbox-data-pvc diff --git a/applications/netbox/netbox/infisicalsecret.yaml b/applications/netbox/netbox/infisicalsecret.yaml new file mode 100644 index 0000000..00fe115 --- /dev/null +++ b/applications/netbox/netbox/infisicalsecret.yaml @@ -0,0 +1,25 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalSecret +metadata: + name: netbox-app-secrets + namespace: infisical +spec: + hostAPI: https://infisical.jsme.be + resyncInterval: 30 + authentication: + kubernetesAuth: + identityId: "5458def7-7c63-40d2-95da-41c614d0933a" + serviceAccountRef: + name: infisical-auth + namespace: infisical + autoCreateServiceAccountToken: true + secretsScope: + projectSlug: "kubernetes" + envSlug: "prod" + secretsPath: "/netbox/netbox" + # Non-recursive: keys are already named as the container expects. + recursive: false + managedSecretReference: + secretName: netbox-app-secrets + secretNamespace: netbox + secretType: Opaque diff --git a/applications/netbox/netbox/persistentvolumeclaim.yaml b/applications/netbox/netbox/persistentvolumeclaim.yaml new file mode 100644 index 0000000..bcf82d0 --- /dev/null +++ b/applications/netbox/netbox/persistentvolumeclaim.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + namespace: netbox + name: netbox-data-pvc +spec: + # RWX: shared by the web pod and the rqworker pod. + accessModes: + - ReadWriteMany + storageClassName: longhorn-static + resources: + requests: + storage: 5Gi diff --git a/applications/netbox/netbox/service.yaml b/applications/netbox/netbox/service.yaml new file mode 100644 index 0000000..9086797 --- /dev/null +++ b/applications/netbox/netbox/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + namespace: netbox + name: netbox +spec: + selector: + app: netbox + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + type: ClusterIP diff --git a/applications/netbox/postgres/infisicalsecret.yaml b/applications/netbox/postgres/infisicalsecret.yaml new file mode 100644 index 0000000..c0afcdc --- /dev/null +++ b/applications/netbox/postgres/infisicalsecret.yaml @@ -0,0 +1,25 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalSecret +metadata: + name: netbox-postgres-secrets + namespace: infisical +spec: + hostAPI: https://infisical.jsme.be + resyncInterval: 30 + authentication: + kubernetesAuth: + identityId: "5458def7-7c63-40d2-95da-41c614d0933a" + serviceAccountRef: + name: infisical-auth + namespace: infisical + autoCreateServiceAccountToken: true + secretsScope: + projectSlug: "kubernetes" + envSlug: "prod" + secretsPath: "/netbox/postgres" + # Non-recursive: keys are already named as the container expects. + recursive: false + managedSecretReference: + secretName: netbox-postgres-secrets + secretNamespace: netbox + secretType: Opaque diff --git a/applications/netbox/postgres/persistentvolumeclaim.yaml b/applications/netbox/postgres/persistentvolumeclaim.yaml new file mode 100644 index 0000000..c1ee72f --- /dev/null +++ b/applications/netbox/postgres/persistentvolumeclaim.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + namespace: netbox + name: netbox-postgres-pvc +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn-static + resources: + requests: + storage: 10Gi diff --git a/applications/netbox/postgres/service.yaml b/applications/netbox/postgres/service.yaml new file mode 100644 index 0000000..a02b7d3 --- /dev/null +++ b/applications/netbox/postgres/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: netbox-postgres + namespace: netbox +spec: + selector: + app: netbox-postgres + type: ClusterIP + ports: + - name: netbox-postgres-port + protocol: TCP + port: 5432 + targetPort: 5432 diff --git a/applications/netbox/postgres/statefulset.yaml b/applications/netbox/postgres/statefulset.yaml new file mode 100644 index 0000000..fcb0b99 --- /dev/null +++ b/applications/netbox/postgres/statefulset.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + namespace: netbox + name: netbox-postgres +spec: + replicas: 1 + serviceName: netbox-postgres + selector: + matchLabels: + app: netbox-postgres + template: + metadata: + labels: + app: netbox-postgres + spec: + restartPolicy: Always + terminationGracePeriodSeconds: 60 + containers: + - name: netbox-postgres + image: postgres:18 + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1000m" + ports: + - containerPort: 5432 + volumeMounts: + - name: netbox-postgres-data + mountPath: /var/lib/postgresql/data + envFrom: + - secretRef: + name: netbox-postgres-secrets + readinessProbe: + exec: + command: ["sh", "-c", "pg_isready -q -U \"$POSTGRES_USER\" -d \"$POSTGRES_DB\""] + initialDelaySeconds: 10 + periodSeconds: 10 + volumes: + - name: netbox-postgres-data + persistentVolumeClaim: + claimName: netbox-postgres-pvc diff --git a/applications/netbox/redis-cache/deployment.yaml b/applications/netbox/redis-cache/deployment.yaml new file mode 100644 index 0000000..65286fb --- /dev/null +++ b/applications/netbox/redis-cache/deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: netbox + name: netbox-redis-cache +spec: + replicas: 1 + selector: + matchLabels: + app: netbox-redis-cache + template: + metadata: + labels: + app: netbox-redis-cache + spec: + restartPolicy: Always + containers: + - name: netbox-redis-cache + image: valkey/valkey:9.1-alpine + # Cache only: no persistence, contents are disposable. + command: ["sh", "-c", "exec valkey-server --requirepass \"$REDIS_PASSWORD\""] + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "256Mi" + cpu: "200m" + ports: + - containerPort: 6379 + envFrom: + - secretRef: + name: netbox-redis-cache-secrets + readinessProbe: + exec: + command: ["sh", "-c", "valkey-cli --pass \"$REDIS_PASSWORD\" ping | grep -q PONG"] + initialDelaySeconds: 5 + periodSeconds: 10 diff --git a/applications/netbox/redis-cache/infisicalsecret.yaml b/applications/netbox/redis-cache/infisicalsecret.yaml new file mode 100644 index 0000000..fdb3415 --- /dev/null +++ b/applications/netbox/redis-cache/infisicalsecret.yaml @@ -0,0 +1,25 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalSecret +metadata: + name: netbox-redis-cache-secrets + namespace: infisical +spec: + hostAPI: https://infisical.jsme.be + resyncInterval: 30 + authentication: + kubernetesAuth: + identityId: "5458def7-7c63-40d2-95da-41c614d0933a" + serviceAccountRef: + name: infisical-auth + namespace: infisical + autoCreateServiceAccountToken: true + secretsScope: + projectSlug: "kubernetes" + envSlug: "prod" + secretsPath: "/netbox/redis-cache" + # Non-recursive: keys are already named as the container expects. + recursive: false + managedSecretReference: + secretName: netbox-redis-cache-secrets + secretNamespace: netbox + secretType: Opaque diff --git a/applications/netbox/redis-cache/service.yaml b/applications/netbox/redis-cache/service.yaml new file mode 100644 index 0000000..3a0353f --- /dev/null +++ b/applications/netbox/redis-cache/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: netbox-redis-cache + namespace: netbox +spec: + selector: + app: netbox-redis-cache + type: ClusterIP + ports: + - name: netbox-redis-cache-port + protocol: TCP + port: 6379 + targetPort: 6379 diff --git a/applications/netbox/redis/infisicalsecret.yaml b/applications/netbox/redis/infisicalsecret.yaml new file mode 100644 index 0000000..42d8e2f --- /dev/null +++ b/applications/netbox/redis/infisicalsecret.yaml @@ -0,0 +1,25 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalSecret +metadata: + name: netbox-redis-secrets + namespace: infisical +spec: + hostAPI: https://infisical.jsme.be + resyncInterval: 30 + authentication: + kubernetesAuth: + identityId: "5458def7-7c63-40d2-95da-41c614d0933a" + serviceAccountRef: + name: infisical-auth + namespace: infisical + autoCreateServiceAccountToken: true + secretsScope: + projectSlug: "kubernetes" + envSlug: "prod" + secretsPath: "/netbox/redis" + # Non-recursive: keys are already named as the container expects. + recursive: false + managedSecretReference: + secretName: netbox-redis-secrets + secretNamespace: netbox + secretType: Opaque diff --git a/applications/netbox/redis/persistentvolumeclaim.yaml b/applications/netbox/redis/persistentvolumeclaim.yaml new file mode 100644 index 0000000..19c6d0f --- /dev/null +++ b/applications/netbox/redis/persistentvolumeclaim.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + namespace: netbox + name: netbox-redis-pvc +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn-static + resources: + requests: + storage: 1Gi diff --git a/applications/netbox/redis/service.yaml b/applications/netbox/redis/service.yaml new file mode 100644 index 0000000..e0439a4 --- /dev/null +++ b/applications/netbox/redis/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: netbox-redis + namespace: netbox +spec: + selector: + app: netbox-redis + type: ClusterIP + ports: + - name: netbox-redis-port + protocol: TCP + port: 6379 + targetPort: 6379 diff --git a/applications/netbox/redis/statefulset.yaml b/applications/netbox/redis/statefulset.yaml new file mode 100644 index 0000000..1e96dda --- /dev/null +++ b/applications/netbox/redis/statefulset.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + namespace: netbox + name: netbox-redis +spec: + replicas: 1 + serviceName: netbox-redis + selector: + matchLabels: + app: netbox-redis + template: + metadata: + labels: + app: netbox-redis + spec: + restartPolicy: Always + containers: + - name: netbox-redis + image: valkey/valkey:9.1-alpine + # Task queue: appendonly so queued background jobs survive a restart. + command: ["sh", "-c", "exec valkey-server --appendonly yes --requirepass \"$REDIS_PASSWORD\""] + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "256Mi" + cpu: "200m" + ports: + - containerPort: 6379 + envFrom: + - secretRef: + name: netbox-redis-secrets + volumeMounts: + - name: netbox-redis-data + mountPath: /data + readinessProbe: + exec: + command: ["sh", "-c", "valkey-cli --pass \"$REDIS_PASSWORD\" ping | grep -q PONG"] + initialDelaySeconds: 5 + periodSeconds: 10 + volumes: + - name: netbox-redis-data + persistentVolumeClaim: + claimName: netbox-redis-pvc diff --git a/applications/netbox/worker/deployment.yaml b/applications/netbox/worker/deployment.yaml new file mode 100644 index 0000000..0c50c10 --- /dev/null +++ b/applications/netbox/worker/deployment.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: netbox + name: netbox-worker +spec: + replicas: 1 + selector: + matchLabels: + app: netbox-worker + template: + metadata: + labels: + app: netbox-worker + spec: + restartPolicy: Always + terminationGracePeriodSeconds: 60 + securityContext: + runAsUser: 999 + runAsGroup: 0 + fsGroup: 0 + containers: + - name: netbox-worker + image: netboxcommunity/netbox:v4.6.8-5.0.2 + # Overrides CMD only, so tini stays the entrypoint and migrations + # are left to the web pod. + args: + - /opt/netbox/venv/bin/python + - /opt/netbox/netbox/manage.py + - rqworker + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1000m" + envFrom: + - secretRef: + name: netbox-app-secrets + volumeMounts: + - name: netbox-data + mountPath: /opt/netbox/netbox/media + subPath: media + - name: netbox-data + mountPath: /opt/netbox/netbox/reports + subPath: reports + - name: netbox-data + mountPath: /opt/netbox/netbox/scripts + subPath: scripts + volumes: + - name: netbox-data + persistentVolumeClaim: + claimName: netbox-data-pvc diff --git a/infra/argocd/applications/netbox-application.yaml b/infra/argocd/applications/netbox-application.yaml new file mode 100644 index 0000000..19c7041 --- /dev/null +++ b/infra/argocd/applications/netbox-application.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: netbox + namespace: argocd +spec: + project: default + source: + repoURL: https://gitea.jsme.be/Jeffrey/Kubernetes-Manifests.git + path: applications/netbox + targetRevision: HEAD + directory: + recurse: true + destination: + server: https://kubernetes.default.svc + namespace: netbox + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - ServerSideApply=true + - PrunePropagationPolicy=foreground diff --git a/infra/envoy-gateway/kustomization.yaml b/infra/envoy-gateway/kustomization.yaml index f26816c..24ff5ae 100644 --- a/infra/envoy-gateway/kustomization.yaml +++ b/infra/envoy-gateway/kustomization.yaml @@ -60,8 +60,7 @@ resources: - pelican/pelican-wings-mc-certificate.yaml - pelican/pelican-wings-ark-certificate.yaml - outline/outline-certificate.yaml - # netbox: uncomment together with the patch below once infra/envoy-gateway/netbox/ is committed - # - netbox/netbox-certificate.yaml + - netbox/netbox-certificate.yaml patches: # Internal gateway patches @@ -293,12 +292,12 @@ patches: kind: Gateway name: gateway-internal path: outline/outline-gateway-patch.yaml - # - target: - # group: gateway.networking.k8s.io - # version: v1 - # kind: Gateway - # name: gateway-internal - # path: netbox/netbox-gateway-patch.yaml + - target: + group: gateway.networking.k8s.io + version: v1 + kind: Gateway + name: gateway-internal + path: netbox/netbox-gateway-patch.yaml # Public gateway patches - target: diff --git a/infra/envoy-gateway/netbox/netbox-certificate.yaml b/infra/envoy-gateway/netbox/netbox-certificate.yaml new file mode 100644 index 0000000..7425039 --- /dev/null +++ b/infra/envoy-gateway/netbox/netbox-certificate.yaml @@ -0,0 +1,14 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: netbox-tls + namespace: envoy-gateway +spec: + secretName: netbox-tls + privateKey: + rotationPolicy: Always + issuerRef: + name: azure-dns + kind: ClusterIssuer + dnsNames: + - netbox.jsme.be diff --git a/infra/envoy-gateway/netbox/netbox-gateway-patch.yaml b/infra/envoy-gateway/netbox/netbox-gateway-patch.yaml new file mode 100644 index 0000000..7b3945f --- /dev/null +++ b/infra/envoy-gateway/netbox/netbox-gateway-patch.yaml @@ -0,0 +1,19 @@ +- op: add + path: /spec/listeners/- + value: + name: netbox + protocol: HTTPS + port: 443 + hostname: "netbox.jsme.be" + allowedRoutes: + namespaces: + from: Selector + selector: + matchLabels: + kubernetes.io/metadata.name: netbox + tls: + mode: Terminate + certificateRefs: + - kind: Secret + name: netbox-tls + namespace: envoy-gateway