From 18cf9556d2bcd2607ecbe997391050747272ac93 Mon Sep 17 00:00:00 2001 From: singchia Date: Sat, 2 May 2026 16:57:19 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(helm):=20full-stack=20chart=20?= =?UTF-8?q?=E2=80=94=20frontier=20+=20frontlas=20+=20bundled=20Redis=20sub?= =?UTF-8?q?chart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the existing frontier-only Helm chart into a complete, single-command deployment for both data plane (frontier) and control plane (frontlas), with an optional bundled bitnami/redis subchart. Defaults track the operator's production-grade settings (M2/M3/M4) so the two install paths converge on the same observable runtime behavior. Chart.yaml - Bump version 0.1.0 -> 1.2.5; appVersion 1.0.0 -> 1.2.5 - Add bitnami/redis dependency (condition: redis.enabled) - Add description, keywords, maintainers, home, sources values.yaml — restructured into four top-level sections: frontier: replicaCount, image, serviceAccount, podAnnotations/Labels, podSecurityContext (nonRoot UID 65532, RuntimeDefault seccomp), containerSecurityContext (drop ALL caps, no privEsc), terminationGracePeriodSeconds (60), drainSeconds (50), lifecycle.preStop (sleep 10), liveness/readinessProbe (TCP), resources, service (servicebound/edgebound/controlplane), ingress, autoscaling, nodeSelector, tolerations, topologySpreadConstraints, affinity, extraEnv frontlas: same shape; readinessProbe defaults to HTTP /cluster/v1/health; externalRedis subsection for BYO Redis when redis.enabled=false observability: frontier { enabled, port: 9091 } frontlas { enabled, port: 9092 } serviceMonitor: enabled (off by default), namespace, interval, scrapeTimeout, labels redis: enabled (bitnami subchart), architecture, auth, master.persistence Templates - _helpers.tpl rewritten with per-component fullname / labels / selectorLabels / serviceAccountName, plus pullPolicy / imageTag / serviceFQDN / frontlas.redis helpers - configmap_frontier.yaml + configmap_frontlas.yaml render configs via `tpl` so values like ports / observability addr land in the actual binary config - deployment_frontier.yaml: rewritten for new helpers; injects FRONTIER_DRAIN_SECONDS / NODE_NAME env; default preferred host anti-affinity (overridable by .Values.frontier.affinity); checksum/config annotation triggers pod restart on ConfigMap change - deployment_frontlas.yaml (NEW): same shape; Redis credentials injected via env (REDIS_PASSWORD as valueFrom.secretKeyRef when bundled redis OR external passwordSecret is configured) - service_frontier.yaml: separates servicebound + edgebound services; optional headless metrics service paired with ServiceMonitor - service_frontlas.yaml (NEW): single Service exposing controlplane + frontierplane ports; optional headless metrics service - serviceaccount_frontier.yaml + serviceaccount_frontlas.yaml: per component, automountServiceAccountToken=false - servicemonitor.yaml (NEW): one file generating ServiceMonitor for both components when serviceMonitor.enabled=true - hpa_frontier.yaml: bump to autoscaling/v2; nest under .Values.frontier.autoscaling - ingress_frontier.yaml: simplified to networking.k8s.io/v1 only Configs - configs/frontier/frontier.yaml: now templated; wires servicebound / edgebound listen ports, frontlas dial address (FQDN of the rendered frontlas Service), and observability addr from values - configs/frontlas/frontlas.yaml (NEW): templated; control_plane + frontier_plane listen, observability addr; Redis specifics come from env vars Verified end-to-end on TKE v1.34: - helm dependency update pulls bitnami/redis 20.13.4 - helm lint clean - helm template renders 21 distinct objects (3 SA, 1 Secret, 5 CM, 7 Service, 2 Deployment, 1 StatefulSet, 1 NetworkPolicy, 1 PDB) - helm install completes; all pods Running; Redis password injected via valueFrom.secretKeyRef (no plaintext); nonRoot UID 65532; preStop + drain env applied - helm uninstall + namespace delete clean Refs: docs/rfc/RFC-001-cloud-native-optimization.md (M5 — Helm chart completeness, originally deferred from M4) Co-Authored-By: Claude Opus 4.7 (1M context) --- dist/helm/.gitignore | 4 + dist/helm/Chart.lock | 6 + dist/helm/Chart.yaml | 46 ++- dist/helm/configs/frontier/frontier.yaml | 19 +- dist/helm/configs/frontlas/frontlas.yaml | 26 ++ dist/helm/templates/_helpers.tpl | 113 ++++-- dist/helm/templates/configmap_frontier.yaml | 8 +- dist/helm/templates/configmap_frontlas.yaml | 12 + dist/helm/templates/deployment_frontier.yaml | 122 +++++-- dist/helm/templates/deployment_frontlas.yaml | 127 +++++++ dist/helm/templates/hpa_frontier.yaml | 27 +- dist/helm/templates/ingress_frontier.yaml | 45 +-- dist/helm/templates/service_frontier.yaml | 61 +++- dist/helm/templates/service_frontlas.yaml | 42 +++ dist/helm/templates/serviceaccount.yaml | 12 - .../templates/serviceaccount_frontier.yaml | 14 + .../templates/serviceaccount_frontlas.yaml | 14 + dist/helm/templates/servicemonitor.yaml | 53 +++ dist/helm/values.yaml | 325 ++++++++++++------ 19 files changed, 821 insertions(+), 255 deletions(-) create mode 100644 dist/helm/.gitignore create mode 100644 dist/helm/Chart.lock create mode 100644 dist/helm/configs/frontlas/frontlas.yaml create mode 100644 dist/helm/templates/configmap_frontlas.yaml create mode 100644 dist/helm/templates/deployment_frontlas.yaml create mode 100644 dist/helm/templates/service_frontlas.yaml delete mode 100644 dist/helm/templates/serviceaccount.yaml create mode 100644 dist/helm/templates/serviceaccount_frontier.yaml create mode 100644 dist/helm/templates/serviceaccount_frontlas.yaml create mode 100644 dist/helm/templates/servicemonitor.yaml diff --git a/dist/helm/.gitignore b/dist/helm/.gitignore new file mode 100644 index 0000000..2ae0e60 --- /dev/null +++ b/dist/helm/.gitignore @@ -0,0 +1,4 @@ +# Subchart tarballs are pulled by `helm dependency update`; not committed. +# Chart.lock pins the version and IS committed. +charts/ +*.tgz diff --git a/dist/helm/Chart.lock b/dist/helm/Chart.lock new file mode 100644 index 0000000..a2ee226 --- /dev/null +++ b/dist/helm/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: redis + repository: https://charts.bitnami.com/bitnami + version: 20.13.4 +digest: sha256:7057fcbbe1c53c3ce689c21a9bd39da646fe8b463bba060eb986040d34bd79ef +generated: "2026-05-02T12:14:24.025459+08:00" diff --git a/dist/helm/Chart.yaml b/dist/helm/Chart.yaml index 0c3e9f4..945d49a 100644 --- a/dist/helm/Chart.yaml +++ b/dist/helm/Chart.yaml @@ -1,24 +1,32 @@ apiVersion: v2 name: frontier -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. +description: Frontier — All-in-one cloud-native gateway for microservices and edge nodes. Deploys both the data plane (frontier) and the control plane (frontlas) with an optional Redis backend. type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +# Chart version. Bump on every non-trivial change to templates / defaults. +version: 1.2.5 + +# App version. Tracks the frontier / frontlas binary version this chart was tested against. +appVersion: "1.2.5" + +home: https://github.com/singchia/frontier +sources: + - https://github.com/singchia/frontier +keywords: + - gateway + - edge + - iot + - microservices + - frontier + - frontlas +maintainers: + - name: singchia + url: https://github.com/singchia -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.0.0" +# Bundled Redis subchart powers the Frontlas control plane. +# Set redis.enabled=false in values.yaml to bring your own external Redis. +dependencies: + - name: redis + version: "20.x.x" + repository: https://charts.bitnami.com/bitnami + condition: redis.enabled diff --git a/dist/helm/configs/frontier/frontier.yaml b/dist/helm/configs/frontier/frontier.yaml index f16f6e0..8cf8472 100644 --- a/dist/helm/configs/frontier/frontier.yaml +++ b/dist/helm/configs/frontier/frontier.yaml @@ -1,5 +1,5 @@ daemon: - frontier_id: frontier01 + frontier_id: "" rlimit: enable: false nofile: 102400 @@ -10,20 +10,29 @@ daemon: servicebound: listen: network: tcp - addr: 0.0.0.0:30011 + addr: 0.0.0.0:{{ .Values.frontier.service.servicebound.port }} edgebound: listen: network: tcp - addr: 0.0.0.0:30012 + addr: 0.0.0.0:{{ .Values.frontier.service.edgebound.port }} edgeid_alloc_when_no_idservice_on: true dao: debug: false +{{- if .Values.frontlas.enabled }} frontlas: - enable: false + enable: true dial: network: tcp - addr: 127.0.0.1:40012 + addrs: + - {{ include "frontier.frontlas.serviceFQDN" . }}:{{ .Values.frontlas.service.frontierplane.port }} metrics: enable: false interval: 0 +{{- else }} +frontlas: + enable: false +{{- end }} +observability: + enable: {{ .Values.observability.frontier.enabled }} + addr: 0.0.0.0:{{ .Values.observability.frontier.port }} mqm: {} diff --git a/dist/helm/configs/frontlas/frontlas.yaml b/dist/helm/configs/frontlas/frontlas.yaml new file mode 100644 index 0000000..f24f558 --- /dev/null +++ b/dist/helm/configs/frontlas/frontlas.yaml @@ -0,0 +1,26 @@ +daemon: + rlimit: + enable: false + nofile: 102400 + pprof: + enable: true + addr: 0.0.0.0:6061 + cpu_profile_rate: 0 +control_plane: + listen: + network: tcp + addr: 0.0.0.0:{{ .Values.frontlas.service.controlplane.port }} +frontier_plane: + listen: + network: tcp + addr: 0.0.0.0:{{ .Values.frontlas.service.frontierplane.port }} + expiration: + edge_meta: 30 + service_meta: 30 +# Redis 实际地址 / 模式 / 用户名 / 密码 都通过环境变量注入 +# (FRONTLAS_REDIS_ADDRS / REDIS_TYPE / REDIS_USER / REDIS_PASSWORD ...). +redis: + mode: standalone +observability: + enable: {{ .Values.observability.frontlas.enabled }} + addr: 0.0.0.0:{{ .Values.observability.frontlas.port }} diff --git a/dist/helm/templates/_helpers.tpl b/dist/helm/templates/_helpers.tpl index 3efddbb..2960239 100644 --- a/dist/helm/templates/_helpers.tpl +++ b/dist/helm/templates/_helpers.tpl @@ -7,8 +7,6 @@ Expand the name of the chart. {{/* Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. */}} {{- define "frontier.fullname" -}} {{- if .Values.fullnameOverride }} @@ -23,40 +21,113 @@ If release name contains chart name it will be used as a full name. {{- end }} {{- end }} -{{/* -Create chart name and version as used by the chart label. -*/}} +{{/* Per-component fullnames. */}} +{{- define "frontier.frontier.fullname" -}} +{{- printf "%s-frontier" (include "frontier.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "frontier.frontlas.fullname" -}} +{{- printf "%s-frontlas" (include "frontier.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* Chart label. */}} {{- define "frontier.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} -{{/* -Common labels -*/}} -{{- define "frontier.labels" -}} +{{/* Common labels (shared by both components). */}} +{{- define "frontier.commonLabels" -}} helm.sh/chart: {{ include "frontier.chart" . }} -{{ include "frontier.selectorLabels" . }} +app.kubernetes.io/instance: {{ .Release.Name }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: {{ include "frontier.name" . }} {{- end }} -{{/* -Selector labels -*/}} -{{- define "frontier.selectorLabels" -}} +{{/* Frontier labels + selector. */}} +{{- define "frontier.frontier.labels" -}} +{{ include "frontier.commonLabels" . }} +app.kubernetes.io/name: {{ include "frontier.name" . }} +app.kubernetes.io/component: frontier +{{- end }} + +{{- define "frontier.frontier.selectorLabels" -}} app.kubernetes.io/name: {{ include "frontier.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: frontier {{- end }} -{{/* -Create the name of the service account to use -*/}} -{{- define "frontier.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "frontier.fullname" .) .Values.serviceAccount.name }} +{{/* Frontlas labels + selector. */}} +{{- define "frontier.frontlas.labels" -}} +{{ include "frontier.commonLabels" . }} +app.kubernetes.io/name: {{ include "frontier.name" . }} +app.kubernetes.io/component: frontlas +{{- end }} + +{{- define "frontier.frontlas.selectorLabels" -}} +app.kubernetes.io/name: {{ include "frontier.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: frontlas +{{- end }} + +{{/* Service account names. */}} +{{- define "frontier.frontier.serviceAccountName" -}} +{{- if .Values.frontier.serviceAccount.create }} +{{- default (include "frontier.frontier.fullname" .) .Values.frontier.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.frontier.serviceAccount.name }} +{{- end }} +{{- end }} + +{{- define "frontier.frontlas.serviceAccountName" -}} +{{- if .Values.frontlas.serviceAccount.create }} +{{- default (include "frontier.frontlas.fullname" .) .Values.frontlas.serviceAccount.name }} {{- else }} -{{- default "default" .Values.serviceAccount.name }} +{{- default "default" .Values.frontlas.serviceAccount.name }} {{- end }} {{- end }} + +{{/* Resolve image pull policy (per-component override > global default). */}} +{{- define "frontier.pullPolicy" -}} +{{- if .comp.image.pullPolicy }}{{ .comp.image.pullPolicy }}{{ else }}{{ .global.imagePullPolicy }}{{ end }} +{{- end }} + +{{/* Resolve image tag (per-component override > Chart.AppVersion). */}} +{{- define "frontier.imageTag" -}} +{{- if .comp.image.tag }}{{ .comp.image.tag }}{{ else }}{{ .root.Chart.AppVersion }}{{ end }} +{{- end }} + +{{/* Frontlas Service FQDN — used by frontier to dial the frontier-plane port. */}} +{{- define "frontier.frontlas.serviceFQDN" -}} +{{ include "frontier.frontlas.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local +{{- end }} + +{{/* +Resolve Redis connection details for Frontlas. Returns a YAML dict that +deployment_frontlas.yaml fromYaml-decodes to build env vars. + +When redis.enabled is true, target the bundled `-redis-master` +service and the auto-generated `-redis` Secret. +Otherwise read from .Values.frontlas.externalRedis. +*/}} +{{- define "frontier.frontlas.redis" -}} +{{- if .Values.redis.enabled -}} +addrs: "{{ .Release.Name }}-redis-master.{{ .Release.Namespace }}.svc.cluster.local:6379" +user: "" +redisType: standalone +masterName: "" +db: 0 +passwordSecretName: "{{ .Release.Name }}-redis" +passwordSecretKey: "redis-password" +{{- else -}} +addrs: "{{ join "," .Values.frontlas.externalRedis.addrs }}" +user: "{{ .Values.frontlas.externalRedis.user }}" +redisType: {{ .Values.frontlas.externalRedis.redisType }} +masterName: "{{ .Values.frontlas.externalRedis.masterName }}" +db: {{ .Values.frontlas.externalRedis.db }} +passwordSecretName: "{{ .Values.frontlas.externalRedis.passwordSecret.name }}" +passwordSecretKey: "{{ .Values.frontlas.externalRedis.passwordSecret.key }}" +{{- end -}} +{{- end }} diff --git a/dist/helm/templates/configmap_frontier.yaml b/dist/helm/templates/configmap_frontier.yaml index 26fa89e..38d837a 100644 --- a/dist/helm/templates/configmap_frontier.yaml +++ b/dist/helm/templates/configmap_frontier.yaml @@ -1,10 +1,12 @@ +{{- if .Values.frontier.enabled }} apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "frontier.fullname" . }} + name: {{ include "frontier.frontier.fullname" . }} namespace: {{ .Release.Namespace | quote }} labels: - {{- include "frontier.labels" . | nindent 4 }} + {{- include "frontier.frontier.labels" . | nindent 4 }} data: frontier.yaml: | -{{ .Files.Get "configs/frontier/frontier.yaml" | indent 4 }} \ No newline at end of file +{{ tpl (.Files.Get "configs/frontier/frontier.yaml") . | indent 4 }} +{{- end }} diff --git a/dist/helm/templates/configmap_frontlas.yaml b/dist/helm/templates/configmap_frontlas.yaml new file mode 100644 index 0000000..f4c7707 --- /dev/null +++ b/dist/helm/templates/configmap_frontlas.yaml @@ -0,0 +1,12 @@ +{{- if .Values.frontlas.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "frontier.frontlas.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "frontier.frontlas.labels" . | nindent 4 }} +data: + frontlas.yaml: | +{{ tpl (.Files.Get "configs/frontlas/frontlas.yaml") . | indent 4 }} +{{- end }} diff --git a/dist/helm/templates/deployment_frontier.yaml b/dist/helm/templates/deployment_frontier.yaml index 8e65c59..570c873 100644 --- a/dist/helm/templates/deployment_frontier.yaml +++ b/dist/helm/templates/deployment_frontier.yaml @@ -1,66 +1,118 @@ +{{- if .Values.frontier.enabled }} apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "frontier.fullname" . }} + name: {{ include "frontier.frontier.fullname" . }} + namespace: {{ .Release.Namespace | quote }} labels: - {{- include "frontier.labels" . | nindent 4 }} + {{- include "frontier.frontier.labels" . | nindent 4 }} spec: - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} + {{- if not .Values.frontier.autoscaling.enabled }} + replicas: {{ .Values.frontier.replicaCount }} {{- end }} selector: matchLabels: - {{- include "frontier.selectorLabels" . | nindent 6 }} + {{- include "frontier.frontier.selectorLabels" . | nindent 6 }} template: metadata: - {{- with .Values.podAnnotations }} annotations: + # Force pod restart when ConfigMap changes. + checksum/config: {{ include (print $.Template.BasePath "/configmap_frontier.yaml") . | sha256sum }} + {{- with .Values.frontier.podAnnotations }} {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} labels: - {{- include "frontier.selectorLabels" . | nindent 8 }} + {{- include "frontier.frontier.selectorLabels" . | nindent 8 }} + {{- with .Values.frontier.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} spec: - {{- with .Values.imagePullSecrets }} + serviceAccountName: {{ include "frontier.frontier.serviceAccountName" . }} + terminationGracePeriodSeconds: {{ .Values.frontier.terminationGracePeriodSeconds }} + {{- with .Values.global.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} - serviceAccountName: {{ include "frontier.serviceAccountName" . }} securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- toYaml .Values.frontier.podSecurityContext | nindent 8 }} + {{- with .Values.frontier.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontier.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontier.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + affinity: + {{- if .Values.frontier.affinity }} + {{- toYaml .Values.frontier.affinity | nindent 8 }} + {{- else }} + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + {{- include "frontier.frontier.selectorLabels" . | nindent 20 }} + {{- end }} containers: - - name: {{ .Chart.Name }} + - name: frontier + image: "{{ .Values.global.registry }}/{{ .Values.frontier.image.repository }}:{{ include "frontier.imageTag" (dict "comp" .Values.frontier "root" $) }}" + imagePullPolicy: {{ include "frontier.pullPolicy" (dict "comp" .Values.frontier "global" .Values.global) }} securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.global.registry }}/{{ .Values.image.frontier.image }}:{{ .Values.image.frontier.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.frontier.pullPolicy }} - volumeMounts: - - name: config - mountPath: /usr/conf/ + {{- toYaml .Values.frontier.containerSecurityContext | nindent 12 }} + args: + - "--config" + - "/usr/conf/frontier.yaml" + env: + - name: FRONTIER_DRAIN_SECONDS + value: {{ .Values.frontier.drainSeconds | quote }} + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + {{- with .Values.frontier.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} ports: - name: controlplane - containerPort: 2430 + containerPort: {{ .Values.frontier.service.controlplane.port }} protocol: TCP - name: servicebound - containerPort: 30011 + containerPort: {{ .Values.frontier.service.servicebound.port }} protocol: TCP - name: edgebound - containerPort: 30012 + containerPort: {{ .Values.frontier.service.edgebound.port }} + protocol: TCP + {{- if .Values.observability.frontier.enabled }} + - name: metrics + containerPort: {{ .Values.observability.frontier.port }} protocol: TCP + {{- end }} + {{- with .Values.frontier.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.frontier.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.frontier.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity.frontier }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} + {{- toYaml .Values.frontier.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /usr/conf/ volumes: - name: config configMap: - name: {{ include "frontier.fullname" . }} + name: {{ include "frontier.frontier.fullname" . }} +{{- end }} diff --git a/dist/helm/templates/deployment_frontlas.yaml b/dist/helm/templates/deployment_frontlas.yaml new file mode 100644 index 0000000..faf713c --- /dev/null +++ b/dist/helm/templates/deployment_frontlas.yaml @@ -0,0 +1,127 @@ +{{- if .Values.frontlas.enabled }} +{{- $redis := include "frontier.frontlas.redis" . | fromYaml }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "frontier.frontlas.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "frontier.frontlas.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.frontlas.replicaCount }} + selector: + matchLabels: + {{- include "frontier.frontlas.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap_frontlas.yaml") . | sha256sum }} + {{- with .Values.frontlas.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "frontier.frontlas.selectorLabels" . | nindent 8 }} + {{- with .Values.frontlas.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "frontier.frontlas.serviceAccountName" . }} + terminationGracePeriodSeconds: {{ .Values.frontlas.terminationGracePeriodSeconds }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.frontlas.podSecurityContext | nindent 8 }} + {{- with .Values.frontlas.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontlas.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.frontlas.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + affinity: + {{- if .Values.frontlas.affinity }} + {{- toYaml .Values.frontlas.affinity | nindent 8 }} + {{- else }} + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + {{- include "frontier.frontlas.selectorLabels" . | nindent 20 }} + {{- end }} + containers: + - name: frontlas + image: "{{ .Values.global.registry }}/{{ .Values.frontlas.image.repository }}:{{ include "frontier.imageTag" (dict "comp" .Values.frontlas "root" $) }}" + imagePullPolicy: {{ include "frontier.pullPolicy" (dict "comp" .Values.frontlas "global" .Values.global) }} + securityContext: + {{- toYaml .Values.frontlas.containerSecurityContext | nindent 12 }} + args: + - "--config" + - "/usr/conf/frontlas.yaml" + env: + - name: FRONTLAS_CONTROLPLANE_PORT + value: {{ .Values.frontlas.service.controlplane.port | quote }} + - name: FRONTLAS_FRONTIERPLANE_PORT + value: {{ .Values.frontlas.service.frontierplane.port | quote }} + - name: REDIS_TYPE + value: {{ $redis.redisType | quote }} + - name: REDIS_ADDRS + value: {{ $redis.addrs | quote }} + - name: REDIS_USER + value: {{ $redis.user | quote }} + - name: REDIS_DB + value: {{ $redis.db | quote }} + - name: MASTER_NAME + value: {{ $redis.masterName | quote }} + {{- if $redis.passwordSecretName }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $redis.passwordSecretName }} + key: {{ $redis.passwordSecretKey }} + {{- else }} + - name: REDIS_PASSWORD + value: "" + {{- end }} + {{- with .Values.frontlas.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: controlplane + containerPort: {{ .Values.frontlas.service.controlplane.port }} + protocol: TCP + - name: frontierplane + containerPort: {{ .Values.frontlas.service.frontierplane.port }} + protocol: TCP + {{- if .Values.observability.frontlas.enabled }} + - name: metrics + containerPort: {{ .Values.observability.frontlas.port }} + protocol: TCP + {{- end }} + {{- with .Values.frontlas.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.frontlas.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.frontlas.resources | nindent 12 }} + volumeMounts: + - name: config + mountPath: /usr/conf/ + volumes: + - name: config + configMap: + name: {{ include "frontier.frontlas.fullname" . }} +{{- end }} diff --git a/dist/helm/templates/hpa_frontier.yaml b/dist/helm/templates/hpa_frontier.yaml index 8b97d24..ab8daf1 100644 --- a/dist/helm/templates/hpa_frontier.yaml +++ b/dist/helm/templates/hpa_frontier.yaml @@ -1,28 +1,33 @@ -{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 +{{- if and .Values.frontier.enabled .Values.frontier.autoscaling.enabled }} +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ include "frontier.fullname" . }} + name: {{ include "frontier.frontier.fullname" . }} + namespace: {{ .Release.Namespace | quote }} labels: - {{- include "frontier.labels" . | nindent 4 }} + {{- include "frontier.frontier.labels" . | nindent 4 }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "frontier.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} + name: {{ include "frontier.frontier.fullname" . }} + minReplicas: {{ .Values.frontier.autoscaling.minReplicas }} + maxReplicas: {{ .Values.frontier.autoscaling.maxReplicas }} metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- if .Values.frontier.autoscaling.targetCPUUtilizationPercentage }} - type: Resource resource: name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + target: + type: Utilization + averageUtilization: {{ .Values.frontier.autoscaling.targetCPUUtilizationPercentage }} {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- if .Values.frontier.autoscaling.targetMemoryUtilizationPercentage }} - type: Resource resource: name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + target: + type: Utilization + averageUtilization: {{ .Values.frontier.autoscaling.targetMemoryUtilizationPercentage }} {{- end }} {{- end }} diff --git a/dist/helm/templates/ingress_frontier.yaml b/dist/helm/templates/ingress_frontier.yaml index bf635a5..6b049c1 100644 --- a/dist/helm/templates/ingress_frontier.yaml +++ b/dist/helm/templates/ingress_frontier.yaml @@ -1,34 +1,24 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "frontier.fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +{{- if and .Values.frontier.enabled .Values.frontier.ingress.enabled }} +{{- $svcName := printf "%s-edgebound" (include "frontier.frontier.fullname" .) -}} +{{- $svcPort := .Values.frontier.service.edgebound.port -}} apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} kind: Ingress metadata: - name: {{ $fullName }} + name: {{ include "frontier.frontier.fullname" . }} + namespace: {{ .Release.Namespace | quote }} labels: - {{- include "frontier.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- include "frontier.frontier.labels" . | nindent 4 }} + {{- with .Values.frontier.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.ingress.className }} + {{- with .Values.frontier.ingress.className }} + ingressClassName: {{ . }} {{- end }} - {{- if .Values.ingress.tls }} + {{- with .Values.frontier.ingress.tls }} tls: - {{- range .Values.ingress.tls }} + {{- range . }} - hosts: {{- range .hosts }} - {{ . | quote }} @@ -37,25 +27,20 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range .Values.frontier.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ .path }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} + {{- with .pathType }} + pathType: {{ . }} {{- end }} backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} service: - name: {{ $fullName }} + name: {{ $svcName }} port: number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/dist/helm/templates/service_frontier.yaml b/dist/helm/templates/service_frontier.yaml index 5f05aca..3df8cd3 100644 --- a/dist/helm/templates/service_frontier.yaml +++ b/dist/helm/templates/service_frontier.yaml @@ -1,36 +1,59 @@ +{{- if .Values.frontier.enabled }} apiVersion: v1 kind: Service metadata: - name: "frontier-servicebound-svc" + name: {{ include "frontier.frontier.fullname" . }}-servicebound + namespace: {{ .Release.Namespace | quote }} labels: - {{- include "frontier.labels" . | nindent 4 }} + {{- include "frontier.frontier.labels" . | nindent 4 }} spec: - type: ClusterIP + type: {{ .Values.frontier.service.servicebound.type }} ports: - {{- range .Values.service.frontier.clusterIP }} - - port: {{ .port }} - targetPort: {{ .targetPort }} - name: {{ .name }} + - name: servicebound + port: {{ .Values.frontier.service.servicebound.port }} + targetPort: servicebound protocol: TCP - {{- end }} selector: - {{- include "frontier.selectorLabels" . | nindent 4 }} + {{- include "frontier.frontier.selectorLabels" . | nindent 4 }} --- apiVersion: v1 kind: Service metadata: - name: "frontier-edgebound-svc" + name: {{ include "frontier.frontier.fullname" . }}-edgebound + namespace: {{ .Release.Namespace | quote }} labels: - {{- include "frontier.labels" . | nindent 4 }} + {{- include "frontier.frontier.labels" . | nindent 4 }} spec: - type: NodePort + type: {{ .Values.frontier.service.edgebound.type }} + ports: + - name: edgebound + port: {{ .Values.frontier.service.edgebound.port }} + targetPort: edgebound + {{- if eq .Values.frontier.service.edgebound.type "NodePort" }} + nodePort: {{ .Values.frontier.service.edgebound.port }} + {{- end }} + protocol: TCP + selector: + {{- include "frontier.frontier.selectorLabels" . | nindent 4 }} +{{- if .Values.observability.frontier.enabled }} +--- +# Headless service for /metrics scraping (paired with the ServiceMonitor). +apiVersion: v1 +kind: Service +metadata: + name: {{ include "frontier.frontier.fullname" . }}-metrics + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "frontier.frontier.labels" . | nindent 4 }} +spec: + type: ClusterIP + clusterIP: None ports: - {{- range .Values.service.frontier.nodePort }} - - port: {{ .port }} - targetPort: {{ .targetPort }} - name: {{ .name }} - nodePort: {{ .port }} + - name: metrics + port: {{ .Values.observability.frontier.port }} + targetPort: metrics protocol: TCP - {{- end }} selector: - {{- include "frontier.selectorLabels" . | nindent 4 }} \ No newline at end of file + {{- include "frontier.frontier.selectorLabels" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/dist/helm/templates/service_frontlas.yaml b/dist/helm/templates/service_frontlas.yaml new file mode 100644 index 0000000..c852f40 --- /dev/null +++ b/dist/helm/templates/service_frontlas.yaml @@ -0,0 +1,42 @@ +{{- if .Values.frontlas.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "frontier.frontlas.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "frontier.frontlas.labels" . | nindent 4 }} +spec: + type: {{ .Values.frontlas.service.controlplane.type }} + ports: + - name: controlplane + port: {{ .Values.frontlas.service.controlplane.port }} + targetPort: controlplane + protocol: TCP + - name: frontierplane + port: {{ .Values.frontlas.service.frontierplane.port }} + targetPort: frontierplane + protocol: TCP + selector: + {{- include "frontier.frontlas.selectorLabels" . | nindent 4 }} +{{- if .Values.observability.frontlas.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "frontier.frontlas.fullname" . }}-metrics + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "frontier.frontlas.labels" . | nindent 4 }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: metrics + port: {{ .Values.observability.frontlas.port }} + targetPort: metrics + protocol: TCP + selector: + {{- include "frontier.frontlas.selectorLabels" . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/dist/helm/templates/serviceaccount.yaml b/dist/helm/templates/serviceaccount.yaml deleted file mode 100644 index 1f94b0b..0000000 --- a/dist/helm/templates/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "frontier.serviceAccountName" . }} - labels: - {{- include "frontier.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/dist/helm/templates/serviceaccount_frontier.yaml b/dist/helm/templates/serviceaccount_frontier.yaml new file mode 100644 index 0000000..398eee2 --- /dev/null +++ b/dist/helm/templates/serviceaccount_frontier.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.frontier.enabled .Values.frontier.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "frontier.frontier.serviceAccountName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "frontier.frontier.labels" . | nindent 4 }} + {{- with .Values.frontier.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: false +{{- end }} diff --git a/dist/helm/templates/serviceaccount_frontlas.yaml b/dist/helm/templates/serviceaccount_frontlas.yaml new file mode 100644 index 0000000..d9faa18 --- /dev/null +++ b/dist/helm/templates/serviceaccount_frontlas.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.frontlas.enabled .Values.frontlas.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "frontier.frontlas.serviceAccountName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "frontier.frontlas.labels" . | nindent 4 }} + {{- with .Values.frontlas.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: false +{{- end }} diff --git a/dist/helm/templates/servicemonitor.yaml b/dist/helm/templates/servicemonitor.yaml new file mode 100644 index 0000000..0fc4d77 --- /dev/null +++ b/dist/helm/templates/servicemonitor.yaml @@ -0,0 +1,53 @@ +{{- if .Values.serviceMonitor.enabled }} +{{- if and .Values.frontier.enabled .Values.observability.frontier.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "frontier.frontier.fullname" . }} + namespace: {{ default .Release.Namespace .Values.serviceMonitor.namespace | quote }} + labels: + {{- include "frontier.frontier.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "frontier.frontier.selectorLabels" . | nindent 6 }} + endpoints: + - port: metrics + path: /metrics + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} +{{- end }} +{{- if and .Values.frontlas.enabled .Values.observability.frontlas.enabled }} +{{- if and .Values.frontier.enabled .Values.observability.frontier.enabled }} +--- +{{- end }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "frontier.frontlas.fullname" . }} + namespace: {{ default .Release.Namespace .Values.serviceMonitor.namespace | quote }} + labels: + {{- include "frontier.frontlas.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "frontier.frontlas.selectorLabels" . | nindent 6 }} + endpoints: + - port: metrics + path: /metrics + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} +{{- end }} +{{- end }} diff --git a/dist/helm/values.yaml b/dist/helm/values.yaml index 99e020a..8558be2 100644 --- a/dist/helm/values.yaml +++ b/dist/helm/values.yaml @@ -1,110 +1,235 @@ -# Default values for frontier. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. +# ============================================================================ +# Frontier Helm chart — default values +# ---------------------------------------------------------------------------- +# This chart deploys the data plane (frontier) + the control plane (frontlas) +# in one shot, with an optional bundled Redis subchart. Defaults mirror the +# operator's production-grade settings (see /docs/operator). +# ============================================================================ + global: registry: singchia - pullPolicy: Always + imagePullPolicy: IfNotPresent + imagePullSecrets: [] -imagePullSecrets: [] nameOverride: "" fullnameOverride: "" -image: - frontier: - image: frontier - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: 1.1.0 - -replicaCount: 1 - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - -podAnnotations: {} - -podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - frontier: - clusterIP: - - name: controlbound - type: ClusterIP - targetPort: controlbound - port: 30010 - - name: frontier-edgebound-svc - type: ClusterIP - targetPort: servicebound - port: 30011 - nodePort: - - name: edgebound - type: NodePort - targetPort: edgebound - port: 30012 - -ingress: - enabled: false - className: "" - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: - - path: / - pathType: ImplementationSpecific - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 +# ---------------------------------------------------------------------------- +# Frontier — data plane +# ---------------------------------------------------------------------------- +frontier: + enabled: true + replicaCount: 1 + + image: + repository: frontier + tag: "" # defaults to .Chart.AppVersion + pullPolicy: "" # defaults to global.imagePullPolicy + + serviceAccount: + create: true + name: "" + annotations: {} + + podAnnotations: {} + podLabels: {} + + # Production defaults: nonRoot uid 65532, dropped capabilities. + podSecurityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + + containerSecurityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + + # Long-lived edge connections need at least 60s grace; drain = grace - 10. + terminationGracePeriodSeconds: 60 + drainSeconds: 50 + + # preStop sleep gives kube-proxy time to remove the pod from Service + # Endpoints before SIGTERM tears connections down. + lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "sleep 10"] + + livenessProbe: + tcpSocket: + port: edgebound + initialDelaySeconds: 10 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 + + readinessProbe: + tcpSocket: + port: servicebound + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + + resources: {} + + service: + servicebound: + type: ClusterIP + port: 30011 + edgebound: + type: NodePort + port: 30012 + controlplane: + type: ClusterIP + port: 30010 + + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: frontier.example.com + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 -nodeSelector: {} + nodeSelector: {} + tolerations: [] + topologySpreadConstraints: [] + # Default: preferred host anti-affinity (rendered by template). Set this to + # a non-empty Affinity object to fully replace the default. + affinity: {} -tolerations: [] + extraEnv: [] -affinity: +# ---------------------------------------------------------------------------- +# Frontlas — control plane +# ---------------------------------------------------------------------------- +frontlas: + enabled: true + replicaCount: 1 + + image: + repository: frontlas + tag: "" # defaults to .Chart.AppVersion + pullPolicy: "" # defaults to global.imagePullPolicy + + serviceAccount: + create: true + name: "" + annotations: {} + + podAnnotations: {} + podLabels: {} + + podSecurityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + + containerSecurityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + + terminationGracePeriodSeconds: 30 + + livenessProbe: + tcpSocket: + port: controlplane + initialDelaySeconds: 10 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 + + readinessProbe: + httpGet: + path: /cluster/v1/health + port: controlplane + periodSeconds: 5 + + resources: {} + + service: + controlplane: + type: ClusterIP + port: 40011 + frontierplane: + type: ClusterIP + port: 40012 + + nodeSelector: {} + tolerations: [] + topologySpreadConstraints: [] + affinity: {} + + extraEnv: [] + + # When `redis.enabled: false`, point Frontlas at an existing Redis here. + externalRedis: + addrs: [] # e.g. ["redis.svc.cluster.local:6379"] + user: "" + redisType: standalone # standalone | sentinel | cluster + masterName: "" + db: 0 + passwordSecret: + name: "" # leave empty for unauthenticated Redis + key: password + +# ---------------------------------------------------------------------------- +# Observability — /healthz, /readyz, /metrics endpoints (since M3) +# ---------------------------------------------------------------------------- +observability: frontier: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: In - values: - - frontier - + enabled: true + port: 9091 + frontlas: + enabled: true + port: 9092 + +# Prometheus Operator ServiceMonitor (off by default; turn on if you have +# prometheus-operator installed). +serviceMonitor: + enabled: false + namespace: "" + interval: 30s + scrapeTimeout: 10s + labels: {} + +# ---------------------------------------------------------------------------- +# Redis — required by Frontlas for cluster metadata +# ---------------------------------------------------------------------------- +# When enabled (default), the bitnami/redis subchart is installed and the +# chart wires Frontlas to it automatically. For an external Redis, set +# `redis.enabled: false` and configure `frontlas.externalRedis` above. +redis: + enabled: true + architecture: standalone + auth: + enabled: true + # Leave empty to let bitnami auto-generate. The chart reads it from + # the `-redis` Secret either way. + password: "" + master: + persistence: + enabled: true + size: 8Gi From 3019e61a54148005602913bdc01344ff898aa6cf Mon Sep 17 00:00:00 2001 From: singchia Date: Sat, 2 May 2026 16:57:29 +0800 Subject: [PATCH 2/2] docs(website): add 'Install with Helm' section to /docs/operator Cover the new dist/helm chart end-to-end as section 2.3: - Quick install with bundled bitnami/redis subchart - BYO external Redis via frontlas.externalRedis + passwordSecret - Knob reference table (replicaCount, image, registry, pullSecrets, edgebound serviceType, security context overrides, grace/drain, HPA, observability ports, ServiceMonitor toggle, redis.enabled) - Operator vs Helm decision matrix (declarative CR, Status conditions, TLS self-healing, multi-cluster isolation, bundled Redis, GitOps) - Hint to publish: helm package -> .tgz / helm push to OCI Verified with `npx next build`; /docs/operator still pre-renders. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/src/app/docs/operator/page.tsx | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/website/src/app/docs/operator/page.tsx b/website/src/app/docs/operator/page.tsx index d3b190d..212eaa2 100644 --- a/website/src/app/docs/operator/page.tsx +++ b/website/src/app/docs/operator/page.tsx @@ -41,6 +41,78 @@ kubectl get all -n frontier-operator-system`}

