Skip to content

Kubernetes ​

HeliosLogs runs well on Kubernetes as a single-node StatefulSet. A single node owns its data and control plane on a persistent volume, so this is a replicas: 1 workload — see Scaling & HA before you try to scale it.

The manifest below is self-contained: a namespace, an admin-bootstrap Secret, a Service, and a StatefulSet with two PVCs (data + secrets). It works on any conformant cluster using the default StorageClass.

The manifest ​

Save as helioslogs.yaml and kubectl apply -f helioslogs.yaml.

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: helioslogs
---
apiVersion: v1
kind: Secret
metadata:
  name: helioslogs-admin
  namespace: helioslogs
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
  name: helioslogs
  namespace: helioslogs
  labels:
    app: helioslogs
spec:
  selector:
    app: helioslogs
  ports:
    - name: http
      port: 7300
      targetPort: http
    - name: syslog-udp
      port: 5514
      targetPort: syslog-udp
      protocol: UDP
    - name: syslog-tcp
      port: 5514
      targetPort: syslog-tcp
      protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: helioslogs
  namespace: helioslogs
  labels:
    app: helioslogs
spec:
  serviceName: helioslogs
  replicas: 1                       # single-node control plane — see Scaling & HA
  selector:
    matchLabels:
      app: helioslogs
  template:
    metadata:
      labels:
        app: helioslogs
    spec:
      securityContext:
        # The image runs as the nonroot user 65532. fsGroup makes the mounted PVCs
        # group-owned and group-writable so that user can write to them.
        runAsNonRoot: true
        runAsUser: 65532
        runAsGroup: 65532
        fsGroup: 65532
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: helioslogs
          image: helioslogs/helioslogs:0.2.2     # pin a version (':latest' also exists)
          args: ["serve", "--host", "0.0.0.0", "--port", "7300", "--data-dir", "/app/data"]
          ports:
            - name: http
              containerPort: 7300
            - name: syslog-udp
              containerPort: 5514
              protocol: UDP
            - name: syslog-tcp
              containerPort: 5514
              protocol: TCP
          envFrom:
            - secretRef:
                name: helioslogs-admin
          volumeMounts:
            - name: data
              mountPath: /app/data
            - name: secret
              mountPath: /app/secret
          readinessProbe:
            httpGet:
              path: /api/health
              port: http
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /api/health
              port: http
            initialDelaySeconds: 15
            periodSeconds: 20
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 2Gi
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 20Gi          # size for your retention — tune this
        # storageClassName: fast   # omit to use the cluster default
    - metadata:
        name: secret
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 64Mi          # just the encryption + JWT keys

Apply and access ​

bash
kubectl apply -f helioslogs.yaml
kubectl -n helioslogs rollout status statefulset/helioslogs

# Reach the UI without an Ingress:
kubectl -n helioslogs port-forward svc/helioslogs 7300:7300

Open http://localhost:7300 and log in with the credentials from the Secret (or complete the setup wizard if you removed it). Next: First steps.

Change the admin password

The Secret above ships a placeholder password. Set a real one before exposing the instance, and consider managing the Secret with a sealed-secrets / external-secrets operator rather than committing it to git.

Expose with an Ingress ​

Point an Ingress at the helioslogs Service on port 7300. Adjust ingressClassName, the host, and TLS to match your cluster:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: helioslogs
  namespace: helioslogs
  # annotations:
  #   cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["logs.example.com"]
      secretName: helioslogs-tls
  rules:
    - host: logs.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: helioslogs
                port:
                  number: 7300

Terminate TLS at the Ingress and read Security hardening before exposing HeliosLogs publicly.

Receiving syslog ​

HeliosLogs also listens for raw syslog (UDP + TCP, RFC 5424 / 3164) on port 5514. The manifest publishes that port on the container and Service, but the listener is off by default — enable it under Admin → Data Ingestion → Syslog (or set HELIOS_SYSLOG_PORT). It has no token auth, so restrict it to trusted senders. See Syslog.

In-cluster senders can target helioslogs.helioslogs.svc:5514. Devices outside the cluster need a LoadBalancer or NodePort Service — an Ingress can't carry raw syslog, only HTTP:

yaml
apiVersion: v1
kind: Service
metadata:
  name: helioslogs-syslog
  namespace: helioslogs
spec:
  type: LoadBalancer
  selector:
    app: helioslogs
  ports:
    - name: syslog-udp
      port: 514                 # standard syslog port → container's 5514
      targetPort: syslog-udp
      protocol: UDP
    - name: syslog-tcp
      port: 514
      targetPort: syslog-tcp
      protocol: TCP

Mixed UDP + TCP on a LoadBalancer

Exposing UDP and TCP on a single LoadBalancer needs Kubernetes ≥ 1.26 (or the MixedProtocolLBService feature gate). If your platform's load balancer can't mix protocols, split them into two Services.

Hardening (optional) ​

For a read-only root filesystem, mount an emptyDir at /tmp and set the flag — all durable writes already go to the two PVCs:

yaml
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            # ... data and secret mounts ...
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
      volumes:
        - name: tmp
          emptyDir: {}

Scaling & HA ​

replicas: 1 is deliberate. A single node keeps its control plane and a block cache on its own ReadWriteOnce volume; bumping replicas would give each pod a separate PVC and a separate control plane — not a cluster.

For high availability across nodes, HeliosLogs uses a shared S3-compatible object store (--shared-store) instead of per-pod local state, with the same secret keys mounted on every pod (use a Kubernetes Secret for /app/secret, not a per-pod PVC). See Multi-node & shared store and Secrets & encryption.

Upgrade ​

Bump the image tag and let the StatefulSet roll the pod (it terminates the old pod before starting the new one, so the ReadWriteOnce volumes hand off cleanly):

bash
kubectl -n helioslogs set image statefulset/helioslogs \
  helioslogs=helioslogs/helioslogs:0.2.3
kubectl -n helioslogs rollout status statefulset/helioslogs

See Upgrades & backups for version policy and backing up the data + secret volumes.