From 416b18232f4bb71a837fbd1a5551b8db9e87dfb1 Mon Sep 17 00:00:00 2001 From: Masaki Yatsu Date: Fri, 12 Sep 2025 23:29:28 +0900 Subject: [PATCH] feat(clickhouse): create ingress --- clickhouse/.gitignore | 1 + clickhouse/clickhouse-ingress.gomplate.yaml | 23 +++ clickhouse/clickhouse.yaml | 4 + clickhouse/justfile | 214 +++++++++++++++----- 4 files changed, 193 insertions(+), 49 deletions(-) create mode 100644 clickhouse/clickhouse-ingress.gomplate.yaml diff --git a/clickhouse/.gitignore b/clickhouse/.gitignore index 34b9e4c..2c7b9cc 100644 --- a/clickhouse/.gitignore +++ b/clickhouse/.gitignore @@ -1 +1,2 @@ clickhouse-credentials-external-secret.yaml +clickhouse-ingress.yaml diff --git a/clickhouse/clickhouse-ingress.gomplate.yaml b/clickhouse/clickhouse-ingress.gomplate.yaml new file mode 100644 index 0000000..f84a3eb --- /dev/null +++ b/clickhouse/clickhouse-ingress.gomplate.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: clickhouse-ingress + namespace: clickhouse + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure +spec: + ingressClassName: traefik + rules: + - host: {{ .Env.CLICKHOUSE_HOST }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: clickhouse-clickhouse + port: + number: 8123 + tls: + - hosts: + - {{ .Env.CLICKHOUSE_HOST }} diff --git a/clickhouse/clickhouse.yaml b/clickhouse/clickhouse.yaml index b2307eb..023dd5f 100644 --- a/clickhouse/clickhouse.yaml +++ b/clickhouse/clickhouse.yaml @@ -21,9 +21,13 @@ spec: admin/k8s_secret_password: clickhouse-credentials/admin admin/networks/ip: "::/0" admin/access_management: 1 + # Disable default user + default/password: "disabled" + default/networks/ip: "127.0.0.1" profiles: default/max_memory_usage: 4000000000 # 4GB default/max_bytes_before_external_group_by: 2000000000 # 2GB + default/add_http_cors_header: 1 templates: volumeClaimTemplates: - name: data-volume-template diff --git a/clickhouse/justfile b/clickhouse/justfile index e58d912..b48363d 100644 --- a/clickhouse/justfile +++ b/clickhouse/justfile @@ -1,6 +1,7 @@ set fallback := true export CLICKHOUSE_NAMESPACE := env("CLICKHOUSE_NAMESPACE", "clickhouse") +export CLICKHOUSE_HOST := env("CLICKHOUSE_HOST", "") export CLICKHOUSE_CHART_VERSION := env("CLICKHOUSE_CHART_VERSION", "0.25.3") export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets") @@ -59,6 +60,16 @@ delete-credentials-secret: # Install ClickHouse install: + #!/bin/bash + set -euo pipefail + export CLICKHOUSE_HOST=${CLICKHOUSE_HOST:-} + while [ -z "${CLICKHOUSE_HOST}" ]; do + CLICKHOUSE_HOST=$( + gum input --prompt="ClickHouse host (FQDN): " --width=100 \ + --placeholder="e.g., clickhouse.example.com" + ) + done + echo "Installing ClickHouse..." just create-namespace just install-zookeeper just create-credentials @@ -69,7 +80,19 @@ install: echo "Waiting for ClickHouse installation to be ready..." kubectl wait --for=jsonpath='{.status.status}'=Completed \ clickhouseinstallation/clickhouse -n ${CLICKHOUSE_NAMESPACE} --timeout=600s + just setup-ingress ${CLICKHOUSE_HOST} echo "ClickHouse installation completed successfully" + echo "ClickHouse API at: https://${CLICKHOUSE_HOST}" + +# Setup ClickHouse Ingress +setup-ingress host: + #!/bin/bash + set -euo pipefail + echo "Setting up ClickHouse Ingress for ${CLICKHOUSE_HOST}..." + export CLICKHOUSE_HOST="{{ host }}" + gomplate -f clickhouse-ingress.gomplate.yaml -o clickhouse-ingress.yaml + kubectl apply -n ${CLICKHOUSE_NAMESPACE} -f clickhouse-ingress.yaml + echo "ClickHouse Ingress configured successfully" # Uninstall ClickHouse uninstall: @@ -95,32 +118,92 @@ uninstall: just delete-namespace echo "ClickHouse uninstalled successfully" -# Print ClickHouse admin password +# Get ClickHouse admin password admin-password: #!/bin/bash set -euo pipefail - if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then - echo "Getting password from Vault..." - just vault::get clickhouse/credentials admin - else - echo "Getting password from Kubernetes Secret..." - kubectl get secret clickhouse-credentials -n ${CLICKHOUSE_NAMESPACE} \ - -o jsonpath='{.data.admin}' | base64 -d - echo - fi + kubectl get secret clickhouse-credentials -n ${CLICKHOUSE_NAMESPACE} \ + -o jsonpath='{.data.admin}' 2>/dev/null | base64 -d | tr -d '\n\r' + echo -# Connect to ClickHouse as admin -connect-admin: check-env +# Connect to ClickHouse as admin (interactive) +connect-admin-interactive: check-env @just utils::check-connection clickhouse-clickhouse.clickhouse 9000 @clickhouse client --host clickhouse-clickhouse.clickhouse --port 9000 \ --user admin --password $(just clickhouse::admin-password) -# Connect to ClickHouse -connect user: check-env +# Execute SQL as admin via kubectl exec +connect-admin: + #!/bin/bash + set -euo pipefail + ADMIN_PASSWORD=$(just admin-password) + POD_NAME=$(just find-clickhouse-pod) + kubectl exec -n ${CLICKHOUSE_NAMESPACE} "${POD_NAME}" -c clickhouse -- \ + clickhouse-client --user admin --password "${ADMIN_PASSWORD}" + +# Connect to ClickHouse (interactive) +connect-interactive user: check-env @just utils::check-connection clickhouse-clickhouse.clickhouse 9000 @clickhouse client --host clickhouse-clickhouse.clickhouse --port 9000 \ --user {{ user }} --ask-password +# Execute SQL as specific user via kubectl exec +connect user password='': + #!/bin/bash + set -euo pipefail + USER="{{ user }}" + PASSWORD="{{ password }}" + if [ -z "${PASSWORD}" ]; then + PASSWORD=$(gum input --prompt="Password for ${USER}: " --password --width=100) + fi + POD_NAME=$(just find-clickhouse-pod) + kubectl exec -n ${CLICKHOUSE_NAMESPACE} "${POD_NAME}" -c clickhouse -- \ + clickhouse-client --user "${USER}" --password "${PASSWORD}" + +# Find ClickHouse pod name +[private] +find-clickhouse-pod: + #!/bin/bash + set -euo pipefail + for SELECTOR in \ + "app=clickhouse-clickhouse" \ + "app.kubernetes.io/name=clickhouse" \ + "clickhouse.altinity.com/chi=clickhouse" \ + "app=clickhouse"; do + + POD_NAME=$(kubectl get pods -n ${CLICKHOUSE_NAMESPACE} -l "${SELECTOR}" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "") + if [ -n "${POD_NAME}" ]; then + echo "${POD_NAME}" + exit 0 + fi + done + POD_NAME=$(kubectl get pods -n ${CLICKHOUSE_NAMESPACE} -o name | grep -i clickhouse | head -1 | cut -d'/' -f2 2>/dev/null || echo "") + if [ -n "${POD_NAME}" ]; then + echo "${POD_NAME}" + exit 0 + fi + echo "No ClickHouse pods found in namespace ${CLICKHOUSE_NAMESPACE}" >&2 + echo "Available pods:" >&2 + kubectl get pods -n ${CLICKHOUSE_NAMESPACE} >&2 + exit 1 + +# Execute SQL command as admin +exec-sql-admin sql='': + #!/bin/bash + set -euo pipefail + SQL="{{ sql }}" + ADMIN_PASSWORD=$(just admin-password) + POD_NAME=$(just find-clickhouse-pod) + if [ -n "${SQL}" ]; then + # Pass SQL via stdin to avoid quoting issues + echo "${SQL}" | kubectl exec -i -n ${CLICKHOUSE_NAMESPACE} "${POD_NAME}" -c clickhouse -- \ + sh -c "export CLICKHOUSE_PASSWORD='${ADMIN_PASSWORD}' && clickhouse-client --user admin" + else + # Read from stdin + kubectl exec -i -n ${CLICKHOUSE_NAMESPACE} "${POD_NAME}" -c clickhouse -- \ + sh -c "export CLICKHOUSE_PASSWORD='${ADMIN_PASSWORD}' && clickhouse-client --user admin" + fi + # Create ClickHouse user create-user username='' password='': #!/bin/bash @@ -143,11 +226,12 @@ create-user username='' password='': echo "Generated random password: ${PASSWORD}" fi echo "Creating ClickHouse user '${USERNAME}'..." - just connect-admin </dev/null || echo "") + if [[ -n "$result" && "$result" == *"$USERNAME"* ]]; then echo "User '$USERNAME' exists." exit 0 @@ -329,9 +401,7 @@ create-db database='default': DATABASE=$(gum input --prompt="Database: " --width=100) done echo "Creating ClickHouse database '${DATABASE}'..." - just connect-admin </dev/null || echo "") + if [[ -n "$result" && "$result" == *"$DATABASE"* ]]; then echo "Database '$DATABASE' exists." exit 0 @@ -362,9 +430,57 @@ user-privilege username='': while [ -z "${USERNAME}" ]; do USERNAME=$(gum input --prompt="Username: " --width=100) done - just connect-admin </dev/null; then + echo "User ${USERNAME} does not exist." >&2 + exit 1 + fi + echo "Changing password for user '${USERNAME}'..." + just exec-sql-admin "ALTER USER '${USERNAME}' IDENTIFIED BY '${PASSWORD}';" + echo "Password changed for user '${USERNAME}'" + +# Grant admin privileges to user +grant-admin username='': + #!/bin/bash + set -euo pipefail + USERNAME="${USERNAME:-"{{ username }}"}" + while [ -z "${USERNAME}" ]; do + USERNAME=$(gum input --prompt="Username: " --width=100) + done + for i in {1..5}; do + if just user-exists ${USERNAME} &>/dev/null; then + break + fi + if [ $i -lt 5 ]; then + echo "User ${USERNAME} not found, retrying in 1 second... (attempt $i/5)" + sleep 1 + else + echo "User ${USERNAME} does not exist after 5 attempts." >&2 + exit 1 + fi + done + echo "Granting admin privileges to user '${USERNAME}'..." + just exec-sql-admin " + GRANT SOURCES ON *.* TO '${USERNAME}' WITH GRANT OPTION; + GRANT TABLE ENGINE ON * TO '${USERNAME}' WITH GRANT OPTION; + GRANT CHECK, SHOW, SELECT, INSERT, ALTER, CREATE, DROP, UNDROP TABLE, TRUNCATE, OPTIMIZE, BACKUP, KILL QUERY, KILL TRANSACTION, MOVE PARTITION BETWEEN SHARDS, ROLE ADMIN, CREATE ROW POLICY, ALTER ROW POLICY, DROP ROW POLICY, CREATE QUOTA, ALTER QUOTA, DROP QUOTA, CREATE SETTINGS PROFILE, ALTER SETTINGS PROFILE, DROP SETTINGS PROFILE, ALLOW SQL SECURITY NONE, SHOW ACCESS, SYSTEM, dictGet, displaySecretsInShowAndSelect, INTROSPECTION, CLUSTER, FILE, URL, REMOTE, MONGO, REDIS, MYSQL, POSTGRES, SQLITE, ODBC, JDBC, HDFS, S3, HIVE, AZURE, KAFKA, NATS, RABBITMQ, SOURCES ON *.* TO '${USERNAME}' WITH GRANT OPTION; + GRANT SET DEFINER ON * TO '${USERNAME}' WITH GRANT OPTION; + GRANT CREATE USER, ALTER USER, DROP USER, CREATE ROLE, ALTER ROLE, DROP ROLE ON * TO '${USERNAME}' WITH GRANT OPTION; + " + echo "Admin privileges granted to user '${USERNAME}'" # Install ZooKeeper install-zookeeper: