diff --git a/.gitignore b/.gitignore index 964230e..1524b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ tests/load_snana_fits_reconstruct_indexes_constraints.sql # Env file /.env +/helm/fastdb/*secrets*.yaml # Emacs backup files **~ diff --git a/HOWTO_LOGIN.md b/HOWTO_LOGIN.md new file mode 100644 index 0000000..3682db0 --- /dev/null +++ b/HOWTO_LOGIN.md @@ -0,0 +1,95 @@ +# How to Create a User and Log In + +FASTDB uses challenge-response authentication (RKAuth). A user record must +contain an RSA public key and an encrypted private key before that user can log +in. The Helm deployment provides two ways to create those credentials: + +1. Use a password-reset email captured by MailHog. This is the intended local + Kind workflow. +2. Generate the credentials locally and insert them directly into PostgreSQL. + This is the intended single-node VM workflow. + +## Local Kind: create a user through MailHog + +The local workflow exercises the same browser-based password-reset process a +normal web user would use. MailHog captures the email locally instead of +sending it to a real address. + +First install FASTDB using the local Kind helpers: + +```bash +./helm/scripts/create-local-cluster.sh +./helm/scripts/install-local-fastdb.sh +``` + +Then run: + +```bash +./helm/scripts/create-user-local.sh +``` + +The script: + +1. Creates the fixed development account `test_user` in PostgreSQL. +2. Requests a password-reset email from FASTDB. +3. Reads the reset link from MailHog. +4. Opens FASTDB, the reset page, and MailHog in the browser. + +Enter the desired password on the reset page. The browser generates the RSA +key pair, encrypts the private key with the password, and stores the resulting +credentials in FASTDB. + +The local services are available at: + +| Service | URL | +|---|---| +| FASTDB | `http://localhost:8080` | +| MailHog | `http://localhost:8025` | + +If `test_user` already exists, use the **Request Password Reset** option on the +FASTDB login page rather than trying to create the account again. + +## Single-node VM: insert a user directly + +A VM may not have browser access or a public route to its FASTDB and MailHog +services. In that case, create a complete user record directly from the VM: + +```bash +./helm/scripts/create-user-singlenode.sh +``` + +By default, the script operates in the `fastdb-arbutus-dev` namespace. Set a +different namespace when necessary: + +```bash +FASTDB_NAMESPACE= ./helm/scripts/create-user-singlenode.sh +``` + +The script prompts for the username, display name, email, and password. It +then: + +1. Uses the FASTDB shell image to generate the RKAuth keys and SQL. +2. Sends the generated SQL directly to PostgreSQL through `kubectl exec`. +3. Removes the password and generated SQL from its shell variables. + +The password is read without displaying it and is not included in the SQL or +passed as a command-line argument. This workflow does not require MailHog or a +browser-accessible password-reset link. + +After either workflow completes, log in with the username and password that +were created. Applications using the FASTDB Python client will also need those +credentials in their own `.fastdb.ini` configuration or secret-management +system; neither user-creation script creates client configuration. + +## Manual MailHog reset + +To reset an existing user's password manually: + +1. Open the FASTDB login page. +2. Select **Request Password Reset**. +3. Enter the username or email address. +4. Open MailHog and follow the link in the captured message. +5. Enter and confirm the new password. + +This requires `mailhog.enabled: true` and network access to both FASTDB and the +MailHog web interface. diff --git a/admin/local/kind-config.yaml b/admin/local/kind-config.yaml new file mode 100644 index 0000000..239d229 --- /dev/null +++ b/admin/local/kind-config.yaml @@ -0,0 +1,28 @@ +# Kind cluster config for local FASTDB Helm deployment. +# +# Used by helm/scripts/create-local-cluster.sh. The script replaces ${PWD} with +# the repo root path before passing this file to `kind create cluster`. +# +# extraMounts expose the host build tree inside the Kind node so pods can +# mount /fastdb-install and /fastdb-db via hostPath (values-local.yaml: +# volumes.codeHostPath: true). +# +# extraPortMappings map the webap NodePort (30080) to localhost:8080 and the +# MailHog HTTP NodePort (30025) to localhost:8025. + +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + extraMounts: + - hostPath: ${PWD}/install + containerPath: /fastdb-install + - hostPath: ${PWD}/db + containerPath: /fastdb-db + extraPortMappings: + - containerPort: 30080 + hostPort: 8080 + protocol: TCP + - containerPort: 30025 + hostPort: 8025 + protocol: TCP diff --git a/docker/webserver/Dockerfile b/docker/webserver/Dockerfile index b043bc7..f0dff85 100644 --- a/docker/webserver/Dockerfile +++ b/docker/webserver/Dockerfile @@ -105,7 +105,9 @@ RUN source /venv/bin/activate && \ "confluent_kafka==2.15.0,<3.0.0" \ "fastavro>=1.10.0,<2.0.0" \ "fink-client>=10.1,<11.0" \ + "hats==0.11.0" \ "healpy>=1.20.0,<2.0.0" \ + "lsdb==0.11.0" \ "nested-pandas>=0.7.1,<0.8.0" \ "numpy>=2.5.1,<3.0.0" \ "pandas>=3.0.5,<4.0.0" \ @@ -149,12 +151,6 @@ RUN git clone https://github.com/mongodb/mongo-tools.git \ && cd .. \ && rm -rf mongo-tools -# lsdb in PyPi requires older versions of pandas and nested_pandas than -# we want. This dev branch has updates in pyproject.toml -RUN source /venv/bin/activate \ - && pip --no-cache install "hats @ git+https://github.com/astronomy-commons/hats.git@pandas3" \ - "lsdb @ git+https://github.com/astronomy-commons/lsdb.git@pandas3" - # ====================================================================== # This is for interaction. It runs something that will stay up that you can exec a shell on. FROM base AS shell diff --git a/helm/LOCAL_KIND.md b/helm/LOCAL_KIND.md new file mode 100644 index 0000000..01ae803 --- /dev/null +++ b/helm/LOCAL_KIND.md @@ -0,0 +1,43 @@ +# Local Kind development + +This optional workflow runs FASTDB on a laptop using Kind. For a single-VM +benchmark deployment, follow the [main guide](README.md). + +Install Docker, Docker Compose, Kind, Helm and kubectl. From the FASTDB +repository root, initialize submodules and run: + +```bash +git submodule update --init --recursive +./helm/scripts/create-local-cluster.sh +./helm/scripts/install-local-fastdb.sh +kubectl --context kind-fastdb-local get pods,pvc -n fastdb-local +``` + +The first script creates the cluster. The second builds and loads the images, +generates `install/`, and installs the chart using +[values-local.yaml](fastdb/values-local.yaml). It uses a different workflow and +image configuration from the single-VM installer. + +Rerun `install-local-fastdb.sh` after changing application code, migrations, +images or chart settings; it updates the existing installation without +deliberately deleting database storage. Verify the configured image tags match +the images actually built by Compose if you change its defaults. + +For local account creation, use: + +```bash +./helm/scripts/create-user-local.sh +``` + +This is the browser/MailHog password-reset workflow described in +[HOWTO_LOGIN.md](../HOWTO_LOGIN.md), rather than the direct user-creation helper +used on a VM. + +To delete the Kind cluster and all FASTDB data stored within it: + +```bash +./helm/scripts/stop-local.sh +``` + +This is a destructive reset, not a pause. The next cluster creation starts a +fresh environment. diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 0000000..8e75eda --- /dev/null +++ b/helm/README.md @@ -0,0 +1,415 @@ +# Deploy FASTDB on a single VM + +This guide is for a maintainer setting up a reproducible FASTDB development or +benchmark installation on one Linux VM. It covers cluster prerequisites, +installation, a test user, simulated LASS alerts, and verification through the +FASTDB Python client on the VM. It is not a full production operations guide. + +Run the commands on the VM as a normal user with Docker and Kubernetes access. +Commands assume the FASTDB repository is your current directory unless stated +otherwise. External access and browser access are outside this guide's scope. + +## Contents + +- [Prepare the VM](#1-prepare-the-vm) +- [Choose deployment settings](#2-choose-deployment-settings) +- [Install FASTDB](#3-install-fastdb) +- [Verify the deployment](#4-verify-the-deployment) +- [Create a user and query FASTDB](#5-create-a-user-and-query-fastdb) +- [Ingest simulated LASS alerts](#6-ingest-simulated-lass-alerts) +- [Update an installation](#update-an-installation) +- [Troubleshooting](#troubleshooting) +- [Reference and other workflows](#reference-and-other-workflows) + +## 1. Prepare the VM + +Install a single-node Kubernetes distribution, such as K3s, and configure its +default storage provisioner to put database files on your chosen disk or mounted +filesystem. Prepare and mount that storage before deploying FASTDB. A PVC requests +storage from the cluster; it does not create a separate cloud disk or a backup. + +Install Git, Docker Engine with Docker Compose, Helm, and kubectl. Confirm that +your normal user can run: + +```bash +docker info +docker compose version +kubectl get nodes +kubectl get storageclass +helm version +df -h / +``` + +`kubectl get nodes` should show `Ready`, and a usable storage class should be available. +Allow space for Docker images, Kubernetes images, database data, logs, and LASS's +Kafka data. The example PVC sizes are requests, not preallocated spare disk space; +monitor the underlying filesystem as data grows. + +For a worked Ubuntu/K3s setup, review +[scripts/setup-arbutus-k3s.sh](scripts/setup-arbutus-k3s.sh). It installs the tools +and configures the user's Kubernetes access. It is an infrastructure-specific +example designed for the DRAC arbutus-cloud: inspect its version pins and `DEFAULT_LOCAL_STORAGE_PATH` before using +it. The current path is `/fastdb/single-staging`, assuming CephFS is already +mounted at `/fastdb`. It does not mount CephFS or change an existing K3s +installation's storage path. + +For cluster setup details, use the official +[K3s installation guide](https://docs.k3s.io/quick-start), +[K3s storage guide](https://docs.k3s.io/add-ons/storage), and +[Kubernetes storage documentation](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). + +Clone FASTDB and select the branch or release you intend to test: + +```bash +git clone --recurse-submodules https://github.com/CanDIAPL/FASTDB.git +cd FASTDB +git checkout +git submodule update --init --recursive +``` + +Optional: You can record the commit used for a benchmark with `git rev-parse HEAD`. + +## 2. Choose deployment settings + +Either use one of the existing values files or copy the registry-based single-VM example into your own values file: + +```bash +cp helm/fastdb/values-arbutus.yaml helm/fastdb/values-my-vm.yaml +``` + +This example describes a single-VM deployment. Review these +settings before installing: + +```yaml +global: + namespace: fastdb-arbutus + imageRegistry: images.canfar.net/candiapl + imageTag: manual-20260918 + imagePullPolicy: IfNotPresent + +volumes: + type: pvc + storageClass: local-path + codeHostPath: true +``` + +The installer reads the registry and tag from this file. Change `imageTag` to +use another published image release; keep the FASTDB images on a compatible tag. +The example images were built for AMD64, so use an AMD64 VM for this workflow. + +Set `storageClass` to the class configured in step 1, or use `storageClass: ""` +to request the cluster's default class. Review PostgreSQL, MongoDB, logs, sessions, +and query-result sizes in the example. PostgreSQL standby is disabled. + +Keep the example's LASS ingestion settings for the walkthrough below: topic +`lass-topic`, schema v10.0, collection base `lass`, and processing version +`lass`. The installer discovers the node's internal IP for the consumer's +connection to a LASS broker on the same VM. Other broker configurations are possible but +not described in this walkthrough. + +For subsequent commands, set the namespace to match your values file: + +```bash +export FASTDB_NAMESPACE=fastdb-arbutus +``` + +The installer reads its namespace from the values file, whereas the user helper +reads `FASTDB_NAMESPACE`. Keep them consistent. + +If registry authentication is needed, run: + +```bash +docker login images.canfar.net +``` + +For Kubernetes to use that login as well, enable the following in your values +file (uncomment it in the supplied example): + +```yaml +global: + imagePullSecrets: + - name: fastdb-registry +``` + +Add this under the existing `global:` section. The installer copies the Docker +login file into a Kubernetes pull Secret named `fastdb-registry`. Docker and +Kubernetes have separate image stores and authentication settings; a successful +Docker pull alone does not configure Kubernetes. + +## 3. Install FASTDB + +From the repository root: + +```bash +./helm/scripts/install-singlenode-fastdb.sh helm/fastdb/values-my-vm.yaml +``` + +If the shell image is missing from Docker, the script explains why it is needed +and asks before pulling it. The script then: + +1. Generates database passwords and an application secret key on the first run. +2. Runs the temporary Compose `makeinstall` service to configure and copy the + current checkout into `install/`. +3. Runs Helm to create the database storage, services, migration Job, and + ingestion workloads. Kubernetes obtains the deployment images. +4. Restarts webap, queryrunner, and shell to load the generated code, and checks + the local web service. + +Keep the generated `helm/fastdb/values-arbutus-secrets.yaml` file for upgrades. +It contains plaintext deployment secrets, is ignored by Git, and is created with +permissions restricted to the owner. It is distinct from a FASTDB user's password. +Do not commit it or replace it while reusing existing databases. + +The current application code comes from your checkout, even when using older +registry images. The images supply the runtime dependencies. Changes requiring +new dependencies may require a newer image tag. + +A successful run ends with Helm reporting `STATUS: deployed`, completed +rollouts, and the final workload/storage listing. Startup can take several +minutes on a small VM. Kubernetes continues working if Helm times out; inspect +its state before starting another installation. + +## 4. Verify the deployment + +```bash +helm status fastdb -n "$FASTDB_NAMESPACE" +kubectl get pods,pvc -n "$FASTDB_NAMESPACE" +kubectl get jobs -n "$FASTDB_NAMESPACE" +``` + +Expect: + +- All PVCs `Bound`. +- PostgreSQL, MongoDB, webap, queryrunner, shell, MailHog and brokerconsumer ready. +- `createdb` and `ingestion-processing-version` Jobs completed. +- Periodic source-importer Jobs; a successful finished pod shows `Completed`. + +Brokerconsumer normally shows `2/2`: one consumer container and one Kafka +connection proxy. It is one pod with two containers. + +Inspect the consumer and scheduled importer: + +```bash +kubectl logs deployment/brokerconsumer -c brokerconsumer \ + -n "$FASTDB_NAMESPACE" --tail=100 +kubectl get cronjob source-importer -n "$FASTDB_NAMESPACE" +kubectl get pods -n "$FASTDB_NAMESPACE" --sort-by=.metadata.creationTimestamp +``` + +Use the latest source-importer pod name from that list: + +```bash +kubectl logs \ + -n "$FASTDB_NAMESPACE" --timestamps --tail=100 +``` + +Before LASS starts, Kafka connection/topic warnings are expected. An +empty-collection `NoneType` error in source importer is a known application +issue on checkouts lacking the empty-collection fix; it does not by itself mean +database installation failed. + +## 5. Create a user and query FASTDB + +Create a test account from the VM: + +```bash +FASTDB_NAMESPACE="$FASTDB_NAMESPACE" ./helm/scripts/create-user-singlenode.sh +``` + +The helper prompts for account details and a password, generates authentication +keys, and inserts the account into PostgreSQL. It does not require MailHog or +create a client configuration file. See [user creation details](../HOWTO_LOGIN.md). + +You can test your login using the web app or the python client (NOTE: documentation on how to do this to come soon) + +## 6. Ingest simulated LASS alerts + +Run LASS on the same VM as FASTDB. Use a separate virtual environment with a +Python version supported by the selected LASS release: + +```bash +python3.13 -m venv "$HOME/lass-venv" +source "$HOME/lass-venv/bin/activate" +python -m pip install lass +lass --help +lass -n 100 --avro +``` + +Optional: Record the LASS version with `python -m pip show lass` when benchmarking. + +The current [LASS CLI](https://github.com/CanDIAPL/lass#usage) starts Docker +container `lassbroker`, publishes Kafka on VM port 19092, and sends to +`lass-topic`. Use schema 10 and `--avro` to match the chart's v10.0 schemaless +Avro consumer. LASS defaults to pickle, which does not match that consumer. + +Verify the broker and follow the consumer: + +```bash +docker ps --filter name=lassbroker +kubectl logs -f deployment/brokerconsumer -c brokerconsumer \ + -n "$FASTDB_NAMESPACE" --tail=50 +``` + +Stop following logs with Ctrl-C. The consumer writes alerts to MongoDB; the +source-importer CronJob transfers them to PostgreSQL, on a schedule configured in the +values file. Default in the dev values files is once per minute, production is expected +to be closer to once per hour. +Check the latest importer logs as in step 4 for imported object/source counts. + +## 7. Check everthing worked with the python client + +To check everything worked correctly, open the FASTDB shell and use the Python client to query the db. + +Enter the FASTDB shell pod from the VM: + +```bash +kubectl exec -it deployment/shell -n "$FASTDB_NAMESPACE" -- /bin/bash +``` + +Inside that shell, start Python: + +```bash +python +``` + +Use the Python client to authenticate and run a query through the web API: + +```python +from getpass import getpass +from fastdb.fastdb_client import FASTDBClient + +fdb = FASTDBClient( + "http://webap:8080/", + username=input("FASTDB username: "), + password=getpass("FASTDB password: "), +) + +#check if you are connected to the DB +print(fdb.submit_short_sql_query("SELECT 1 AS connected")) + +#count the number of entries in the diasource table +print(fdb.submit_short_sql_query("SELECT COUNT(*) AS sources FROM diasource")) + +#print the IDs and times for the first new diasources +print(fdb.submit_short_sql_query( + "SELECT diasourceid, diaobjectid, midpointmjdtai FROM diasource LIMIT 5" +)) +``` + +Use `exit()` to leave Python, then `exit` to leave the pod shell. + +After import succeeds, expect a nonzero source count and example rows. Do not +assume the total equals the number just sent if the database already contains data. +Start with this small run before increasing alert counts, and monitor free space +on both the database filesystem and the filesystem holding Docker/Kafka data. + +This completes the basic check: LASS → Kafka → brokerconsumer → MongoDB → +source importer → PostgreSQL → authenticated FASTDB client. + +## Update an installation + +Keep the same namespace, values file, checkout location, and generated secrets. +After selecting the next code revision, rerun: + +```bash +./helm/scripts/install-singlenode-fastdb.sh helm/fastdb/values-my-vm.yaml +``` + +The installer regenerates `install/`, recreates the one-shot setup Jobs, applies +migrations and Helm changes, and restarts services. It should preserve existing +database PVCs, it is not a database reset. Review migrations and back up useful +data before an update. Changing the namespace creates a different deployment. +An image-tag change and a source-code change are separate choices. + +## Troubleshooting + +AI-generated tips on troubleshooting issues with the fastdb deployment: + +Collect evidence before deleting resources: + +```bash +helm status fastdb -n "$FASTDB_NAMESPACE" +kubectl get pods,pvc,jobs -n "$FASTDB_NAMESPACE" +kubectl get events -n "$FASTDB_NAMESPACE" --sort-by=.metadata.creationTimestamp +kubectl logs job/createdb -n "$FASTDB_NAMESPACE" --all-containers=true +kubectl logs job/ingestion-processing-version -n "$FASTDB_NAMESPACE" +``` + +For a particular failing pod: + +```bash +kubectl describe pod -n "$FASTDB_NAMESPACE" +kubectl logs -n "$FASTDB_NAMESPACE" --all-containers=true --tail=100 +kubectl logs -n "$FASTDB_NAMESPACE" --previous --tail=100 +``` + +`--previous` reads the previous container attempt and only works if one exists. +Use `-c ` for a pod with multiple containers. Completed Jobs and +pods can be cleaned up, so collect their logs promptly. + +| Symptom | What to inspect | +| --- | --- | +| PVC stays `Pending` | `kubectl describe pvc -n "$FASTDB_NAMESPACE"`; confirm the storage class, mount and free space. For K3s, inspect `kubectl logs -n kube-system deployment/local-path-provisioner`. | +| Helm times out | Check PVCs, pod events and setup Job logs. A failed Helm status can remain even if workloads later recover. | +| MongoDB repeatedly restarts | Inspect pod events for failed liveness checks and previous logs. Use a chart revision with the startup-probe fix. | +| Ingestion reports missing tables/columns | Confirm migrations completed before processing-version setup and importer execution. Do not repeatedly start ingestion against an incomplete schema. | +| Image pull fails | Verify registry, tag, architecture, credentials and Kubernetes pull Secret. | +| Cannot write `install/` | Check ownership of the checkout's `install/` and `db/` directories; use the maintainer account rather than running the installer as root. | + +For VM resource diagnostics, use `df -h`, `free -h`, `uptime`, +`docker system df`, and `kubectl top nodes` (if metrics are available). +A PVC's requested capacity is not a measurement of disk consumption. + +## Reference and other workflows + +### What the installation uses + +- [Chart defaults](fastdb/values.yaml) supply settings not overridden in your file. +- [Registry-image VM example](fastdb/values-arbutus.yaml) is the walkthrough's base. +- [Locally built image example](fastdb/values-arbutus-dev.yaml) uses `fastdb.local`. +- [Templates](fastdb/templates/) define Deployments, Jobs, Services, Secrets and PVCs. +- `install/` contains generated runtime files. Pods mount it at `/fastdb` using + `hostPath`; migrations are mounted separately from `db/`. Keep the checkout + in place. This is a single-node workflow. +- Database credentials are supplied through Kubernetes Secrets; user accounts + are separate from those infrastructure credentials. + +For tool details, see [Helm's usage guide](https://helm.sh/docs/intro/using_helm/) +and [Kubernetes workload documentation](https://kubernetes.io/docs/concepts/workloads/). + +### Build images locally + +For K3s, this helper builds images from the checkout and loads them into K3s: + +```bash +./helm/scripts/build-local-images.sh fastdb.local test20260428 +./helm/scripts/install-singlenode-fastdb.sh helm/fastdb/values-arbutus-dev.yaml +``` + +If you choose another registry/tag for the build, update the values file to match. +The image-loading helper specifically uses K3s; other distributions need their +own image import method or a reachable registry. + +### Run the preparation and Helm steps manually + +The installer is the recommended walkthrough because it also prepares secrets, +registry access, host paths and service restarts. Its two principal operations are +Compose `makeinstall` and `helm upgrade --install`. See +[scripts/install-singlenode-fastdb.sh](scripts/install-singlenode-fastdb.sh) +for the complete commands and overrides. Running bare `helm install` against +the example values does not generate the host's `install/` tree. + +### Local laptop development + +Use the separate [Kind guide](LOCAL_KIND.md). + +### Removing a benchmark installation + +Removing a Helm release and deleting its data are different operations. +Inspect the namespace's PVCs and the volumes' reclaim policies before uninstalling. +Deleting PVCs or the namespace can permanently delete the benchmark databases. +Do not use namespace deletion as an upgrade step. LASS's `lass --quit` removes +its Kafka container and simulated broker data independently of FASTDB. + +After verification, keep a record of the FASTDB commit, image tag, values, +Kubernetes version and LASS version alongside benchmark results. diff --git a/helm/fastdb/.helmignore b/helm/fastdb/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/fastdb/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/fastdb/Chart.yaml b/helm/fastdb/Chart.yaml new file mode 100644 index 0000000..10bc4ff --- /dev/null +++ b/helm/fastdb/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: fastdb +description: FASTDB - Fast Astronomical Source Transient Database +type: application +version: 0.1.0 +appVersion: "1.0.0" +keywords: + - astronomy + - database + - LSST + - transients +maintainers: + - name: FASTDB Team diff --git a/helm/fastdb/templates/_helpers.tpl b/helm/fastdb/templates/_helpers.tpl new file mode 100644 index 0000000..9f44aa2 --- /dev/null +++ b/helm/fastdb/templates/_helpers.tpl @@ -0,0 +1,131 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "fastdb.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "fastdb.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "fastdb.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Namespace +*/}} +{{- define "fastdb.namespace" -}} +{{- .Values.global.namespace }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "fastdb.labels" -}} +helm.sh/chart: {{ include "fastdb.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Build image reference from registry, repository, and tag +Usage: {{ include "fastdb.image" (dict "image" .Values.postgres.image "global" .Values.global) }} +*/}} +{{- define "fastdb.image" -}} +{{- $registry := .global.imageRegistry -}} +{{- $repository := .image.repository -}} +{{- $tag := default .global.imageTag .image.tag -}} +{{- if $registry -}} +{{- printf "%s/%s:%s" $registry $repository $tag -}} +{{- else -}} +{{- printf "%s:%s" $repository $tag -}} +{{- end -}} +{{- end }} + +{{/* +Build image reference for external images (like mailhog) +Usage: {{ include "fastdb.externalImage" .Values.mailhog.image }} +*/}} +{{- define "fastdb.externalImage" -}} +{{- $registry := default "" .registry -}} +{{- $repository := .repository -}} +{{- $tag := default "latest" .tag -}} +{{- if $registry -}} +{{- printf "%s/%s:%s" $registry $repository $tag -}} +{{- else -}} +{{- printf "%s:%s" $repository $tag -}} +{{- end -}} +{{- end }} + +{{/* +Image pull secrets +*/}} +{{- define "fastdb.imagePullSecrets" -}} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ .name }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +PVC name helper +Usage: {{ include "fastdb.pvcName" (dict "namespace" .Values.global.namespace "component" "postgres") }} +*/}} +{{- define "fastdb.pvcName" -}} +{{- printf "%s-%s-pvc" .namespace .component -}} +{{- end }} + +{{/* Wait for a prerequisite Job using Python's standard library and the pod's +service account. A failed Job stops the dependent workload with a clear error. */}} +{{- define "fastdb.waitForJob" -}} +/venv/bin/python -u - <<'PY' +import json +import pathlib +import ssl +import time +import urllib.error +import urllib.request + +account = pathlib.Path('/var/run/secrets/kubernetes.io/serviceaccount') +namespace = (account / 'namespace').read_text().strip() +token = (account / 'token').read_text().strip() +context = ssl.create_default_context(cafile=str(account / 'ca.crt')) +job = {{ . | quote }} +url = f'https://kubernetes.default.svc/apis/batch/v1/namespaces/{namespace}/jobs/{job}' +print(f'Waiting for Job {job} to complete...') +while True: + request = urllib.request.Request(url, headers={'Authorization': f'Bearer {token}'}) + try: + with urllib.request.urlopen(request, context=context, timeout=30) as response: + status = json.load(response).get('status', {}) + except urllib.error.HTTPError as error: + if error.code != 404: + raise + else: + conditions = {item['type']: item['status'] for item in status.get('conditions', [])} + if conditions.get('Complete') == 'True': + print(f'Job {job} completed.') + break + if conditions.get('Failed') == 'True': + raise SystemExit(f'Prerequisite Job {job} failed; inspect its logs.') + time.sleep(5) +PY +{{- end }} diff --git a/helm/fastdb/templates/createdb-job.yaml b/helm/fastdb/templates/createdb-job.yaml new file mode 100644 index 0000000..8109331 --- /dev/null +++ b/helm/fastdb/templates/createdb-job.yaml @@ -0,0 +1,59 @@ +{{- if .Values.createdb.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: createdb + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +spec: + # Ingestion checks this Job's completion status. Keep it until the installer + # recreates it on the next upgrade, rather than deleting it after five minutes. + {{- if not .Values.ingestion.enabled }} + ttlSecondsAfterFinished: {{ .Values.createdb.ttlSecondsAfterFinished }} + {{- end }} + template: + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + restartPolicy: OnFailure + initContainers: + - name: wait-for-postgres + image: {{ include "fastdb.externalImage" .Values.utilityImages.busybox }} + imagePullPolicy: {{ .Values.utilityImages.busybox.pullPolicy }} + command: ["sh", "-c", "until nc -z postgres 5432; do echo 'Waiting for PostgreSQL...'; sleep 2; done"] + containers: + - name: createdb + image: {{ include "fastdb.image" (dict "image" .Values.createdb.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + command: ["/bin/sh", "-c"] + args: + - 'exec python apply_migrations.py -p "$(cat /secrets/pgpasswd)"' + workingDir: /fastdb/db + volumeMounts: + - name: secrets + mountPath: /secrets + readOnly: true + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-db + mountPath: /fastdb/db + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + mountPath: /fastdb + {{- end }} + volumes: + - name: secrets + secret: + secretName: secrets + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-db + hostPath: + path: {{ .Values.volumes.hostPaths.db | quote }} + type: Directory + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + persistentVolumeClaim: + claimName: {{ $namespace }}-code-pvc + {{- end }} +{{- end }} diff --git a/helm/fastdb/templates/ingestion-job-access.yaml b/helm/fastdb/templates/ingestion-job-access.yaml new file mode 100644 index 0000000..7c3950d --- /dev/null +++ b/helm/fastdb/templates/ingestion-job-access.yaml @@ -0,0 +1,34 @@ +{{- if .Values.ingestion.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +# Ingestion only needs permission to read the status of its prerequisite Jobs. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ingestion-job-waiter + namespace: {{ $namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: ingestion-job-waiter + namespace: {{ $namespace }} +rules: +- apiGroups: ["batch"] + resources: ["jobs"] + resourceNames: ["createdb", "ingestion-processing-version"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: ingestion-job-waiter + namespace: {{ $namespace }} +subjects: +- kind: ServiceAccount + name: ingestion-job-waiter + namespace: {{ $namespace }} +roleRef: + kind: Role + name: ingestion-job-waiter + apiGroup: rbac.authorization.k8s.io +{{- end }} diff --git a/helm/fastdb/templates/mailhog.yaml b/helm/fastdb/templates/mailhog.yaml new file mode 100644 index 0000000..f1e9105 --- /dev/null +++ b/helm/fastdb/templates/mailhog.yaml @@ -0,0 +1,78 @@ +{{- if .Values.mailhog.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mailhog + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: mailhog +spec: + replicas: {{ .Values.mailhog.replicas }} + selector: + matchLabels: + app: mailhog + template: + metadata: + labels: + app: mailhog + spec: + containers: + - name: mailhog + image: {{ include "fastdb.externalImage" .Values.mailhog.image }} + imagePullPolicy: {{ .Values.mailhog.image.pullPolicy }} + ports: + - containerPort: 1025 + name: smtp + - containerPort: 8025 + name: http + readinessProbe: + tcpSocket: + port: 8025 + initialDelaySeconds: 5 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: mailhog + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: mailhog +spec: + selector: + app: mailhog + ports: + - name: smtp + port: 1025 + targetPort: 1025 + - name: http + port: 8025 + targetPort: 8025 + type: ClusterIP +{{- if .Values.mailhog.externalAccess.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: mailhog-external + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: mailhog +spec: + selector: + app: mailhog + ports: + - name: http + port: 8025 + targetPort: 8025 + {{- if eq .Values.mailhog.externalAccess.type "NodePort" }} + nodePort: {{ .Values.mailhog.externalAccess.nodePort }} + {{- end }} + type: {{ .Values.mailhog.externalAccess.type }} +{{- end }} +{{- end }} diff --git a/helm/fastdb/templates/mongodb.yaml b/helm/fastdb/templates/mongodb.yaml new file mode 100644 index 0000000..af5cb54 --- /dev/null +++ b/helm/fastdb/templates/mongodb.yaml @@ -0,0 +1,85 @@ +{{- if .Values.mongodb.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongodb + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: mongodb +spec: + replicas: {{ .Values.mongodb.replicas }} + strategy: + type: {{ .Values.mongodb.updateStrategy.type }} + selector: + matchLabels: + app: mongodb + template: + metadata: + labels: + app: mongodb + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + containers: + - name: mongodb + image: {{ include "fastdb.image" (dict "image" .Values.mongodb.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + envFrom: + - configMapRef: + name: mongodb-config + ports: + - containerPort: 27017 + volumeMounts: + - name: mongodb-data + mountPath: /data/db + # Initialization uses a temporary localhost-only server. Wait for the + # final server to listen on the pod IP before enabling health checks. + # Allow up to ten minutes for initialization on a slow VM. + startupProbe: + tcpSocket: + port: 27017 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 120 + readinessProbe: + exec: + command: + - mongosh + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 10 + livenessProbe: + exec: + command: + - mongosh + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 10 + volumes: + - name: mongodb-data + persistentVolumeClaim: + claimName: {{ $namespace }}-mongodb-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: mongodb + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: mongodb +spec: + selector: + app: mongodb + ports: + - name: mongodb + port: 27017 + targetPort: 27017 + type: ClusterIP +{{- end }} diff --git a/helm/fastdb/templates/postgres-standby.yaml b/helm/fastdb/templates/postgres-standby.yaml new file mode 100644 index 0000000..9f774a6 --- /dev/null +++ b/helm/fastdb/templates/postgres-standby.yaml @@ -0,0 +1,94 @@ +{{- if .Values.postgresStandby.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres-standby + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres-standby +spec: + replicas: {{ .Values.postgresStandby.replicas }} + selector: + matchLabels: + app: postgres-standby + template: + metadata: + labels: + app: postgres-standby + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + initContainers: + - name: volume-mount-hack + image: {{ include "fastdb.externalImage" .Values.utilityImages.busybox }} + imagePullPolicy: {{ .Values.utilityImages.busybox.pullPolicy }} + command: ["sh", "-c", "chown 100:108 /pgdata"] + securityContext: + runAsUser: 0 + volumeMounts: + - name: postgres-standby-data + mountPath: /pgdata + containers: + - name: postgres-standby + image: {{ include "fastdb.image" (dict "image" .Values.postgresStandby.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + env: + - name: POSTGRES_DATA_DIR + value: /var/lib/postgresql/data + - name: PRIMARY_HOST + value: {{ .Values.postgresStandby.primaryHost }} + - name: PRIMARY_PORT + value: {{ .Values.postgresStandby.primaryPort | quote }} + - name: REPLICATION_SLOT + value: {{ .Values.postgresStandby.replicationSlot }} + ports: + - containerPort: 5432 + volumeMounts: + - name: postgres-standby-data + mountPath: /var/lib/postgresql/data + - name: secrets + mountPath: /secrets + readOnly: true + - name: dshm + mountPath: /dev/shm + readinessProbe: + tcpSocket: + port: 5432 + initialDelaySeconds: 30 + periodSeconds: 5 + livenessProbe: + tcpSocket: + port: 5432 + initialDelaySeconds: 60 + periodSeconds: 10 + volumes: + - name: postgres-standby-data + persistentVolumeClaim: + claimName: {{ $namespace }}-postgres-standby-pvc + - name: secrets + secret: + secretName: secrets + - name: dshm + emptyDir: + medium: Memory + sizeLimit: {{ .Values.postgresStandby.sharedMemory }} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres-ro + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres-standby +spec: + selector: + app: postgres-standby + ports: + - name: postgres + port: 5432 + targetPort: 5432 + type: ClusterIP +{{- end }} diff --git a/helm/fastdb/templates/postgres.yaml b/helm/fastdb/templates/postgres.yaml new file mode 100644 index 0000000..479af47 --- /dev/null +++ b/helm/fastdb/templates/postgres.yaml @@ -0,0 +1,155 @@ +{{- if .Values.postgres.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres +spec: + replicas: {{ .Values.postgres.replicas }} + strategy: + type: {{ .Values.postgres.updateStrategy.type }} + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + initContainers: + - name: volume-mount-hack + image: {{ include "fastdb.externalImage" .Values.utilityImages.busybox }} + imagePullPolicy: {{ .Values.utilityImages.busybox.pullPolicy }} + {{- if .Values.postgres.walArchive.enabled }} + command: ["sh", "-c", "chown 100:108 /pgdata && mkdir -p /wal-archive && chown 100:108 /wal-archive"] + {{- else }} + command: ["sh", "-c", "chown 100:108 /pgdata"] + {{- end }} + securityContext: + runAsUser: 0 + volumeMounts: + - name: postgres-data + mountPath: /pgdata + {{- if .Values.postgres.walArchive.enabled }} + - name: wal-archive + mountPath: /wal-archive + {{- end }} + containers: + - name: postgres + image: {{ include "fastdb.image" (dict "image" .Values.postgres.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + {{- if .Values.postgres.securityContext.runAsUser }} + securityContext: + runAsUser: {{ .Values.postgres.securityContext.runAsUser }} + runAsNonRoot: {{ .Values.postgres.securityContext.runAsNonRoot }} + allowPrivilegeEscalation: {{ .Values.postgres.securityContext.allowPrivilegeEscalation }} + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: false + {{- end }} + env: + - name: POSTGRES_DATA_DIR + value: /var/lib/postgresql/data + ports: + - containerPort: 5432 + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + - name: secrets + mountPath: /secrets + readOnly: true + - name: dshm + mountPath: /dev/shm + {{- if .Values.postgres.walArchive.enabled }} + - name: wal-archive + mountPath: /wal-archive + {{- end }} + # First-time initialization creates the database and extensions before + # PostgreSQL starts listening on TCP. Keep the liveness probe disabled + # until that work has had enough time to finish on a small VM. + startupProbe: + tcpSocket: + port: 5432 + periodSeconds: 5 + failureThreshold: 120 + readinessProbe: + tcpSocket: + port: 5432 + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + tcpSocket: + port: 5432 + initialDelaySeconds: 30 + periodSeconds: 10 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: {{ $namespace }}-postgres-pvc + - name: secrets + secret: + secretName: secrets + - name: dshm + emptyDir: + medium: Memory + sizeLimit: {{ .Values.postgres.sharedMemory }} + {{- if .Values.postgres.walArchive.enabled }} + - name: wal-archive + persistentVolumeClaim: + claimName: {{ $namespace }}-wal-archive-pvc + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres +spec: + selector: + app: postgres + ports: + - name: postgres + port: {{ .Values.postgres.service.port }} + targetPort: 5432 + type: {{ .Values.postgres.service.type }} +{{- if .Values.postgres.externalAccess.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres-external + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres + {{- with .Values.postgres.externalAccess.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + app: postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 + {{- if eq .Values.postgres.externalAccess.type "NodePort" }} + nodePort: {{ .Values.postgres.externalAccess.nodePort }} + {{- end }} + type: {{ .Values.postgres.externalAccess.type }} + {{- if eq .Values.postgres.externalAccess.type "LoadBalancer" }} + allocateLoadBalancerNodePorts: false + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/fastdb/templates/pvcs.yaml b/helm/fastdb/templates/pvcs.yaml new file mode 100644 index 0000000..8ad1aa9 --- /dev/null +++ b/helm/fastdb/templates/pvcs.yaml @@ -0,0 +1,167 @@ +{{- if eq .Values.volumes.type "pvc" }} +{{- $namespace := include "fastdb.namespace" . }} + +{{/* PostgreSQL PVC */}} +{{- if .Values.postgres.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-postgres-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres +spec: + accessModes: + - {{ .Values.postgres.persistence.accessMode }} + {{- if .Values.volumes.storageClass }} + storageClassName: {{ .Values.volumes.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgres.persistence.size }} + volumeMode: Filesystem +{{- end }} + +{{/* PostgreSQL WAL Archive PVC */}} +{{- if and .Values.postgres.enabled .Values.postgres.walArchive.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-wal-archive-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres +spec: + accessModes: + - {{ .Values.postgres.walArchive.accessMode }} + {{- if .Values.volumes.storageClass }} + storageClassName: {{ .Values.volumes.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgres.walArchive.size }} + volumeMode: Filesystem +{{- end }} + +{{/* PostgreSQL Standby PVC */}} +{{- if .Values.postgresStandby.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-postgres-standby-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: postgres-standby +spec: + accessModes: + - {{ .Values.postgresStandby.persistence.accessMode }} + {{- if .Values.volumes.storageClass }} + storageClassName: {{ .Values.volumes.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgresStandby.persistence.size }} + volumeMode: Filesystem +{{- end }} + +{{/* MongoDB PVC */}} +{{- if .Values.mongodb.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-mongodb-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: mongodb +spec: + accessModes: + - {{ .Values.mongodb.persistence.accessMode }} + {{- if .Values.volumes.storageClass }} + storageClassName: {{ .Values.volumes.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.mongodb.persistence.size }} + volumeMode: Filesystem +{{- end }} + +{{/* Shared Volumes */}} +{{- if .Values.webap.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-sessions-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.webap.volumes.sessions.accessMode }} + resources: + requests: + storage: {{ .Values.webap.volumes.sessions.size }} + volumeMode: Filesystem +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-query-results-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.webap.volumes.queryResults.accessMode }} + resources: + requests: + storage: {{ .Values.webap.volumes.queryResults.size }} + volumeMode: Filesystem +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-logs-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.webap.volumes.logs.accessMode }} + resources: + requests: + storage: {{ .Values.webap.volumes.logs.size }} + volumeMode: Filesystem +{{- end }} + +{{/* Code PVC - shared volume for application code */}} +{{- if .Values.volumes.codePvc.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $namespace }}-code-pvc + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.volumes.codePvc.accessMode }} + {{- if .Values.volumes.storageClass }} + storageClassName: {{ .Values.volumes.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.volumes.codePvc.size }} + volumeMode: Filesystem +{{- end }} + +{{- end }} diff --git a/helm/fastdb/templates/queryrunner.yaml b/helm/fastdb/templates/queryrunner.yaml new file mode 100644 index 0000000..22fe721 --- /dev/null +++ b/helm/fastdb/templates/queryrunner.yaml @@ -0,0 +1,57 @@ +{{- if .Values.queryrunner.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: queryrunner + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: queryrunner +spec: + replicas: {{ .Values.queryrunner.replicas }} + selector: + matchLabels: + app: queryrunner + template: + metadata: + labels: + app: queryrunner + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + containers: + - name: queryrunner + image: {{ include "fastdb.image" (dict "image" .Values.queryrunner.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + volumeMounts: + - name: secrets + mountPath: /secrets + readOnly: true + - name: query-results + mountPath: /query_results + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + mountPath: /fastdb + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + mountPath: /fastdb + {{- end }} + volumes: + - name: secrets + secret: + secretName: secrets + - name: query-results + persistentVolumeClaim: + claimName: {{ $namespace }}-query-results-pvc + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + hostPath: + path: {{ .Values.volumes.hostPaths.install | quote }} + type: Directory + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + persistentVolumeClaim: + claimName: {{ $namespace }}-code-pvc + {{- end }} +{{- end }} diff --git a/helm/fastdb/templates/secrets.yaml b/helm/fastdb/templates/secrets.yaml new file mode 100644 index 0000000..34d26e5 --- /dev/null +++ b/helm/fastdb/templates/secrets.yaml @@ -0,0 +1,44 @@ +{{- if .Values.global.registryCredentials.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.global.registryCredentials.secretName }} + namespace: {{ include "fastdb.namespace" . }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ printf `{"auths":{"%s":{"username":"%s","password":"%s","auth":"%s"}}}` .Values.global.registryCredentials.server .Values.global.registryCredentials.username .Values.global.registryCredentials.password (printf "%s:%s" .Values.global.registryCredentials.username .Values.global.registryCredentials.password | b64enc) | b64enc }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: secrets + namespace: {{ include "fastdb.namespace" . }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +type: Opaque +stringData: + pgpasswd: {{ .Values.secrets.postgres.password | quote }} + postgres_ro_password: {{ .Values.secrets.postgres.roPassword | quote }} + replicator_password: {{ .Values.secrets.postgres.replicatorPassword | quote }} + secretkey: {{ .Values.secrets.secretKey | quote }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: mongodb-config + namespace: {{ include "fastdb.namespace" . }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +data: + MONGODB_HOST: {{ .Values.mongodb.config.host | quote }} + MONGODB_DBNAME: {{ .Values.mongodb.config.database | quote }} + MONGODB_COLLECTION: {{ .Values.mongodb.config.collection | quote }} + MONGODB_ADMIN_USER: {{ .Values.secrets.mongodb.adminUser | quote }} + MONGODB_ADMIN_PASSWD: {{ .Values.secrets.mongodb.adminPassword | quote }} + MONGODB_ALERT_READER_USER: {{ .Values.secrets.mongodb.readerUser | quote }} + MONGODB_ALERT_READER_PASSWD: {{ .Values.secrets.mongodb.readerPassword | quote }} + MONGODB_ALERT_WRITER_USER: {{ .Values.secrets.mongodb.writerUser | quote }} + MONGODB_ALERT_WRITER_PASSWD: {{ .Values.secrets.mongodb.writerPassword | quote }} diff --git a/helm/fastdb/templates/shell.yaml b/helm/fastdb/templates/shell.yaml new file mode 100644 index 0000000..3d7aea9 --- /dev/null +++ b/helm/fastdb/templates/shell.yaml @@ -0,0 +1,78 @@ +{{- if .Values.shell.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shell + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: shell +spec: + replicas: {{ .Values.shell.replicas }} + selector: + matchLabels: + app: shell + template: + metadata: + labels: + app: shell + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + containers: + - name: shell + image: {{ include "fastdb.image" (dict "image" .Values.shell.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + # Keep container running for interactive shell access + command: ["sleep", "infinity"] + envFrom: + - configMapRef: + name: mongodb-config + volumeMounts: + - name: secrets + mountPath: /secrets + readOnly: true + - name: sessions + mountPath: /sessions + - name: query-results + mountPath: /query_results + - name: logs + mountPath: /logs + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + mountPath: /fastdb + - name: fastdb-db + mountPath: /fastdb/db + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + mountPath: /fastdb + {{- end }} + volumes: + - name: secrets + secret: + secretName: secrets + - name: sessions + persistentVolumeClaim: + claimName: {{ $namespace }}-sessions-pvc + - name: query-results + persistentVolumeClaim: + claimName: {{ $namespace }}-query-results-pvc + - name: logs + persistentVolumeClaim: + claimName: {{ $namespace }}-logs-pvc + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + hostPath: + path: {{ .Values.volumes.hostPaths.install | quote }} + type: Directory + - name: fastdb-db + hostPath: + path: {{ .Values.volumes.hostPaths.db | quote }} + type: Directory + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + persistentVolumeClaim: + claimName: {{ $namespace }}-code-pvc + {{- end }} +{{- end }} diff --git a/helm/fastdb/templates/single-broker-ingestion.yaml b/helm/fastdb/templates/single-broker-ingestion.yaml new file mode 100644 index 0000000..2d94e19 --- /dev/null +++ b/helm/fastdb/templates/single-broker-ingestion.yaml @@ -0,0 +1,233 @@ +{{- if .Values.ingestion.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +{{- $image := include "fastdb.image" (dict "image" .Values.ingestion.image "global" .Values.global) }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ingestion-config + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +data: + brokerconsumer.yaml: | + brokers: + {{ required "ingestion.brokerConsumer.name is required when ingestion is enabled" .Values.ingestion.brokerConsumer.name | quote }}: + class: BrokerConsumer + server: {{ required "ingestion.brokerConsumer.server is required when ingestion is enabled" .Values.ingestion.brokerConsumer.server | quote }} + groupid: {{ required "ingestion.brokerConsumer.groupId is required when ingestion is enabled" .Values.ingestion.brokerConsumer.groupId | quote }} + topics: [{{ required "ingestion.brokerConsumer.topic is required when ingestion is enabled" .Values.ingestion.brokerConsumer.topic | quote }}] + schemaless: true + schemafile: {{ .Values.ingestion.brokerConsumer.schemaFile | quote }} + brokername_for_alerts: {{ .Values.ingestion.brokerConsumer.brokerName | quote }} + mongodb_collection_base: {{ .Values.ingestion.brokerConsumer.collectionBase | quote }} + loggername: {{ .Values.ingestion.brokerConsumer.loggerName | quote }} + loggername_prefix: "" + consume_timeout: 1 + nomsg_sleeptime: 5 + extraconfig: + auto.offset.reset: earliest + pollkwargs: + restart_time_min: {{ .Values.ingestion.brokerConsumer.poll.restartTimeMinutes }} + notopic_sleeptime: {{ .Values.ingestion.brokerConsumer.poll.noTopicSleepSeconds }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: brokerconsumer + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: brokerconsumer +spec: + replicas: {{ .Values.ingestion.brokerConsumer.replicas }} + selector: + matchLabels: + app: brokerconsumer + template: + metadata: + labels: + app: brokerconsumer + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + {{- if .Values.ingestion.brokerConsumer.dockerHostIp }} + hostAliases: + - ip: {{ .Values.ingestion.brokerConsumer.dockerHostIp | quote }} + hostnames: + - host.docker.internal + {{- end }} + containers: + {{- if .Values.ingestion.brokerConsumer.localhostProxy }} + - name: kafka-localhost-proxy + image: {{ $image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + command: + - /bin/bash + - -ec + - | + socat TCP-LISTEN:19092,bind=127.0.0.1,fork,reuseaddr TCP:host.docker.internal:19092 & + exec socat TCP6-LISTEN:19092,bind=[::1],fork,reuseaddr TCP4:host.docker.internal:19092 + {{- end }} + - name: brokerconsumer + image: {{ $image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + command: ["/venv/bin/python", "/fastdb/services/brokerconsumer.py", "/config/brokerconsumer.yaml"] + envFrom: + - configMapRef: + name: mongodb-config + volumeMounts: + - name: config + mountPath: /config + readOnly: true + - name: secrets + mountPath: /secrets + readOnly: true + - name: logs + mountPath: /logs + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + mountPath: /fastdb + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + mountPath: /fastdb + {{- end }} + volumes: + - name: config + configMap: + name: ingestion-config + - name: secrets + secret: + secretName: secrets + - name: logs + persistentVolumeClaim: + claimName: {{ $namespace }}-logs-pvc + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + hostPath: + path: {{ .Values.volumes.hostPaths.install | quote }} + type: Directory + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + persistentVolumeClaim: + claimName: {{ $namespace }}-code-pvc + {{- end }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: ingestion-processing-version + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +spec: + backoffLimit: 12 + template: + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + restartPolicy: OnFailure + serviceAccountName: ingestion-job-waiter + containers: + - name: setup-processing-version + image: {{ $image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + command: + - /bin/bash + - -ec + - | + {{- if .Values.createdb.enabled }} + {{- include "fastdb.waitForJob" "createdb" | nindent 10 }} + {{- end }} + until /venv/bin/python -c 'import db; db.get_dbcon().close()'; do sleep 5; done + /venv/bin/python -c 'from admin.fastdb_loader import FastDBLoader; loader = FastDBLoader({{ .Values.ingestion.processingVersion.name | quote }}); loader.make_procver()' + volumeMounts: + - name: secrets + mountPath: /secrets + readOnly: true + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + mountPath: /fastdb + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + mountPath: /fastdb + {{- end }} + volumes: + - name: secrets + secret: + secretName: secrets + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + hostPath: + path: {{ .Values.volumes.hostPaths.install | quote }} + type: Directory + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + persistentVolumeClaim: + claimName: {{ $namespace }}-code-pvc + {{- end }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: source-importer + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.ingestion.sourceImporter.schedule | quote }} + startingDeadlineSeconds: {{ .Values.ingestion.sourceImporter.startingDeadlineSeconds }} + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: {{ .Values.ingestion.sourceImporter.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.ingestion.sourceImporter.failedJobsHistoryLimit }} + jobTemplate: + spec: + backoffLimit: 3 + template: + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 10 }} + restartPolicy: OnFailure + serviceAccountName: ingestion-job-waiter + containers: + - name: source-importer + image: {{ $image }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + command: + - /bin/bash + - -ec + - | + {{- include "fastdb.waitForJob" "ingestion-processing-version" | nindent 14 }} + /venv/bin/python /fastdb/services/source_importer.py \ + -c {{ .Values.ingestion.brokerConsumer.collectionBase | quote }} \ + -o {{ .Values.ingestion.processingVersion.name | quote }} \ + -p {{ .Values.ingestion.processingVersion.name | quote }} \ + -s {{ .Values.ingestion.processingVersion.name | quote }} \ + -f {{ .Values.ingestion.processingVersion.name | quote }} + /venv/bin/python -c 'import ltcv; ltcv.create_object_stats_materialized_view({{ .Values.ingestion.processingVersion.name | quote }})' + envFrom: + - configMapRef: + name: mongodb-config + volumeMounts: + - name: secrets + mountPath: /secrets + readOnly: true + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + mountPath: /fastdb + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + mountPath: /fastdb + {{- end }} + volumes: + - name: secrets + secret: + secretName: secrets + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + hostPath: + path: {{ .Values.volumes.hostPaths.install | quote }} + type: Directory + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + persistentVolumeClaim: + claimName: {{ $namespace }}-code-pvc + {{- end }} +{{- end }} diff --git a/helm/fastdb/templates/wal-webserver.yaml b/helm/fastdb/templates/wal-webserver.yaml new file mode 100644 index 0000000..39cca86 --- /dev/null +++ b/helm/fastdb/templates/wal-webserver.yaml @@ -0,0 +1,134 @@ +{{- if .Values.walWebserver.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: wal-nginx-config + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} +data: + default.conf: | + server { + listen 8080; + server_name _; + root /usr/share/nginx/html; + + location {{ .Values.walWebserver.ingress.path }} { + alias /usr/share/nginx/html; + autoindex on; + autoindex_exact_size off; + autoindex_localtime on; + } + + location / { + autoindex on; + autoindex_exact_size off; + autoindex_localtime on; + } + } +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wal-webserver + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: wal-webserver +spec: + replicas: {{ .Values.walWebserver.replicas }} + selector: + matchLabels: + app: wal-webserver + template: + metadata: + labels: + app: wal-webserver + spec: + containers: + - name: nginx + image: {{ include "fastdb.externalImage" .Values.walWebserver.image }} + imagePullPolicy: {{ .Values.walWebserver.image.pullPolicy }} + ports: + - containerPort: 8080 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 101 + volumeMounts: + - name: wal-archive + mountPath: /usr/share/nginx/html + readOnly: true + - name: nginx-config + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-run + mountPath: /var/run + volumes: + - name: wal-archive + persistentVolumeClaim: + claimName: {{ $namespace }}-wal-archive-pvc + - name: nginx-config + configMap: + name: wal-nginx-config + - name: nginx-cache + emptyDir: {} + - name: nginx-run + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: wal-webserver + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: wal-webserver +spec: + selector: + app: wal-webserver + ports: + - name: http + port: 80 + targetPort: 8080 + type: ClusterIP +{{- if .Values.walWebserver.ingress.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wal-webserver + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + {{- with .Values.walWebserver.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + rules: + - host: {{ .Values.walWebserver.ingress.host }} + http: + paths: + - path: {{ .Values.walWebserver.ingress.path }} + pathType: Prefix + backend: + service: + name: wal-webserver + port: + number: 80 + {{- with .Values.walWebserver.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/fastdb/templates/webap.yaml b/helm/fastdb/templates/webap.yaml new file mode 100644 index 0000000..3ffcf35 --- /dev/null +++ b/helm/fastdb/templates/webap.yaml @@ -0,0 +1,175 @@ +{{- if .Values.webap.enabled }} +{{- $namespace := include "fastdb.namespace" . }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webap + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: webap +spec: + replicas: {{ .Values.webap.replicas }} + selector: + matchLabels: + app: webap + template: + metadata: + labels: + app: webap + spec: + {{- include "fastdb.imagePullSecrets" . | nindent 6 }} + containers: + - name: webap + image: {{ include "fastdb.image" (dict "image" .Values.webap.image "global" .Values.global) }} + imagePullPolicy: {{ .Values.global.imagePullPolicy }} + envFrom: + - configMapRef: + name: mongodb-config + {{- if .Values.webap.basePath }} + env: + - name: SCRIPT_NAME + value: {{ .Values.webap.basePath | quote }} + {{- end }} + ports: + - containerPort: 8080 + volumeMounts: + - name: secrets + mountPath: /secrets + readOnly: true + - name: sessions + mountPath: /sessions + - name: query-results + mountPath: /query_results + - name: logs + mountPath: /logs + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + mountPath: /fastdb + - name: fastdb-db + mountPath: /fastdb/db + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + mountPath: /fastdb + {{- end }} + readinessProbe: + httpGet: + path: {{ .Values.webap.basePath | default "" }}/ + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + livenessProbe: + httpGet: + path: {{ .Values.webap.basePath | default "" }}/ + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + volumes: + - name: secrets + secret: + secretName: secrets + - name: sessions + persistentVolumeClaim: + claimName: {{ $namespace }}-sessions-pvc + - name: query-results + persistentVolumeClaim: + claimName: {{ $namespace }}-query-results-pvc + - name: logs + persistentVolumeClaim: + claimName: {{ $namespace }}-logs-pvc + {{- if .Values.volumes.codeHostPath }} + - name: fastdb-install + hostPath: + path: {{ .Values.volumes.hostPaths.install | quote }} + type: Directory + - name: fastdb-db + hostPath: + path: {{ .Values.volumes.hostPaths.db | quote }} + type: Directory + {{- else if .Values.volumes.codePvc.enabled }} + - name: code + persistentVolumeClaim: + claimName: {{ $namespace }}-code-pvc + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: webap + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: webap +spec: + selector: + app: webap + ports: + - name: http + port: {{ .Values.webap.service.port }} + targetPort: 8080 + type: {{ .Values.webap.service.type }} +{{- if .Values.webap.externalAccess.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: webap-external + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: webap +spec: + selector: + app: webap + ports: + - name: http + port: 8080 + targetPort: 8080 + {{- if eq .Values.webap.externalAccess.type "NodePort" }} + nodePort: {{ .Values.webap.externalAccess.nodePort }} + {{- end }} + type: {{ .Values.webap.externalAccess.type }} + {{- if eq .Values.webap.externalAccess.type "LoadBalancer" }} + allocateLoadBalancerNodePorts: false + {{- end }} +{{- end }} +{{- if .Values.webap.ingress.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: webap + namespace: {{ $namespace }} + labels: + {{- include "fastdb.labels" . | nindent 4 }} + app: webap + {{- with .Values.webap.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.webap.ingress.ingressClassName }} + ingressClassName: {{ .Values.webap.ingress.ingressClassName }} + {{- end }} + rules: + - host: {{ .Values.webap.ingress.host }} + http: + paths: + - path: {{ .Values.webap.ingress.path }} + pathType: Prefix + backend: + service: + name: webap + port: + number: {{ .Values.webap.service.port }} + {{- if .Values.webap.ingress.tls }} + tls: + - hosts: + - {{ .Values.webap.ingress.host }} + {{- if .Values.webap.ingress.tlsSecretName }} + secretName: {{ .Values.webap.ingress.tlsSecretName }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/helm/fastdb/values-arbutus-dev.yaml b/helm/fastdb/values-arbutus-dev.yaml new file mode 100644 index 0000000..1f1bbfc --- /dev/null +++ b/helm/fastdb/values-arbutus-dev.yaml @@ -0,0 +1,89 @@ +# FASTDB development deployment on a single-node Arbutus K3s VM. +# Images should be pre-built with build-local-images.sh and imported into k3s + +global: + namespace: fastdb-arbutus-dev + environment: dev + # This is a local image name, not a remote registry used for pulling images. + imageRegistry: fastdb.local + imageTag: test20260428 + imagePullPolicy: Never + imagePullSecrets: [] + +volumes: + type: pvc + storageClass: local-path + codeHostPath: true + +postgres: + enabled: true + persistence: + size: 8Gi + accessMode: ReadWriteOnce + externalAccess: + enabled: false + +# We will not start the postgres-standby because a standby on the same VM would consume resources without protecting against failure of that VM. +postgresStandby: + enabled: false + +mongodb: + enabled: true + persistence: + size: 8Gi + accessMode: ReadWriteOnce + +webap: + enabled: true + externalAccess: + enabled: true + type: NodePort + nodePort: 30080 + volumes: + sessions: + size: 1Gi + accessMode: ReadWriteOnce + queryResults: + size: 1Gi + accessMode: ReadWriteOnce + logs: + size: 1Gi + accessMode: ReadWriteOnce + +shell: + enabled: true + +queryrunner: + enabled: true + +mailhog: + enabled: true + externalAccess: + enabled: true + type: NodePort + nodePort: 30025 + +createdb: + enabled: true + +ingestion: + enabled: true + brokerConsumer: + name: lass + loggerName: LASS + server: host.docker.internal:19092 + groupId: fastdb-arbutus-lass + topic: lass-topic + schemaFile: /fastdb/share/avsc/lsst.v10_0.alert.avsc + collectionBase: lass + brokerName: lass + # install-singlenode-fastdb.sh discovers the Kubernetes node IP and + # supplies it to Helm, so this file is reusable on different Arbutus VMs. + dockerHostIp: "" + # LASS advertises localhost:19092. This proxy forwards that pod-local + # address to the Docker-published Kafka port on the VM. + localhostProxy: true + processingVersion: + name: lass + sourceImporter: + schedule: "* * * * *" diff --git a/helm/fastdb/values-arbutus.yaml b/helm/fastdb/values-arbutus.yaml new file mode 100644 index 0000000..952472f --- /dev/null +++ b/helm/fastdb/values-arbutus.yaml @@ -0,0 +1,90 @@ +# FASTDB development deployment on a single-node Arbutus K3s VM. +# This example uses prebuilt images from the CanDIAPL registry namespace. + +global: + namespace: fastdb-arbutus + environment: dev + imageRegistry: images.canfar.net/candiapl + imageTag: manual-20260918 + imagePullPolicy: IfNotPresent + # imagePullSecrets: + # - name: fastdb-registry + +volumes: + type: pvc + storageClass: local-path # This uses the k3s local path provisioner and is not part of k8s + codeHostPath: true + +postgres: + enabled: true + persistence: + size: 8Gi + accessMode: ReadWriteOnce + externalAccess: + enabled: false + +# A standby on the same VM would consume resources without protecting against +# failure of that VM. +postgresStandby: + enabled: false + +mongodb: + enabled: true + persistence: + size: 8Gi + accessMode: ReadWriteOnce + +webap: + enabled: true + externalAccess: + enabled: true + type: NodePort + nodePort: 30080 + volumes: + sessions: + size: 1Gi + accessMode: ReadWriteOnce + queryResults: + size: 1Gi + accessMode: ReadWriteOnce + logs: + size: 1Gi + accessMode: ReadWriteOnce + +shell: + enabled: true + +queryrunner: + enabled: true + +mailhog: + enabled: true + externalAccess: + enabled: true + type: NodePort + nodePort: 30025 + +createdb: + enabled: true + +ingestion: + enabled: true + brokerConsumer: + name: lass + loggerName: LASS + server: host.docker.internal:19092 + groupId: fastdb-arbutus-lass + topic: lass-topic + schemaFile: /fastdb/share/avsc/lsst.v10_0.alert.avsc + collectionBase: lass + brokerName: lass + # install-singlenode-fastdb.sh discovers the Kubernetes node IP and + # supplies it to Helm, so this file is reusable on different Arbutus VMs. + dockerHostIp: "" + # LASS advertises localhost:19092. This proxy forwards that pod-local + # address to the Docker-published Kafka port on the VM. + localhostProxy: true + processingVersion: + name: lass + sourceImporter: + schedule: "* * * * *" diff --git a/helm/fastdb/values-local.yaml b/helm/fastdb/values-local.yaml new file mode 100644 index 0000000..3b543e5 --- /dev/null +++ b/helm/fastdb/values-local.yaml @@ -0,0 +1,127 @@ +# FASTDB Local (Kind) Environment Values +# Usage: helm install fastdb ./helm/fastdb -f ./helm/fastdb/values-local.yaml + +global: + namespace: fastdb-local + environment: local + + # Images built by docker-compose and loaded into Kind via kind load. + # imagePullPolicy: Never means K8s uses the local store only — no registry contact. + imageRegistry: "ghcr.io/lsstdesc" + imageTag: "test20260428" + imagePullPolicy: Never + imagePullSecrets: [] + +utilityImages: + busybox: + pullPolicy: Never + +# Use PVC for database storage, hostPath for code (via Kind extraMounts) +volumes: + type: pvc # PVCs for database storage + storageClass: "" + + # Code is mounted via Kind extraMounts + codeHostPath: true # Enable hostPath for /fastdb-install and /fastdb-db + +# Local development secrets (not for production!) +secrets: + postgres: + password: "fragile" + roPassword: "readonly1234" + replicatorPassword: "repl1c4t0r" + secretKey: "fragilesecretkey" + + mongodb: + adminUser: "admin" + adminPassword: "fragile" + readerUser: "alertreader" + readerPassword: "reader" + writerUser: "alertwriter" + writerPassword: "writer" + +# PostgreSQL +postgres: + enabled: true + persistence: + size: 10Gi + + # NodePort for external access + externalAccess: + enabled: true + type: NodePort + nodePort: 30432 + +# Standby enabled for local testing +postgresStandby: + enabled: true + persistence: + size: 10Gi + +# MongoDB +mongodb: + enabled: true + persistence: + size: 5Gi + +# Web application with NodePort +webap: + enabled: true + externalAccess: + enabled: true + type: NodePort + nodePort: 30080 + # Kind's local-path provisioner only supports ReadWriteOnce + volumes: + sessions: + accessMode: ReadWriteOnce + queryResults: + accessMode: ReadWriteOnce + logs: + accessMode: ReadWriteOnce + +# Shell pod for debugging +shell: + enabled: true + +# Query runner +queryrunner: + enabled: true + +# Mailhog with NodePort +mailhog: + enabled: true + image: + pullPolicy: Never + externalAccess: + enabled: true + type: NodePort + nodePort: 30025 + +# Database migrations +createdb: + enabled: true + +# Consume Avro alerts produced by the local LASS Kafka container. Kind reaches +# a Docker-published host port through host.docker.internal. +ingestion: + enabled: true + brokerConsumer: + name: lass + loggerName: LASS + server: host.docker.internal:19092 + groupId: fastdb-local-lass + topic: lass-topic + schemaFile: /fastdb/share/avsc/lsst.v10_0.alert.avsc + collectionBase: lass + brokerName: lass + # Docker Desktop's IPv4 gateway. Pinning it avoids a non-routable IPv6 + # host.docker.internal record inside the Kind node. + dockerHostIp: 192.168.65.254 + # LASS advertises localhost:19092. This pod-local proxy forwards that + # Kafka connection back to LASS on the Docker host. + localhostProxy: true + processingVersion: + name: lass + sourceImporter: + schedule: "* * * * *" diff --git a/helm/fastdb/values-slac.yaml b/helm/fastdb/values-slac.yaml new file mode 100644 index 0000000..1a6dcc2 --- /dev/null +++ b/helm/fastdb/values-slac.yaml @@ -0,0 +1,106 @@ +# FASTDB SLAC S3DF Environment Values +# Usage: helm install fastdb ./helm/fastdb -f ./helm/fastdb/values-slac.yaml --set global.namespace=your-namespace + +global: + namespace: fastdb-slac # Override with --set global.namespace=ccosta-dev + environment: dev + + # GitHub Container Registry + imageRegistry: "ghcr.io/fifteen3" + imageTag: "latest" + imagePullPolicy: Always + imagePullSecrets: + - name: ghcr-secret + + namespaceLabels: + owner: "" # Override with --set global.namespaceLabels.owner=username + environment: dev + +# Use PVC-based storage +volumes: + type: pvc + storageClass: "" # Use default storage class + +# Secrets - should be overridden or managed externally +secrets: + postgres: + password: "changeme" + roPassword: "changeme" + replicatorPassword: "changeme" + secretKey: "changeme" + + mongodb: + adminUser: "admin" + adminPassword: "changeme" + readerUser: "alertreader" + readerPassword: "changeme" + writerUser: "alertwriter" + writerPassword: "changeme" + +# PostgreSQL with WAL archiving and LoadBalancer +postgres: + enabled: true + persistence: + size: 10Gi + + # Security context for SLAC + securityContext: + runAsUser: 100 + runAsNonRoot: true + allowPrivilegeEscalation: false + + # LoadBalancer for external access + externalAccess: + enabled: true + type: LoadBalancer + annotations: + metallb.io/address-pool: sdf-services + + # WAL archiving for replication + walArchive: + enabled: true + size: 20Gi + accessMode: ReadWriteMany + +# Standby not enabled by default for SLAC +postgresStandby: + enabled: false + +# MongoDB disabled for SLAC (postgres-only setup) +mongodb: + enabled: false + +# Web application disabled for SLAC postgres-only setup +webap: + enabled: false + +# Shell disabled +shell: + enabled: false + +# Query runner disabled +queryrunner: + enabled: false + +# Mailhog disabled +mailhog: + enabled: false + +# Database migrations +createdb: + enabled: true + +# WAL webserver for replication +walWebserver: + enabled: true + replicas: 1 + + ingress: + enabled: true + host: desc-fastdb.slac.stanford.edu + path: /fastdb-wal + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "100m" + tls: + - hosts: + - desc-fastdb.slac.stanford.edu diff --git a/helm/fastdb/values.yaml b/helm/fastdb/values.yaml new file mode 100644 index 0000000..b9943bb --- /dev/null +++ b/helm/fastdb/values.yaml @@ -0,0 +1,299 @@ +# FASTDB Helm Chart Values +# Base configuration - override with environment-specific values files + +# Global settings +global: + namespace: fastdb + environment: dev + + # Image configuration + imageRegistry: "" # e.g., "ghcr.io/fifteen3", "registry.nersc.gov/m1727/raknop" + imageTag: "latest" + imagePullPolicy: Always + imagePullSecrets: [] + # - name: ghcr-secret + + # Registry credentials (creates imagePullSecret automatically) + # Pass via --set to avoid committing credentials: + # --set global.registryCredentials.server=ghcr.io + # --set global.registryCredentials.username=myuser + # --set global.registryCredentials.password=mytoken + registryCredentials: + enabled: false + secretName: ghcr-secret + server: "" + username: "" + password: "" + + # Namespace labels + namespaceLabels: {} + # owner: username + # environment: dev + +# Small third-party images used by chart infrastructure. +utilityImages: + busybox: + registry: docker.io + repository: library/busybox + tag: "1.36.1" + pullPolicy: IfNotPresent + +# Volume configuration +volumes: + # Type: "pvc" for persistent volume claims + type: pvc + storageClass: "" + + # Enable hostPath for code directories (for local Kind deployment) + # When true, mounts /fastdb-install and /fastdb-db from Kind extraMounts + codeHostPath: false + hostPaths: + install: /fastdb-install + db: /fastdb-db + + # Code PVC - for loading code via git clone (for vcluster/cloud deployments) + codePvc: + enabled: false + size: 2Gi + accessMode: ReadWriteMany + gitRepo: "https://github.com/LSSTDESC/FASTDB.git" + gitBranch: "main" + +# Secrets +secrets: + postgres: + password: "changeme" + roPassword: "changeme" + replicatorPassword: "changeme" + secretKey: "changeme" + + # MongoDB credentials + mongodb: + adminUser: "admin" + adminPassword: "changeme" + readerUser: "alertreader" + readerPassword: "changeme" + writerUser: "alertwriter" + writerPassword: "changeme" + +# PostgreSQL primary +postgres: + enabled: true + image: + repository: fastdb-postgres + tag: "" # Falls back to global.imageTag + replicas: 1 + + # Stop the existing PostgreSQL pod before starting its replacement so that + # both pods never try to use the same database files simultaneously. + updateStrategy: + type: Recreate + + persistence: + size: 10Gi + accessMode: ReadWriteOnce + + sharedMemory: 1Gi + + service: + type: ClusterIP + port: 5432 + + # External access (NodePort or LoadBalancer) + externalAccess: + enabled: false + type: NodePort # or LoadBalancer + nodePort: 30432 + # LoadBalancer annotations (for SLAC metallb) + annotations: {} + + # Security context + securityContext: + runAsUser: null # null for local, 100 for SLAC + runAsNonRoot: false + allowPrivilegeEscalation: false + + # WAL archiving for replication + walArchive: + enabled: false + size: 20Gi + accessMode: ReadWriteMany + +# PostgreSQL standby (read replica) +postgresStandby: + enabled: false + image: + repository: fastdb-postgres + tag: "" + replicas: 1 + + persistence: + size: 10Gi + accessMode: ReadWriteOnce + + sharedMemory: 1Gi + + primaryHost: postgres + primaryPort: 5432 + replicationSlot: standby_slot + +# MongoDB +mongodb: + enabled: true + image: + repository: fastdb-mongodb + tag: "" + replicas: 1 + + # Stop the existing MongoDB pod before starting its replacement so that + # both pods never try to use the same database files simultaneously. + updateStrategy: + type: Recreate + + persistence: + size: 5Gi + accessMode: ReadWriteOnce + + config: + host: mongodb + database: brokeralert + collection: alerts + +# Web application +webap: + enabled: true + image: + repository: fastdb-webap + tag: "" + replicas: 1 + + service: + type: ClusterIP + port: 8080 + + externalAccess: + enabled: false + type: NodePort + nodePort: 30080 + + # Base path for subdirectory deployments (sets SCRIPT_NAME env var) + basePath: "" + + # Ingress configuration + ingress: + enabled: false + host: "" + path: "/" + annotations: {} + ingressClassName: "" + tls: false + tlsSecretName: "" + + # Volume mounts + volumes: + sessions: + size: 1Gi + accessMode: ReadWriteMany + queryResults: + size: 1Gi + accessMode: ReadWriteMany + logs: + size: 1Gi + accessMode: ReadWriteMany + +# Shell pod for debugging +shell: + enabled: true + image: + repository: fastdb-shell + tag: "" + replicas: 1 + +# Query runner +queryrunner: + enabled: true + image: + repository: fastdb-query-runner + tag: "" + replicas: 1 + +# Mailhog (email testing) +mailhog: + enabled: true + image: + repository: mailhog/mailhog + tag: "v1.0.1" + registry: "docker.io" + pullPolicy: IfNotPresent + replicas: 1 + + externalAccess: + enabled: false + type: NodePort + nodePort: 30025 + +# Database migrations job +createdb: + enabled: true + image: + repository: fastdb-shell + tag: "" + ttlSecondsAfterFinished: 300 + +# Optional alert ingestion pipeline. It is disabled by default because broker +# addresses, topics, schemas, and processing-version policy are deployment +# specific. values-local.yaml enables it for a local LASS Kafka broker. +ingestion: + enabled: false + image: + repository: fastdb-shell + tag: "" + + brokerConsumer: + replicas: 1 + name: broker + loggerName: BROKER + server: "" + groupId: fastdb + topic: "" + schemaFile: /fastdb/share/avsc/lsst.v10_0.alert.avsc + collectionBase: alerts + brokerName: kafka + # Optional IPv4 address for host.docker.internal. Set this only when a + # local Docker-host Kafka listener must be reached from Kind. + dockerHostIp: "" + # When Kafka advertises localhost to clients, a local proxy lets the + # consumer follow that advertised address from inside its pod. + localhostProxy: false + poll: + restartTimeMinutes: 30 + noTopicSleepSeconds: 10 + + processingVersion: + name: realtime + + sourceImporter: + # A CronJob is used because SourceImporter persists its last successful + # import time in PostgreSQL, so each run imports only newly received data. + schedule: "0 * * * *" + startingDeadlineSeconds: 30 + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + +# WAL webserver (for replication - SLAC specific) +walWebserver: + enabled: false + replicas: 1 + + image: + registry: docker.io + repository: library/nginx + tag: "1.27.4-alpine" + pullPolicy: IfNotPresent + + ingress: + enabled: false + host: "" + path: /fastdb-wal + annotations: {} + tls: [] diff --git a/helm/scripts/build-local-images.sh b/helm/scripts/build-local-images.sh new file mode 100755 index 0000000..0b7a94d --- /dev/null +++ b/helm/scripts/build-local-images.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# Build FASTDB images from this checkout and load them into local K3s. +set -euo pipefail + +#################### +##### Preamble +#################### + +# Locate the repository and accept the desired registry and tag as a pair. +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +case $# in + 0) + DEFAULT_IMAGE_REGISTRY="fastdb.local" + DEFAULT_IMAGE_TAG="test20260428" + echo "No image registry or tag was provided." + echo "Suggested defaults:" + echo " Image registry: $DEFAULT_IMAGE_REGISTRY" + echo " Image tag: $DEFAULT_IMAGE_TAG" + read -r -p "Use these defaults? [Y/n] " defaults_reply + case "$defaults_reply" in + ""|y|Y|yes|YES|Yes) + export DOCKER_ARCHIVE="$DEFAULT_IMAGE_REGISTRY" + export DOCKER_VERSION="$DEFAULT_IMAGE_TAG" + ;; + n|N|no|NO|No) + echo "Rerun with both the desired registry and tag:" + echo " $0 " + exit 0 + ;; + *) + echo "Error: please answer y or n." >&2 + exit 1 + ;; + esac + ;; + 1) + echo "Error: image registry and image tag must be provided together." >&2 + echo "Usage: $0 [ ]" >&2 + exit 1 + ;; + 2) + export DOCKER_ARCHIVE="$1" + export DOCKER_VERSION="$2" + ;; + *) + echo "Usage: $0 [ ]" >&2 + echo "Example: $0 fastdb.local test20260428" >&2 + exit 1 + ;; +esac + +# Require a normal user with access to Docker, K3s, Git, and sudo. +if [[ $EUID -eq 0 ]]; then + echo "Error: run this script as your normal user, not with sudo." >&2 + exit 1 +fi +for command_name in docker git kubectl sudo; do + command -v "$command_name" >/dev/null || { + echo "Error: $command_name is required." >&2 + exit 1 + } +done +docker info >/dev/null 2>&1 || { + echo "Error: cannot access Docker as $(id -un)." >&2 + exit 1 +} +kubectl get node >/dev/null || { + echo "Error: the K3s cluster is not accessible through kubectl." >&2 + exit 1 +} + +# Initialize build inputs before starting the long image builds. +echo "Initializing Git submodules..." +git -C "$REPO_ROOT" submodule update --init --recursive + +# Authenticate once and keep sudo active until all images are loaded into K3s. +echo "Administrator access will be needed to load images into K3s." +sudo -v +SUDO_KEEPALIVE_PID="" +cleanup() { + if [[ -n "$SUDO_KEEPALIVE_PID" ]]; then + kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true + wait "$SUDO_KEEPALIVE_PID" 2>/dev/null || true + fi +} +trap cleanup EXIT +( + while true; do + sudo -n -v || exit + sleep 60 + done +) & +SUDO_KEEPALIVE_PID=$! + +echo "Building images as $DOCKER_ARCHIVE/fastdb-*:$DOCKER_VERSION" + +#################### +##### End of preamble +#################### + +cd "$REPO_ROOT" + +# Build every FASTDB runtime image sequentially to limit peak disk and memory use. +services=(postgres mongodb createdb webap queryrunner) +repositories=( + fastdb-postgres + fastdb-mongodb + fastdb-shell + fastdb-webap + fastdb-query-runner +) +for index in "${!services[@]}"; do + echo "Building ${repositories[$index]}..." + docker compose build "${services[$index]}" +done + +# Copy the Docker images into K3s's separate containerd image store. +echo "Loading images into K3s..." +for repository in "${repositories[@]}"; do + image_name="$DOCKER_ARCHIVE/$repository:$DOCKER_VERSION" + echo " Loading $image_name" + docker image save "$image_name" | + sudo k3s ctr --namespace k8s.io images import - +done + +echo +echo "Local FASTDB images are ready." +echo "Use a Helm values file containing:" +echo " imageRegistry: $DOCKER_ARCHIVE" +echo " imageTag: $DOCKER_VERSION" +echo " imagePullPolicy: Never" +echo "Then pass that values file to install-singlenode-fastdb.sh." diff --git a/helm/scripts/create-local-cluster.sh b/helm/scripts/create-local-cluster.sh new file mode 100755 index 0000000..58e7382 --- /dev/null +++ b/helm/scripts/create-local-cluster.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Create the Kind cluster used for local FASTDB development. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +CLUSTER_NAME="fastdb-local" +KUBE_CONTEXT="kind-$CLUSTER_NAME" +KIND_CONFIG="$REPO_ROOT/admin/local/kind-config.yaml" + +for command_name in docker kind kubectl sed; do + command -v "$command_name" >/dev/null || { + echo "Error: $command_name is required." >&2 + exit 1 + } +done + +docker info >/dev/null 2>&1 || { + echo "Error: Docker is not running." >&2 + exit 1 +} + +if kind get clusters | grep -Fxq "$CLUSTER_NAME"; then + echo "Kind cluster '$CLUSTER_NAME' already exists." +else + echo "Creating Kind cluster '$CLUSTER_NAME'..." + sed "s|\${PWD}|$REPO_ROOT|g" "$KIND_CONFIG" | + kind create cluster --name "$CLUSTER_NAME" --config - +fi + +echo "Checking cluster access..." +kubectl --context "$KUBE_CONTEXT" cluster-info + +echo +echo "Local Kind cluster is ready." +echo "Context: $KUBE_CONTEXT" diff --git a/helm/scripts/create-user-local.sh b/helm/scripts/create-user-local.sh new file mode 100755 index 0000000..0280c49 --- /dev/null +++ b/helm/scripts/create-user-local.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# Create the fixed local test user and open its password-reset page. +set -euo pipefail + +CLUSTER="fastdb-local" +NAMESPACE="$CLUSTER" +CONTEXT="kind-$CLUSTER" +WEB_URL="http://localhost:8080" +MAILHOG_URL="http://localhost:8025" + +USERNAME="test_user" +DISPLAY_NAME="TESTUSER" +EMAIL="test_user@mailhog" + +for command_name in kubectl curl python3 open; do + command -v "$command_name" >/dev/null || { + echo "Error: $command_name is required." >&2 + exit 1 + } +done + +echo "Username: $USERNAME" + +kubectl --context "$CONTEXT" get namespace "$NAMESPACE" >/dev/null + +if kubectl --context "$CONTEXT" exec -i -n "$NAMESPACE" deployment/postgres -- \ + psql -U postgres -d fastdb -tAc \ + "SELECT 1 FROM authuser WHERE username = '$USERNAME'" | grep -Fxq '1'; then + echo "Error: user '$USERNAME' already exists." >&2 + echo "Use the password-reset page at $WEB_URL to set a new password." >&2 + exit 1 +fi + +kubectl --context "$CONTEXT" exec -i -n "$NAMESPACE" deployment/postgres -- \ + psql -U postgres -d fastdb \ + -c "INSERT INTO authuser (username, displayname, email) + VALUES ('$USERNAME', '$DISPLAY_NAME', '$EMAIL');" + +MESSAGES_JSON="$(mktemp)" +trap 'rm -f "$MESSAGES_JSON"' EXIT + +MAILHOG_READY=false +for _ in {1..30}; do + if curl -fsS "$MAILHOG_URL/api/v2/messages" >"$MESSAGES_JSON"; then + MAILHOG_READY=true + break + fi + sleep 1 +done + +if [[ "$MAILHOG_READY" != true ]]; then + echo "Error: MailHog is not reachable at $MAILHOG_URL." >&2 + exit 1 +fi + +curl -fsS -X POST "$WEB_URL/auth/getpasswordresetlink" \ + -H "Content-Type: application/json" \ + --data "{\"username\":\"$USERNAME\"}" >/dev/null + +RESET_URL="" +for _ in {1..30}; do + curl -fsS "$MAILHOG_URL/api/v2/messages" >"$MESSAGES_JSON" + + RESET_URL="$( + python3 - "$MESSAGES_JSON" "$USERNAME" <<'PY' +import json +import re +import sys + +message_file, username = sys.argv[1:] +with open(message_file, encoding="utf-8") as stream: + messages = json.load(stream) + +for message in messages.get("items", []): + text = ( + message.get("Content", {}).get("Body", "") + + "\n" + + message.get("Raw", {}).get("Data", "") + ) + if f"password reset for {username}" not in text: + continue + + match = re.search( + r"https?://[^\s<]+/auth/resetpassword\?uuid=[0-9a-f-]+", + text, + ) + if match: + print(match.group(0)) + break +PY + )" + + [[ -n "$RESET_URL" ]] && break + sleep 1 +done + +if [[ -z "$RESET_URL" ]]; then + echo "Error: no password-reset link found in MailHog." >&2 + exit 1 +fi + +# A locally rebuilt install should already generate HTTP links. Retain this +# fallback for clusters built before the local external URL was configured. +if [[ "$RESET_URL" == https://* ]]; then + RESET_URL="http://${RESET_URL#https://}" +fi + +echo "Password-reset URL: $RESET_URL" +echo "Set the password manually in the browser." +open "$WEB_URL" +open "$RESET_URL" +open "$MAILHOG_URL" diff --git a/helm/scripts/create-user-singlenode.sh b/helm/scripts/create-user-singlenode.sh new file mode 100755 index 0000000..b726a2c --- /dev/null +++ b/helm/scripts/create-user-singlenode.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +# Create a FASTDB user on a single-node deployment without using MailHog. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +NAMESPACE="${FASTDB_NAMESPACE:-fastdb-arbutus-dev}" + +if [[ $EUID -eq 0 ]]; then + echo "Error: run this script as your normal user, not with sudo." >&2 + exit 1 +fi + +for command_name in docker kubectl; do + command -v "$command_name" >/dev/null || { + echo "Error: $command_name is required." >&2 + exit 1 + } +done + +docker info >/dev/null 2>&1 || { + echo "Error: cannot access Docker as $(id -un)." >&2 + exit 1 +} + +kubectl get deployment/postgres --namespace "$NAMESPACE" >/dev/null || { + echo "Error: FASTDB is not installed in namespace $NAMESPACE." >&2 + exit 1 +} + +# Use the same shell image as the running FASTDB deployment. +SHELL_IMAGE="$( + kubectl get deployment/shell \ + --namespace "$NAMESPACE" \ + --output=jsonpath='{.spec.template.spec.containers[0].image}' +)" +if [[ -z "$SHELL_IMAGE" ]]; then + echo "Error: could not determine the shell image in namespace $NAMESPACE." >&2 + exit 1 +fi + +docker image inspect "$SHELL_IMAGE" >/dev/null 2>&1 || { + echo "Error: shell image not found in Docker: $SHELL_IMAGE" >&2 + echo "Pull or build this image before creating a user." >&2 + exit 1 +} + +read -r -p "Username [test_user]: " USERNAME +USERNAME="${USERNAME:-test_user}" +read -r -p "Display name [TESTUSER]: " DISPLAY_NAME +DISPLAY_NAME="${DISPLAY_NAME:-TESTUSER}" +read -r -p "Email [test_user@mailhog]: " EMAIL +EMAIL="${EMAIL:-test_user@mailhog}" + +# make_password.py emits SQL, so restrict these fields to simple values before +# passing them to it. The password is never included in that SQL. +if [[ ! "$USERNAME" =~ ^[A-Za-z0-9_.-]+$ ]]; then + echo "Error: username may contain only letters, numbers, '.', '_' and '-'." >&2 + exit 1 +fi +if [[ ! "$DISPLAY_NAME" =~ ^[A-Za-z0-9_.[:space:]-]+$ ]]; then + echo "Error: display name contains unsupported characters." >&2 + exit 1 +fi +if [[ ! "$EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+$ ]]; then + echo "Error: enter a simple email address without quotes or spaces." >&2 + exit 1 +fi + +if kubectl exec --namespace "$NAMESPACE" deployment/postgres -- \ + psql -U postgres -d fastdb -tAc \ + "SELECT 1 FROM authuser WHERE username = '$USERNAME'" | + grep -Fxq 1; then + echo "Error: user '$USERNAME' already exists." >&2 + exit 1 +fi + +# Never expose password-bearing commands if the script was invoked with +# tracing enabled (for example, with `bash -x`). +set +x +read -r -s -p "Password: " PASSWORD +echo +read -r -s -p "Confirm password: " PASSWORD_CONFIRMATION +echo +if (( ${#PASSWORD} < 4 )); then + echo "Error: password must be at least 4 characters long." >&2 + exit 1 +fi +if [[ "$PASSWORD" != "$PASSWORD_CONFIRMATION" ]]; then + echo "Error: passwords do not match." >&2 + exit 1 +fi + +echo "Generating FASTDB authentication keys..." +INSERT_SQL="$({ + printf '%s\n' "$PASSWORD" +} | docker run --rm -i \ + --network none \ + --volume "$REPO_ROOT:/code:ro" \ + --entrypoint python \ + "$SHELL_IMAGE" \ + -c ' +import runpy +import sys + +script, username, email, display_name = sys.argv[1:] +password = sys.stdin.readline().rstrip("\n") +sys.argv = [ + script, + "--username", username, + "--email", email, + "--displayname", display_name, + "--password", password, +] +runpy.run_path(script, run_name="__main__") +' \ + /code/extern/rkwebutil/rkwebutil/make_password.py \ + "$USERNAME" \ + "$EMAIL" \ + "$DISPLAY_NAME")" +unset PASSWORD PASSWORD_CONFIRMATION + +echo "Creating user '$USERNAME'..." +printf '%s;\n' "$INSERT_SQL" | + kubectl exec -i --namespace "$NAMESPACE" deployment/postgres -- \ + psql -v ON_ERROR_STOP=1 -U postgres -d fastdb +unset INSERT_SQL + +echo +echo "User '$USERNAME' is ready." +echo "Use the username and password you entered from any authorized FASTDB client." +echo "No client configuration or .fastdb.ini file was created by this script." diff --git a/helm/scripts/install-local-fastdb.sh b/helm/scripts/install-local-fastdb.sh new file mode 100755 index 0000000..218e0f3 --- /dev/null +++ b/helm/scripts/install-local-fastdb.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# Build and install FASTDB on the existing local Kind cluster. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +CLUSTER_NAME="fastdb-local" +KUBE_CONTEXT="kind-$CLUSTER_NAME" +NAMESPACE="fastdb-local" +RELEASE_NAME="fastdb" +VALUES_FILE="$REPO_ROOT/helm/fastdb/values-local.yaml" +EXTERNAL_URL="http://localhost:8080/" + +DOCKER_ARCHIVE="${DOCKER_ARCHIVE:-ghcr.io/lsstdesc}" +DOCKER_VERSION="${DOCKER_VERSION:-test20260428}" +BUSYBOX_IMAGE="docker.io/library/busybox:1.36.1" +MAILHOG_IMAGE="docker.io/mailhog/mailhog:v1.0.1" + +for command_name in docker helm kind kubectl; do + command -v "$command_name" >/dev/null || { + echo "Error: $command_name is required." >&2 + exit 1 + } +done + +docker info >/dev/null 2>&1 || { + echo "Error: Docker is not running." >&2 + exit 1 +} + +if ! kind get clusters | grep -Fxq "$CLUSTER_NAME"; then + echo "Error: Kind cluster '$CLUSTER_NAME' does not exist." >&2 + echo "Create it first with ./helm/scripts/create-local-cluster.sh" >&2 + exit 1 +fi + +cd "$REPO_ROOT" + +echo "Building FASTDB container images..." +# The createdb service builds the shared fastdb-shell image used by createdb, +# shell, and the install builder. Build it exactly once. +docker compose build postgres mongodb createdb webap queryrunner + +echo "Building FASTDB install/ for $EXTERNAL_URL..." +docker compose run --rm --entrypoint "" makeinstall /bin/bash -ec " + touch aclocal.m4 configure + find . -name Makefile.am -exec touch {} \\; + find . -name Makefile.in -exec touch {} \\; + ./configure \\ + --with-installdir=/fastdb \\ + --with-smtp-server=mailhog \\ + --with-smtp-port=1025 \\ + --with-external-url=$EXTERNAL_URL + make install +" + +echo "Making external images available locally..." +for image_name in "$BUSYBOX_IMAGE" "$MAILHOG_IMAGE"; do + if ! docker image inspect "$image_name" >/dev/null 2>&1; then + docker pull "$image_name" + fi +done + +echo "Loading images into Kind..." +for image_name in \ + "$DOCKER_ARCHIVE/fastdb-postgres:$DOCKER_VERSION" \ + "$DOCKER_ARCHIVE/fastdb-mongodb:$DOCKER_VERSION" \ + "$DOCKER_ARCHIVE/fastdb-shell:$DOCKER_VERSION" \ + "$DOCKER_ARCHIVE/fastdb-webap:$DOCKER_VERSION" \ + "$DOCKER_ARCHIVE/fastdb-query-runner:$DOCKER_VERSION"; do + echo " $image_name" + kind load docker-image --name "$CLUSTER_NAME" "$image_name" +done + +# Docker Desktop stores pulled multi-platform images differently from locally +# built images. Import only the downloaded platform into Kind's containerd; +# asking for every platform fails because the other platforms are not local. +for image_name in "$BUSYBOX_IMAGE" "$MAILHOG_IMAGE"; do + echo " $image_name" + image_archive="$(mktemp "/tmp/fastdb-kind-image.XXXXXX.tar")" + docker image save --output "$image_archive" "$image_name" + docker exec --privileged -i "$CLUSTER_NAME-control-plane" \ + ctr --namespace=k8s.io images import --digests --snapshotter=overlayfs - \ + < "$image_archive" + rm -f "$image_archive" +done + +echo "Installing FASTDB with Helm..." +# A Helm Job has an immutable pod template. Remove an earlier migration Job so +# every installation runs the current migrations and Helm can recreate it. +kubectl --context "$KUBE_CONTEXT" delete job createdb \ + --namespace "$NAMESPACE" --ignore-not-found + +if ! helm upgrade --install "$RELEASE_NAME" "$REPO_ROOT/helm/fastdb" \ + --kube-context "$KUBE_CONTEXT" \ + --namespace "$NAMESPACE" \ + --create-namespace \ + --values "$VALUES_FILE" \ + --wait \ + --wait-for-jobs \ + --timeout 5m; then + echo "Helm installation failed. Current pod status:" >&2 + kubectl --context "$KUBE_CONTEXT" get pods --namespace "$NAMESPACE" || true + kubectl --context "$KUBE_CONTEXT" logs \ + --namespace "$NAMESPACE" job/createdb || true + exit 1 +fi + +echo "Checking the migration Job..." +if ! kubectl --context "$KUBE_CONTEXT" wait \ + --namespace "$NAMESPACE" \ + --for=condition=complete job/createdb \ + --timeout=30s; then + kubectl --context "$KUBE_CONTEXT" logs \ + --namespace "$NAMESPACE" job/createdb || true + exit 1 +fi + +echo "Restarting services that load FASTDB Python code..." +for deployment_name in webap queryrunner brokerconsumer; do + if kubectl --context "$KUBE_CONTEXT" get deployment "$deployment_name" \ + --namespace "$NAMESPACE" >/dev/null 2>&1; then + echo " $deployment_name" + kubectl --context "$KUBE_CONTEXT" rollout restart \ + "deployment/$deployment_name" --namespace "$NAMESPACE" + kubectl --context "$KUBE_CONTEXT" rollout status \ + "deployment/$deployment_name" --namespace "$NAMESPACE" --timeout=120s + fi +done + +echo +kubectl --context "$KUBE_CONTEXT" get pods --namespace "$NAMESPACE" +echo +echo "FASTDB is ready: http://localhost:8080" +echo "MailHog is ready: http://localhost:8025" diff --git a/helm/scripts/install-singlenode-fastdb.sh b/helm/scripts/install-singlenode-fastdb.sh new file mode 100755 index 0000000..579089a --- /dev/null +++ b/helm/scripts/install-singlenode-fastdb.sh @@ -0,0 +1,289 @@ +#!/usr/bin/env bash +# Prepare and install FASTDB on a single-node Kubernetes cluster. +set -euo pipefail + +#################### +##### Preamble +#################### + +# Locate the repository and require one environment-specific Helm values file. +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +RELEASE_NAME="fastdb" +SECRETS_FILE="$REPO_ROOT/helm/fastdb/values-arbutus-secrets.yaml" +EXTERNAL_URL="${FASTDB_EXTERNAL_URL:-http://localhost:30080/}" +REGISTRY_PULL_SECRET="fastdb-registry" + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + echo "Example: $0 ../fastdb/values-arbutus.yaml" >&2 + exit 1 +fi + +VALUES_FILE="$1" +if [[ ! -f "$VALUES_FILE" ]]; then + echo "Error: values file not found: $VALUES_FILE" >&2 + exit 1 +fi +VALUES_FILE="$(cd "$(dirname "$VALUES_FILE")" && pwd)/$(basename "$VALUES_FILE")" + +# Read one simple scalar from the controlled single-node values files without +# requiring a separate YAML command-line tool. +# We run makeinstall before Helm, so extract the image metadata that Docker +# Compose needs from the values file. +read_values_scalar() { + local key="$1" + local line + local value + + line="$(grep -m1 -E "^[[:space:]]*${key}:" "$VALUES_FILE" || true)" + value="${line#*:}" + value="${value%%#*}" + value="$(printf '%s' "$value" | sed \ + -e 's/^[[:space:]]*//' \ + -e 's/[[:space:]]*$//' \ + -e 's/^"//' -e 's/"$//' \ + -e "s/^'//" -e "s/'$//")" + if [[ -z "$line" || -z "$value" ]]; then + echo "Error: could not read '$key' from $VALUES_FILE." >&2 + exit 1 + fi + printf '%s\n' "$value" +} + +NAMESPACE="$(read_values_scalar namespace)" +export DOCKER_ARCHIVE="$(read_values_scalar imageRegistry)" +export DOCKER_VERSION="$(read_values_scalar imageTag)" +SHELL_IMAGE="$DOCKER_ARCHIVE/fastdb-shell:$DOCKER_VERSION" +REGISTRY_HOST="${DOCKER_ARCHIVE%%/*}" + +# Require a normal user and every command used by the installer. +if [[ $EUID -eq 0 ]]; then + echo "Error: run this script as your normal user, not with sudo." >&2 + exit 1 +fi +for command_name in curl docker git helm kubectl openssl; do + command -v "$command_name" >/dev/null || { + echo "Error: $command_name is required." >&2 + exit 1 + } +done + +# Confirm that this user can access Docker and the Kubernetes cluster. +docker info >/dev/null 2>&1 || { + echo "Error: cannot access Docker as $(id -un)." >&2 + echo "Ensure this user has Docker access, then log out and back in." >&2 + exit 1 +} +kubectl get node >/dev/null || { + echo "Error: the Kubernetes cluster is not accessible through kubectl." >&2 + exit 1 +} + +# Discover the node address used by the broker-consumer proxy. +NODE_IP="$( + kubectl get nodes \ + --output=jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' +)" +if [[ -z "$NODE_IP" ]]; then + echo "Error: could not determine the Kubernetes node's internal IP address." >&2 + exit 1 +fi + +# Initialize source dependencies and verify writable host-mounted directories. +echo "Initializing Git submodules..." +git -C "$REPO_ROOT" submodule update --init --recursive +mkdir -p "$REPO_ROOT/install" "$REPO_ROOT/db" +for directory in "$REPO_ROOT/install" "$REPO_ROOT/db"; do + if [[ ! -w "$directory" || ! -x "$directory" ]]; then + echo "Error: $(id -un) cannot write to $directory." >&2 + echo "Fix its ownership, then rerun this script:" >&2 + echo " sudo chown -R $(id -u):$(id -g) '$directory'" >&2 + exit 1 + fi +done + +# Warn before deployment if the VM's root disk is running low on space. +AVAILABLE_KB="$(df -Pk / | awk 'NR == 2 { print $4 }')" +if (( AVAILABLE_KB < 10 * 1024 * 1024 )); then + echo "Warning: less than 10 GiB is available on the root filesystem." >&2 +fi + +echo "Values file: $VALUES_FILE" +echo "Kubernetes namespace: $NAMESPACE" +echo "FASTDB images: $DOCKER_ARCHIVE/fastdb-*:$DOCKER_VERSION" +echo "Kubernetes node address: $NODE_IP" + +#################### +##### End of preamble +#################### + +# Generate deployment credentials once and reuse them on later upgrades. +if [[ ! -f "$SECRETS_FILE" ]]; then + echo "Creating persistent development secrets in $SECRETS_FILE..." + umask 077 + cat >"$SECRETS_FILE" </dev/null 2>&1; then + echo + echo "The FASTDB shell image is not available in Docker:" + echo " $SHELL_IMAGE" + echo + echo "This image is needed before Helm runs because the temporary" + echo "makeinstall container uses it to prepare FASTDB/install/ from" + echo "the current checkout." + echo + + if [[ "$DOCKER_ARCHIVE" == "fastdb.local" ]]; then + echo "fastdb.local is a local image label, not a remote registry." + echo "Build and load that image tag first:" + echo " $REPO_ROOT/helm/scripts/build-local-images.sh '$DOCKER_ARCHIVE' '$DOCKER_VERSION'" + exit 1 + fi + + echo "The configured registry is:" + echo " $DOCKER_ARCHIVE" + echo + read -r -p "Attempt to pull the shell image now? [y/N] " pull_reply + case "$pull_reply" in + y|Y|yes|YES|Yes) + docker pull "$SHELL_IMAGE" + ;; + ""|n|N|no|NO|No) + echo "Installation cancelled before Helm was run." + exit 0 + ;; + *) + echo "Error: please answer y or n." >&2 + exit 1 + ;; + esac +fi + +# For any remote registry, verify every deployment image before running Helm. +# If the values file requests the standard pull secret, copy the existing +# Docker login into the namespace so Kubernetes can authenticate to it. +if [[ "$DOCKER_ARCHIVE" != "fastdb.local" ]]; then + echo "Checking prebuilt registry images..." + for repository in \ + fastdb-postgres \ + fastdb-mongodb \ + fastdb-shell \ + fastdb-webap \ + fastdb-query-runner + do + image_name="$DOCKER_ARCHIVE/$repository:$DOCKER_VERSION" + echo " Checking $image_name" + if ! docker manifest inspect "$image_name" >/dev/null 2>&1; then + echo "Error: cannot access $image_name." >&2 + echo "Run 'docker login $REGISTRY_HOST', verify the repository and tag, and rerun." >&2 + exit 1 + fi + done + + if grep -Eq "^[[:space:]]*-[[:space:]]*name:[[:space:]]*$REGISTRY_PULL_SECRET([[:space:]]|$)" "$VALUES_FILE"; then + docker_config_file="${DOCKER_CONFIG:-$HOME/.docker}/config.json" + if [[ ! -f "$docker_config_file" ]]; then + echo "Error: Docker credentials not found at $docker_config_file." >&2 + echo "Run 'docker login $REGISTRY_HOST' and rerun this script." >&2 + exit 1 + fi + + kubectl create namespace "$NAMESPACE" \ + --dry-run=client \ + --output=yaml | + kubectl apply --filename=- >/dev/null + kubectl create secret generic "$REGISTRY_PULL_SECRET" \ + --namespace "$NAMESPACE" \ + --type=kubernetes.io/dockerconfigjson \ + --from-file=.dockerconfigjson="$docker_config_file" \ + --dry-run=client \ + --output=yaml | + kubectl apply --filename=- >/dev/null + fi +fi + +# Use the configured shell image to copy and configure the current checkout in +# the host's install/ directory. Kubernetes later mounts it at /fastdb. +echo "Building FASTDB install/ for $EXTERNAL_URL..." +USERID="$(id -u)" GROUPID="$(id -g)" \ +docker compose run --rm --entrypoint "" makeinstall /bin/bash -ec " + touch aclocal.m4 configure + find . -name Makefile.am -exec touch {} \\; + find . -name Makefile.in -exec touch {} \\; + ./configure \\ + --with-installdir=/fastdb \\ + --with-smtp-server=mailhog \\ + --with-smtp-port=1025 \\ + --with-external-url=$EXTERNAL_URL + make install +" + +# Recreate one-shot Jobs because Kubernetes does not allow Helm to update their +# pod templates in place, then install or upgrade the remaining resources. +echo "Installing FASTDB with Helm..." +kubectl delete job createdb ingestion-processing-version \ + --namespace "$NAMESPACE" \ + --ignore-not-found + +if ! helm upgrade --install "$RELEASE_NAME" "$REPO_ROOT/helm/fastdb" \ + --namespace "$NAMESPACE" \ + --create-namespace \ + --values "$VALUES_FILE" \ + --values "$SECRETS_FILE" \ + --set-string "volumes.hostPaths.install=$REPO_ROOT/install" \ + --set-string "volumes.hostPaths.db=$REPO_ROOT/db" \ + --set-string "ingestion.brokerConsumer.dockerHostIp=$NODE_IP" \ + --wait \ + --wait-for-jobs \ + --timeout 30m; then + echo "Helm installation failed. Current status:" >&2 + kubectl get pods --namespace "$NAMESPACE" >&2 || true + kubectl logs --namespace "$NAMESPACE" job/createdb >&2 || true + exit 1 +fi + +# Restart long-running Python processes so they load code from the refreshed +# host-mounted install/ directory. +echo "Restarting services that load the host-mounted FASTDB code..." +for deployment_name in webap queryrunner shell; do + kubectl rollout restart "deployment/$deployment_name" --namespace "$NAMESPACE" + kubectl rollout status "deployment/$deployment_name" \ + --namespace "$NAMESPACE" \ + --timeout=180s +done + +# Verify the web application, then display final workload and storage status. +echo "Checking FASTDB WebAP..." +curl --fail --silent --show-error \ + --retry 12 \ + --retry-all-errors \ + --retry-delay 5 \ + "http://127.0.0.1:30080/" >/dev/null + +echo +kubectl get pods --namespace "$NAMESPACE" +kubectl get pvc --namespace "$NAMESPACE" +df -h / +echo +echo "FASTDB is ready inside the VM at http://localhost:30080" +echo "MailHog is ready inside the VM at http://localhost:30025" diff --git a/helm/scripts/setup-arbutus-k3s.sh b/helm/scripts/setup-arbutus-k3s.sh new file mode 100755 index 0000000..774763d --- /dev/null +++ b/helm/scripts/setup-arbutus-k3s.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# Set up a single-node K3s cluster on an Ubuntu VM (tested on 24.04). +set -euo pipefail + +K3S_VERSION="v1.36.4+k3s1" +KUBECTL_VERSION="v1.36.4" +HELM_VERSION="v4.2.4" + +# This assumes the cephfs share is mounted on the VM +DEFAULT_LOCAL_STORAGE_PATH=/fastdb/single-staging # https://docs.k3s.io/add-ons/storage + +if [[ $EUID -eq 0 ]]; then + echo "Error: run this script as your normal user, not with sudo." >&2 + exit 1 +fi + +# shellcheck disable=SC1091 +source /etc/os-release +if [[ ${ID:-} != "ubuntu" ]]; then + echo "Error: this script supports Ubuntu only; found ${PRETTY_NAME:-unknown}." >&2 + exit 1 +fi +if [[ ${VERSION_ID:-} != "24.04" ]]; then + echo "Warning: this script was tested on Ubuntu 24.04; found ${PRETTY_NAME:-unknown}." >&2 +fi + +case "$(uname -m)" in + x86_64) DOWNLOAD_ARCH="amd64" ;; + aarch64|arm64) DOWNLOAD_ARCH="arm64" ;; + *) + echo "Error: unsupported CPU architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + +SETUP_TMPDIR="$(mktemp -d)" +trap 'rm -rf "$SETUP_TMPDIR"' EXIT + +echo "Installing required Ubuntu packages..." +sudo env DEBIAN_FRONTEND=noninteractive apt-get update +sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates \ + curl \ + git \ + docker.io \ + docker-compose-v2 + +echo "Enabling Docker..." +sudo systemctl enable --now docker +if ! id -nG | tr ' ' '\n' | grep -Fxq docker; then + sudo usermod -aG docker "$(id -un)" + DOCKER_LOGIN_REQUIRED=true +else + DOCKER_LOGIN_REQUIRED=false +fi + +echo "Installing kubectl $KUBECTL_VERSION..." +KUBECTL_URL="https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/$DOWNLOAD_ARCH/kubectl" +curl -fsSL "$KUBECTL_URL" -o "$SETUP_TMPDIR/kubectl" +curl -fsSL "$KUBECTL_URL.sha256" -o "$SETUP_TMPDIR/kubectl.sha256" +echo "$(<"$SETUP_TMPDIR/kubectl.sha256") $SETUP_TMPDIR/kubectl" | + sha256sum --check --status + +# An earlier K3s installation may have created this link. Replace only that +# known link; never remove an unrelated kubectl installation here. +if [[ -L /usr/local/bin/kubectl ]] && + [[ "$(readlink -f /usr/local/bin/kubectl)" == "/usr/local/bin/k3s" ]]; then + sudo unlink /usr/local/bin/kubectl +fi +sudo install -o root -g root -m 0755 "$SETUP_TMPDIR/kubectl" /usr/local/bin/kubectl + +if command -v k3s >/dev/null 2>&1; then + INSTALLED_K3S_VERSION="$(k3s --version | awk 'NR == 1 { print $3 }')" + if [[ "$INSTALLED_K3S_VERSION" != "$K3S_VERSION" ]]; then + echo "Error: K3s $INSTALLED_K3S_VERSION is installed; expected $K3S_VERSION." >&2 + echo "Refusing to replace an existing cluster automatically." >&2 + exit 1 + fi + echo "K3s $K3S_VERSION is already installed." +else + # We are using k3s's local path provisioner, which by default goes to /var/lib/rancher/k3s/storage + # The --default-local-storage-path changes it to use the cephfs share, assuming it is already mounted at /fastdb/ + echo "Installing K3s $K3S_VERSION..." + curl -fsSL https://get.k3s.io -o "$SETUP_TMPDIR/install-k3s.sh" + sudo env \ + INSTALL_K3S_VERSION="$K3S_VERSION" \ + INSTALL_K3S_EXEC="server --disable traefik --disable servicelb --default-local-storage-path $DEFAULT_LOCAL_STORAGE_PATH" \ + INSTALL_K3S_SYMLINK="skip" \ + sh "$SETUP_TMPDIR/install-k3s.sh" +fi + +echo "Configuring Kubernetes access for $(id -un)..." +mkdir -p "$HOME/.kube" +sudo install \ + -o "$(id -u)" \ + -g "$(id -g)" \ + -m 0600 \ + /etc/rancher/k3s/k3s.yaml \ + "$HOME/.kube/config" + +echo "Waiting for the Kubernetes node to become ready..." +kubectl wait \ + --for=condition=Ready \ + "node/$(hostname)" \ + --timeout=120s + +echo "Installing Helm $HELM_VERSION..." +HELM_ARCHIVE="helm-${HELM_VERSION}-linux-${DOWNLOAD_ARCH}.tar.gz" +HELM_URL="https://get.helm.sh/$HELM_ARCHIVE" +curl -fsSL "$HELM_URL" -o "$SETUP_TMPDIR/$HELM_ARCHIVE" +curl -fsSL "$HELM_URL.sha256sum" -o "$SETUP_TMPDIR/$HELM_ARCHIVE.sha256sum" +( + cd "$SETUP_TMPDIR" + sha256sum --check --status "$HELM_ARCHIVE.sha256sum" + tar -xzf "$HELM_ARCHIVE" +) +sudo install -o root -g root -m 0755 \ + "$SETUP_TMPDIR/linux-${DOWNLOAD_ARCH}/helm" \ + /usr/local/bin/helm + +echo +kubectl get nodes +kubectl get pods --all-namespaces +kubectl version --client +helm version --short +sudo docker info --format 'Docker server: {{.ServerVersion}}' +docker compose version +df -h / + +echo +echo "K3s is ready." +if [[ "$DOCKER_LOGIN_REQUIRED" == true ]]; then + echo "Log out and back in before running Docker without sudo." +fi diff --git a/helm/scripts/stop-local.sh b/helm/scripts/stop-local.sh new file mode 100755 index 0000000..13bef17 --- /dev/null +++ b/helm/scripts/stop-local.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Delete the local Kind cluster and all data stored inside it. +set -euo pipefail + +CLUSTER="fastdb-local" + +command -v kind >/dev/null || { + echo "Error: kind is required." >&2 + exit 1 +} + +if kind get clusters | grep -Fxq "$CLUSTER"; then + echo "Deleting Kind cluster $CLUSTER (including its local FASTDB data)..." + kind delete cluster --name "$CLUSTER" +else + echo "Kind cluster $CLUSTER does not exist; nothing to delete." +fi