diff --git a/k8s/faq-system-deployment.yaml b/k8s/faq-system-deployment.yaml new file mode 100644 index 00000000..8405f686 --- /dev/null +++ b/k8s/faq-system-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: teachlink-faq-system + namespace: production + labels: + app: teachlink + component: faq-system +spec: + replicas: 2 + selector: + matchLabels: + app: teachlink + component: faq-system + template: + metadata: + labels: + app: teachlink + component: faq-system + spec: + containers: + - name: faq-system + image: rinafcode/teachlink-faq-system:latest + imagePullPolicy: Always + ports: + - containerPort: 3000 + resources: + requests: + cpu: 150m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + env: + - name: NODE_ENV + value: 'production' + - name: PORT + value: '3000' + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 15 + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 30 diff --git a/k8s/faq-system-vpa.test.ts b/k8s/faq-system-vpa.test.ts new file mode 100644 index 00000000..868532c1 --- /dev/null +++ b/k8s/faq-system-vpa.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const deploymentManifest = readFileSync( + join(process.cwd(), 'k8s/faq-system-deployment.yaml'), + 'utf8', +); +const vpaManifest = readFileSync(join(process.cwd(), 'k8s/faq-system-vpa.yaml'), 'utf8'); + +describe('faq-system deployment manifest', () => { + it('declares deployment metadata and namespace correctly', () => { + expect(deploymentManifest).toContain('name: teachlink-faq-system'); + expect(deploymentManifest).toContain('namespace: production'); + expect(deploymentManifest).toContain('component: faq-system'); + }); + + it('configures container image and port', () => { + expect(deploymentManifest).toContain('image: rinafcode/teachlink-faq-system:latest'); + expect(deploymentManifest).toContain('imagePullPolicy: Always'); + expect(deploymentManifest).toContain('containerPort: 3000'); + }); + + it('sets production environment variables', () => { + expect(deploymentManifest).toContain('name: NODE_ENV'); + expect(deploymentManifest).toContain("value: 'production'"); + expect(deploymentManifest).toContain('name: PORT'); + expect(deploymentManifest).toContain("value: '3000'"); + }); + + it('defines initial resource requests and limits compatible with VPA', () => { + expect(deploymentManifest).toContain('cpu: 150m'); + expect(deploymentManifest).toContain('memory: 256Mi'); + expect(deploymentManifest).toContain('cpu: 500m'); + expect(deploymentManifest).toContain('memory: 512Mi'); + }); + + it('includes readiness and liveness probes', () => { + expect(deploymentManifest).toContain('readinessProbe'); + expect(deploymentManifest).toContain('livenessProbe'); + expect(deploymentManifest).toContain('path: /api/health'); + }); +}); + +describe('faq-system VPA manifest', () => { + it('uses the VPA API version and kind', () => { + expect(vpaManifest).toContain('apiVersion: autoscaling.k8s.io/v1'); + expect(vpaManifest).toContain('kind: VerticalPodAutoscaler'); + }); + + it('declares VPA name and namespace', () => { + expect(vpaManifest).toContain('name: teachlink-faq-system-vpa'); + expect(vpaManifest).toContain('namespace: production'); + expect(vpaManifest).toContain('component: faq-system'); + }); + + it('targets the faq-system deployment', () => { + expect(vpaManifest).toContain('apiVersion: apps/v1'); + expect(vpaManifest).toContain('kind: Deployment'); + expect(vpaManifest).toContain('name: teachlink-faq-system'); + }); + + it('sets update mode to Auto', () => { + expect(vpaManifest).toContain('updateMode: "Auto"'); + }); + + it('specifies container resource policy for faq-system container', () => { + expect(vpaManifest).toContain('containerName: faq-system'); + expect(vpaManifest).toContain('controlledResources'); + expect(vpaManifest).toContain('- cpu'); + expect(vpaManifest).toContain('- memory'); + }); + + it('enforces minimum resource allowances', () => { + expect(vpaManifest).toContain('minAllowed'); + expect(vpaManifest).toContain('cpu: 100m'); + expect(vpaManifest).toContain('memory: 128Mi'); + }); + + it('enforces maximum resource allowances', () => { + expect(vpaManifest).toContain('maxAllowed'); + expect(vpaManifest).toContain('cpu: 2000m'); + expect(vpaManifest).toContain('memory: 2Gi'); + }); + + it('controls both requests and limits', () => { + expect(vpaManifest).toContain('controlledValues: RequestsAndLimits'); + }); +}); diff --git a/k8s/faq-system-vpa.yaml b/k8s/faq-system-vpa.yaml new file mode 100644 index 00000000..b3d5da80 --- /dev/null +++ b/k8s/faq-system-vpa.yaml @@ -0,0 +1,28 @@ +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + name: teachlink-faq-system-vpa + namespace: production + labels: + app: teachlink + component: faq-system +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: teachlink-faq-system + updatePolicy: + updateMode: "Auto" + resourcePolicy: + containerPolicies: + - containerName: faq-system + minAllowed: + cpu: 100m + memory: 128Mi + maxAllowed: + cpu: 2000m + memory: 2Gi + controlledResources: + - cpu + - memory + controlledValues: RequestsAndLimits