Objects

Namespace

# Namespace = a folder that groups related resources.
# Everything Vocynex lives under "vocynex" so it stays separate from other apps.
apiVersion: v1 # core Kubernetes API group
kind: Namespace
metadata:
  name: vocynex # must be unique cluster-wide; used in kubectl -n vocynex
  labels:
    # Recommended label: marks ownership for filtering (kubectl get all -l …).
    app.kubernetes.io/part-of: vocynex

Kustomize

A template-free tool to customize application configuration that simplifies the use of off-the-shelf applications.

# Kustomize "base": portable workloads with no cloud-specific bits.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# Force every resource below into this namespace (adds metadata.namespace).
namespace: vocynex

# Attach these labels to all resources when building.
labels:
  - pairs:
      app.kubernetes.io/part-of: vocynex
    # false = don't also inject these into Service/Deployment selectors
    # (selectors must stay stable; changing them would break matching).
    includeSelectors: false

# Manifests to include. Order is mostly for readability
resources:
  - namespace.yaml
  - api-service.yaml
  - api-deployment.yaml

# Rewrite image references found in the manifests above.
# "name" matches the image string in YAML; newTag replaces :tag.
images:
  - name: vocynex-api
    newTag: latest

Deployment

# Deployment = keep N identical Pods running (here: the Go API).
# Kubernetes restarts crashed pods and can roll out new images gradually.
apiVersion: apps/v1 # apps API group (Deployments, StatefulSets, …)
kind: Deployment
metadata:
  name: vocynex-api # object name inside the namespace
  labels:
    app.kubernetes.io/name: vocynex-api # identity label (used by Service selector)
    app.kubernetes.io/component: api # role within the app
spec:
  replicas: 2 # desired number of identical pods
  # Which pods this Deployment owns (must match **template.metadata.labels**).
  selector:
    matchLabels:
      app.kubernetes.io/name: vocynex-api
  # Pod blueprint — copied once per replica.
  template:
    metadata:
      labels:
        app.kubernetes.io/name: vocynex-api
        app.kubernetes.io/component: api
    spec:
      containers:
        - name: api # container name **inside** the pod (for logs/exec)
          image: vocynex-api:latest # base image; GKE overlay rewrites registry path
          # Always = re-pull image every time a pod starts (needed with mutable :latest).
          imagePullPolicy: Always
          ports:
            - name: http # named port referenced by Service targetPort / probes
              containerPort: 8080 # port the process listens on inside the container
          # Load every key from these objects as environment variables.
          envFrom:
            - configMapRef:
                name: vocynex-api-config # non-secret settings
            - secretRef:
                name: vocynex-api-secrets # passwords/keys
          env:
            # Soft Go heap target; leave room under the 1Gi container memory limit.
            - name: GOMEMLIMIT
              value: 768MiB
          # Ready = eligible for Service traffic (dependencies must be healthy).
          readinessProbe:
            httpGet:
              path: /health?check=ready
              port: http # uses the named port above
            initialDelaySeconds: 5 # wait before first check
            periodSeconds: 10 # how often to check
            timeoutSeconds: 3 # fail the check if no response in this time
          # Startup = allow slow boot; liveness is paused until this succeeds.
          startupProbe:
            httpGet:
              path: /health?check=live
              port: http
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 24 # 24 × 5s ≈ 2 minutes max startup window
          # Live = process is up; restart the container if this keeps failing.
          # Uses "live" (not "ready") so a Redis blip doesn't kill the API.
          livenessProbe:
            httpGet:
              path: /health?check=live
              port: http
            initialDelaySeconds: 15
            periodSeconds: 20
            timeoutSeconds: 3
          # requests = scheduler guarantee; limits = hard cap (cgroup).
          # cpu: **100m = 0.1 core**; "1" = 1 full core. memory: Mi/Gi = mebibytes/gibibytes.
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: "1"
              memory: 1Gi

How readiness, liveness and startup probes differ?

Probe Question it asks If it fails Traffic
Startup Has the container finished booting? Keeps checking until success or timeout No effect on traffic yet
Readiness Can this pod handle requests right now? Removed from Service endpoints Stops receiving traffic
Liveness Is the process still alive (not deadlocked/crashed)? Container is restarted Pod may be killed regardless of readiness

How Kubernetes requests differ from limits?

Requests Limits
Role Scheduling (Kubernetes only places the pod on a node with enough available requested resources) + reserved minimum Hard maximum
CPU overage Can burst above request (up to limit) Throttled at limit
Memory overage Can use more than request (up to limit) OOM (Out Of Memory) kill at limit
If omitted Not scheduled meaningfully / best-effort No cap (can starve others)

StatefulSet / PV / PVC

Source: https://blog.nashtechglobal.com/how-to-create-pv-and-pvc-in-kubernetes/

Source: https://blog.nashtechglobal.com/how-to-create-pv-and-pvc-in-kubernetes/

PersistentVolume (PV): A cluster-wide storage resource provisioned by an administrator or dynamically generated. It captures the underlying storage technical details (e.g., AWS EBS) and exists independently of any Pod's lifecycle.

PersistentVolumeClaim (PVC): A request for storage by a user or Pod. It specifies the desired storage size, access modes, and optionally a specific StorageClass.

# StatefulSet = like Deployment, but stable pod name + **dedicated disk per pod**.
# Used for NATS JetStream so message data survives restarts.
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: vocynex-nats
  labels:
    app.kubernetes.io/name: vocynex-nats
    app.kubernetes.io/component: nats
spec:
  # Must point at the headless Service for stable DNS.
  # Pod 0 becomes **vocynex-nats-0.vocynex-nats.…**
  serviceName: vocynex-nats
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: vocynex-nats
  template:
    metadata:
      labels:
        app.kubernetes.io/name: vocynex-nats
        app.kubernetes.io/component: nats
    spec:
      containers:
        - name: nats
          image: nats:2-alpine # official NATS 2.x on Alpine
          args:
            - --config
            - /etc/nats/nats-server.conf # mounted from ConfigMap below
          ports:
            - name: client
              containerPort: 4222 # client connections (NATS protocol)
            - name: monitor
              containerPort: 8222 # HTTP monitoring /healthz
          readinessProbe:
            httpGet:
              path: /healthz
              port: monitor
            initialDelaySeconds: 3
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: monitor
            initialDelaySeconds: 10
            periodSeconds: 10
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 512Mi
          volumeMounts:
            - name: data # JetStream store (from volumeClaimTemplates)
              mountPath: /data
            - name: config # server config files
              mountPath: /etc/nats
              readOnly: true # config should not be writable by the process
      volumes:
        - name: config
          configMap:
            name: vocynex-nats-config # keys become files under /etc/nats/
  # Creates one **PersistentVolumeClaim (PVC)** per pod automatically (here: **data-vocynex-nats-0**).
  volumeClaimTemplates:
    - metadata:
        name: data # volume name inside the pod
      spec:
        accessModes: [ReadWriteOnce] # one node at a time
        resources:
          requests:
            storage: 1Gi

accessModes: [ReadWriteOnce] means the volume can be mounted read-write by only one pod at a time. A second pod trying to use the same PVC typically stays Pending or fails to mount.

There is ReadWriteMany, but that's for things like reading shared static files. If two NATS pods mounted the same /data and both ran JetStream, they'd corrupt each other's files. NATS doesn't coordinate writes through a shared filesystem.

Why StatefulSet instead of Deployment?