From a2a27e176242d8f60242de123a027a55941bfc43 Mon Sep 17 00:00:00 2001 From: Darren Maverick Date: Sun, 19 Jul 2026 08:36:56 +0000 Subject: [PATCH] migrate gitops-demo to generic chart --- apps/gitops-demo.yaml | 33 +++++++++----- charts/app/app/Chart.yaml | 11 +++++ charts/app/app/templates/_helpers.tpl | 24 ++++++++++ charts/app/app/templates/deployment.yaml | 40 +++++++++++++++++ charts/app/app/templates/ingress.yaml | 32 ++++++++++++++ charts/app/app/templates/service.yaml | 12 +++++ charts/app/app/values.yaml | 56 ++++++++++++++++++++++++ gitops-demo/dev/deployment.yaml | 23 ---------- gitops-demo/dev/ingress.yaml | 27 ------------ gitops-demo/dev/service.yaml | 10 ----- gitops-demo/dev/values.yaml | 15 +++++++ 11 files changed, 212 insertions(+), 71 deletions(-) create mode 100644 charts/app/app/Chart.yaml create mode 100644 charts/app/app/templates/_helpers.tpl create mode 100644 charts/app/app/templates/deployment.yaml create mode 100644 charts/app/app/templates/ingress.yaml create mode 100644 charts/app/app/templates/service.yaml create mode 100644 charts/app/app/values.yaml delete mode 100644 gitops-demo/dev/deployment.yaml delete mode 100644 gitops-demo/dev/ingress.yaml delete mode 100644 gitops-demo/dev/service.yaml create mode 100644 gitops-demo/dev/values.yaml diff --git a/apps/gitops-demo.yaml b/apps/gitops-demo.yaml index 2ed4c7d..08abefb 100644 --- a/apps/gitops-demo.yaml +++ b/apps/gitops-demo.yaml @@ -1,10 +1,15 @@ -# A child Application in the app-of-apps tree. ArgoCD's root app finds this file -# and creates this Application, which in turn syncs gitops-demo/dev into the -# cluster. To add another service, copy this file and change name + path. +# gitops-demo child Application — now Helm-based, using the shared chart. # -# NOTE: replace REPLACE_WITH_MANIFEST_REPO_URL with this repo's real HTTPS URL -# (see README) — child Applications can't be templated by our shell scripts -# because ArgoCD reads them straight from git. +# MULTI-SOURCE explained: ArgoCD lets an Application pull from more than one +# source. We use two, BOTH pointing at this same manifest repo: +# 1. ref: values — exposes the repo as "$values" so we can reference a file +# from it (the per-service values.yaml). +# 2. path: charts/app — the generic chart, rendered with that values file. +# This keeps the chart shared and each service's values in its own clean file, +# instead of duplicating manifests or inlining values here. +# +# NOTE: replace REPLACE_WITH_MANIFEST_REPO_URL in BOTH sources with the real +# repo URL (same as before — ArgoCD reads these straight from git). apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -12,13 +17,19 @@ metadata: namespace: argocd spec: project: default - source: - repoURL: https://gitea.lgkentang.com/darren.maverick/k3s-manifests.git - targetRevision: HEAD - path: gitops-demo/dev + sources: + - repoURL: https://gitea.lgkentang.com/darren.maverick/k3s-manifests.git + targetRevision: HEAD + ref: values + - repoURL: https://gitea.lgkentang.com/darren.maverick/k3s-manifests.git + targetRevision: HEAD + path: charts/app + helm: + valueFiles: + - $values/gitops-demo/dev/values.yaml destination: server: https://kubernetes.default.svc - namespace: dev # per-environment namespace convention + namespace: dev syncPolicy: automated: prune: true diff --git a/charts/app/app/Chart.yaml b/charts/app/app/Chart.yaml new file mode 100644 index 0000000..5024e55 --- /dev/null +++ b/charts/app/app/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: app +description: >- + Generic application chart for lgkentang services. Renders a Deployment + + Service + Ingress (with cert-manager TLS) from a small values.yaml, so a new + service is a handful of values instead of copy-pasted manifests. +type: application +# Chart version: bump when the TEMPLATES change (the chart's own releases). +version: 0.1.0 +# The default app version; individual services override via image.tag. +appVersion: "1.0.0" diff --git a/charts/app/app/templates/_helpers.tpl b/charts/app/app/templates/_helpers.tpl new file mode 100644 index 0000000..a483afc --- /dev/null +++ b/charts/app/app/templates/_helpers.tpl @@ -0,0 +1,24 @@ +{{/* +Resource name: explicit .Values.name wins, else the release name. +Keeping this in one helper means every template names things identically. +*/}} +{{- define "app.name" -}} +{{- default .Release.Name .Values.name -}} +{{- end -}} + +{{/* +Common labels stamped on every object, so `kubectl get all -l ...` and ArgoCD +can group a service's resources together. +*/}} +{{- define "app.labels" -}} +app.kubernetes.io/name: {{ include "app.name" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels: the stable subset used to wire Deployment <-> Service. Must +NOT change over a release's life or the Service would stop matching the pods. +*/}} +{{- define "app.selectorLabels" -}} +app: {{ include "app.name" . }} +{{- end -}} diff --git a/charts/app/app/templates/deployment.yaml b/charts/app/app/templates/deployment.yaml new file mode 100644 index 0000000..716571e --- /dev/null +++ b/charts/app/app/templates/deployment.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "app.name" . }} + labels: + {{- include "app.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + {{- include "app.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "app.selectorLabels" . | nindent 8 }} + spec: + containers: + - name: {{ include "app.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.containerPort }} + {{- with .Values.env }} + env: + {{- range $key, $val := . }} + - name: {{ $key }} + value: {{ $val | quote }} + {{- end }} + {{- end }} + {{- if .Values.envFromSecret }} + # Loads every key of this Secret as an env var. In Phase 5 this Secret + # is created + kept in sync by the External Secrets Operator. + envFrom: + - secretRef: + name: {{ .Values.envFromSecret }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/charts/app/app/templates/ingress.yaml b/charts/app/app/templates/ingress.yaml new file mode 100644 index 0000000..f569d52 --- /dev/null +++ b/charts/app/app/templates/ingress.yaml @@ -0,0 +1,32 @@ +{{- if .Values.ingress.enabled }} +{{- if not .Values.ingress.host }} +{{- fail "ingress.enabled is true but ingress.host is empty — set the hostname in your service's values.yaml" }} +{{- end }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "app.name" . }} + labels: + {{- include "app.labels" . | nindent 4 }} + annotations: + # Phase 1: cert-manager issues the cert. Phase 2: external-dns reads the + # host below and creates the DNS record. Both driven off this one Ingress. + cert-manager.io/cluster-issuer: {{ .Values.ingress.clusterIssuer }} +spec: + ingressClassName: {{ .Values.ingress.className }} + tls: + - hosts: + - {{ .Values.ingress.host }} + secretName: {{ .Values.ingress.tlsSecretName | default (printf "%s-tls" (include "app.name" .)) }} + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "app.name" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/charts/app/app/templates/service.yaml b/charts/app/app/templates/service.yaml new file mode 100644 index 0000000..baf1115 --- /dev/null +++ b/charts/app/app/templates/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "app.name" . }} + labels: + {{- include "app.labels" . | nindent 4 }} +spec: + selector: + {{- include "app.selectorLabels" . | nindent 4 }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.containerPort }} diff --git a/charts/app/app/values.yaml b/charts/app/app/values.yaml new file mode 100644 index 0000000..f3da3cd --- /dev/null +++ b/charts/app/app/values.yaml @@ -0,0 +1,56 @@ +# ============================================================================ +# THE CONTRACT: this is the shape every lgkentang service fills in. +# Most services only need to set: name, image.repository, image.tag, +# and ingress.host. Everything else has a sensible default below. +# ============================================================================ + +# Resource name for the Deployment/Service/Ingress. Defaults to the ArgoCD +# release name if left empty. Set it explicitly so names stay stable even if +# the release is ever renamed. +name: "" + +image: + repository: traefik/whoami + tag: latest + pullPolicy: IfNotPresent + +# How many pod replicas to run. +replicas: 1 + +# The port your container listens on inside the pod. +containerPort: 80 + +service: + # Cluster-internal Service port (what the Ingress routes to). + port: 80 + +ingress: + # Set false for internal-only services that shouldn't be exposed/DNS'd. + enabled: true + className: traefik + # REQUIRED when enabled, e.g. myapp.dev.lgkentang.com. external-dns turns + # this into a DNS record and cert-manager into a cert (Phases 1-2). + host: "" + clusterIssuer: letsencrypt-prod + # tlsSecretName defaults to "-tls" if left unset. + tlsSecretName: "" + +# Plain, non-secret environment variables as key: value pairs. +# SECRET env vars are deliberately NOT here — those arrive in Phase 5 via the +# External Secrets Operator, so we don't build a pattern now that fights it. +env: {} + # LOG_LEVEL: info + # FEATURE_X_ENABLED: "true" + +# Name of a Kubernetes Secret whose keys are loaded as env vars (envFrom). +# Empty = none. In Phase 5, the External Secrets Operator syncs a Secret from +# Infisical/Vault and you point this at it — so app secrets never touch git. +envFromSecret: "" + +# Resource requests/limits. Conservative defaults suited to the single node. +resources: + requests: + cpu: 10m + memory: 32Mi + limits: + memory: 128Mi diff --git a/gitops-demo/dev/deployment.yaml b/gitops-demo/dev/deployment.yaml deleted file mode 100644 index f58ccf6..0000000 --- a/gitops-demo/dev/deployment.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Plain Deployment — note there is NO `namespace:` here. ArgoCD places it in the -# namespace named by the Application's destination (dev). Leaving it out keeps -# these manifests reusable for prod later (a prod Application would drop the -# same files into the prd namespace). -apiVersion: apps/v1 -kind: Deployment -metadata: - name: gitops-demo -spec: - replicas: 3 - selector: - matchLabels: - app: gitops-demo - template: - metadata: - labels: - app: gitops-demo - spec: - containers: - - name: whoami - image: traefik/whoami:latest - ports: - - containerPort: 80 diff --git a/gitops-demo/dev/ingress.yaml b/gitops-demo/dev/ingress.yaml deleted file mode 100644 index 7201105..0000000 --- a/gitops-demo/dev/ingress.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Same Ingress shape as Phases 1-2, now delivered via GitOps. When ArgoCD syncs -# this, the full platform reacts: cert-manager issues the cert, external-dns -# creates the A record, Traefik routes it. A brand-new host proves the whole -# chain end-to-end from a single git commit. -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: gitops-demo - annotations: - cert-manager.io/cluster-issuer: letsencrypt-prod -spec: - ingressClassName: traefik - tls: - - hosts: - - gitops-demo.dev.lgkentang.com - secretName: gitops-demo-tls - rules: - - host: gitops-demo.dev.lgkentang.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: gitops-demo - port: - number: 80 diff --git a/gitops-demo/dev/service.yaml b/gitops-demo/dev/service.yaml deleted file mode 100644 index 12cfddb..0000000 --- a/gitops-demo/dev/service.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: gitops-demo -spec: - selector: - app: gitops-demo - ports: - - port: 80 - targetPort: 80 diff --git a/gitops-demo/dev/values.yaml b/gitops-demo/dev/values.yaml new file mode 100644 index 0000000..4594384 --- /dev/null +++ b/gitops-demo/dev/values.yaml @@ -0,0 +1,15 @@ +# gitops-demo, dev environment — the ENTIRE per-service config now. +# This replaces the old gitops-demo/dev/{deployment,service,ingress}.yaml: +# three hand-written manifests collapse into these few lines. The shared +# charts/app chart turns this into the same Deployment + Service + Ingress. +name: gitops-demo + +image: + repository: traefik/whoami + tag: latest + +replicas: 1 + +ingress: + host: gitops-demo.dev.lgkentang.com + clusterIssuer: letsencrypt-prod