diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index cb9a4ef..e692024 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -76,6 +76,59 @@ jobs: req-concurrent: "4" secrets: inherit + # Chained into this run rather than triggered by the tag: release-please tags + # with GITHUB_TOKEN, and a tag pushed that way starts no `on: push: tags` + # workflow. The release is also the only place that already knows the + # version, so nothing has to derive one. + # + # Charts are published on a release only. A branch build is deployed by + # pointing an existing release of the chart at the branch's image tag, which + # needs no chart of its own. + helm: + name: Publish the Helm chart + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + + - name: Set up Helm + uses: azure/setup-helm@v5.0.1 + + # Both versions come from the release rather than from Chart.yaml. + # Release Please keeps that file current anyway, but stating them here + # makes a mismatch impossible instead of something to check for: the + # chart cannot be packaged pointing at an image tag nobody built. + - name: Package + run: helm package charts/vulpes-backend --destination dist --version "${{ needs.release-please.outputs.version }}" --app-version "${{ needs.release-please.outputs.version }}" + + - name: Log in to OneLiteFeather Harbor + env: + REGISTRY: ${{ secrets.HARBOR_REGISTRY }} + USERNAME: ${{ secrets.HARBOR_USERNAME }} + PASSWORD: ${{ secrets.HARBOR_PASSWORD }} + run: printf '%s' "$PASSWORD" | helm registry login "$REGISTRY" --username "$USERNAME" --password-stdin + + # Into the same Harbor project as the image, under charts/ - the layout + # apus and stelaris-ui already use, so a Flux OCIRepository pointing here + # looks like every other one in the cluster repository. + - name: Push + env: + REGISTRY: ${{ secrets.HARBOR_REGISTRY }} + VERSION: ${{ needs.release-please.outputs.version }} + run: helm push "dist/vulpes-backend-${VERSION}.tgz" "oci://${REGISTRY}/onelitefeather/charts" + + - name: Log out + if: always() + env: + REGISTRY: ${{ secrets.HARBOR_REGISTRY }} + run: helm registry logout "$REGISTRY" || true + sbom: name: Attach SBOM to release needs: release-please diff --git a/charts/vulpes-backend/Chart.yaml b/charts/vulpes-backend/Chart.yaml new file mode 100644 index 0000000..0d8ad12 --- /dev/null +++ b/charts/vulpes-backend/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: vulpes-backend +description: The Vulpes backend, a Micronaut service deployed to Kubernetes +type: application + +# Both lines are kept current by Release Please, which is why the chart version +# and the app version move together: the chart only ever describes the image +# built from the same commit, so a separate chart version would be a second +# number to reason about with nothing extra to say. The cluster repository's +# OCIRepository pins the chart, and image.tag is left empty so it falls back to +# this appVersion -- pinning the chart therefore pins the image. +version: 2.5.0 # x-release-please-version +appVersion: "2.5.0" # x-release-please-version + +home: https://github.com/OneLiteFeatherNET/Vulpes-Backend +sources: + - https://github.com/OneLiteFeatherNET/Vulpes-Backend +maintainers: + - name: OneLiteFeatherNET + email: contact@onelitefeather.net + url: https://onelitefeather.net +keywords: + - vulpes + - micronaut diff --git a/charts/vulpes-backend/templates/NOTES.txt b/charts/vulpes-backend/templates/NOTES.txt new file mode 100644 index 0000000..24d9a9c --- /dev/null +++ b/charts/vulpes-backend/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "micronaut.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "micronaut.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "micronaut.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "micronaut.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/charts/vulpes-backend/templates/_helpers.tpl b/charts/vulpes-backend/templates/_helpers.tpl new file mode 100644 index 0000000..19cb6ae --- /dev/null +++ b/charts/vulpes-backend/templates/_helpers.tpl @@ -0,0 +1,73 @@ +{{/* +The helpers below are still named `micronaut.*`, not `vulpes-backend.*`, and +that is deliberate: this chart is a fork of helm/micronaut in the cluster +repository, and keeping the templates byte-identical is what lets a later fix +over there be carried across with `diff -r`. Renaming them would touch every +template file and turn that diff into noise. + +The rendered resource names are unaffected either way -- they come from +.Chart.Name/.Release.Name, and the cluster overlays pin them with +nameOverride/fullnameOverride so the Deployment's immutable selector survives +this chart move. +*/}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "micronaut.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +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 "micronaut.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- if contains .Chart.Name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Chart name and version +*/}} +{{- define "micronaut.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "micronaut.labels" -}} +app.kubernetes.io/name: {{ include "micronaut.name" . }} +helm.sh/chart: {{ include "micronaut.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + + +{{/* +Create the name of the service account to use +*/}} +{{- define "micronaut.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "micronaut.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "micronaut.selectorLabels" -}} +app.kubernetes.io/name: {{ include "micronaut.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/charts/vulpes-backend/templates/configmap.yaml b/charts/vulpes-backend/templates/configmap.yaml new file mode 100644 index 0000000..bb8dc36 --- /dev/null +++ b/charts/vulpes-backend/templates/configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "micronaut.fullname" . }}-config + labels: + {{- include "micronaut.labels" . | nindent 4 }} +data: + application.yml: |- +{{- nindent 4 .Values.config.base }} +{{- range $p, $content := .Values.config.profiles }} + application-{{ $p }}.yml: |- +{{- nindent 4 $content }} +{{- end }} diff --git a/charts/vulpes-backend/templates/deployment.yaml b/charts/vulpes-backend/templates/deployment.yaml new file mode 100644 index 0000000..fe9d040 --- /dev/null +++ b/charts/vulpes-backend/templates/deployment.yaml @@ -0,0 +1,170 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "micronaut.fullname" . }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + {{- with .Values.strategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "micronaut.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "micronaut.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- with .Values.podLabels }} +{{ toYaml . | indent 8 }} + {{- end }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- if and .Values.secrets.enabled .Values.secrets.files }} + checksum/secrets: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.podAnnotations }} +{{ toYaml . | indent 8 }} + {{- end }} + spec: + {{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} + serviceAccountName: {{- if .Values.serviceAccount.create }} {{ include "micronaut.fullname" . }} {{- else }} {{ default "default" .Values.serviceAccount.name }} {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: + {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- with .Values.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.tracing.enabled }} + initContainers: + - name: otel-agent + image: {{ .Values.tracing.image }} + command: ["cp", "/javaagent.jar", "/otel/javaagent.jar"] + {{- if .Values.containerSecurityContext.enabled }} + securityContext: + {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.tracing.resources | nindent 12 }} + volumeMounts: + - name: otel-agent + mountPath: /otel + {{- end }} + containers: + - name: app + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: + {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + env: + - name: MICRONAUT_ENVIRONMENTS + value: "{{ join "," .Values.profiles }}" + - name: MICRONAUT_CONFIG_FILES + value: "/config/application.yml{{- range $p := .Values.profiles }},/config/application-{{$p}}.yml{{- end }}{{- if and .Values.secrets.enabled .Values.secrets.files }}{{- range $p := .Values.profiles }},/secrets/application-{{$p}}.yml{{- end }}{{- end }}" + {{- range .Values.env }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- if .Values.tracing.enabled }} + # JDK_JAVA_OPTIONS is independent of JAVA_TOOL_OPTIONS (which + # apps commonly override wholesale via .Values.env above), so + # the agent attaches regardless of what that list contains. + - name: JDK_JAVA_OPTIONS + value: "-javaagent:/otel/javaagent.jar" + - name: OTEL_SERVICE_NAME + value: {{ .Release.Name | quote }} + - name: OTEL_TRACES_EXPORTER + value: "otlp" + - name: OTEL_METRICS_EXPORTER + value: "none" + - name: OTEL_LOGS_EXPORTER + value: "none" + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: {{ .Values.tracing.endpoint | quote }} + - name: OTEL_EXPORTER_OTLP_PROTOCOL + value: "grpc" + {{- end }} + {{- if .Values.envFrom }} + envFrom: +{{ toYaml .Values.envFrom | indent 12 }} + {{- end }} + {{- if .Values.startupProbe.enabled }} + startupProbe: + httpGet: + path: {{ .Values.readinessProbe.path }} + port: http + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + {{- end }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.path }} + port: http + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.path }} + port: http + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + resources: +{{ toYaml .Values.resources | indent 12 }} + volumeMounts: + # The root filesystem is read-only; the JVM still wants a scratch + # dir (java.io.tmpdir, Netty, JIT dumps). + - name: tmp + mountPath: /tmp + - name: config + mountPath: /config + {{- if and .Values.secrets.enabled .Values.secrets.files }} + - name: secrets + mountPath: /secrets + {{- end }} + {{- if .Values.tracing.enabled }} + - name: otel-agent + mountPath: /otel + readOnly: true + {{- end }} + volumes: + - name: tmp + emptyDir: {} + - name: config + configMap: + name: {{ include "micronaut.fullname" . }}-config + {{- if and .Values.secrets.enabled .Values.secrets.files }} + - name: secrets + secret: + secretName: {{ include "micronaut.fullname" . }}-secrets + {{- end }} + {{- if .Values.tracing.enabled }} + - name: otel-agent + emptyDir: {} + {{- end }} diff --git a/charts/vulpes-backend/templates/hpa.yaml b/charts/vulpes-backend/templates/hpa.yaml new file mode 100644 index 0000000..bf264fb --- /dev/null +++ b/charts/vulpes-backend/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "micronaut.fullname" . }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "micronaut.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/vulpes-backend/templates/httproute.yaml b/charts/vulpes-backend/templates/httproute.yaml new file mode 100644 index 0000000..63f1a02 --- /dev/null +++ b/charts/vulpes-backend/templates/httproute.yaml @@ -0,0 +1,38 @@ +{{- if .Values.httpRoute.enabled -}} +{{- $fullName := include "micronaut.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} diff --git a/charts/vulpes-backend/templates/ingress.yaml b/charts/vulpes-backend/templates/ingress.yaml new file mode 100644 index 0000000..7967720 --- /dev/null +++ b/charts/vulpes-backend/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "micronaut.fullname" . }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "micronaut.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/vulpes-backend/templates/pdb.yaml b/charts/vulpes-backend/templates/pdb.yaml new file mode 100644 index 0000000..18e3afe --- /dev/null +++ b/charts/vulpes-backend/templates/pdb.yaml @@ -0,0 +1,17 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "micronaut.fullname" . }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} +spec: + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- else }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable | default 1 }} + {{- end }} + selector: + matchLabels: + {{- include "micronaut.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/vulpes-backend/templates/rbac.yaml b/charts/vulpes-backend/templates/rbac.yaml new file mode 100644 index 0000000..9bbd2d2 --- /dev/null +++ b/charts/vulpes-backend/templates/rbac.yaml @@ -0,0 +1,33 @@ +{{- if .Values.rbac.create -}} +{{- /* + Namespace-scoped read access for micronaut-kubernetes. services/endpoints are + used by the discovery client; pods is read by the KubernetesHealthIndicator + (it looks up its own pod for /health). Scoped to the release namespace only, + so it grants no cross-namespace access. +*/ -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "micronaut.fullname" . }}-discovery + labels: + {{- include "micronaut.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["services", "endpoints", "pods"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "micronaut.fullname" . }}-discovery + labels: + {{- include "micronaut.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "micronaut.fullname" . }}-discovery +subjects: + - kind: ServiceAccount + name: {{ include "micronaut.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/vulpes-backend/templates/secret.yaml b/charts/vulpes-backend/templates/secret.yaml new file mode 100644 index 0000000..94421f1 --- /dev/null +++ b/charts/vulpes-backend/templates/secret.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.secrets.enabled .Values.secrets.files }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ include "micronaut.fullname" . }}-secrets + labels: + {{- include "micronaut.labels" . | nindent 4 }} +stringData: +{{- range $p, $content := .Values.secrets.files }} + application-{{ $p }}.yml: |- +{{- nindent 4 $content }} +{{- end }} +{{- end }} diff --git a/charts/vulpes-backend/templates/service.yaml b/charts/vulpes-backend/templates/service.yaml new file mode 100644 index 0000000..bc6c31a --- /dev/null +++ b/charts/vulpes-backend/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "micronaut.fullname" . }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "micronaut.selectorLabels" . | nindent 4 }} diff --git a/charts/vulpes-backend/templates/serviceaccount.yaml b/charts/vulpes-backend/templates/serviceaccount.yaml new file mode 100644 index 0000000..748e7b2 --- /dev/null +++ b/charts/vulpes-backend/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "micronaut.serviceAccountName" . }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/charts/vulpes-backend/templates/servicemonitor.yaml b/charts/vulpes-backend/templates/servicemonitor.yaml new file mode 100644 index 0000000..3cfa22d --- /dev/null +++ b/charts/vulpes-backend/templates/servicemonitor.yaml @@ -0,0 +1,43 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "micronaut.fullname" . }} + {{- with .Values.metrics.serviceMonitor.namespace }} + namespace: {{ . }} + {{- end }} + labels: + {{- include "micronaut.labels" . | nindent 4 }} + {{- with .Values.metrics.serviceMonitor.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "micronaut.selectorLabels" . | nindent 6 }} + endpoints: + - port: {{ .Values.metrics.port }} + path: {{ .Values.metrics.path }} + scheme: {{ .Values.metrics.scheme }} + {{- with .Values.metrics.serviceMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/vulpes-backend/templates/tests/test-connection.yaml b/charts/vulpes-backend/templates/tests/test-connection.yaml new file mode 100644 index 0000000..77121e6 --- /dev/null +++ b/charts/vulpes-backend/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "micronaut.fullname" . }}-test-connection" + labels: + {{- include "micronaut.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "micronaut.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/vulpes-backend/values.yaml b/charts/vulpes-backend/values.yaml new file mode 100644 index 0000000..7c1a30a --- /dev/null +++ b/charts/vulpes-backend/values.yaml @@ -0,0 +1,276 @@ +# Default values for vulpes-backend. +# +# This chart is a fork of the generic `micronaut` chart that lives in the +# cluster repository (Kubernetes-FLUX, helm/micronaut), taken so that Vulpes +# ships its own deployment alongside its image and the two are versioned from +# one release. `otis` still uses the original, so the two will drift; the +# template helpers here are deliberately still named `micronaut.*` and the +# files kept otherwise identical, which makes `diff -r` against helm/micronaut +# the way to pull a later fix across. +# +# Only what identifies *this* application is changed from the original below +# (image, port, metrics path). Everything environment-specific -- hostnames, +# replica counts, database secrets, log labels -- stays in the cluster +# repository's overlays. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +# Deployment update strategy. Empty -> Kubernetes default (RollingUpdate 25%/25%). +# For seamless updates with >=2 replicas, set maxUnavailable: 0 / maxSurge: 1. +strategy: {} + # type: RollingUpdate + # rollingUpdate: + # maxUnavailable: 0 + # maxSurge: 1 + +# Scheduling priority. Empty -> cluster default PriorityClass. +priorityClassName: "" + +# PodDisruptionBudget for the workload. Disabled by default so single-replica +# apps are unaffected. Set minAvailable OR maxUnavailable (minAvailable wins). +podDisruptionBudget: + enabled: false + minAvailable: "" + maxUnavailable: "" + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: harbor.onelitefeather.dev/onelitefeather/vulpes-backend + # This sets the pull policy for images. + pullPolicy: IfNotPresent + # Left empty on purpose: it falls back to .Chart.AppVersion, which Release + # Please moves together with the chart version. Setting it in an overlay + # pins the image away from the chart it was released with -- and Spegel + # caches mutable tags P2P, so "latest" is not an option here either. + tag: "" + +# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: 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: "" + +# RBAC for Micronaut Kubernetes service discovery. +# When enabled, creates a namespace-scoped Role + RoleBinding granting the +# ServiceAccount read access to services and endpoints in the release namespace, +# as required by micronaut-kubernetes-discovery-client. Off by default. +rbac: + create: false + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +# Pod-level. Verified against the otis and vulpes-backend images with a probe +# pod: both reach Micronaut context startup as uid 1000 with a read-only root +# filesystem, so nothing here depends on running as root. +securityContext: + enabled: true + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + # The port the Micronaut server binds (micronaut.server.port), not the + # chart's generic 80. + port: 8080 + +# Micrometer's Prometheus endpoint, which this application exposes in every +# environment -- so it is on here rather than repeated in each overlay. +metrics: + enabled: true + path: /prometheus + port: http + scheme: http + serviceMonitor: + enabled: false + namespace: "" + additionalLabels: {} + annotations: {} + interval: "" + scrapeTimeout: "" + relabelings: [] + metricRelabelings: [] + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +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 + +httpRoute: + enabled: false + annotations: {} + parentRefs: + - name: gateway + sectionName: http + hostnames: + - chart-example.local + rules: + - matches: + - path: + type: PathPrefix + value: / + +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: 1 + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: /health + port: http +readinessProbe: + httpGet: + path: /health + port: http + +# Off by default. Mainly needed when tracing.enabled: OTel bytecode +# instrumentation adds real seconds to JVM cold start (class transformation +# on Micronaut/Hibernate/Hikari), which can push first-successful-health +# past livenessProbe's steady-state deadline and SIGTERM-loop the pod +# before it ever finishes starting. Uses the readiness path/port since +# that's the only health endpoint the app exposes. +startupProbe: + enabled: false + failureThreshold: 30 + periodSeconds: 10 + +# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Specific +profiles: ["prod"] # This is for setting up the Micronaut application profiles. + +# This section is for setting up the Micronaut application configuration. +config: + base: |- + micronaut: + application: + name: Vulpes Backend + profiles: + prod: |- + # application-prod.yml + logger: + levels: + root: INFO + feature-x: |- + # application-feature-x.yml + feature: + x: true + +secrets: + enabled: false + files: {} + # Example: + # files: + # prod: |- + # datasource: + # username: ${DB_USER} + # password: ${DB_PASS} + +# Environment-Variablen +env: + - name: JAVA_TOOL_OPTIONS + value: "-XX:MaxRAMPercentage=75.0 -Duser.timezone=UTC" + +envFrom: [] + +# Traces-only OTel Java agent instrumentation. Metrics/logs already flow via +# the chart's own Prometheus scrape (metrics.serviceMonitor) and container +# stdout shipping, so only OTEL_TRACES_EXPORTER is enabled here to avoid +# duplicating either. An initContainer copies the javaagent jar out of the +# upstream image into an emptyDir the main container mounts read-only -- +# no OpenTelemetry Operator/CRDs involved, just the jar-holder image it publishes. +tracing: + enabled: false + # Matches the tempo-gateway target already used by EnvoyProxy's native + # tracing and the alloy-receiver OTLP gateway elsewhere in this cluster. + endpoint: http://tempo-gateway.grafana.svc.cluster.local:4317 + # otis/vulpes-backend run JDK 25; older javaagent releases fail to attach + # there (InaccessibleObjectException on ClassLoader.findLoadedClass -- + # module java.base doesn't opens java.lang to the unnamed module under + # newer JDKs). 2.29.0 is the latest available and includes the JDK23+ + # MethodHandles.Lookup fix for boot-loader class injection. + image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.30.0 + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + +containerSecurityContext: + enabled: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] diff --git a/release-please-config.json b/release-please-config.json index 75d48ae..f8204f9 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -13,6 +13,10 @@ { "type": "generic", "path": "build.gradle.kts" + }, + { + "type": "generic", + "path": "charts/vulpes-backend/Chart.yaml" } ] }