# 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
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 = 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
| 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 |
| 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) |

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.