If you tighten this further, keep at least get;list;watch on those resources or reconcile will fail.

+

2.3 Alternative: install with Helm

+

+ Prefer Helm? The chart at dist/helm/ deploys both frontier and frontlas in one shot, with an optional bundled bitnami/redis subchart. Defaults track the operator's production-grade settings (non-root UID 65532, drop-all capabilities, preferred host anti-affinity, preStop sleep, configurable drain window, observability endpoints on 9091/9092). Pick this path if your platform standardizes on Helm/ArgoCD/Flux and you don't need the operator's reconcile-driven self-healing for TLS Secrets and Status conditions. +

+ +

Quick install with the bundled Redis:

+
{`# 1. Pull the bitnami/redis subchart
+cd frontier/dist/helm
+helm repo add bitnami https://charts.bitnami.com/bitnami
+helm dependency update
+
+# 2. Install
+helm install frontier . \\
+  --namespace frontier --create-namespace \\
+  --set frontier.replicaCount=2 \\
+  --set frontlas.replicaCount=1`}
+ +

Bring your own Redis (set redis.enabled: false and point Frontlas at it):

+
{`# my-values.yaml
+redis:
+  enabled: false
+
+frontlas:
+  externalRedis:
+    addrs:
+      - redis.shared:6379
+    redisType: standalone
+    passwordSecret:
+      name: redis-creds      # must already exist in the release namespace
+      key:  password`}
+ +
{`helm install frontier . -n frontier --create-namespace -f my-values.yaml`}
+ +

