From cf28e427c20b84924ab453c432bc10e8de332f4a Mon Sep 17 00:00:00 2001 From: Masaki Yatsu Date: Fri, 12 Sep 2025 23:30:16 +0900 Subject: [PATCH] feat(ch-ui): add CH-UI --- ch-ui/.gitignore | 2 + ...-credentials-external-secret.gomplate.yaml | 22 ++ ch-ui/ch-ui-values.gomplate.yaml | 71 +++++ ch-ui/justfile | 135 +++++++++ charts/ch-ui/.gitignore | 1 + charts/ch-ui/.helmignore | 23 ++ charts/ch-ui/Chart.yaml | 24 ++ charts/ch-ui/README.md | 273 ++++++++++++++++++ charts/ch-ui/templates/NOTES.txt | 35 +++ charts/ch-ui/templates/_helpers.tpl | 62 ++++ charts/ch-ui/templates/deployment.yaml | 108 +++++++ charts/ch-ui/templates/hpa.yaml | 32 ++ charts/ch-ui/templates/ingress.yaml | 43 +++ charts/ch-ui/templates/secret.yaml | 11 + charts/ch-ui/templates/service.yaml | 15 + charts/ch-ui/templates/serviceaccount.yaml | 13 + .../templates/tests/test-connection.yaml | 15 + charts/ch-ui/values.yaml | 116 ++++++++ justfile | 1 + 19 files changed, 1002 insertions(+) create mode 100644 ch-ui/.gitignore create mode 100644 ch-ui/ch-ui-credentials-external-secret.gomplate.yaml create mode 100644 ch-ui/ch-ui-values.gomplate.yaml create mode 100644 ch-ui/justfile create mode 100644 charts/ch-ui/.gitignore create mode 100644 charts/ch-ui/.helmignore create mode 100644 charts/ch-ui/Chart.yaml create mode 100644 charts/ch-ui/README.md create mode 100644 charts/ch-ui/templates/NOTES.txt create mode 100644 charts/ch-ui/templates/_helpers.tpl create mode 100644 charts/ch-ui/templates/deployment.yaml create mode 100644 charts/ch-ui/templates/hpa.yaml create mode 100644 charts/ch-ui/templates/ingress.yaml create mode 100644 charts/ch-ui/templates/secret.yaml create mode 100644 charts/ch-ui/templates/service.yaml create mode 100644 charts/ch-ui/templates/serviceaccount.yaml create mode 100644 charts/ch-ui/templates/tests/test-connection.yaml create mode 100644 charts/ch-ui/values.yaml diff --git a/ch-ui/.gitignore b/ch-ui/.gitignore new file mode 100644 index 0000000..9268761 --- /dev/null +++ b/ch-ui/.gitignore @@ -0,0 +1,2 @@ +ch-ui-values.yaml +ch-ui-credentials-external-secret.yaml diff --git a/ch-ui/ch-ui-credentials-external-secret.gomplate.yaml b/ch-ui/ch-ui-credentials-external-secret.gomplate.yaml new file mode 100644 index 0000000..2028281 --- /dev/null +++ b/ch-ui/ch-ui-credentials-external-secret.gomplate.yaml @@ -0,0 +1,22 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: ch-ui-credentials-external-secret + namespace: {{ .Env.CH_UI_NAMESPACE }} +spec: + refreshInterval: 1h + secretStoreRef: + name: vault-secret-store + kind: ClusterSecretStore + target: + name: ch-ui-credentials + creationPolicy: Owner + template: + type: Opaque + data: + clickhouse-password: "{{ `{{ .clickhouse_password }}` }}" + data: + - secretKey: clickhouse_password + remoteRef: + key: ch-ui/credentials + property: clickhouse-password \ No newline at end of file diff --git a/ch-ui/ch-ui-values.gomplate.yaml b/ch-ui/ch-ui-values.gomplate.yaml new file mode 100644 index 0000000..6936e50 --- /dev/null +++ b/ch-ui/ch-ui-values.gomplate.yaml @@ -0,0 +1,71 @@ +# CH-UI Helm chart values +replicaCount: 1 + +image: + repository: ghcr.io/caioricciuti/ch-ui + pullPolicy: IfNotPresent + tag: "" + +service: + type: ClusterIP + port: 80 + targetPort: 5521 + +ingress: + enabled: true + className: traefik + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: websecure + hosts: + - host: {{ .Env.CH_UI_HOST }} + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - {{ .Env.CH_UI_HOST }} + +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +clickhouse: + # ClickHouse server URL + url: {{ .Env.CLICKHOUSE_HOST }} + + # Authentication configuration + auth: + # ClickHouse username + username: {{ env.Getenv "CH_UI_CLICKHOUSE_USERNAME" "ch-ui" }} + # Reference to existing secret + existingSecret: "ch-ui-credentials" + # Secret keys for authentication credentials + secretKeys: + password: "clickhouse-password" + + # Advanced configuration + useAdvanced: {{ env.Getenv "CH_UI_USE_ADVANCED" "false" }} + requestTimeout: {{ env.Getenv "CH_UI_REQUEST_TIMEOUT" "30000" }} + basePath: {{ env.Getenv "CH_UI_BASE_PATH" "/" }} + +# Additional environment variables +extraEnvVars: [] + +# Environment variables from existing ConfigMaps or Secrets +extraEnvVarsCM: "" +extraEnvVarsSecret: "" + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/ch-ui/justfile b/ch-ui/justfile new file mode 100644 index 0000000..c65a788 --- /dev/null +++ b/ch-ui/justfile @@ -0,0 +1,135 @@ +set fallback := true + +export CH_UI_NAMESPACE := env("CH_UI_NAMESPACE", "clickhouse") +export CH_UI_HOST := env("CH_UI_HOST", "") +export CLICKHOUSE_NAMESPACE := env("CLICKHOUSE_NAMESPACE", "clickhouse") +export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets") + +[private] +default: + @just --list --unsorted --list-submodules + +# Create CH-UI user in ClickHouse +create-ch-ui-user: + #!/bin/bash + set -euo pipefail + echo "Creating CH-UI dedicated user in ClickHouse..." + CH_UI_USERNAME="ch-ui" + if just clickhouse::user-exists ${CH_UI_USERNAME} &>/dev/null; then + echo "User '${CH_UI_USERNAME}' already exists." + if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then + CH_PASSWORD=$(just vault::get ch-ui/credentials clickhouse-password 2>/dev/null || echo "") + else + CH_PASSWORD=$(kubectl get secret ch-ui-credentials -n ${CH_UI_NAMESPACE} \ + -o jsonpath='{.data.clickhouse-password}' 2>/dev/null | base64 -d || echo "") + fi + if [ -z "${CH_PASSWORD}" ]; then + echo "Existing password not found. Generating new password..." + CH_PASSWORD=$(just utils::random-password) + # Update user password + just clickhouse::change-password ${CH_UI_USERNAME} ${CH_PASSWORD} + else + echo "Existing password found. Syncing password to ClickHouse..." + # Update user password in ClickHouse to match stored password + just clickhouse::change-password ${CH_UI_USERNAME} ${CH_PASSWORD} + fi + else + CH_PASSWORD=$(just utils::random-password) + just clickhouse::create-user ${CH_UI_USERNAME} ${CH_PASSWORD} + just clickhouse::grant-admin ${CH_UI_USERNAME} + fi + if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then + echo "Storing password in Vault..." + just vault::put ch-ui/credentials clickhouse-password="${CH_PASSWORD}" + else + echo "Storing password in Kubernetes Secret..." + kubectl delete secret ch-ui-credentials -n ${CH_UI_NAMESPACE} --ignore-not-found + kubectl create secret generic ch-ui-credentials -n ${CH_UI_NAMESPACE} \ + --from-literal=clickhouse-password="${CH_PASSWORD}" + fi + echo "CH-UI user setup completed" + +# Create CH-UI credentials secret +create-credentials: + #!/bin/bash + set -euo pipefail + echo "Setting up CH-UI credentials..." + just create-ch-ui-user + if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then + echo "Creating ExternalSecret for CH-UI credentials..." + gomplate -f ch-ui-credentials-external-secret.gomplate.yaml -o ch-ui-credentials-external-secret.yaml + kubectl apply -f ch-ui-credentials-external-secret.yaml + echo "Waiting for credentials secret to be ready..." + kubectl wait --for=condition=Ready externalsecret/ch-ui-credentials-external-secret \ + -n ${CH_UI_NAMESPACE} --timeout=60s + fi + echo "CH-UI credentials setup completed" + +# Delete CH-UI user from ClickHouse +delete-ch-ui-user: + #!/bin/bash + set -euo pipefail + CH_UI_USERNAME="ch-ui" + if just clickhouse::user-exists ${CH_UI_USERNAME} &>/dev/null; then + echo "Deleting CH-UI user from ClickHouse..." + just clickhouse::delete-user ${CH_UI_USERNAME} + echo "CH-UI user deleted" + else + echo "CH-UI user does not exist" + fi + +# Delete CH-UI credentials secret +delete-credentials-secret: + @kubectl delete secret ch-ui-credentials -n ${CH_UI_NAMESPACE} --ignore-not-found + @kubectl delete externalsecret ch-ui-credentials-external-secret -n ${CH_UI_NAMESPACE} --ignore-not-found + +# Install CH-UI +install: + #!/bin/bash + set -euo pipefail + export CH_UI_HOST=${CH_UI_HOST:-} + while [ -z "${CH_UI_HOST}" ]; do + CH_UI_HOST=$( + gum input --prompt="CH-UI host (FQDN): " --width=100 \ + --placeholder="e.g., ch-ui.example.com" + ) + done + echo "Installing CH-UI..." + if ! kubectl get namespace ${CLICKHOUSE_NAMESPACE} &>/dev/null; then + echo "Error: ClickHouse namespace '${CLICKHOUSE_NAMESPACE}' does not exist." + echo "Please install ClickHouse first: just clickhouse::install" + exit 1 + fi + if ! kubectl get clickhouseinstallation -n ${CLICKHOUSE_NAMESPACE} &>/dev/null; then + echo "Error: ClickHouse is not installed in namespace '${CLICKHOUSE_NAMESPACE}'." + echo "Please install ClickHouse first: just clickhouse::install" + exit 1 + fi + CLICKHOUSE_HOST=$(kubectl get ingress -n ${CLICKHOUSE_NAMESPACE} clickhouse-ingress \ + -o jsonpath='{.spec.rules[0].host}' 2>/dev/null || echo "") + if [ -z "${CLICKHOUSE_HOST}" ]; then + echo "Error: ClickHouse Ingress not found." + echo "Please ensure ClickHouse is properly installed with Ingress configuration." + echo "Run: just clickhouse::setup-ingress" + exit 1 + fi + export CLICKHOUSE_HOST="https://${CLICKHOUSE_HOST}" + just create-credentials + gomplate -f ch-ui-values.gomplate.yaml -o ch-ui-values.yaml + helm upgrade --install ch-ui ../charts/ch-ui \ + --values ch-ui-values.yaml \ + --namespace ${CH_UI_NAMESPACE} \ + --wait + echo "CH-UI installation completed successfully" + echo "Access CH-UI at: https://${CH_UI_HOST}" + echo "ClickHouse API at: ${CLICKHOUSE_HOST}" + +# Uninstall CH-UI +uninstall: + #!/bin/bash + set -euo pipefail + echo "Uninstalling CH-UI..." + helm uninstall ch-ui -n ${CH_UI_NAMESPACE} --wait --ignore-not-found + just delete-credentials-secret + just delete-ch-ui-user + echo "CH-UI uninstalled successfully" diff --git a/charts/ch-ui/.gitignore b/charts/ch-ui/.gitignore new file mode 100644 index 0000000..a5aa1c3 --- /dev/null +++ b/charts/ch-ui/.gitignore @@ -0,0 +1 @@ +ch-ui-values.yaml diff --git a/charts/ch-ui/.helmignore b/charts/ch-ui/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/ch-ui/.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/charts/ch-ui/Chart.yaml b/charts/ch-ui/Chart.yaml new file mode 100644 index 0000000..765554b --- /dev/null +++ b/charts/ch-ui/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: ch-ui +description: A modern, feature-rich web interface for ClickHouse databases + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.6.1" diff --git a/charts/ch-ui/README.md b/charts/ch-ui/README.md new file mode 100644 index 0000000..46b0710 --- /dev/null +++ b/charts/ch-ui/README.md @@ -0,0 +1,273 @@ +# CH-UI + +[CH-UI](https://github.com/caioricciuti/ch-ui) is a modern, feature-rich web interface for ClickHouse databases. + +## TL;DR + +```bash +helm install ch-ui ./charts/ch-ui \ + --set clickhouse.url="http://clickhouse:8123" \ + --set clickhouse.auth.password="your-password" +``` + +## Introduction + +This chart bootstraps a [CH-UI](https://github.com/caioricciuti/ch-ui) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ +- PV provisioner support in the underlying infrastructure (if persistence is needed) +- ClickHouse server accessible from the cluster + +## Installing the Chart + +To install the chart with the release name `ch-ui`: + +```bash +helm install ch-ui ./charts/ch-ui +``` + +The command deploys CH-UI on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `ch-ui` deployment: + +```bash +helm delete ch-ui +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +### Global parameters + +| Name | Description | Value | +| ------------------------- | ----------------------------------------------- | ----- | +| `nameOverride` | String to partially override ch-ui.fullname | `""` | +| `fullnameOverride` | String to fully override ch-ui.fullname | `""` | +| `imagePullSecrets` | Global Docker registry secret names as an array | `[]` | + +### Common parameters + +| Name | Description | Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `replicaCount` | Number of CH-UI replicas to deploy | `1` | +| `image.repository` | CH-UI image repository | `ghcr.io/caioricciuti/ch-ui` | +| `image.pullPolicy` | CH-UI image pull policy | `IfNotPresent` | +| `image.tag` | CH-UI image tag (immutable tags are recommended) | `""` | +| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | +| `serviceAccount.automount` | Automatically mount a ServiceAccount's API credentials? | `true` | +| `serviceAccount.annotations` | Annotations to add to the service account | `{}` | +| `serviceAccount.name` | The name of the ServiceAccount to use | `""` | +| `podAnnotations` | Annotations for CH-UI pods | `{}` | +| `podLabels` | Extra labels for CH-UI pods | `{}` | +| `podSecurityContext` | Set CH-UI pod's Security Context | `{}` | +| `securityContext` | Set CH-UI container's Security Context | `{}` | + +### CH-UI Configuration parameters + +| Name | Description | Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `clickhouse.url` | ClickHouse server URL | `"http://clickhouse:8123"` | +| `clickhouse.auth.username` | ClickHouse username | `"default"` | +| `clickhouse.auth.password` | ClickHouse password (ignored if existingSecret is set) | `""` | +| `clickhouse.auth.existingSecret` | Name of existing Secret containing ClickHouse password | `""` | +| `clickhouse.auth.secretKeys.password` | Key in the existing Secret containing the password | `"clickhouse-password"` | +| `clickhouse.useAdvanced` | Enable advanced mode | `false` | +| `clickhouse.requestTimeout` | Request timeout in milliseconds | `30000` | +| `clickhouse.basePath` | Base path for the application | `"/"` | + +### Exposure parameters + +| Name | Description | Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `service.type` | CH-UI service type | `ClusterIP` | +| `service.port` | CH-UI service HTTP port | `80` | +| `service.targetPort` | CH-UI container port | `5521` | +| `ingress.enabled` | Enable ingress record generation for CH-UI | `false` | +| `ingress.className` | IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) | `""` | +| `ingress.annotations` | Additional annotations for the Ingress resource | `{}` | +| `ingress.hosts[0].host` | Default host for the ingress record | `ch-ui.local` | +| `ingress.hosts[0].paths[0].path` | Default path for the default host | `/` | +| `ingress.hosts[0].paths[0].pathType` | Ingress path type | `ImplementationSpecific` | +| `ingress.tls` | Enable TLS configuration for the host defined at `ingress.hosts[0].host` parameter | `[]` | + +### Resource parameters + +| Name | Description | Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `resources.limits.cpu` | The CPU limits for the CH-UI containers | `500m` | +| `resources.limits.memory` | The memory limits for the CH-UI containers | `512Mi` | +| `resources.requests.cpu` | The requested CPU for the CH-UI containers | `100m` | +| `resources.requests.memory` | The requested memory for the CH-UI containers | `128Mi` | +| `livenessProbe.httpGet.path` | Path for liveness probe | `/` | +| `livenessProbe.httpGet.port` | Port for liveness probe | `http` | +| `livenessProbe.initialDelaySeconds` | Initial delay seconds for liveness probe | `30` | +| `livenessProbe.timeoutSeconds` | Timeout seconds for liveness probe | `5` | +| `readinessProbe.httpGet.path` | Path for readiness probe | `/` | +| `readinessProbe.httpGet.port` | Port for readiness probe | `http` | +| `readinessProbe.initialDelaySeconds` | Initial delay seconds for readiness probe | `5` | +| `readinessProbe.timeoutSeconds` | Timeout seconds for readiness probe | `5` | +| `autoscaling.enabled` | Enable Horizontal POD autoscaling for CH-UI | `false` | +| `autoscaling.minReplicas` | Minimum number of CH-UI replicas | `1` | +| `autoscaling.maxReplicas` | Maximum number of CH-UI replicas | `3` | +| `autoscaling.targetCPUUtilizationPercentage`| Target CPU utilization percentage | `80` | + +### Additional parameters + +| Name | Description | Value | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `extraEnvVars` | Array with extra environment variables to add to CH-UI containers | `[]` | +| `extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for CH-UI containers | `""` | +| `extraEnvVarsSecret` | Name of existing Secret containing extra env vars for CH-UI containers | `""` | +| `volumes` | Optionally specify extra list of additional volumes for the CH-UI pod(s) | `[]` | +| `volumeMounts` | Optionally specify extra list of additional volumeMounts for the CH-UI container(s) | `[]` | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `affinity` | Affinity for pod assignment | `{}` | + +## Configuration and installation details + +### Using an existing Secret + +Instead of passing the ClickHouse password directly, you can use an existing Kubernetes Secret: + +1. Create a Secret with your ClickHouse password: + +```bash +kubectl create secret generic clickhouse-secret \ + --from-literal=clickhouse-password="your-password" +``` + +2. Install the chart using the existing Secret: + +```bash +helm install ch-ui ./charts/ch-ui \ + --set clickhouse.auth.existingSecret="clickhouse-secret" \ + --set clickhouse.auth.secretKeys.password="clickhouse-password" +``` + +### Exposing CH-UI + +#### Using Ingress + +To expose CH-UI using an Ingress: + +```bash +helm install ch-ui ./charts/ch-ui \ + --set ingress.enabled=true \ + --set ingress.hosts[0].host="ch-ui.example.com" \ + --set ingress.className="nginx" +``` + +#### Using LoadBalancer + +To expose CH-UI using a LoadBalancer service: + +```bash +helm install ch-ui ./charts/ch-ui \ + --set service.type=LoadBalancer +``` + +#### Using NodePort + +To expose CH-UI using a NodePort service: + +```bash +helm install ch-ui ./charts/ch-ui \ + --set service.type=NodePort +``` + +### Adding extra environment variables + +You can add extra environment variables using `extraEnvVars`: + +```bash +helm install ch-ui ./charts/ch-ui \ + --set extraEnvVars[0].name=LOG_LEVEL \ + --set extraEnvVars[0].value=debug +``` + +Or by referencing an existing ConfigMap or Secret: + +```bash +helm install ch-ui ./charts/ch-ui \ + --set extraEnvVarsCM=my-configmap \ + --set extraEnvVarsSecret=my-secret +``` + +## Examples + +### Basic installation with password + +```bash +helm install ch-ui ./charts/ch-ui \ + --set clickhouse.url="http://my-clickhouse:8123" \ + --set clickhouse.auth.username="admin" \ + --set clickhouse.auth.password="secretpassword" +``` + +### Installation with external ClickHouse and Ingress + +```bash +helm install ch-ui ./charts/ch-ui \ + --set clickhouse.url="http://clickhouse.example.com:8123" \ + --set clickhouse.auth.existingSecret="clickhouse-credentials" \ + --set ingress.enabled=true \ + --set ingress.hosts[0].host="ch-ui.example.com" \ + --set ingress.tls[0].secretName="ch-ui-tls" \ + --set ingress.tls[0].hosts[0]="ch-ui.example.com" +``` + +### Installation with resource limits + +```bash +helm install ch-ui ./charts/ch-ui \ + --set resources.requests.memory="256Mi" \ + --set resources.requests.cpu="250m" \ + --set resources.limits.memory="1Gi" \ + --set resources.limits.cpu="1" +``` + +## Troubleshooting + +### CH-UI cannot connect to ClickHouse + +1. Verify that the ClickHouse URL is correct and accessible from within the cluster: + +```bash +kubectl run -it --rm debug --image=busybox --restart=Never -- wget -O- http://clickhouse:8123 +``` + +2. Check the CH-UI logs: + +```bash +kubectl logs -l app.kubernetes.io/name=ch-ui +``` + +3. Verify the credentials are correct by checking the Secret: + +```bash +kubectl get secret ch-ui-secret -o jsonpath='{.data.clickhouse-password}' | base64 -d +``` + +### CH-UI is not accessible + +1. Check the service is running: + +```bash +kubectl get svc -l app.kubernetes.io/name=ch-ui +``` + +2. For Ingress issues, check the Ingress controller logs and ensure the Ingress resource is created: + +```bash +kubectl get ingress +kubectl describe ingress ch-ui +``` diff --git a/charts/ch-ui/templates/NOTES.txt b/charts/ch-ui/templates/NOTES.txt new file mode 100644 index 0000000..3bd03f9 --- /dev/null +++ b/charts/ch-ui/templates/NOTES.txt @@ -0,0 +1,35 @@ +CH-UI has been deployed successfully! + +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "ch-ui.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "ch-ui.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "ch-ui.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "ch-ui.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} + +2. Configure ClickHouse connection: + The application is configured to connect to: {{ .Values.clickhouse.url }} + Using username: {{ .Values.clickhouse.auth.username }} + + Make sure your ClickHouse server is accessible and the credentials are correct. + +3. To update the ClickHouse password, run: + kubectl create secret generic {{ include "ch-ui.fullname" . }}-secret \ + --from-literal=clickhouse-password="your-password" \ + --namespace {{ .Release.Namespace }} --dry-run=client -o yaml | kubectl apply -f - diff --git a/charts/ch-ui/templates/_helpers.tpl b/charts/ch-ui/templates/_helpers.tpl new file mode 100644 index 0000000..c2b854e --- /dev/null +++ b/charts/ch-ui/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "ch-ui.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "ch-ui.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 "ch-ui.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "ch-ui.labels" -}} +helm.sh/chart: {{ include "ch-ui.chart" . }} +{{ include "ch-ui.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "ch-ui.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ch-ui.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "ch-ui.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "ch-ui.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/ch-ui/templates/deployment.yaml b/charts/ch-ui/templates/deployment.yaml new file mode 100644 index 0000000..29ff7cc --- /dev/null +++ b/charts/ch-ui/templates/deployment.yaml @@ -0,0 +1,108 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ch-ui.fullname" . }} + labels: + {{- include "ch-ui.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "ch-ui.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "ch-ui.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "ch-ui.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + env: + - name: VITE_CLICKHOUSE_URL + value: {{ .Values.clickhouse.url | quote }} + - name: VITE_CLICKHOUSE_USER + value: {{ .Values.clickhouse.auth.username | quote }} + - name: VITE_CLICKHOUSE_PASS + valueFrom: + secretKeyRef: + name: {{ .Values.clickhouse.auth.existingSecret | default (printf "%s-secret" (include "ch-ui.fullname" .)) }} + key: {{ .Values.clickhouse.auth.secretKeys.password }} + - name: VITE_CLICKHOUSE_USE_ADVANCED + value: {{ .Values.clickhouse.useAdvanced | quote }} + - name: VITE_CLICKHOUSE_REQUEST_TIMEOUT + value: {{ .Values.clickhouse.requestTimeout | quote }} + - name: VITE_BASE_PATH + value: {{ .Values.clickhouse.basePath | quote }} + {{- if .Values.extraEnvVars }} + {{- toYaml .Values.extraEnvVars | nindent 12 }} + {{- end }} + {{- if or .Values.extraEnvVarsCM .Values.extraEnvVarsSecret }} + envFrom: + {{- if .Values.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.extraEnvVarsCM }} + {{- end }} + {{- if .Values.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.extraEnvVarsSecret }} + {{- end }} + {{- end }} + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/ch-ui/templates/hpa.yaml b/charts/ch-ui/templates/hpa.yaml new file mode 100644 index 0000000..5b9a429 --- /dev/null +++ b/charts/ch-ui/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "ch-ui.fullname" . }} + labels: + {{- include "ch-ui.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "ch-ui.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/ch-ui/templates/ingress.yaml b/charts/ch-ui/templates/ingress.yaml new file mode 100644 index 0000000..7c1a28b --- /dev/null +++ b/charts/ch-ui/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "ch-ui.fullname" . }} + labels: + {{- include "ch-ui.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "ch-ui.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/ch-ui/templates/secret.yaml b/charts/ch-ui/templates/secret.yaml new file mode 100644 index 0000000..100b788 --- /dev/null +++ b/charts/ch-ui/templates/secret.yaml @@ -0,0 +1,11 @@ +{{- if not .Values.clickhouse.auth.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "ch-ui.fullname" . }}-secret + labels: + {{- include "ch-ui.labels" . | nindent 4 }} +type: Opaque +data: + {{ .Values.clickhouse.auth.secretKeys.password }}: {{ .Values.clickhouse.auth.password | b64enc }} +{{- end }} \ No newline at end of file diff --git a/charts/ch-ui/templates/service.yaml b/charts/ch-ui/templates/service.yaml new file mode 100644 index 0000000..a279d02 --- /dev/null +++ b/charts/ch-ui/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ch-ui.fullname" . }} + labels: + {{- include "ch-ui.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "ch-ui.selectorLabels" . | nindent 4 }} diff --git a/charts/ch-ui/templates/serviceaccount.yaml b/charts/ch-ui/templates/serviceaccount.yaml new file mode 100644 index 0000000..a0631c9 --- /dev/null +++ b/charts/ch-ui/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "ch-ui.serviceAccountName" . }} + labels: + {{- include "ch-ui.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/charts/ch-ui/templates/tests/test-connection.yaml b/charts/ch-ui/templates/tests/test-connection.yaml new file mode 100644 index 0000000..26fe915 --- /dev/null +++ b/charts/ch-ui/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "ch-ui.fullname" . }}-test-connection" + labels: + {{- include "ch-ui.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "ch-ui.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/ch-ui/values.yaml b/charts/ch-ui/values.yaml new file mode 100644 index 0000000..59b2ab2 --- /dev/null +++ b/charts/ch-ui/values.yaml @@ -0,0 +1,116 @@ +# Default values for ch-ui. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: ghcr.io/caioricciuti/ch-ui + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + automount: true + annotations: {} + name: "" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + targetPort: 5521 + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: ch-ui.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + timeoutSeconds: 5 +readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + timeoutSeconds: 5 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + +volumes: [] +volumeMounts: [] +nodeSelector: {} +tolerations: [] +affinity: {} + +# CH-UI specific configuration +clickhouse: + # ClickHouse server URL + url: "http://clickhouse:8123" + + # Authentication configuration + auth: + # ClickHouse username + username: "default" + # ClickHouse password (ignored if existingSecret is set) + password: "" + # Name of an existing secret containing the authentication credentials + existingSecret: "" + # Secret keys for authentication credentials + secretKeys: + # Key in the secret containing the password + password: "clickhouse-password" + + # Advanced configuration + useAdvanced: false + requestTimeout: 30000 + basePath: "/" + +# Additional environment variables +extraEnvVars: [] +# - name: MY_ENV_VAR +# value: my-value + +# Environment variables from existing ConfigMaps or Secrets +extraEnvVarsCM: "" +extraEnvVarsSecret: "" diff --git a/justfile b/justfile index d836a24..f103d8d 100644 --- a/justfile +++ b/justfile @@ -7,6 +7,7 @@ default: @just --list --unsorted --list-submodules mod airflow +mod ch-ui mod clickhouse mod datahub mod env