Common knobs in values.yaml (full listing: helm show values dist/helm/):

+ + + + + + + + + + + + + + + + + +
PathDefaultNotes
frontier.replicaCount / frontlas.replicaCount1 / 1Independent scaling per component
frontier.image.tag / frontlas.image.tag{`""`}Chart.AppVersionOverride to pin a specific binary version
global.registrysingchiaMirror to your private registry
global.imagePullSecrets[]Private registry credentials
frontier.service.edgebound.typeNodePortSwitch to LoadBalancer for cloud edge ingress
frontier.podSecurityContext / containerSecurityContextnonRoot UID 65532, drop ALL capsSet to {`{}`} if your custom image needs root
frontier.terminationGracePeriodSeconds / frontier.drainSeconds60 / 50Long-lived edge connections; drain < grace
frontier.autoscaling.enabledfalseHPA on the frontier Deployment
observability.frontier.enabled / observability.frontlas.enabledtrue / trueToggle the /healthz /readyz /metrics endpoints (ports 9091 / 9092)
serviceMonitor.enabledfalseOpt in if prometheus-operator is installed
redis.enabledtrueSet false to use external Redis (configure under frontlas.externalRedis)
+ +

Operator vs Helm — pick one:

+ + + + + + + + + + + + + +
ConcernOperatorHelm
Custom Resource (declarative)✅ FrontierCluster CR❌ values.yaml + Deployments directly
Status & Conditions per cluster✅ Available / Progressing / Degraded❌ Inspect underlying Deployments
Self-healing on TLS Secret rotation✅ Reconciler watches Secrets❌ Manual helm upgrade
Multiple clusters in one namespace✅ Each CR is isolated⚠️ Need separate releases
Bundled Redis❌ BYObitnami/redis subchart
ArgoCD / Flux GitOps✅ (commit the CR)✅ (commit values.yaml)
Initial install footprintOperator pod + CRD + RBACNo long-running operator
+

+ You can also publish the chart for downstream consumers: helm package dist/helm/ -d /path/to/repo produces frontier-1.2.5.tgz; serve the directory with any HTTP server or helm push to OCI. +

+

3. Quick start

Minimum viable cluster: 2 frontier replicas, 1 frontlas replica, external Redis. Save as frontiercluster.yaml:

{`apiVersion: frontier.singchia.io/v1alpha1