From fd750404e27525142c3037a1208683291cfce040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:14:24 +0800 Subject: [PATCH 01/14] feat: add prometheus stack version BOM --- versions.env | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 versions.env diff --git a/versions.env b/versions.env new file mode 100644 index 0000000..bbb9d56 --- /dev/null +++ b/versions.env @@ -0,0 +1,11 @@ +APP_VERSION=0.2.0 +KUBE_PROMETHEUS_STACK_VERSION=90.2.0 +PROMETHEUS_OPERATOR_VERSION=0.93.1 +PROMETHEUS_VERSION=3.14.0-distroless +ALERTMANAGER_VERSION=0.34.0 +GRAFANA_CHART_VERSION=13.2.4 +GRAFANA_VERSION=13.2.1-distroless +KUBE_STATE_METRICS_CHART_VERSION=8.4.2 +KUBE_STATE_METRICS_VERSION=2.20.0 +NODE_EXPORTER_CHART_VERSION=4.57.0 +NODE_EXPORTER_VERSION=1.12.1 From c9b60ae19b27cc31ad4915a08499a7493a5bd68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:14:31 +0800 Subject: [PATCH 02/14] docs: add v0.2.0 component BOM --- docs/VERSION-BOM.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 docs/VERSION-BOM.md diff --git a/docs/VERSION-BOM.md b/docs/VERSION-BOM.md new file mode 100644 index 0000000..84b47ae --- /dev/null +++ b/docs/VERSION-BOM.md @@ -0,0 +1,38 @@ +# Prometheus Stack Version BOM + +Release target: `app_prometheus-stack v0.2.0` + +| Component | Version | +|---|---:| +| kube-prometheus-stack | 90.2.0 | +| Prometheus Operator | 0.93.1 | +| Prometheus | 3.14.0-distroless | +| Alertmanager | 0.34.0 | +| Grafana Helm chart | 13.2.4 | +| Grafana | 13.2.1-distroless | +| kube-state-metrics Helm chart | 8.4.2 | +| kube-state-metrics | 2.20.0 | +| prometheus-node-exporter Helm chart | 4.57.0 | +| node-exporter | 1.12.1 | +| Thanos | 0.42.4 | +| k8s-sidecar | 2.11.2 | +| kube-rbac-proxy | 0.22.1 | +| kube-webhook-certgen | 1.8.8 | +| Grafana image renderer | 5.10.3 | + +## Build model + +The release artifact no longer depends on the vendored chart directory being manually kept in sync. +`build.sh` pulls the exact `kube-prometheus-stack` OCI chart version declared in `versions.env`, +embeds it into the `.run` package, and verifies the chart version before packaging. + +## Upgrade behavior + +- First install: phase 1 installs CRDs/operator, phase 2 installs the complete stack. +- Existing Helm release: bundled CRDs are applied with server-side apply, then a single full-stack Helm upgrade is executed. +- `--skip-crd-upgrade` is available for controlled exception cases. + +## Parameter model + +Top-level platform defaults are exposed through `-h/--help` and can be overridden by CLI. +For settings not modeled directly by the installer, `--values-file` can be repeated to add site-specific Helm values. From 4932bfc0d171dd0d40aad700beb45448d8b20b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:14:56 +0800 Subject: [PATCH 03/14] build: pull pinned upstream prometheus chart --- build.sh | 91 +++++++++++++++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/build.sh b/build.sh index 7a8b284..7561f6a 100644 --- a/build.sh +++ b/build.sh @@ -3,6 +3,9 @@ set -Eeuo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "${ROOT_DIR}/versions.env" + TEMP_DIR="${ROOT_DIR}/.build-payload" PAYLOAD_DIR="${TEMP_DIR}/payload" PAYLOAD_FILE="${TEMP_DIR}/payload.tar.gz" @@ -11,7 +14,7 @@ IMAGES_DIR="${ROOT_DIR}/images" IMAGE_JSON="${IMAGES_DIR}/image.json" INSTALLER_TEMPLATE="${ROOT_DIR}/install.sh" INSTALLER_BASENAME="prometheus-stack-installer" -CHART_SRC_DIR="${ROOT_DIR}/charts/kube-prometheus-stack" +CHART_OCI="oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack" ARCH="amd64" PLATFORM="linux/amd64" @@ -19,34 +22,25 @@ BUILD_ALL_ARCH="false" RED='\033[0;31m' GREEN='\033[0;32m' -YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' -log() { - echo -e "${CYAN}[INFO]${NC} $*" -} - -success() { - echo -e "${GREEN}[OK]${NC} $*" -} - -die() { - echo -e "${RED}[ERROR]${NC} $*" >&2 - exit 1 -} - -cleanup() { - rm -rf "${TEMP_DIR}" -} +log() { echo -e "${CYAN}[INFO]${NC} $*"; } +success() { echo -e "${GREEN}[OK]${NC} $*"; } +die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } +cleanup() { rm -rf "${TEMP_DIR}"; } trap cleanup EXIT usage() { - cat <<'EOF' + cat </dev/null 2>&1 || die "jq is required" command -v docker >/dev/null 2>&1 || die "docker is required" + command -v helm >/dev/null 2>&1 || die "helm is required" command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required" [[ -f "${INSTALLER_TEMPLATE}" ]] || die "install.sh is missing" [[ -f "${IMAGE_JSON}" ]] || die "images/image.json is missing" - [[ -d "${CHART_SRC_DIR}" ]] || die "charts/kube-prometheus-stack is missing" + [[ -f "${ROOT_DIR}/versions.env" ]] || die "versions.env is missing" grep -q '^__PAYLOAD_BELOW__$' "${INSTALLER_TEMPLATE}" || die "install.sh is missing __PAYLOAD_BELOW__ marker" } @@ -109,6 +87,24 @@ prepare_directories() { mkdir -p "${PAYLOAD_DIR}/charts" "${PAYLOAD_DIR}/images" "${DIST_DIR}" } +prepare_chart() { + log "Pulling kube-prometheus-stack ${KUBE_PROMETHEUS_STACK_VERSION}" + helm pull "${CHART_OCI}" \ + --version "${KUBE_PROMETHEUS_STACK_VERSION}" \ + --untar \ + --untardir "${PAYLOAD_DIR}/charts" + + local chart_file="${PAYLOAD_DIR}/charts/kube-prometheus-stack/Chart.yaml" + [[ -f "${chart_file}" ]] || die "Pulled chart is missing Chart.yaml" + + local actual_version + actual_version="$(awk '$1=="version:" {print $2; exit}' "${chart_file}")" + [[ "${actual_version}" == "${KUBE_PROMETHEUS_STACK_VERSION}" ]] \ + || die "Chart version mismatch: expected=${KUBE_PROMETHEUS_STACK_VERSION}, actual=${actual_version}" + + success "Prepared kube-prometheus-stack ${actual_version}" +} + image_name_tag_from_ref() { local ref="$1" echo "${ref##*/}" @@ -162,9 +158,6 @@ package_payload() { local installer_path="${DIST_DIR}/${INSTALLER_BASENAME}-${arch}.run" local checksum_path="${installer_path}.sha256" - log "Copying chart payload" - cp -R "${CHART_SRC_DIR}" "${PAYLOAD_DIR}/charts/" - log "Creating payload archive" tar -C "${PAYLOAD_DIR}" -czf "${PAYLOAD_FILE}" . tar -tzf "${PAYLOAD_FILE}" >/dev/null @@ -173,7 +166,10 @@ package_payload() { cat "${INSTALLER_TEMPLATE}" "${PAYLOAD_FILE}" > "${installer_path}" chmod +x "${installer_path}" - sha256sum "${installer_path}" | awk '{print $1}' > "${checksum_path}" + ( + cd "${DIST_DIR}" + sha256sum "$(basename "${installer_path}")" > "$(basename "${checksum_path}")" + ) success "Generated $(basename "${installer_path}")" } @@ -196,6 +192,7 @@ build_one() { PLATFORM="${platform}" prepare_directories + prepare_chart prepare_images "${arch}" "${platform}" package_payload "${arch}" show_result "${arch}" From fa30a54c748b1b36db3dbb416d3ef78119c4f972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:15:27 +0800 Subject: [PATCH 04/14] build: refresh prometheus stack image BOM --- images/image.json | 236 +++++++++++++++++++++++----------------------- 1 file changed, 118 insertions(+), 118 deletions(-) diff --git a/images/image.json b/images/image.json index 4a7626e..c5edc88 100644 --- a/images/image.json +++ b/images/image.json @@ -2,211 +2,211 @@ { "arch": "amd64", "platform": "linux/amd64", - "pull": "quay.io/prometheus/alertmanager:v0.30.1", - "tag": "sealos.hub:5000/kube4/alertmanager:v0.30.1", - "tar": "alertmanager-v0.30.1-amd64.tar" + "pull": "quay.io/prometheus/alertmanager:v0.34.0", + "tag": "sealos.hub:5000/kube4/alertmanager:v0.34.0", + "tar": "alertmanager-v0.34.0-amd64.tar" }, { - "arch": "arm64", - "platform": "linux/arm64", - "pull": "quay.io/prometheus/alertmanager:v0.30.1", - "tag": "sealos.hub:5000/kube4/alertmanager:v0.30.1", - "tar": "alertmanager-v0.30.1-arm64.tar" + "arch": "amd64", + "platform": "linux/amd64", + "pull": "quay.io/prometheus-operator/admission-webhook:v0.93.1", + "tag": "sealos.hub:5000/kube4/admission-webhook:v0.93.1", + "tar": "admission-webhook-v0.93.1-amd64.tar" }, { "arch": "amd64", "platform": "linux/amd64", - "pull": "quay.io/prometheus-operator/admission-webhook:v0.88.1", - "tag": "sealos.hub:5000/kube4/admission-webhook:v0.88.1", - "tar": "admission-webhook-v0.88.1-amd64.tar" + "pull": "quay.io/prometheus-operator/prometheus-operator:v0.93.1", + "tag": "sealos.hub:5000/kube4/prometheus-operator:v0.93.1", + "tar": "prometheus-operator-v0.93.1-amd64.tar" }, { - "arch": "arm64", - "platform": "linux/arm64", - "pull": "quay.io/prometheus-operator/admission-webhook:v0.88.1", - "tag": "sealos.hub:5000/kube4/admission-webhook:v0.88.1", - "tar": "admission-webhook-v0.88.1-arm64.tar" + "arch": "amd64", + "platform": "linux/amd64", + "pull": "quay.io/prometheus-operator/prometheus-config-reloader:v0.93.1", + "tag": "sealos.hub:5000/kube4/prometheus-config-reloader:v0.93.1", + "tar": "prometheus-config-reloader-v0.93.1-amd64.tar" }, { "arch": "amd64", "platform": "linux/amd64", - "pull": "quay.io/prometheus-operator/prometheus-operator:v0.88.1", - "tag": "sealos.hub:5000/kube4/prometheus-operator:v0.88.1", - "tar": "prometheus-operator-v0.88.1-amd64.tar" + "pull": "quay.io/thanos/thanos:v0.42.4", + "tag": "sealos.hub:5000/kube4/thanos:v0.42.4", + "tar": "thanos-v0.42.4-amd64.tar" }, { - "arch": "arm64", - "platform": "linux/arm64", - "pull": "quay.io/prometheus-operator/prometheus-operator:v0.88.1", - "tag": "sealos.hub:5000/kube4/prometheus-operator:v0.88.1", - "tar": "prometheus-operator-v0.88.1-arm64.tar" + "arch": "amd64", + "platform": "linux/amd64", + "pull": "quay.io/prometheus/prometheus:v3.14.0-distroless", + "tag": "sealos.hub:5000/kube4/prometheus:v3.14.0-distroless", + "tar": "prometheus-v3.14.0-distroless-amd64.tar" }, { "arch": "amd64", "platform": "linux/amd64", - "pull": "quay.io/prometheus-operator/prometheus-config-reloader:v0.88.1", - "tag": "sealos.hub:5000/kube4/prometheus-config-reloader:v0.88.1", - "tar": "prometheus-config-reloader-v0.88.1-amd64.tar" + "pull": "docker.io/grafana/grafana:13.2.1-distroless", + "tag": "sealos.hub:5000/kube4/grafana:13.2.1-distroless", + "tar": "grafana-13.2.1-distroless-amd64.tar" }, { - "arch": "arm64", - "platform": "linux/arm64", - "pull": "quay.io/prometheus-operator/prometheus-config-reloader:v0.88.1", - "tag": "sealos.hub:5000/kube4/prometheus-config-reloader:v0.88.1", - "tar": "prometheus-config-reloader-v0.88.1-arm64.tar" + "arch": "amd64", + "platform": "linux/amd64", + "pull": "docker.io/curlimages/curl:8.22.0", + "tag": "sealos.hub:5000/kube4/curl:8.22.0", + "tar": "curl-8.22.0-amd64.tar" }, { "arch": "amd64", "platform": "linux/amd64", - "pull": "quay.io/thanos/thanos:v0.40.1", - "tag": "sealos.hub:5000/kube4/thanos:v0.40.1", - "tar": "thanos-v0.40.1-amd64.tar" + "pull": "docker.io/library/busybox:1.38.0", + "tag": "sealos.hub:5000/kube4/busybox:1.38.0", + "tar": "busybox-1.38.0-amd64.tar" }, { - "arch": "arm64", - "platform": "linux/arm64", - "pull": "quay.io/thanos/thanos:v0.40.1", - "tag": "sealos.hub:5000/kube4/thanos:v0.40.1", - "tar": "thanos-v0.40.1-arm64.tar" + "arch": "amd64", + "platform": "linux/amd64", + "pull": "quay.io/kiwigrid/k8s-sidecar:2.11.2", + "tag": "sealos.hub:5000/kube4/k8s-sidecar:2.11.2", + "tar": "k8s-sidecar-2.11.2-amd64.tar" }, { "arch": "amd64", "platform": "linux/amd64", - "pull": "quay.io/prometheus/prometheus:v3.9.1", - "tag": "sealos.hub:5000/kube4/prometheus:v3.9.1", - "tar": "prometheus-v3.9.1-amd64.tar" + "pull": "docker.io/grafana/grafana-image-renderer:5.10.3", + "tag": "sealos.hub:5000/kube4/grafana-image-renderer:5.10.3", + "tar": "grafana-image-renderer-5.10.3-amd64.tar" }, { - "arch": "arm64", - "platform": "linux/arm64", - "pull": "quay.io/prometheus/prometheus:v3.9.1", - "tag": "sealos.hub:5000/kube4/prometheus:v3.9.1", - "tar": "prometheus-v3.9.1-arm64.tar" + "arch": "amd64", + "platform": "linux/amd64", + "pull": "quay.io/brancz/kube-rbac-proxy:v0.22.1", + "tag": "sealos.hub:5000/kube4/kube-rbac-proxy:v0.22.1", + "tar": "kube-rbac-proxy-v0.22.1-amd64.tar" }, { "arch": "amd64", "platform": "linux/amd64", - "pull": "docker.io/grafana/grafana:12.3.1", - "tag": "sealos.hub:5000/kube4/grafana:12.3.1", - "tar": "grafana-12.3.1-amd64.tar" + "pull": "registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.20.0", + "tag": "sealos.hub:5000/kube4/kube-state-metrics:v2.20.0", + "tar": "kube-state-metrics-v2.20.0-amd64.tar" }, { - "arch": "arm64", - "platform": "linux/arm64", - "pull": "docker.io/grafana/grafana:12.3.1", - "tag": "sealos.hub:5000/kube4/grafana:12.3.1", - "tar": "grafana-12.3.1-arm64.tar" + "arch": "amd64", + "platform": "linux/amd64", + "pull": "quay.io/prometheus/node-exporter:v1.12.1", + "tag": "sealos.hub:5000/kube4/node-exporter:v1.12.1", + "tar": "node-exporter-v1.12.1-amd64.tar" }, { "arch": "amd64", "platform": "linux/amd64", - "pull": "docker.io/curlimages/curl:8.9.1", - "tag": "sealos.hub:5000/kube4/curl:8.9.1", - "tar": "curl-8.9.1-amd64.tar" + "pull": "ghcr.io/jkroepke/kube-webhook-certgen:1.8.8", + "tag": "sealos.hub:5000/kube4/kube-webhook-certgen:1.8.8", + "tar": "kube-webhook-certgen-1.8.8-amd64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "docker.io/curlimages/curl:8.9.1", - "tag": "sealos.hub:5000/kube4/curl:8.9.1", - "tar": "curl-8.9.1-arm64.tar" + "pull": "quay.io/prometheus/alertmanager:v0.34.0", + "tag": "sealos.hub:5000/kube4/alertmanager:v0.34.0", + "tar": "alertmanager-v0.34.0-arm64.tar" }, { - "arch": "amd64", - "platform": "linux/amd64", - "pull": "docker.io/busybox:1.36.1", - "tag": "sealos.hub:5000/kube4/busybox:v1", - "tar": "busybox-v1-amd64.tar" + "arch": "arm64", + "platform": "linux/arm64", + "pull": "quay.io/prometheus-operator/admission-webhook:v0.93.1", + "tag": "sealos.hub:5000/kube4/admission-webhook:v0.93.1", + "tar": "admission-webhook-v0.93.1-arm64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "docker.io/busybox:1.36.1", - "tag": "sealos.hub:5000/kube4/busybox:v1", - "tar": "busybox-v1-arm64.tar" + "pull": "quay.io/prometheus-operator/prometheus-operator:v0.93.1", + "tag": "sealos.hub:5000/kube4/prometheus-operator:v0.93.1", + "tar": "prometheus-operator-v0.93.1-arm64.tar" }, { - "arch": "amd64", - "platform": "linux/amd64", - "pull": "docker.io/kiwigrid/k8s-sidecar:2.5.0", - "tag": "sealos.hub:5000/kube4/k8s-sidecar:2.5.0", - "tar": "k8s-sidecar-2.5.0-amd64.tar" + "arch": "arm64", + "platform": "linux/arm64", + "pull": "quay.io/prometheus-operator/prometheus-config-reloader:v0.93.1", + "tag": "sealos.hub:5000/kube4/prometheus-config-reloader:v0.93.1", + "tar": "prometheus-config-reloader-v0.93.1-arm64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "docker.io/kiwigrid/k8s-sidecar:2.5.0", - "tag": "sealos.hub:5000/kube4/k8s-sidecar:2.5.0", - "tar": "k8s-sidecar-2.5.0-arm64.tar" + "pull": "quay.io/thanos/thanos:v0.42.4", + "tag": "sealos.hub:5000/kube4/thanos:v0.42.4", + "tar": "thanos-v0.42.4-arm64.tar" }, { - "arch": "amd64", - "platform": "linux/amd64", - "pull": "docker.io/grafana/grafana-image-renderer:latest", - "tag": "sealos.hub:5000/kube4/grafana-image-renderer:latest", - "tar": "grafana-image-renderer-latest-amd64.tar" + "arch": "arm64", + "platform": "linux/arm64", + "pull": "quay.io/prometheus/prometheus:v3.14.0-distroless", + "tag": "sealos.hub:5000/kube4/prometheus:v3.14.0-distroless", + "tar": "prometheus-v3.14.0-distroless-arm64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "docker.io/grafana/grafana-image-renderer:latest", - "tag": "sealos.hub:5000/kube4/grafana-image-renderer:latest", - "tar": "grafana-image-renderer-latest-arm64.tar" + "pull": "docker.io/grafana/grafana:13.2.1-distroless", + "tag": "sealos.hub:5000/kube4/grafana:13.2.1-distroless", + "tar": "grafana-13.2.1-distroless-arm64.tar" }, { - "arch": "amd64", - "platform": "linux/amd64", - "pull": "quay.io/brancz/kube-rbac-proxy:v0.20.2", - "tag": "sealos.hub:5000/kube4/kube-rbac-proxy:v0.20.2", - "tar": "kube-rbac-proxy-v0.20.2-amd64.tar" + "arch": "arm64", + "platform": "linux/arm64", + "pull": "docker.io/curlimages/curl:8.22.0", + "tag": "sealos.hub:5000/kube4/curl:8.22.0", + "tar": "curl-8.22.0-arm64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "quay.io/brancz/kube-rbac-proxy:v0.20.2", - "tag": "sealos.hub:5000/kube4/kube-rbac-proxy:v0.20.2", - "tar": "kube-rbac-proxy-v0.20.2-arm64.tar" + "pull": "docker.io/library/busybox:1.38.0", + "tag": "sealos.hub:5000/kube4/busybox:1.38.0", + "tar": "busybox-1.38.0-arm64.tar" }, { - "arch": "amd64", - "platform": "linux/amd64", - "pull": "registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.18.0", - "tag": "sealos.hub:5000/kube4/kube-state-metrics:v2.18.0", - "tar": "kube-state-metrics-v2.18.0-amd64.tar" + "arch": "arm64", + "platform": "linux/arm64", + "pull": "quay.io/kiwigrid/k8s-sidecar:2.11.2", + "tag": "sealos.hub:5000/kube4/k8s-sidecar:2.11.2", + "tar": "k8s-sidecar-2.11.2-arm64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.18.0", - "tag": "sealos.hub:5000/kube4/kube-state-metrics:v2.18.0", - "tar": "kube-state-metrics-v2.18.0-arm64.tar" + "pull": "docker.io/grafana/grafana-image-renderer:5.10.3", + "tag": "sealos.hub:5000/kube4/grafana-image-renderer:5.10.3", + "tar": "grafana-image-renderer-5.10.3-arm64.tar" }, { - "arch": "amd64", - "platform": "linux/amd64", - "pull": "quay.io/prometheus/node-exporter:v1.10.2", - "tag": "sealos.hub:5000/kube4/node-exporter:v1.10.2", - "tar": "node-exporter-v1.10.2-amd64.tar" + "arch": "arm64", + "platform": "linux/arm64", + "pull": "quay.io/brancz/kube-rbac-proxy:v0.22.1", + "tag": "sealos.hub:5000/kube4/kube-rbac-proxy:v0.22.1", + "tar": "kube-rbac-proxy-v0.22.1-arm64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "quay.io/prometheus/node-exporter:v1.10.2", - "tag": "sealos.hub:5000/kube4/node-exporter:v1.10.2", - "tar": "node-exporter-v1.10.2-arm64.tar" + "pull": "registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.20.0", + "tag": "sealos.hub:5000/kube4/kube-state-metrics:v2.20.0", + "tar": "kube-state-metrics-v2.20.0-arm64.tar" }, { - "arch": "amd64", - "platform": "linux/amd64", - "pull": "ghcr.io/jkroepke/kube-webhook-certgen:1.7.4", - "tag": "sealos.hub:5000/kube4/kube-webhook-certgen:1.7.4", - "tar": "kube-webhook-certgen-1.7.4-amd64.tar" + "arch": "arm64", + "platform": "linux/arm64", + "pull": "quay.io/prometheus/node-exporter:v1.12.1", + "tag": "sealos.hub:5000/kube4/node-exporter:v1.12.1", + "tar": "node-exporter-v1.12.1-arm64.tar" }, { "arch": "arm64", "platform": "linux/arm64", - "pull": "ghcr.io/jkroepke/kube-webhook-certgen:1.7.4", - "tag": "sealos.hub:5000/kube4/kube-webhook-certgen:1.7.4", - "tar": "kube-webhook-certgen-1.7.4-arm64.tar" + "pull": "ghcr.io/jkroepke/kube-webhook-certgen:1.8.8", + "tag": "sealos.hub:5000/kube4/kube-webhook-certgen:1.8.8", + "tar": "kube-webhook-certgen-1.8.8-arm64.tar" } ] From cf957a82dc9a219c2036020fe9c04014a599c4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:15:38 +0800 Subject: [PATCH 05/14] ci: validate and build pinned prometheus stack --- .github/workflows/build-offline-installer.yml | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-offline-installer.yml b/.github/workflows/build-offline-installer.yml index 99f5cac..98c90de 100644 --- a/.github/workflows/build-offline-installer.yml +++ b/.github/workflows/build-offline-installer.yml @@ -12,25 +12,50 @@ on: - ".github/workflows/build-offline-installer.yml" - "build.sh" - "install.sh" + - "versions.env" - "README.md" - "docs/**" - "images/**" - - "charts/**" pull_request: paths: - ".github/workflows/build-offline-installer.yml" - "build.sh" - "install.sh" + - "versions.env" - "README.md" - "docs/**" - "images/**" - - "charts/**" permissions: contents: write jobs: + validate: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Shell syntax + run: bash -n build.sh install.sh + + - name: Validate image metadata + run: | + jq empty images/image.json + test "$(jq '[.[] | select(.arch == "amd64")] | length' images/image.json)" -gt 0 + test "$(jq '[.[] | select(.arch == "arm64")] | length' images/image.json)" -gt 0 + + - name: Validate installer help + run: | + chmod +x install.sh + ./install.sh --help | tee /tmp/help.txt + grep -q "kube-prometheus-stack.*90.2.0" /tmp/help.txt + grep -q -- "--values-file" /tmp/help.txt + grep -q -- "--stack-label-key" /tmp/help.txt + grep -q -- "--grafana-admin-user" /tmp/help.txt + build: + needs: validate runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -49,12 +74,22 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.19.0 + - name: Ensure executable permissions run: chmod +x build.sh install.sh - name: Build installer run: ./build.sh --arch "${{ matrix.arch }}" + - name: Verify checksum + run: | + cd dist + sha256sum -c "prometheus-stack-installer-${{ matrix.arch }}.run.sha256" + - name: Upload artifacts uses: actions/upload-artifact@v4 with: From ca0b75f9e6fac8f3b4c38bab4069de40df930221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:18:01 +0800 Subject: [PATCH 06/14] feat: add parameterized v0.2 installer --- install-v2.sh | 422 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 install-v2.sh diff --git a/install-v2.sh b/install-v2.sh new file mode 100644 index 0000000..8a1348c --- /dev/null +++ b/install-v2.sh @@ -0,0 +1,422 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +APP_VERSION="0.2.0" +STACK_VERSION="90.2.0" +WORKDIR="/tmp/prometheus-stack-installer" +CHART_DIR="${WORKDIR}/charts/kube-prometheus-stack" +IMAGE_DIR="${WORKDIR}/images" +IMAGE_INDEX="${IMAGE_DIR}/image-index.tsv" +VALUES="${WORKDIR}/values.yaml" +PHASE1="${WORKDIR}/phase1.yaml" + +ACTION="help"; RELEASE="prometheus-stack"; NS="monitoring"; TIMEOUT="10m"; YES=false +REGISTRY="sealos.hub:5000/kube4"; REGISTRY_SET=false; REGISTRY_USER="admin"; REGISTRY_PASS="passw0rd"; REGISTRY_SECRET="" +PULL_POLICY="IfNotPresent"; SKIP_IMAGES=false; SKIP_CRD_UPGRADE=false; DELETE_CRDS=false +PROM_SC="nfs"; PROM_SIZE="200Gi"; PROM_MODE="ReadWriteOnce"; PROM_RETENTION="14d"; PROM_RETENTION_SIZE="30GiB"; PROM_REPLICAS=1; PROM_SVC="NodePort"; PROM_PORT=30091 +AM_SC="nfs"; AM_SIZE="10Gi"; AM_MODE="ReadWriteOnce"; AM_REPLICAS=1; AM_CONFIG=""; ENABLE_AM=true +GRAFANA_SC="nfs"; GRAFANA_SIZE="10Gi"; GRAFANA_MODE="ReadWriteOnce"; GRAFANA_REPLICAS=1; GRAFANA_USER="admin"; GRAFANA_PASS="admin@passw0rd"; GRAFANA_SVC="NodePort"; GRAFANA_PORT=30090; ENABLE_GRAFANA=true +ENABLE_RULES=true; ENABLE_KSM=true; ENABLE_NODE=true +STACK_LABEL_KEY="monitoring.archinfra.io/stack"; STACK_LABEL_VALUE="default" +DASH_LABEL_KEY="grafana_dashboard"; DASH_LABEL_VALUE="1"; DASH_FOLDER_ANNO="grafana_folder"; DASH_NS="ALL" +EXTRA_VALUES=(); HELM_ARGS=(); PAYLOAD_OFFSET="" + +CRDS=(alertmanagerconfigs.monitoring.coreos.com alertmanagers.monitoring.coreos.com podmonitors.monitoring.coreos.com probes.monitoring.coreos.com prometheusagents.monitoring.coreos.com prometheuses.monitoring.coreos.com prometheusrules.monitoring.coreos.com scrapeconfigs.monitoring.coreos.com servicemonitors.monitoring.coreos.com thanosrulers.monitoring.coreos.com) + +die(){ echo "[ERROR] $*" >&2; exit 1; } +log(){ echo "[INFO] $*"; } +ok(){ echo "[OK] $*"; } +bool(){ [[ "$2" == true || "$2" == false ]] || die "$1 must be true|false"; } +posint(){ [[ "$2" =~ ^[1-9][0-9]*$ ]] || die "$1 must be a positive integer"; } +svc(){ [[ "$2" =~ ^(ClusterIP|NodePort|LoadBalancer)$ ]] || die "$1 must be ClusterIP|NodePort|LoadBalancer"; } +q(){ printf '%s' "$1" | sed "s/'/''/g"; } + +help(){ +cat < [options] [-- ] + +Core: + -n, --namespace [default: ${NS}] + --release-name [default: ${RELEASE}] + --wait-timeout [default: ${TIMEOUT}] + --values-file extra Helm values; repeatable + +Prometheus: + --prometheus-storage-class [default: ${PROM_SC}] + --prometheus-storage-size [default: ${PROM_SIZE}] + --prometheus-storage-access-mode [default: ${PROM_MODE}] + --prometheus-retention [default: ${PROM_RETENTION}] + --prometheus-retention-size [default: ${PROM_RETENTION_SIZE}] + --prometheus-replicas [default: ${PROM_REPLICAS}] + --prometheus-service-type [default: ${PROM_SVC}] + --prometheus-node-port [default: ${PROM_PORT}] + +Alertmanager: + --alertmanager-storage-class [default: ${AM_SC}] + --alertmanager-storage-size [default: ${AM_SIZE}] + --alertmanager-storage-access-mode [default: ${AM_MODE}] + --alertmanager-replicas [default: ${AM_REPLICAS}] + --alertmanager-config-file [default: null receiver] + --enable-alertmanager [default: ${ENABLE_AM}] + +Grafana: + --grafana-storage-class [default: ${GRAFANA_SC}] + --grafana-storage-size [default: ${GRAFANA_SIZE}] + --grafana-storage-access-mode [default: ${GRAFANA_MODE}] + --grafana-replicas [default: ${GRAFANA_REPLICAS}] + --grafana-admin-user [default: ${GRAFANA_USER}] + --grafana-admin-password [default: ${GRAFANA_PASS}] + --grafana-service-type [default: ${GRAFANA_SVC}] + --grafana-node-port [default: ${GRAFANA_PORT}] + --enable-grafana [default: ${ENABLE_GRAFANA}] + +Discovery / dashboards: + --stack-label-key [default: ${STACK_LABEL_KEY}] + --stack-label-value [default: ${STACK_LABEL_VALUE}] + --dashboard-label-key [default: ${DASH_LABEL_KEY}] + --dashboard-label-value [default: ${DASH_LABEL_VALUE}] + --dashboard-folder-annotation [default: ${DASH_FOLDER_ANNO}] + --dashboard-search-namespace [default: ${DASH_NS}] + --enable-default-rules [default: ${ENABLE_RULES}] + --enable-kube-state-metrics [default: ${ENABLE_KSM}] + --enable-node-exporter [default: ${ENABLE_NODE}] + +Registry: + --registry [default: ${REGISTRY}] + --registry-user [default: ${REGISTRY_USER}] + --registry-password [default: ] + --registry-secret [default: ] + --image-pull-policy [default: ${PULL_POLICY}] + --skip-image-prepare + +Upgrade / cleanup: + --skip-crd-upgrade + --delete-crds + +Other: + -y, --yes + -h, --help + -- +EOF +} + +need(){ [[ $# -ge 2 ]] || die "missing value for $1"; } +parse(){ + [[ $# -gt 0 ]] || return 0 + while [[ $# -gt 0 ]]; do + case "$1" in + install|uninstall|status|help) ACTION="$1"; shift;; + -n|--namespace) need "$@"; NS="$2"; shift 2;; + --release-name) need "$@"; RELEASE="$2"; shift 2;; + --wait-timeout) need "$@"; TIMEOUT="$2"; shift 2;; + --values-file) need "$@"; EXTRA_VALUES+=("$2"); shift 2;; + --prometheus-storage-class) need "$@"; PROM_SC="$2"; shift 2;; + --prometheus-storage-size) need "$@"; PROM_SIZE="$2"; shift 2;; + --prometheus-storage-access-mode) need "$@"; PROM_MODE="$2"; shift 2;; + --prometheus-retention) need "$@"; PROM_RETENTION="$2"; shift 2;; + --prometheus-retention-size) need "$@"; PROM_RETENTION_SIZE="$2"; shift 2;; + --prometheus-replicas) need "$@"; PROM_REPLICAS="$2"; shift 2;; + --prometheus-service-type) need "$@"; PROM_SVC="$2"; shift 2;; + --prometheus-node-port) need "$@"; PROM_PORT="$2"; shift 2;; + --alertmanager-storage-class) need "$@"; AM_SC="$2"; shift 2;; + --alertmanager-storage-size) need "$@"; AM_SIZE="$2"; shift 2;; + --alertmanager-storage-access-mode) need "$@"; AM_MODE="$2"; shift 2;; + --alertmanager-replicas) need "$@"; AM_REPLICAS="$2"; shift 2;; + --alertmanager-config-file) need "$@"; AM_CONFIG="$2"; shift 2;; + --enable-alertmanager) need "$@"; ENABLE_AM="$2"; shift 2;; + --grafana-storage-class) need "$@"; GRAFANA_SC="$2"; shift 2;; + --grafana-storage-size) need "$@"; GRAFANA_SIZE="$2"; shift 2;; + --grafana-storage-access-mode) need "$@"; GRAFANA_MODE="$2"; shift 2;; + --grafana-replicas) need "$@"; GRAFANA_REPLICAS="$2"; shift 2;; + --grafana-admin-user) need "$@"; GRAFANA_USER="$2"; shift 2;; + --grafana-admin-password) need "$@"; GRAFANA_PASS="$2"; shift 2;; + --grafana-service-type) need "$@"; GRAFANA_SVC="$2"; shift 2;; + --grafana-node-port) need "$@"; GRAFANA_PORT="$2"; shift 2;; + --enable-grafana) need "$@"; ENABLE_GRAFANA="$2"; shift 2;; + --stack-label-key) need "$@"; STACK_LABEL_KEY="$2"; shift 2;; + --stack-label-value) need "$@"; STACK_LABEL_VALUE="$2"; shift 2;; + --dashboard-label-key) need "$@"; DASH_LABEL_KEY="$2"; shift 2;; + --dashboard-label-value) need "$@"; DASH_LABEL_VALUE="$2"; shift 2;; + --dashboard-folder-annotation) need "$@"; DASH_FOLDER_ANNO="$2"; shift 2;; + --dashboard-search-namespace) need "$@"; DASH_NS="$2"; shift 2;; + --enable-default-rules) need "$@"; ENABLE_RULES="$2"; shift 2;; + --enable-kube-state-metrics) need "$@"; ENABLE_KSM="$2"; shift 2;; + --enable-node-exporter) need "$@"; ENABLE_NODE="$2"; shift 2;; + --registry) need "$@"; REGISTRY="$2"; REGISTRY_SET=true; shift 2;; + --registry-user) need "$@"; REGISTRY_USER="$2"; shift 2;; + --registry-password) need "$@"; REGISTRY_PASS="$2"; shift 2;; + --registry-secret) need "$@"; REGISTRY_SECRET="$2"; shift 2;; + --image-pull-policy) need "$@"; PULL_POLICY="$2"; shift 2;; + --skip-image-prepare) SKIP_IMAGES=true; shift;; + --skip-crd-upgrade) SKIP_CRD_UPGRADE=true; shift;; + --delete-crds) DELETE_CRDS=true; shift;; + -y|--yes) YES=true; shift;; + -h|--help) ACTION=help; shift;; + --) shift; while [[ $# -gt 0 ]]; do HELM_ARGS+=("$1"); shift; done;; + *) die "unknown argument: $1";; + esac + done +} + +validate(){ + [[ "$PULL_POLICY" =~ ^(Always|IfNotPresent|Never)$ ]] || die "invalid image pull policy" + svc --prometheus-service-type "$PROM_SVC"; svc --grafana-service-type "$GRAFANA_SVC" + bool --enable-alertmanager "$ENABLE_AM"; bool --enable-grafana "$ENABLE_GRAFANA"; bool --enable-default-rules "$ENABLE_RULES"; bool --enable-kube-state-metrics "$ENABLE_KSM"; bool --enable-node-exporter "$ENABLE_NODE" + posint --prometheus-replicas "$PROM_REPLICAS"; posint --alertmanager-replicas "$AM_REPLICAS"; posint --grafana-replicas "$GRAFANA_REPLICAS" + [[ "$PROM_SVC" != NodePort || "$PROM_PORT" =~ ^3[0-2][0-9]{3}$ ]] || die "invalid prometheus nodePort" + [[ "$GRAFANA_SVC" != NodePort || "$GRAFANA_PORT" =~ ^3[0-2][0-9]{3}$ ]] || die "invalid grafana nodePort" + local f; for f in "${EXTRA_VALUES[@]}"; do [[ -f "$f" ]] || die "values file not found: $f"; done + [[ -z "$AM_CONFIG" || -f "$AM_CONFIG" ]] || die "alertmanager config not found: $AM_CONFIG" +} + +deps(){ + command -v helm >/dev/null || die "helm required" + command -v kubectl >/dev/null || die "kubectl required" + [[ "$ACTION" != install || "$SKIP_IMAGES" == true ]] || command -v docker >/dev/null || die "docker required unless --skip-image-prepare" +} + +confirm(){ + [[ "$YES" == true ]] && return + echo "Action=${ACTION} Release=${RELEASE} Namespace=${NS} Stack=${STACK_VERSION}" + read -r -p "Continue? [y/N] " a + [[ "$a" =~ ^[Yy]$ ]] || exit 1 +} + +payload_offset(){ + [[ -n "$PAYLOAD_OFFSET" ]] && return + local n; n="$(awk '/^__PAYLOAD_BELOW__$/{print NR;exit}' "$0")"; [[ -n "$n" ]] || die "payload marker missing" + PAYLOAD_OFFSET="$(( $(head -n "$n" "$0" | wc -c) + 1 ))" + while [[ "$(dd if="$0" bs=1 skip="$((PAYLOAD_OFFSET-1))" count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')" =~ ^(0a|0d)$ ]]; do PAYLOAD_OFFSET=$((PAYLOAD_OFFSET+1)); done +} +stream(){ payload_offset; tail -c +"$PAYLOAD_OFFSET" "$0"; } +extract(){ + rm -rf "$WORKDIR"; mkdir -p "$WORKDIR" + if [[ "$SKIP_IMAGES" == true ]]; then stream | tar -xzf - -C "$WORKDIR" ./charts ./images/image-index.tsv >/dev/null + else stream | tar -xzf - -C "$WORKDIR" >/dev/null; fi + [[ -d "$CHART_DIR" && -f "$IMAGE_INDEX" ]] || die "invalid embedded payload" +} + +declare -A IMG=() +load_meta(){ + local tar load target + while IFS=$'\t' read -r tar load target; do + [[ -n "$tar" ]] || continue + if [[ "$REGISTRY_SET" == true ]]; then target="${REGISTRY}/${target##*/}"; fi + IMG["${target##*/}"]="$target" + done < "$IMAGE_INDEX" +} +find_img(){ + local name="$1" k + for k in "${!IMG[@]}"; do [[ "${k%%:*}" == "$name" ]] && { echo "${IMG[$k]}"; return; }; done + die "image not found: $name" +} +ireg(){ echo "${1%%/*}"; } +irepo(){ local x="${1#*/}"; echo "${x%:*}"; } +itag(){ echo "${1##*:}"; } + +prepare_images(){ + [[ "$SKIP_IMAGES" == true ]] && return + echo "$REGISTRY_PASS" | docker login "${REGISTRY%%/*}" -u "$REGISTRY_USER" --password-stdin >/dev/null || true + local tar load target + while IFS=$'\t' read -r tar load target; do + [[ -n "$tar" ]] || continue + [[ "$REGISTRY_SET" == true ]] && target="${REGISTRY}/${target##*/}" + docker load -i "${IMAGE_DIR}/${tar}" >/dev/null + [[ "$load" == "$target" ]] || docker tag "$load" "$target" + docker push "$target" >/dev/null + done < "$IMAGE_INDEX" +} + +write_values(){ + local op reload webhook cert am prom grafana curl busy sidecar renderer rbac ksm node thanos + op="$(find_img prometheus-operator)"; reload="$(find_img prometheus-config-reloader)"; webhook="$(find_img admission-webhook)"; cert="$(find_img kube-webhook-certgen)" + am="$(find_img alertmanager)"; prom="$(find_img prometheus)"; grafana="$(find_img grafana)"; curl="$(find_img curl)"; busy="$(find_img busybox)" + sidecar="$(find_img k8s-sidecar)"; renderer="$(find_img grafana-image-renderer)"; rbac="$(find_img kube-rbac-proxy)"; ksm="$(find_img kube-state-metrics)"; node="$(find_img node-exporter)"; thanos="$(find_img thanos)" + + cat > "$VALUES" < "$PHASE1" </dev/null 2>&1; } +crd_upgrade(){ + [[ "$SKIP_CRD_UPGRADE" == true ]] && return + local d="${CHART_DIR}/charts/crds/crds"; [[ -d "$d" ]] || die "CRD dir missing" + kubectl apply --server-side --force-conflicts -f "$d" >/dev/null +} +wait_crds(){ local c; for c in "${CRDS[@]}"; do kubectl wait --for=condition=Established --timeout="$TIMEOUT" "crd/$c" >/dev/null; done; } + +helm_run(){ + local phase="$1"; shift + local args=(helm upgrade --install "$RELEASE" "$CHART_DIR" -n "$NS" --create-namespace --wait --wait-for-jobs --timeout "$TIMEOUT" -f "$VALUES") + [[ "$phase" == first ]] && args+=(-f "$PHASE1") + local f; for f in "${EXTRA_VALUES[@]}"; do args+=(-f "$f"); done + args+=("${HELM_ARGS[@]}") + printf '[INFO] '; printf '%q ' "${args[@]}"; echo + "${args[@]}" +} + +install(){ + extract; load_meta; prepare_images; write_values + kubectl get ns "$NS" >/dev/null 2>&1 || kubectl create ns "$NS" >/dev/null + if release_exists; then + log "existing release detected: CRD apply + single-stage upgrade" + crd_upgrade; wait_crds; helm_run upgrade + else + log "first install: phase 1 CRDs/operator" + helm_run first; wait_crds + log "first install: phase 2 full stack" + helm_run full + fi + kubectl get pods,svc,deploy,statefulset,daemonset -n "$NS" -l "app.kubernetes.io/instance=${RELEASE}" || true + ok "Prometheus Stack ${STACK_VERSION} installed/upgraded" +} + +uninstall(){ + release_exists && helm uninstall "$RELEASE" -n "$NS" || true + if [[ "$DELETE_CRDS" == true ]]; then + echo "[WARN] deleting CRDs deletes cluster-wide monitoring custom resources" + local c; for c in "${CRDS[@]}"; do kubectl delete crd "$c" --ignore-not-found >/dev/null || true; done + fi +} + +status(){ + helm status "$RELEASE" -n "$NS" || true + kubectl get pods,svc,deploy,statefulset,daemonset -n "$NS" -l "app.kubernetes.io/instance=${RELEASE}" || true +} + +main(){ + parse "$@"; validate + case "$ACTION" in + help) help;; + install) deps; confirm; install;; + uninstall) deps; confirm; uninstall;; + status) deps; status;; + *) die "unsupported action: $ACTION";; + esac +} +trap 'rm -rf "$WORKDIR"' EXIT +main "$@" +exit 0 + +__PAYLOAD_BELOW__ From c770a5d51945e73de96716297d60e232ba056650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:18:08 +0800 Subject: [PATCH 07/14] refactor: route installer entrypoint to v0.2 implementation --- install.sh | 1102 +--------------------------------------------------- 1 file changed, 2 insertions(+), 1100 deletions(-) diff --git a/install.sh b/install.sh index dd236b9..82db4af 100644 --- a/install.sh +++ b/install.sh @@ -1,1102 +1,4 @@ #!/usr/bin/env bash - set -Eeuo pipefail - -APP_NAME="prometheus-stack" -APP_VERSION="0.1.3" -PACKAGE_PROFILE="integrated" -WORKDIR="/tmp/${APP_NAME}-installer" -CHART_DIR="${WORKDIR}/charts/kube-prometheus-stack" -IMAGE_DIR="${WORKDIR}/images" -IMAGE_INDEX="${IMAGE_DIR}/image-index.tsv" -BASE_VALUES_FILE="${WORKDIR}/values-base.yaml" -PHASE1_VALUES_FILE="${WORKDIR}/values-phase1.yaml" -PHASE2_VALUES_FILE="${WORKDIR}/values-phase2.yaml" - -ACTION="help" -RELEASE_NAME="prometheus-stack" -NAMESPACE="monitoring" -WAIT_TIMEOUT="10m" -IMAGE_PULL_POLICY="IfNotPresent" -REGISTRY_REPO="sealos.hub:5000/kube4" -REGISTRY_REPO_EXPLICIT="false" -REGISTRY_USER="admin" -REGISTRY_PASS="passw0rd" -SKIP_IMAGE_PREPARE="false" -DELETE_CRDS="false" -AUTO_YES="false" - -PROMETHEUS_STORAGE_CLASS="nfs" -PROMETHEUS_STORAGE_SIZE="200Gi" -PROMETHEUS_RETENTION="14d" -PROMETHEUS_RETENTION_SIZE="30GiB" -ALERTMANAGER_STORAGE_CLASS="nfs" -ALERTMANAGER_STORAGE_SIZE="10Gi" -GRAFANA_STORAGE_CLASS="nfs" -GRAFANA_STORAGE_SIZE="10Gi" -GRAFANA_ADMIN_PASSWORD="admin@passw0rd" -GRAFANA_SERVICE_TYPE="NodePort" -GRAFANA_NODE_PORT="30090" -PROMETHEUS_SERVICE_TYPE="NodePort" -PROMETHEUS_NODE_PORT="30091" -ALERTMANAGER_CONFIG_FILE="" - -DASHBOARD_LABEL_KEY="grafana_dashboard" -DASHBOARD_LABEL_VALUE="1" -DASHBOARD_FOLDER_ANNOTATION="grafana_folder" - -STACK_LABEL_KEY="monitoring.archinfra.io/stack" -STACK_LABEL_VALUE="default" - -HELM_ARGS=() -PAYLOAD_OFFSET="" - -readonly CRDS=( - "alertmanagerconfigs.monitoring.coreos.com" - "alertmanagers.monitoring.coreos.com" - "podmonitors.monitoring.coreos.com" - "probes.monitoring.coreos.com" - "prometheusagents.monitoring.coreos.com" - "prometheuses.monitoring.coreos.com" - "prometheusrules.monitoring.coreos.com" - "scrapeconfigs.monitoring.coreos.com" - "servicemonitors.monitoring.coreos.com" - "thanosrulers.monitoring.coreos.com" -) - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -BOLD='\033[1m' -NC='\033[0m' - -log() { - echo -e "${CYAN}[INFO]${NC} $*" -} - -success() { - echo -e "${GREEN}[OK]${NC} $*" -} - -warn() { - echo -e "${YELLOW}[WARN]${NC} $*" >&2 -} - -die() { - echo -e "${RED}[ERROR]${NC} $*" >&2 - exit 1 -} - -section() { - echo - echo -e "${BLUE}${BOLD}============================================================${NC}" - echo -e "${BLUE}${BOLD}$*${NC}" - echo -e "${BLUE}${BOLD}============================================================${NC}" -} - -program_name() { - basename "$0" -} - -banner() { - echo - echo -e "${GREEN}${BOLD}Prometheus Stack 离线安装器${NC}" - echo -e "${CYAN}版本: ${APP_VERSION}${NC}" - echo -e "${CYAN}产物包: ${PACKAGE_PROFILE}${NC}" -} - -usage() { - local cmd="./$(program_name)" - cat < [options] [-- ] - ${cmd} -h|--help - -Actions: - install Install the Prometheus Stack in two phases: CRDs/operator first, full stack second - uninstall Uninstall the release and optionally delete CRDs - status Show CRD, Helm release and workload status - help Show this message - -Core options: - -n, --namespace Namespace, default: ${NAMESPACE} - --release-name Helm release name, default: ${RELEASE_NAME} - --wait-timeout Wait timeout, default: ${WAIT_TIMEOUT} - -Storage and runtime: - --prometheus-storage-class Default: ${PROMETHEUS_STORAGE_CLASS} - --prometheus-storage-size Default: ${PROMETHEUS_STORAGE_SIZE} - --prometheus-retention Default: ${PROMETHEUS_RETENTION} - --prometheus-retention-size Default: ${PROMETHEUS_RETENTION_SIZE} - --prometheus-service-type ClusterIP|NodePort|LoadBalancer, default: ${PROMETHEUS_SERVICE_TYPE} - --prometheus-node-port Used when Prometheus service is NodePort, default: ${PROMETHEUS_NODE_PORT} - --alertmanager-storage-class Default: ${ALERTMANAGER_STORAGE_CLASS} - --alertmanager-storage-size Default: ${ALERTMANAGER_STORAGE_SIZE} - --alertmanager-config-file Optional Alertmanager config YAML for real notifications - --grafana-storage-class Default: ${GRAFANA_STORAGE_CLASS} - --grafana-storage-size Default: ${GRAFANA_STORAGE_SIZE} - --grafana-admin-password Default: ${GRAFANA_ADMIN_PASSWORD} - --grafana-service-type ClusterIP|NodePort|LoadBalancer, default: ${GRAFANA_SERVICE_TYPE} - --grafana-node-port Used when Grafana service is NodePort, default: ${GRAFANA_NODE_PORT} - -Image and registry: - --registry Target image repo prefix, default: ${REGISTRY_REPO} - --registry-user Registry username, default: ${REGISTRY_USER} - --registry-password Registry password, default: - --image-pull-policy Always|IfNotPresent|Never, default: ${IMAGE_PULL_POLICY} - --skip-image-prepare Reuse images already present in the target registry - -Cleanup: - --delete-crds With uninstall, also delete monitoring CRDs - -Other: - -y, --yes Skip confirmation - -h, --help Show help - -Examples: - ${cmd} install -n monitoring --grafana-admin-password 'Admin@123' -y - ${cmd} install --grafana-node-port 30090 --prometheus-node-port 30091 -y - ${cmd} install --alertmanager-config-file ./examples/alertmanager-config-webhook.yaml -y - ${cmd} install --registry harbor.example.com/kube4 --skip-image-prepare -y - ${cmd} status -n monitoring - ${cmd} uninstall -n monitoring --delete-crds -y -EOF -} - -cleanup() { - rm -rf "${WORKDIR}" -} - -trap cleanup EXIT - -parse_args() { - if [[ $# -eq 0 ]]; then - ACTION="help" - return - fi - - while [[ $# -gt 0 ]]; do - case "$1" in - install|uninstall|status|help) - ACTION="$1" - shift - ;; - -n|--namespace) - [[ $# -ge 2 ]] || die "Missing value for $1" - NAMESPACE="$2" - shift 2 - ;; - --release-name) - [[ $# -ge 2 ]] || die "Missing value for $1" - RELEASE_NAME="$2" - shift 2 - ;; - --prometheus-storage-class) - [[ $# -ge 2 ]] || die "Missing value for $1" - PROMETHEUS_STORAGE_CLASS="$2" - shift 2 - ;; - --prometheus-storage-size) - [[ $# -ge 2 ]] || die "Missing value for $1" - PROMETHEUS_STORAGE_SIZE="$2" - shift 2 - ;; - --prometheus-retention) - [[ $# -ge 2 ]] || die "Missing value for $1" - PROMETHEUS_RETENTION="$2" - shift 2 - ;; - --prometheus-retention-size) - [[ $# -ge 2 ]] || die "Missing value for $1" - PROMETHEUS_RETENTION_SIZE="$2" - shift 2 - ;; - --prometheus-service-type) - [[ $# -ge 2 ]] || die "Missing value for $1" - PROMETHEUS_SERVICE_TYPE="$2" - shift 2 - ;; - --prometheus-node-port) - [[ $# -ge 2 ]] || die "Missing value for $1" - PROMETHEUS_NODE_PORT="$2" - shift 2 - ;; - --alertmanager-storage-class) - [[ $# -ge 2 ]] || die "Missing value for $1" - ALERTMANAGER_STORAGE_CLASS="$2" - shift 2 - ;; - --alertmanager-storage-size) - [[ $# -ge 2 ]] || die "Missing value for $1" - ALERTMANAGER_STORAGE_SIZE="$2" - shift 2 - ;; - --alertmanager-config-file) - [[ $# -ge 2 ]] || die "Missing value for $1" - ALERTMANAGER_CONFIG_FILE="$2" - shift 2 - ;; - --grafana-storage-class) - [[ $# -ge 2 ]] || die "Missing value for $1" - GRAFANA_STORAGE_CLASS="$2" - shift 2 - ;; - --grafana-storage-size) - [[ $# -ge 2 ]] || die "Missing value for $1" - GRAFANA_STORAGE_SIZE="$2" - shift 2 - ;; - --grafana-admin-password) - [[ $# -ge 2 ]] || die "Missing value for $1" - GRAFANA_ADMIN_PASSWORD="$2" - shift 2 - ;; - --grafana-service-type) - [[ $# -ge 2 ]] || die "Missing value for $1" - GRAFANA_SERVICE_TYPE="$2" - shift 2 - ;; - --grafana-node-port) - [[ $# -ge 2 ]] || die "Missing value for $1" - GRAFANA_NODE_PORT="$2" - shift 2 - ;; - --registry) - [[ $# -ge 2 ]] || die "Missing value for $1" - REGISTRY_REPO="$2" - REGISTRY_REPO_EXPLICIT="true" - shift 2 - ;; - --registry-user) - [[ $# -ge 2 ]] || die "Missing value for $1" - REGISTRY_USER="$2" - shift 2 - ;; - --registry-password) - [[ $# -ge 2 ]] || die "Missing value for $1" - REGISTRY_PASS="$2" - shift 2 - ;; - --image-pull-policy) - [[ $# -ge 2 ]] || die "Missing value for $1" - IMAGE_PULL_POLICY="$2" - shift 2 - ;; - --skip-image-prepare) - SKIP_IMAGE_PREPARE="true" - shift - ;; - --wait-timeout) - [[ $# -ge 2 ]] || die "Missing value for $1" - WAIT_TIMEOUT="$2" - shift 2 - ;; - --delete-crds) - DELETE_CRDS="true" - shift - ;; - -y|--yes) - AUTO_YES="true" - shift - ;; - -h|--help) - ACTION="help" - shift - ;; - --) - shift - while [[ $# -gt 0 ]]; do - HELM_ARGS+=("$1") - shift - done - break - ;; - *) - die "Unknown argument: $1" - ;; - esac - done -} - -normalize_flags() { - case "${IMAGE_PULL_POLICY}" in - Always|IfNotPresent|Never) ;; - *) - die "Unsupported image pull policy: ${IMAGE_PULL_POLICY}" - ;; - esac - - case "${PROMETHEUS_SERVICE_TYPE}" in - ClusterIP|NodePort|LoadBalancer) ;; - *) - die "Unsupported prometheus service type: ${PROMETHEUS_SERVICE_TYPE}" - ;; - esac - - case "${GRAFANA_SERVICE_TYPE}" in - ClusterIP|NodePort|LoadBalancer) ;; - *) - die "Unsupported grafana service type: ${GRAFANA_SERVICE_TYPE}" - ;; - esac - - if [[ "${PROMETHEUS_SERVICE_TYPE}" == "NodePort" ]]; then - [[ "${PROMETHEUS_NODE_PORT}" =~ ^[0-9]+$ ]] || die "Prometheus nodePort must be numeric" - (( PROMETHEUS_NODE_PORT >= 30000 && PROMETHEUS_NODE_PORT <= 32767 )) || die "Prometheus nodePort must be within 30000-32767" - fi - - if [[ "${GRAFANA_SERVICE_TYPE}" == "NodePort" ]]; then - [[ "${GRAFANA_NODE_PORT}" =~ ^[0-9]+$ ]] || die "Grafana nodePort must be numeric" - (( GRAFANA_NODE_PORT >= 30000 && GRAFANA_NODE_PORT <= 32767 )) || die "Grafana nodePort must be within 30000-32767" - fi -} - -check_deps() { - command -v helm >/dev/null 2>&1 || die "helm is required" - command -v kubectl >/dev/null 2>&1 || die "kubectl is required" - if [[ "${ACTION}" == "install" && "${SKIP_IMAGE_PREPARE}" != "true" ]]; then - command -v docker >/dev/null 2>&1 || die "docker is required unless --skip-image-prepare is used" - fi - if [[ -n "${ALERTMANAGER_CONFIG_FILE}" && ! -f "${ALERTMANAGER_CONFIG_FILE}" ]]; then - die "Alertmanager config file not found: ${ALERTMANAGER_CONFIG_FILE}" - fi -} - -confirm() { - [[ "${AUTO_YES}" == "true" ]] && return 0 - - section "部署配置确认" - echo "Action : ${ACTION}" - echo "Release : ${RELEASE_NAME}" - echo "Namespace : ${NAMESPACE}" - if [[ "${ACTION}" == "install" ]]; then - echo "Prometheus storageClass : ${PROMETHEUS_STORAGE_CLASS}" - echo "Prometheus storage : ${PROMETHEUS_STORAGE_SIZE}" - echo "Prometheus retention : ${PROMETHEUS_RETENTION}" - echo "Prometheus retention size : ${PROMETHEUS_RETENTION_SIZE}" - echo "Prometheus service type : ${PROMETHEUS_SERVICE_TYPE}" - echo "Prometheus nodePort : ${PROMETHEUS_NODE_PORT}" - echo "Alertmanager storageClass : ${ALERTMANAGER_STORAGE_CLASS}" - echo "Alertmanager storage : ${ALERTMANAGER_STORAGE_SIZE}" - echo "Alertmanager config file : ${ALERTMANAGER_CONFIG_FILE:-}" - echo "Grafana storageClass : ${GRAFANA_STORAGE_CLASS}" - echo "Grafana storage : ${GRAFANA_STORAGE_SIZE}" - echo "Grafana service type : ${GRAFANA_SERVICE_TYPE}" - echo "Grafana nodePort : ${GRAFANA_NODE_PORT}" - echo "Registry repo : ${REGISTRY_REPO}" - echo "Skip image prepare : ${SKIP_IMAGE_PREPARE}" - echo "Wait timeout : ${WAIT_TIMEOUT}" - fi - if [[ "${ACTION}" == "uninstall" ]]; then - echo "Delete CRDs : ${DELETE_CRDS}" - fi - if [[ ${#HELM_ARGS[@]} -gt 0 ]]; then - echo "Helm extra args : ${HELM_ARGS[*]}" - fi - echo - read -r -p "Continue? [y/N] " answer - [[ "${answer}" =~ ^[Yy]$ ]] || die "Cancelled" -} - -init_payload_offset() { - [[ -n "${PAYLOAD_OFFSET}" ]] && return 0 - - local marker_line payload_offset skip_bytes byte_hex - marker_line="$(awk '/^__PAYLOAD_BELOW__$/ { print NR; exit }' "$0")" - [[ -n "${marker_line}" ]] || die "Unable to locate embedded payload" - - payload_offset="$(( $(head -n "${marker_line}" "$0" | wc -c | tr -d ' ') + 1 ))" - skip_bytes=0 - while :; do - byte_hex="$(dd if="$0" bs=1 skip="$((payload_offset + skip_bytes - 1))" count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')" - case "${byte_hex}" in - 0a|0d) - skip_bytes=$((skip_bytes + 1)) - ;; - "") - die "Invalid payload boundary" - ;; - *) - break - ;; - esac - done - - PAYLOAD_OFFSET="$((payload_offset + skip_bytes))" -} - -payload_stream() { - init_payload_offset - tail -c +"${PAYLOAD_OFFSET}" "$0" -} - -payload_extract_entries() { - local destination="$1" - shift - - payload_stream | tar -xzf - -C "${destination}" "$@" >/dev/null -} - -extract_payload() { - log "Extracting embedded payload to ${WORKDIR}" - rm -rf "${WORKDIR}" - mkdir -p "${WORKDIR}" - - if [[ "${SKIP_IMAGE_PREPARE}" == "true" ]]; then - payload_extract_entries "${WORKDIR}" "./charts" "./images/image-index.tsv" - else - payload_stream | tar -xzf - -C "${WORKDIR}" >/dev/null - fi - - [[ -d "${CHART_DIR}" ]] || die "Missing chart payload" - [[ -f "${IMAGE_INDEX}" ]] || die "Missing image metadata payload" -} - -image_name_from_ref() { - local ref="$1" - local name_tag="${ref##*/}" - echo "${name_tag%%:*}" -} - -image_name_tag_from_ref() { - local ref="$1" - echo "${ref##*/}" -} - -resolve_target_ref() { - local default_ref="$1" - if [[ "${REGISTRY_REPO_EXPLICIT}" == "true" ]]; then - echo "${REGISTRY_REPO}/$(image_name_tag_from_ref "${default_ref}")" - else - echo "${default_ref}" - fi -} - -image_registry_from_ref() { - local ref="$1" - echo "${ref%%/*}" -} - -image_repository_from_ref() { - local ref="$1" - local remainder="${ref#*/}" - echo "${remainder%:*}" -} - -image_tag_from_ref() { - local ref="$1" - echo "${ref##*:}" -} - -yaml_single_quote() { - printf '%s' "$1" | sed "s/'/''/g" -} - -declare -A IMAGE_DEFAULT_TARGETS=() -declare -A IMAGE_EFFECTIVE_TARGETS=() -declare -A IMAGE_LOAD_REFS=() - -load_image_metadata() { - while IFS=$'\t' read -r tar_name load_ref default_target_ref; do - [[ -n "${tar_name}" ]] || continue - IMAGE_LOAD_REFS["${tar_name}"]="${load_ref}" - IMAGE_DEFAULT_TARGETS["${tar_name}"]="${default_target_ref}" - IMAGE_EFFECTIVE_TARGETS["${tar_name}"]="$(resolve_target_ref "${default_target_ref}")" - done < "${IMAGE_INDEX}" -} - -find_image_ref_by_name() { - local wanted_name="$1" - local tar_name - for tar_name in "${!IMAGE_EFFECTIVE_TARGETS[@]}"; do - if [[ "$(image_name_from_ref "${IMAGE_EFFECTIVE_TARGETS[${tar_name}]}")" == "${wanted_name}" ]]; then - echo "${IMAGE_EFFECTIVE_TARGETS[${tar_name}]}" - return 0 - fi - done - return 1 -} - -docker_login() { - local registry_host="${REGISTRY_REPO%%/*}" - log "Logging into registry ${registry_host}" - if ! echo "${REGISTRY_PASS}" | docker login "${registry_host}" -u "${REGISTRY_USER}" --password-stdin >/dev/null 2>&1; then - warn "docker login failed for ${registry_host}; continuing and letting push decide" - fi -} - -prepare_images() { - [[ "${SKIP_IMAGE_PREPARE}" == "true" ]] && { - log "Skipping image prepare because --skip-image-prepare was requested" - return 0 - } - - docker_login - - local tar_name load_ref target_ref tar_path - while IFS=$'\t' read -r tar_name load_ref _; do - [[ -n "${tar_name}" ]] || continue - tar_path="${IMAGE_DIR}/${tar_name}" - [[ -f "${tar_path}" ]] || die "Missing image tar: ${tar_path}" - - target_ref="${IMAGE_EFFECTIVE_TARGETS[${tar_name}]}" - - log "Loading ${tar_name}" - docker load -i "${tar_path}" >/dev/null - - if [[ "${load_ref}" != "${target_ref}" ]]; then - log "Tagging ${load_ref} -> ${target_ref}" - docker tag "${load_ref}" "${target_ref}" - fi - - log "Pushing ${target_ref}" - docker push "${target_ref}" >/dev/null - done < "${IMAGE_INDEX}" - - success "Image prepare completed" -} - -ensure_namespace() { - if ! kubectl get namespace "${NAMESPACE}" >/dev/null 2>&1; then - log "Creating namespace ${NAMESPACE}" - kubectl create namespace "${NAMESPACE}" >/dev/null - fi -} - -resolve_required_images() { - ALERTMANAGER_IMAGE="$(find_image_ref_by_name "alertmanager")" || die "Unable to resolve alertmanager image" - ADMISSION_WEBHOOK_IMAGE="$(find_image_ref_by_name "admission-webhook")" || die "Unable to resolve admission-webhook image" - OPERATOR_IMAGE="$(find_image_ref_by_name "prometheus-operator")" || die "Unable to resolve prometheus-operator image" - CONFIG_RELOADER_IMAGE="$(find_image_ref_by_name "prometheus-config-reloader")" || die "Unable to resolve prometheus-config-reloader image" - THANOS_IMAGE="$(find_image_ref_by_name "thanos")" || die "Unable to resolve thanos image" - PROMETHEUS_IMAGE="$(find_image_ref_by_name "prometheus")" || die "Unable to resolve prometheus image" - GRAFANA_IMAGE="$(find_image_ref_by_name "grafana")" || die "Unable to resolve grafana image" - BUSYBOX_IMAGE="$(find_image_ref_by_name "busybox")" || die "Unable to resolve busybox image" - CURL_IMAGE="$(find_image_ref_by_name "curl")" || die "Unable to resolve curl image" - K8S_SIDECAR_IMAGE="$(find_image_ref_by_name "k8s-sidecar")" || die "Unable to resolve k8s-sidecar image" - GRAFANA_RENDERER_IMAGE="$(find_image_ref_by_name "grafana-image-renderer")" || die "Unable to resolve grafana-image-renderer image" - KUBE_RBAC_PROXY_IMAGE="$(find_image_ref_by_name "kube-rbac-proxy")" || die "Unable to resolve kube-rbac-proxy image" - KUBE_STATE_METRICS_IMAGE="$(find_image_ref_by_name "kube-state-metrics")" || die "Unable to resolve kube-state-metrics image" - NODE_EXPORTER_IMAGE="$(find_image_ref_by_name "node-exporter")" || die "Unable to resolve node-exporter image" - WEBHOOK_CERTGEN_IMAGE="$(find_image_ref_by_name "kube-webhook-certgen")" || die "Unable to resolve kube-webhook-certgen image" -} - -write_base_values_file() { - cat > "${BASE_VALUES_FILE}" < "${PHASE1_VALUES_FILE}" < "${PHASE2_VALUES_FILE}" </dev/null - done - success "CRDs are established" -} - -wait_for_release_workloads() { - local kind resource - local -a resources=() - - for kind in deployment statefulset daemonset; do - mapfile -t resources < <(kubectl get "${kind}" -n "${NAMESPACE}" -l "app.kubernetes.io/instance=${RELEASE_NAME}" -o name 2>/dev/null || true) - for resource in "${resources[@]}"; do - [[ -n "${resource}" ]] || continue - log "Waiting for ${resource}" - kubectl rollout status -n "${NAMESPACE}" "${resource}" --timeout="${WAIT_TIMEOUT}" - done - done -} - -wait_for_operator_managed_statefulset() { - local resource_kind="$1" - local sts_prefix="$2" - local resource_name statefulset_name - - resource_name="$(kubectl get "${resource_kind}" -n "${NAMESPACE}" -l "app.kubernetes.io/instance=${RELEASE_NAME}" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)" - [[ -n "${resource_name}" ]] || return 0 - - statefulset_name="${sts_prefix}-${resource_name}" - if kubectl get "statefulset/${statefulset_name}" -n "${NAMESPACE}" >/dev/null 2>&1; then - log "Waiting for statefulset/${statefulset_name}" - kubectl rollout status -n "${NAMESPACE}" "statefulset/${statefulset_name}" --timeout="${WAIT_TIMEOUT}" - fi -} - -install_release() { - ensure_namespace - resolve_required_images - write_phase_values_files - - section "阶段一: 安装 CRD 与 Prometheus Operator" - run_helm_phase "phase-1" "${PHASE1_VALUES_FILE}" - wait_for_crds_established - wait_for_release_workloads - - section "阶段二: 安装完整监控栈" - run_helm_phase "phase-2" "${PHASE2_VALUES_FILE}" - wait_for_release_workloads - wait_for_operator_managed_statefulset "prometheus" "prometheus" - wait_for_operator_managed_statefulset "alertmanager" "alertmanager" - - success "Prometheus Stack install or upgrade completed" -} - -show_post_install_info() { - section "部署结果" - kubectl get pods,svc,deploy,statefulset,daemonset -n "${NAMESPACE}" -l "app.kubernetes.io/instance=${RELEASE_NAME}" || true - - if kubectl get crd servicemonitors.monitoring.coreos.com >/dev/null 2>&1; then - echo - kubectl get servicemonitor,podmonitor,prometheusrule,prometheus,alertmanager -n "${NAMESPACE}" -l "app.kubernetes.io/instance=${RELEASE_NAME}" || true - fi - - section "Access Information" - if [[ "${GRAFANA_SERVICE_TYPE}" == "NodePort" ]]; then - echo "Grafana URL : http://:${GRAFANA_NODE_PORT}" - fi - if [[ "${PROMETHEUS_SERVICE_TYPE}" == "NodePort" ]]; then - echo "Prometheus URL : http://:${PROMETHEUS_NODE_PORT}" - fi - echo "Grafana admin user : admin" - echo "Grafana admin password : ${GRAFANA_ADMIN_PASSWORD}" - if [[ -n "${ALERTMANAGER_CONFIG_FILE}" ]]; then - echo "Alert notifications : custom config loaded from ${ALERTMANAGER_CONFIG_FILE}" - else - echo "Alert notifications : default null receiver, provide --alertmanager-config-file to enable real notifications" - fi - echo "Dashboard label contract : ${DASHBOARD_LABEL_KEY}=${DASHBOARD_LABEL_VALUE}" - echo "Dashboard folder anno : ${DASHBOARD_FOLDER_ANNOTATION}" - echo "Monitor label contract : ${STACK_LABEL_KEY}=${STACK_LABEL_VALUE}" -} - -delete_crds_if_requested() { - [[ "${DELETE_CRDS}" == "true" ]] || return 0 - - section "删除监控 CRD" - local crd - for crd in "${CRDS[@]}"; do - kubectl delete crd "${crd}" --ignore-not-found >/dev/null || true - done - success "CRD cleanup requested" -} - -uninstall_release() { - if helm status "${RELEASE_NAME}" -n "${NAMESPACE}" >/dev/null 2>&1; then - helm uninstall "${RELEASE_NAME}" -n "${NAMESPACE}" - success "Release ${RELEASE_NAME} uninstalled" - else - warn "Helm release ${RELEASE_NAME} not found in namespace ${NAMESPACE}" - fi - - delete_crds_if_requested -} - -show_crd_status() { - section "CRD 状态" - local crd - for crd in "${CRDS[@]}"; do - if kubectl get crd "${crd}" >/dev/null 2>&1; then - echo "[OK] ${crd}" - else - echo "[--] ${crd}" - fi - done -} - -show_status() { - show_crd_status - - section "Helm 状态" - helm status "${RELEASE_NAME}" -n "${NAMESPACE}" || warn "Release ${RELEASE_NAME} not found" - - section "工作负载状态" - kubectl get pods,svc,deploy,statefulset,daemonset -n "${NAMESPACE}" -l "app.kubernetes.io/instance=${RELEASE_NAME}" || true - - if kubectl get crd servicemonitors.monitoring.coreos.com >/dev/null 2>&1; then - echo - kubectl get servicemonitor,podmonitor,prometheusrule,prometheus,alertmanager -n "${NAMESPACE}" -l "app.kubernetes.io/instance=${RELEASE_NAME}" || true - fi -} - -main() { - parse_args "$@" - normalize_flags - banner - - case "${ACTION}" in - help) - usage - ;; - install) - check_deps - confirm - extract_payload - load_image_metadata - prepare_images - install_release - show_post_install_info - ;; - uninstall) - check_deps - confirm - uninstall_release - ;; - status) - check_deps - show_status - ;; - *) - die "Unsupported action: ${ACTION}" - ;; - esac -} - -main "$@" -exit 0 - -__PAYLOAD_BELOW__ +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "${ROOT_DIR}/install-v2.sh" "$@" From 39c5b2199b1e97ac87ba3e18b61af4a664c4020b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:18:27 +0800 Subject: [PATCH 08/14] build: use v0.2 installer template --- build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index 7561f6a..ef4a6ae 100644 --- a/build.sh +++ b/build.sh @@ -12,7 +12,7 @@ PAYLOAD_FILE="${TEMP_DIR}/payload.tar.gz" DIST_DIR="${ROOT_DIR}/dist" IMAGES_DIR="${ROOT_DIR}/images" IMAGE_JSON="${IMAGES_DIR}/image.json" -INSTALLER_TEMPLATE="${ROOT_DIR}/install.sh" +INSTALLER_TEMPLATE="${ROOT_DIR}/install-v2.sh" INSTALLER_BASENAME="prometheus-stack-installer" CHART_OCI="oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack" @@ -76,10 +76,10 @@ check_requirements() { command -v docker >/dev/null 2>&1 || die "docker is required" command -v helm >/dev/null 2>&1 || die "helm is required" command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required" - [[ -f "${INSTALLER_TEMPLATE}" ]] || die "install.sh is missing" + [[ -f "${INSTALLER_TEMPLATE}" ]] || die "install-v2.sh is missing" [[ -f "${IMAGE_JSON}" ]] || die "images/image.json is missing" [[ -f "${ROOT_DIR}/versions.env" ]] || die "versions.env is missing" - grep -q '^__PAYLOAD_BELOW__$' "${INSTALLER_TEMPLATE}" || die "install.sh is missing __PAYLOAD_BELOW__ marker" + grep -q '^__PAYLOAD_BELOW__$' "${INSTALLER_TEMPLATE}" || die "install-v2.sh is missing __PAYLOAD_BELOW__ marker" } prepare_directories() { From c257d7fd7cf1681be4a42836e3996678eb309b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:19:47 +0800 Subject: [PATCH 09/14] ci: validate v0.2 installer entrypoints --- .github/workflows/build-offline-installer.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-offline-installer.yml b/.github/workflows/build-offline-installer.yml index 98c90de..0a14468 100644 --- a/.github/workflows/build-offline-installer.yml +++ b/.github/workflows/build-offline-installer.yml @@ -12,6 +12,7 @@ on: - ".github/workflows/build-offline-installer.yml" - "build.sh" - "install.sh" + - "install-v2.sh" - "versions.env" - "README.md" - "docs/**" @@ -21,6 +22,7 @@ on: - ".github/workflows/build-offline-installer.yml" - "build.sh" - "install.sh" + - "install-v2.sh" - "versions.env" - "README.md" - "docs/**" @@ -37,7 +39,7 @@ jobs: uses: actions/checkout@v4 - name: Shell syntax - run: bash -n build.sh install.sh + run: bash -n build.sh install.sh install-v2.sh - name: Validate image metadata run: | @@ -47,12 +49,14 @@ jobs: - name: Validate installer help run: | - chmod +x install.sh + chmod +x install.sh install-v2.sh ./install.sh --help | tee /tmp/help.txt grep -q "kube-prometheus-stack.*90.2.0" /tmp/help.txt grep -q -- "--values-file" /tmp/help.txt grep -q -- "--stack-label-key" /tmp/help.txt grep -q -- "--grafana-admin-user" /tmp/help.txt + grep -q -- "--prometheus-storage-access-mode" /tmp/help.txt + grep -q -- "--registry-secret" /tmp/help.txt build: needs: validate @@ -80,7 +84,7 @@ jobs: version: v3.19.0 - name: Ensure executable permissions - run: chmod +x build.sh install.sh + run: chmod +x build.sh install.sh install-v2.sh - name: Build installer run: ./build.sh --arch "${{ matrix.arch }}" From c5169514c1d38915744451ef3cf299f2b8dfd7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:22:01 +0800 Subject: [PATCH 10/14] fix: validate top-level chart version --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index ef4a6ae..b0b78ef 100644 --- a/build.sh +++ b/build.sh @@ -98,7 +98,7 @@ prepare_chart() { [[ -f "${chart_file}" ]] || die "Pulled chart is missing Chart.yaml" local actual_version - actual_version="$(awk '$1=="version:" {print $2; exit}' "${chart_file}")" + actual_version="$(awk '/^version:/ {print $2; exit}' "${chart_file}")" [[ "${actual_version}" == "${KUBE_PROMETHEUS_STACK_VERSION}" ]] \ || die "Chart version mismatch: expected=${KUBE_PROMETHEUS_STACK_VERSION}, actual=${actual_version}" From b1932491efeea8fe09b191b8c7d48e442727de3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:23:47 +0800 Subject: [PATCH 11/14] fix: align v0.2 values with upstream subcharts --- install-v2.sh | 964 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 672 insertions(+), 292 deletions(-) diff --git a/install-v2.sh b/install-v2.sh index 8a1348c..e2c46ce 100644 --- a/install-v2.sh +++ b/install-v2.sh @@ -5,417 +5,797 @@ APP_VERSION="0.2.0" STACK_VERSION="90.2.0" WORKDIR="/tmp/prometheus-stack-installer" CHART_DIR="${WORKDIR}/charts/kube-prometheus-stack" +CRD_DIR="${WORKDIR}/crds" IMAGE_DIR="${WORKDIR}/images" IMAGE_INDEX="${IMAGE_DIR}/image-index.tsv" -VALUES="${WORKDIR}/values.yaml" -PHASE1="${WORKDIR}/phase1.yaml" - -ACTION="help"; RELEASE="prometheus-stack"; NS="monitoring"; TIMEOUT="10m"; YES=false -REGISTRY="sealos.hub:5000/kube4"; REGISTRY_SET=false; REGISTRY_USER="admin"; REGISTRY_PASS="passw0rd"; REGISTRY_SECRET="" -PULL_POLICY="IfNotPresent"; SKIP_IMAGES=false; SKIP_CRD_UPGRADE=false; DELETE_CRDS=false -PROM_SC="nfs"; PROM_SIZE="200Gi"; PROM_MODE="ReadWriteOnce"; PROM_RETENTION="14d"; PROM_RETENTION_SIZE="30GiB"; PROM_REPLICAS=1; PROM_SVC="NodePort"; PROM_PORT=30091 -AM_SC="nfs"; AM_SIZE="10Gi"; AM_MODE="ReadWriteOnce"; AM_REPLICAS=1; AM_CONFIG=""; ENABLE_AM=true -GRAFANA_SC="nfs"; GRAFANA_SIZE="10Gi"; GRAFANA_MODE="ReadWriteOnce"; GRAFANA_REPLICAS=1; GRAFANA_USER="admin"; GRAFANA_PASS="admin@passw0rd"; GRAFANA_SVC="NodePort"; GRAFANA_PORT=30090; ENABLE_GRAFANA=true -ENABLE_RULES=true; ENABLE_KSM=true; ENABLE_NODE=true -STACK_LABEL_KEY="monitoring.archinfra.io/stack"; STACK_LABEL_VALUE="default" -DASH_LABEL_KEY="grafana_dashboard"; DASH_LABEL_VALUE="1"; DASH_FOLDER_ANNO="grafana_folder"; DASH_NS="ALL" -EXTRA_VALUES=(); HELM_ARGS=(); PAYLOAD_OFFSET="" - -CRDS=(alertmanagerconfigs.monitoring.coreos.com alertmanagers.monitoring.coreos.com podmonitors.monitoring.coreos.com probes.monitoring.coreos.com prometheusagents.monitoring.coreos.com prometheuses.monitoring.coreos.com prometheusrules.monitoring.coreos.com scrapeconfigs.monitoring.coreos.com servicemonitors.monitoring.coreos.com thanosrulers.monitoring.coreos.com) - -die(){ echo "[ERROR] $*" >&2; exit 1; } -log(){ echo "[INFO] $*"; } -ok(){ echo "[OK] $*"; } -bool(){ [[ "$2" == true || "$2" == false ]] || die "$1 must be true|false"; } -posint(){ [[ "$2" =~ ^[1-9][0-9]*$ ]] || die "$1 must be a positive integer"; } -svc(){ [[ "$2" =~ ^(ClusterIP|NodePort|LoadBalancer)$ ]] || die "$1 must be ClusterIP|NodePort|LoadBalancer"; } -q(){ printf '%s' "$1" | sed "s/'/''/g"; } - -help(){ -cat <&2; } +die() { echo "[ERROR] $*" >&2; exit 1; } + +program_name() { basename "$0"; } +yaml_quote() { printf '%s' "$1" | sed "s/'/''/g"; } + +usage() { + local cmd="./$(program_name)" + cat < [options] [-- ] + ${cmd} [options] [-- ] + ${cmd} -h|--help + +Actions: + install Install a new stack or upgrade an existing release + uninstall Uninstall the Helm release; CRDs are preserved by default + status Show Helm release, CRD and workload status + help Show this help Core: - -n, --namespace [default: ${NS}] - --release-name [default: ${RELEASE}] - --wait-timeout [default: ${TIMEOUT}] - --values-file extra Helm values; repeatable + -n, --namespace [default: ${NAMESPACE}] + --release-name [default: ${RELEASE}] + --wait-timeout [default: ${WAIT_TIMEOUT}] + --values-file Extra Helm values file; repeatable [default: none] Prometheus: - --prometheus-storage-class [default: ${PROM_SC}] - --prometheus-storage-size [default: ${PROM_SIZE}] - --prometheus-storage-access-mode [default: ${PROM_MODE}] - --prometheus-retention [default: ${PROM_RETENTION}] - --prometheus-retention-size [default: ${PROM_RETENTION_SIZE}] - --prometheus-replicas [default: ${PROM_REPLICAS}] - --prometheus-service-type [default: ${PROM_SVC}] - --prometheus-node-port [default: ${PROM_PORT}] + --prometheus-storage-class [default: ${PROMETHEUS_STORAGE_CLASS}] + --prometheus-storage-size [default: ${PROMETHEUS_STORAGE_SIZE}] + --prometheus-storage-access-mode [default: ${PROMETHEUS_STORAGE_ACCESS_MODE}] + --prometheus-retention [default: ${PROMETHEUS_RETENTION}] + --prometheus-retention-size [default: ${PROMETHEUS_RETENTION_SIZE}] + --prometheus-replicas [default: ${PROMETHEUS_REPLICAS}] + --prometheus-service-type ClusterIP|NodePort|LoadBalancer [default: ${PROMETHEUS_SERVICE_TYPE}] + --prometheus-node-port [default: ${PROMETHEUS_NODE_PORT}] Alertmanager: - --alertmanager-storage-class [default: ${AM_SC}] - --alertmanager-storage-size [default: ${AM_SIZE}] - --alertmanager-storage-access-mode [default: ${AM_MODE}] - --alertmanager-replicas [default: ${AM_REPLICAS}] - --alertmanager-config-file [default: null receiver] - --enable-alertmanager [default: ${ENABLE_AM}] + --alertmanager-storage-class [default: ${ALERTMANAGER_STORAGE_CLASS}] + --alertmanager-storage-size [default: ${ALERTMANAGER_STORAGE_SIZE}] + --alertmanager-storage-access-mode [default: ${ALERTMANAGER_STORAGE_ACCESS_MODE}] + --alertmanager-replicas [default: ${ALERTMANAGER_REPLICAS}] + --alertmanager-config-file [default: null receiver] + --enable-alertmanager [default: ${ENABLE_ALERTMANAGER}] Grafana: - --grafana-storage-class [default: ${GRAFANA_SC}] - --grafana-storage-size [default: ${GRAFANA_SIZE}] - --grafana-storage-access-mode [default: ${GRAFANA_MODE}] - --grafana-replicas [default: ${GRAFANA_REPLICAS}] - --grafana-admin-user [default: ${GRAFANA_USER}] - --grafana-admin-password [default: ${GRAFANA_PASS}] - --grafana-service-type [default: ${GRAFANA_SVC}] - --grafana-node-port [default: ${GRAFANA_PORT}] - --enable-grafana [default: ${ENABLE_GRAFANA}] + --grafana-storage-class [default: ${GRAFANA_STORAGE_CLASS}] + --grafana-storage-size [default: ${GRAFANA_STORAGE_SIZE}] + --grafana-storage-access-mode [default: ${GRAFANA_STORAGE_ACCESS_MODE}] + --grafana-replicas [default: ${GRAFANA_REPLICAS}] + --grafana-admin-user [default: ${GRAFANA_ADMIN_USER}] + --grafana-admin-password [default: ${GRAFANA_ADMIN_PASSWORD}] + --grafana-service-type ClusterIP|NodePort|LoadBalancer [default: ${GRAFANA_SERVICE_TYPE}] + --grafana-node-port [default: ${GRAFANA_NODE_PORT}] + --enable-grafana [default: ${ENABLE_GRAFANA}] Discovery / dashboards: - --stack-label-key [default: ${STACK_LABEL_KEY}] - --stack-label-value [default: ${STACK_LABEL_VALUE}] - --dashboard-label-key [default: ${DASH_LABEL_KEY}] - --dashboard-label-value [default: ${DASH_LABEL_VALUE}] - --dashboard-folder-annotation [default: ${DASH_FOLDER_ANNO}] - --dashboard-search-namespace [default: ${DASH_NS}] - --enable-default-rules [default: ${ENABLE_RULES}] - --enable-kube-state-metrics [default: ${ENABLE_KSM}] - --enable-node-exporter [default: ${ENABLE_NODE}] - -Registry: - --registry [default: ${REGISTRY}] - --registry-user [default: ${REGISTRY_USER}] - --registry-password [default: ] - --registry-secret [default: ] - --image-pull-policy [default: ${PULL_POLICY}] - --skip-image-prepare + --stack-label-key [default: ${STACK_LABEL_KEY}] + --stack-label-value [default: ${STACK_LABEL_VALUE}] + --dashboard-label-key [default: ${DASHBOARD_LABEL_KEY}] + --dashboard-label-value [default: ${DASHBOARD_LABEL_VALUE}] + --dashboard-folder-annotation [default: ${DASHBOARD_FOLDER_ANNOTATION}] + --dashboard-search-namespace [default: ${DASHBOARD_SEARCH_NAMESPACE}] + --enable-default-rules [default: ${ENABLE_DEFAULT_RULES}] + --enable-kube-state-metrics [default: ${ENABLE_KUBE_STATE_METRICS}] + --enable-node-exporter [default: ${ENABLE_NODE_EXPORTER}] + +Registry / images: + --registry [default: ${REGISTRY}] + --registry-user [default: ${REGISTRY_USER}] + --registry-password [default: ] + --registry-secret Kubernetes imagePullSecret [default: none] + --image-pull-policy Always|IfNotPresent|Never [default: ${IMAGE_PULL_POLICY}] + --skip-image-prepare Reuse images already present in registry [default: false] Upgrade / cleanup: - --skip-crd-upgrade - --delete-crds + --skip-crd-upgrade Do not apply bundled CRDs before upgrade [default: false] + --delete-crds Delete monitoring CRDs during uninstall [default: false] Other: - -y, --yes - -h, --help - -- + -y, --yes Skip confirmation [default: false] + -h, --help Show help + -- Pass remaining arguments directly to Helm + +Examples: + ${cmd} install -n monitoring --grafana-admin-password 'Admin@123' -y + ${cmd} install --prometheus-retention 30d --prometheus-retention-size 160GiB -y + ${cmd} install --registry harbor.example.com/monitoring --registry-secret harbor-pull -y + ${cmd} install --values-file ./site-values.yaml -y + ${cmd} status -n monitoring EOF } -need(){ [[ $# -ge 2 ]] || die "missing value for $1"; } -parse(){ +require_value() { [[ $# -ge 2 ]] || die "Missing value for $1"; } + +parse_args() { [[ $# -gt 0 ]] || return 0 while [[ $# -gt 0 ]]; do case "$1" in - install|uninstall|status|help) ACTION="$1"; shift;; - -n|--namespace) need "$@"; NS="$2"; shift 2;; - --release-name) need "$@"; RELEASE="$2"; shift 2;; - --wait-timeout) need "$@"; TIMEOUT="$2"; shift 2;; - --values-file) need "$@"; EXTRA_VALUES+=("$2"); shift 2;; - --prometheus-storage-class) need "$@"; PROM_SC="$2"; shift 2;; - --prometheus-storage-size) need "$@"; PROM_SIZE="$2"; shift 2;; - --prometheus-storage-access-mode) need "$@"; PROM_MODE="$2"; shift 2;; - --prometheus-retention) need "$@"; PROM_RETENTION="$2"; shift 2;; - --prometheus-retention-size) need "$@"; PROM_RETENTION_SIZE="$2"; shift 2;; - --prometheus-replicas) need "$@"; PROM_REPLICAS="$2"; shift 2;; - --prometheus-service-type) need "$@"; PROM_SVC="$2"; shift 2;; - --prometheus-node-port) need "$@"; PROM_PORT="$2"; shift 2;; - --alertmanager-storage-class) need "$@"; AM_SC="$2"; shift 2;; - --alertmanager-storage-size) need "$@"; AM_SIZE="$2"; shift 2;; - --alertmanager-storage-access-mode) need "$@"; AM_MODE="$2"; shift 2;; - --alertmanager-replicas) need "$@"; AM_REPLICAS="$2"; shift 2;; - --alertmanager-config-file) need "$@"; AM_CONFIG="$2"; shift 2;; - --enable-alertmanager) need "$@"; ENABLE_AM="$2"; shift 2;; - --grafana-storage-class) need "$@"; GRAFANA_SC="$2"; shift 2;; - --grafana-storage-size) need "$@"; GRAFANA_SIZE="$2"; shift 2;; - --grafana-storage-access-mode) need "$@"; GRAFANA_MODE="$2"; shift 2;; - --grafana-replicas) need "$@"; GRAFANA_REPLICAS="$2"; shift 2;; - --grafana-admin-user) need "$@"; GRAFANA_USER="$2"; shift 2;; - --grafana-admin-password) need "$@"; GRAFANA_PASS="$2"; shift 2;; - --grafana-service-type) need "$@"; GRAFANA_SVC="$2"; shift 2;; - --grafana-node-port) need "$@"; GRAFANA_PORT="$2"; shift 2;; - --enable-grafana) need "$@"; ENABLE_GRAFANA="$2"; shift 2;; - --stack-label-key) need "$@"; STACK_LABEL_KEY="$2"; shift 2;; - --stack-label-value) need "$@"; STACK_LABEL_VALUE="$2"; shift 2;; - --dashboard-label-key) need "$@"; DASH_LABEL_KEY="$2"; shift 2;; - --dashboard-label-value) need "$@"; DASH_LABEL_VALUE="$2"; shift 2;; - --dashboard-folder-annotation) need "$@"; DASH_FOLDER_ANNO="$2"; shift 2;; - --dashboard-search-namespace) need "$@"; DASH_NS="$2"; shift 2;; - --enable-default-rules) need "$@"; ENABLE_RULES="$2"; shift 2;; - --enable-kube-state-metrics) need "$@"; ENABLE_KSM="$2"; shift 2;; - --enable-node-exporter) need "$@"; ENABLE_NODE="$2"; shift 2;; - --registry) need "$@"; REGISTRY="$2"; REGISTRY_SET=true; shift 2;; - --registry-user) need "$@"; REGISTRY_USER="$2"; shift 2;; - --registry-password) need "$@"; REGISTRY_PASS="$2"; shift 2;; - --registry-secret) need "$@"; REGISTRY_SECRET="$2"; shift 2;; - --image-pull-policy) need "$@"; PULL_POLICY="$2"; shift 2;; - --skip-image-prepare) SKIP_IMAGES=true; shift;; - --skip-crd-upgrade) SKIP_CRD_UPGRADE=true; shift;; - --delete-crds) DELETE_CRDS=true; shift;; - -y|--yes) YES=true; shift;; - -h|--help) ACTION=help; shift;; - --) shift; while [[ $# -gt 0 ]]; do HELM_ARGS+=("$1"); shift; done;; - *) die "unknown argument: $1";; + install|uninstall|status|help) ACTION="$1"; shift ;; + -n|--namespace) require_value "$@"; NAMESPACE="$2"; shift 2 ;; + --release-name) require_value "$@"; RELEASE="$2"; shift 2 ;; + --wait-timeout) require_value "$@"; WAIT_TIMEOUT="$2"; shift 2 ;; + --values-file) require_value "$@"; EXTRA_VALUES_FILES+=("$2"); shift 2 ;; + --prometheus-storage-class) require_value "$@"; PROMETHEUS_STORAGE_CLASS="$2"; shift 2 ;; + --prometheus-storage-size) require_value "$@"; PROMETHEUS_STORAGE_SIZE="$2"; shift 2 ;; + --prometheus-storage-access-mode) require_value "$@"; PROMETHEUS_STORAGE_ACCESS_MODE="$2"; shift 2 ;; + --prometheus-retention) require_value "$@"; PROMETHEUS_RETENTION="$2"; shift 2 ;; + --prometheus-retention-size) require_value "$@"; PROMETHEUS_RETENTION_SIZE="$2"; shift 2 ;; + --prometheus-replicas) require_value "$@"; PROMETHEUS_REPLICAS="$2"; shift 2 ;; + --prometheus-service-type) require_value "$@"; PROMETHEUS_SERVICE_TYPE="$2"; shift 2 ;; + --prometheus-node-port) require_value "$@"; PROMETHEUS_NODE_PORT="$2"; shift 2 ;; + --alertmanager-storage-class) require_value "$@"; ALERTMANAGER_STORAGE_CLASS="$2"; shift 2 ;; + --alertmanager-storage-size) require_value "$@"; ALERTMANAGER_STORAGE_SIZE="$2"; shift 2 ;; + --alertmanager-storage-access-mode) require_value "$@"; ALERTMANAGER_STORAGE_ACCESS_MODE="$2"; shift 2 ;; + --alertmanager-replicas) require_value "$@"; ALERTMANAGER_REPLICAS="$2"; shift 2 ;; + --alertmanager-config-file) require_value "$@"; ALERTMANAGER_CONFIG_FILE="$2"; shift 2 ;; + --enable-alertmanager) require_value "$@"; ENABLE_ALERTMANAGER="$2"; shift 2 ;; + --grafana-storage-class) require_value "$@"; GRAFANA_STORAGE_CLASS="$2"; shift 2 ;; + --grafana-storage-size) require_value "$@"; GRAFANA_STORAGE_SIZE="$2"; shift 2 ;; + --grafana-storage-access-mode) require_value "$@"; GRAFANA_STORAGE_ACCESS_MODE="$2"; shift 2 ;; + --grafana-replicas) require_value "$@"; GRAFANA_REPLICAS="$2"; shift 2 ;; + --grafana-admin-user) require_value "$@"; GRAFANA_ADMIN_USER="$2"; shift 2 ;; + --grafana-admin-password) require_value "$@"; GRAFANA_ADMIN_PASSWORD="$2"; shift 2 ;; + --grafana-service-type) require_value "$@"; GRAFANA_SERVICE_TYPE="$2"; shift 2 ;; + --grafana-node-port) require_value "$@"; GRAFANA_NODE_PORT="$2"; shift 2 ;; + --enable-grafana) require_value "$@"; ENABLE_GRAFANA="$2"; shift 2 ;; + --stack-label-key) require_value "$@"; STACK_LABEL_KEY="$2"; shift 2 ;; + --stack-label-value) require_value "$@"; STACK_LABEL_VALUE="$2"; shift 2 ;; + --dashboard-label-key) require_value "$@"; DASHBOARD_LABEL_KEY="$2"; shift 2 ;; + --dashboard-label-value) require_value "$@"; DASHBOARD_LABEL_VALUE="$2"; shift 2 ;; + --dashboard-folder-annotation) require_value "$@"; DASHBOARD_FOLDER_ANNOTATION="$2"; shift 2 ;; + --dashboard-search-namespace) require_value "$@"; DASHBOARD_SEARCH_NAMESPACE="$2"; shift 2 ;; + --enable-default-rules) require_value "$@"; ENABLE_DEFAULT_RULES="$2"; shift 2 ;; + --enable-kube-state-metrics) require_value "$@"; ENABLE_KUBE_STATE_METRICS="$2"; shift 2 ;; + --enable-node-exporter) require_value "$@"; ENABLE_NODE_EXPORTER="$2"; shift 2 ;; + --registry) require_value "$@"; REGISTRY="$2"; REGISTRY_EXPLICIT="true"; shift 2 ;; + --registry-user) require_value "$@"; REGISTRY_USER="$2"; shift 2 ;; + --registry-password) require_value "$@"; REGISTRY_PASSWORD="$2"; shift 2 ;; + --registry-secret) require_value "$@"; REGISTRY_SECRET="$2"; shift 2 ;; + --image-pull-policy) require_value "$@"; IMAGE_PULL_POLICY="$2"; shift 2 ;; + --skip-image-prepare) SKIP_IMAGE_PREPARE="true"; shift ;; + --skip-crd-upgrade) SKIP_CRD_UPGRADE="true"; shift ;; + --delete-crds) DELETE_CRDS="true"; shift ;; + -y|--yes) AUTO_YES="true"; shift ;; + -h|--help) ACTION="help"; shift ;; + --) shift; while [[ $# -gt 0 ]]; do HELM_ARGS+=("$1"); shift; done; break ;; + *) die "Unknown argument: $1" ;; esac done } -validate(){ - [[ "$PULL_POLICY" =~ ^(Always|IfNotPresent|Never)$ ]] || die "invalid image pull policy" - svc --prometheus-service-type "$PROM_SVC"; svc --grafana-service-type "$GRAFANA_SVC" - bool --enable-alertmanager "$ENABLE_AM"; bool --enable-grafana "$ENABLE_GRAFANA"; bool --enable-default-rules "$ENABLE_RULES"; bool --enable-kube-state-metrics "$ENABLE_KSM"; bool --enable-node-exporter "$ENABLE_NODE" - posint --prometheus-replicas "$PROM_REPLICAS"; posint --alertmanager-replicas "$AM_REPLICAS"; posint --grafana-replicas "$GRAFANA_REPLICAS" - [[ "$PROM_SVC" != NodePort || "$PROM_PORT" =~ ^3[0-2][0-9]{3}$ ]] || die "invalid prometheus nodePort" - [[ "$GRAFANA_SVC" != NodePort || "$GRAFANA_PORT" =~ ^3[0-2][0-9]{3}$ ]] || die "invalid grafana nodePort" - local f; for f in "${EXTRA_VALUES[@]}"; do [[ -f "$f" ]] || die "values file not found: $f"; done - [[ -z "$AM_CONFIG" || -f "$AM_CONFIG" ]] || die "alertmanager config not found: $AM_CONFIG" +validate_bool() { [[ "$2" == "true" || "$2" == "false" ]] || die "$1 must be true|false"; } +validate_positive_int() { [[ "$2" =~ ^[1-9][0-9]*$ ]] || die "$1 must be a positive integer"; } +validate_service_type() { [[ "$2" =~ ^(ClusterIP|NodePort|LoadBalancer)$ ]] || die "$1 must be ClusterIP|NodePort|LoadBalancer"; } +validate_node_port() { + local name="$1" value="$2" + [[ "$value" =~ ^[0-9]+$ ]] || die "$name must be numeric" + (( value >= 30000 && value <= 32767 )) || die "$name must be within 30000-32767" } -deps(){ - command -v helm >/dev/null || die "helm required" - command -v kubectl >/dev/null || die "kubectl required" - [[ "$ACTION" != install || "$SKIP_IMAGES" == true ]] || command -v docker >/dev/null || die "docker required unless --skip-image-prepare" +validate_args() { + [[ "$IMAGE_PULL_POLICY" =~ ^(Always|IfNotPresent|Never)$ ]] || die "Unsupported image pull policy: ${IMAGE_PULL_POLICY}" + validate_service_type --prometheus-service-type "$PROMETHEUS_SERVICE_TYPE" + validate_service_type --grafana-service-type "$GRAFANA_SERVICE_TYPE" + validate_bool --enable-alertmanager "$ENABLE_ALERTMANAGER" + validate_bool --enable-grafana "$ENABLE_GRAFANA" + validate_bool --enable-default-rules "$ENABLE_DEFAULT_RULES" + validate_bool --enable-kube-state-metrics "$ENABLE_KUBE_STATE_METRICS" + validate_bool --enable-node-exporter "$ENABLE_NODE_EXPORTER" + validate_positive_int --prometheus-replicas "$PROMETHEUS_REPLICAS" + validate_positive_int --alertmanager-replicas "$ALERTMANAGER_REPLICAS" + validate_positive_int --grafana-replicas "$GRAFANA_REPLICAS" + [[ "$PROMETHEUS_SERVICE_TYPE" != "NodePort" ]] || validate_node_port --prometheus-node-port "$PROMETHEUS_NODE_PORT" + [[ "$GRAFANA_SERVICE_TYPE" != "NodePort" ]] || validate_node_port --grafana-node-port "$GRAFANA_NODE_PORT" + + local file + for file in "${EXTRA_VALUES_FILES[@]}"; do + [[ -f "$file" ]] || die "Values file not found: $file" + done + [[ -z "$ALERTMANAGER_CONFIG_FILE" || -f "$ALERTMANAGER_CONFIG_FILE" ]] || die "Alertmanager config file not found: ${ALERTMANAGER_CONFIG_FILE}" } -confirm(){ - [[ "$YES" == true ]] && return - echo "Action=${ACTION} Release=${RELEASE} Namespace=${NS} Stack=${STACK_VERSION}" - read -r -p "Continue? [y/N] " a - [[ "$a" =~ ^[Yy]$ ]] || exit 1 +check_deps() { + command -v helm >/dev/null 2>&1 || die "helm is required" + command -v kubectl >/dev/null 2>&1 || die "kubectl is required" + if [[ "$ACTION" == "install" && "$SKIP_IMAGE_PREPARE" != "true" ]]; then + command -v docker >/dev/null 2>&1 || die "docker is required unless --skip-image-prepare is used" + fi } -payload_offset(){ - [[ -n "$PAYLOAD_OFFSET" ]] && return - local n; n="$(awk '/^__PAYLOAD_BELOW__$/{print NR;exit}' "$0")"; [[ -n "$n" ]] || die "payload marker missing" - PAYLOAD_OFFSET="$(( $(head -n "$n" "$0" | wc -c) + 1 ))" - while [[ "$(dd if="$0" bs=1 skip="$((PAYLOAD_OFFSET-1))" count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')" =~ ^(0a|0d)$ ]]; do PAYLOAD_OFFSET=$((PAYLOAD_OFFSET+1)); done +confirm() { + [[ "$AUTO_YES" == "true" ]] && return 0 + echo "Action : ${ACTION}" + echo "Release : ${RELEASE}" + echo "Namespace : ${NAMESPACE}" + echo "kube-prometheus-stack : ${STACK_VERSION}" + if [[ "$ACTION" == "install" ]]; then + echo "Prometheus storage : ${PROMETHEUS_STORAGE_CLASS}/${PROMETHEUS_STORAGE_SIZE}/${PROMETHEUS_STORAGE_ACCESS_MODE}" + echo "Prometheus retention : ${PROMETHEUS_RETENTION}/${PROMETHEUS_RETENTION_SIZE}" + echo "Prometheus replicas : ${PROMETHEUS_REPLICAS}" + echo "Grafana replicas : ${GRAFANA_REPLICAS}" + echo "Alertmanager replicas : ${ALERTMANAGER_REPLICAS}" + echo "Registry : ${REGISTRY}" + echo "Registry pull secret : ${REGISTRY_SECRET:-}" + echo "Extra values files : ${EXTRA_VALUES_FILES[*]:-}" + fi + echo + read -r -p "Continue? [y/N] " answer + [[ "$answer" =~ ^[Yy]$ ]] || die "Cancelled" } -stream(){ payload_offset; tail -c +"$PAYLOAD_OFFSET" "$0"; } -extract(){ - rm -rf "$WORKDIR"; mkdir -p "$WORKDIR" - if [[ "$SKIP_IMAGES" == true ]]; then stream | tar -xzf - -C "$WORKDIR" ./charts ./images/image-index.tsv >/dev/null - else stream | tar -xzf - -C "$WORKDIR" >/dev/null; fi - [[ -d "$CHART_DIR" && -f "$IMAGE_INDEX" ]] || die "invalid embedded payload" + +init_payload_offset() { + [[ -n "$PAYLOAD_OFFSET" ]] && return 0 + local marker_line offset byte_hex + marker_line="$(awk '/^__PAYLOAD_BELOW__$/ {print NR; exit}' "$0")" + [[ -n "$marker_line" ]] || die "Unable to locate embedded payload" + offset="$(( $(head -n "$marker_line" "$0" | wc -c | tr -d ' ') + 1 ))" + while :; do + byte_hex="$(dd if="$0" bs=1 skip="$((offset - 1))" count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')" + case "$byte_hex" in + 0a|0d) offset=$((offset + 1)) ;; + "") die "Invalid payload boundary" ;; + *) break ;; + esac + done + PAYLOAD_OFFSET="$offset" +} + +payload_stream() { init_payload_offset; tail -c +"${PAYLOAD_OFFSET}" "$0"; } + +extract_payload() { + rm -rf "$WORKDIR" + mkdir -p "$WORKDIR" + if [[ "$SKIP_IMAGE_PREPARE" == "true" ]]; then + payload_stream | tar -xzf - -C "$WORKDIR" ./charts ./crds ./images/image-index.tsv >/dev/null + else + payload_stream | tar -xzf - -C "$WORKDIR" >/dev/null + fi + [[ -d "$CHART_DIR" ]] || die "Missing chart payload" + [[ -d "$CRD_DIR" ]] || die "Missing CRD payload" + [[ -f "$IMAGE_INDEX" ]] || die "Missing image metadata payload" +} + +resolve_target_ref() { + local default_ref="$1" + if [[ "$REGISTRY_EXPLICIT" == "true" ]]; then + echo "${REGISTRY}/${default_ref##*/}" + else + echo "$default_ref" + fi } -declare -A IMG=() -load_meta(){ - local tar load target - while IFS=$'\t' read -r tar load target; do - [[ -n "$tar" ]] || continue - if [[ "$REGISTRY_SET" == true ]]; then target="${REGISTRY}/${target##*/}"; fi - IMG["${target##*/}"]="$target" +load_image_metadata() { + local tar_name load_ref target_ref + while IFS=$'\t' read -r tar_name load_ref target_ref; do + [[ -n "$tar_name" ]] || continue + target_ref="$(resolve_target_ref "$target_ref")" + IMAGES["${target_ref##*/}"]="$target_ref" done < "$IMAGE_INDEX" } -find_img(){ - local name="$1" k - for k in "${!IMG[@]}"; do [[ "${k%%:*}" == "$name" ]] && { echo "${IMG[$k]}"; return; }; done - die "image not found: $name" + +find_image() { + local name="$1" key + for key in "${!IMAGES[@]}"; do + if [[ "${key%%:*}" == "$name" ]]; then + echo "${IMAGES[$key]}" + return 0 + fi + done + die "Unable to resolve image: ${name}" } -ireg(){ echo "${1%%/*}"; } -irepo(){ local x="${1#*/}"; echo "${x%:*}"; } -itag(){ echo "${1##*:}"; } - -prepare_images(){ - [[ "$SKIP_IMAGES" == true ]] && return - echo "$REGISTRY_PASS" | docker login "${REGISTRY%%/*}" -u "$REGISTRY_USER" --password-stdin >/dev/null || true - local tar load target - while IFS=$'\t' read -r tar load target; do - [[ -n "$tar" ]] || continue - [[ "$REGISTRY_SET" == true ]] && target="${REGISTRY}/${target##*/}" - docker load -i "${IMAGE_DIR}/${tar}" >/dev/null - [[ "$load" == "$target" ]] || docker tag "$load" "$target" - docker push "$target" >/dev/null + +image_registry() { echo "${1%%/*}"; } +image_repository() { local rest="${1#*/}"; echo "${rest%:*}"; } +image_tag() { echo "${1##*:}"; } + +prepare_images() { + [[ "$SKIP_IMAGE_PREPARE" == "true" ]] && { log "Skipping image prepare"; return 0; } + + local registry_host="${REGISTRY%%/*}" + if ! echo "$REGISTRY_PASSWORD" | docker login "$registry_host" -u "$REGISTRY_USER" --password-stdin >/dev/null 2>&1; then + warn "docker login failed for ${registry_host}; push will determine whether anonymous access is allowed" + fi + + local tar_name load_ref default_ref target_ref tar_path + while IFS=$'\t' read -r tar_name load_ref default_ref; do + [[ -n "$tar_name" ]] || continue + tar_path="${IMAGE_DIR}/${tar_name}" + [[ -f "$tar_path" ]] || die "Missing image tar: ${tar_path}" + target_ref="$(resolve_target_ref "$default_ref")" + log "Loading ${tar_name}" + docker load -i "$tar_path" >/dev/null + [[ "$load_ref" == "$target_ref" ]] || docker tag "$load_ref" "$target_ref" + log "Pushing ${target_ref}" + docker push "$target_ref" >/dev/null done < "$IMAGE_INDEX" } -write_values(){ - local op reload webhook cert am prom grafana curl busy sidecar renderer rbac ksm node thanos - op="$(find_img prometheus-operator)"; reload="$(find_img prometheus-config-reloader)"; webhook="$(find_img admission-webhook)"; cert="$(find_img kube-webhook-certgen)" - am="$(find_img alertmanager)"; prom="$(find_img prometheus)"; grafana="$(find_img grafana)"; curl="$(find_img curl)"; busy="$(find_img busybox)" - sidecar="$(find_img k8s-sidecar)"; renderer="$(find_img grafana-image-renderer)"; rbac="$(find_img kube-rbac-proxy)"; ksm="$(find_img kube-state-metrics)"; node="$(find_img node-exporter)"; thanos="$(find_img thanos)" +write_pull_secret_values() { + if [[ -n "$REGISTRY_SECRET" ]]; then + cat < "$VALUES" < "$VALUES_FILE" < "$PHASE1" < "$PHASE1_FILE" </dev/null 2>&1; } -crd_upgrade(){ - [[ "$SKIP_CRD_UPGRADE" == true ]] && return - local d="${CHART_DIR}/charts/crds/crds"; [[ -d "$d" ]] || die "CRD dir missing" - kubectl apply --server-side --force-conflicts -f "$d" >/dev/null +release_exists() { helm status "$RELEASE" -n "$NAMESPACE" >/dev/null 2>&1; } + +apply_bundled_crds() { + [[ "$SKIP_CRD_UPGRADE" == "true" ]] && { log "Skipping bundled CRD apply"; return 0; } + log "Applying bundled Prometheus Operator CRDs" + kubectl apply --server-side --force-conflicts -f "$CRD_DIR" >/dev/null } -wait_crds(){ local c; for c in "${CRDS[@]}"; do kubectl wait --for=condition=Established --timeout="$TIMEOUT" "crd/$c" >/dev/null; done; } - -helm_run(){ - local phase="$1"; shift - local args=(helm upgrade --install "$RELEASE" "$CHART_DIR" -n "$NS" --create-namespace --wait --wait-for-jobs --timeout "$TIMEOUT" -f "$VALUES") - [[ "$phase" == first ]] && args+=(-f "$PHASE1") - local f; for f in "${EXTRA_VALUES[@]}"; do args+=(-f "$f"); done - args+=("${HELM_ARGS[@]}") - printf '[INFO] '; printf '%q ' "${args[@]}"; echo - "${args[@]}" + +wait_for_crds() { + local crd + for crd in "${CRDS[@]}"; do + kubectl wait --for=condition=Established --timeout="$WAIT_TIMEOUT" "crd/${crd}" >/dev/null + done +} + +helm_run() { + local phase="$1" + local -a cmd=( + helm upgrade --install "$RELEASE" "$CHART_DIR" + -n "$NAMESPACE" + --create-namespace + --wait + --wait-for-jobs + --timeout "$WAIT_TIMEOUT" + -f "$VALUES_FILE" + ) + + [[ "$phase" == "first" ]] && cmd+=(-f "$PHASE1_FILE") + + local file + for file in "${EXTRA_VALUES_FILES[@]}"; do + cmd+=(-f "$file") + done + cmd+=("${HELM_ARGS[@]}") + + printf '[INFO] Helm: ' + printf '%q ' "${cmd[@]}" + echo + "${cmd[@]}" } -install(){ - extract; load_meta; prepare_images; write_values - kubectl get ns "$NS" >/dev/null 2>&1 || kubectl create ns "$NS" >/dev/null +install_stack() { + extract_payload + load_image_metadata + prepare_images + write_values + + kubectl get namespace "$NAMESPACE" >/dev/null 2>&1 || kubectl create namespace "$NAMESPACE" >/dev/null + if release_exists; then - log "existing release detected: CRD apply + single-stage upgrade" - crd_upgrade; wait_crds; helm_run upgrade + log "Existing release detected: CRD apply + single-stage Helm upgrade" + apply_bundled_crds + wait_for_crds + helm_run upgrade else - log "first install: phase 1 CRDs/operator" - helm_run first; wait_crds - log "first install: phase 2 full stack" + log "First install: applying bundled CRDs" + apply_bundled_crds + wait_for_crds + log "First install phase 1: Prometheus Operator" + helm_run first + log "First install phase 2: full monitoring stack" helm_run full fi - kubectl get pods,svc,deploy,statefulset,daemonset -n "$NS" -l "app.kubernetes.io/instance=${RELEASE}" || true - ok "Prometheus Stack ${STACK_VERSION} installed/upgraded" + + kubectl get pods,svc,deploy,statefulset,daemonset -n "$NAMESPACE" -l "app.kubernetes.io/instance=${RELEASE}" || true + ok "Prometheus Stack ${STACK_VERSION} install/upgrade completed" } -uninstall(){ - release_exists && helm uninstall "$RELEASE" -n "$NS" || true - if [[ "$DELETE_CRDS" == true ]]; then - echo "[WARN] deleting CRDs deletes cluster-wide monitoring custom resources" - local c; for c in "${CRDS[@]}"; do kubectl delete crd "$c" --ignore-not-found >/dev/null || true; done +uninstall_stack() { + if release_exists; then + helm uninstall "$RELEASE" -n "$NAMESPACE" + else + warn "Helm release ${RELEASE} not found in namespace ${NAMESPACE}" + fi + + if [[ "$DELETE_CRDS" == "true" ]]; then + warn "Deleting CRDs also deletes cluster-wide monitoring custom resources" + local crd + for crd in "${CRDS[@]}"; do + kubectl delete crd "$crd" --ignore-not-found >/dev/null || true + done fi } -status(){ - helm status "$RELEASE" -n "$NS" || true - kubectl get pods,svc,deploy,statefulset,daemonset -n "$NS" -l "app.kubernetes.io/instance=${RELEASE}" || true +show_status() { + echo "Installer version : ${APP_VERSION}" + echo "kube-prometheus-stack : ${STACK_VERSION}" + helm status "$RELEASE" -n "$NAMESPACE" || true + echo + kubectl get pods,svc,deploy,statefulset,daemonset -n "$NAMESPACE" -l "app.kubernetes.io/instance=${RELEASE}" || true + echo + local crd + for crd in "${CRDS[@]}"; do + kubectl get crd "$crd" >/dev/null 2>&1 && echo "[OK] ${crd}" || echo "[--] ${crd}" + done } -main(){ - parse "$@"; validate +cleanup() { rm -rf "$WORKDIR"; } +trap cleanup EXIT + +main() { + parse_args "$@" + validate_args + case "$ACTION" in - help) help;; - install) deps; confirm; install;; - uninstall) deps; confirm; uninstall;; - status) deps; status;; - *) die "unsupported action: $ACTION";; + help) usage ;; + install) check_deps; confirm; install_stack ;; + uninstall) check_deps; confirm; uninstall_stack ;; + status) check_deps; show_status ;; + *) die "Unsupported action: ${ACTION}" ;; esac } -trap 'rm -rf "$WORKDIR"' EXIT + main "$@" exit 0 From 011b3d625236d24a8c58347ffb88a028ee898c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:24:15 +0800 Subject: [PATCH 12/14] build: bundle extracted operator CRDs --- build.sh | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index b0b78ef..e69d6c2 100644 --- a/build.sh +++ b/build.sh @@ -84,7 +84,7 @@ check_requirements() { prepare_directories() { rm -rf "${TEMP_DIR}" - mkdir -p "${PAYLOAD_DIR}/charts" "${PAYLOAD_DIR}/images" "${DIST_DIR}" + mkdir -p "${PAYLOAD_DIR}/charts" "${PAYLOAD_DIR}/images" "${PAYLOAD_DIR}/crds" "${DIST_DIR}" } prepare_chart() { @@ -102,9 +102,34 @@ prepare_chart() { [[ "${actual_version}" == "${KUBE_PROMETHEUS_STACK_VERSION}" ]] \ || die "Chart version mismatch: expected=${KUBE_PROMETHEUS_STACK_VERSION}, actual=${actual_version}" + helm lint "${PAYLOAD_DIR}/charts/kube-prometheus-stack" >/dev/null success "Prepared kube-prometheus-stack ${actual_version}" } +prepare_crds() { + local chart_root="${PAYLOAD_DIR}/charts/kube-prometheus-stack" + local unpacked_crd_dir="${chart_root}/charts/crds/crds" + local source_dir="" + + if [[ -d "${unpacked_crd_dir}" ]]; then + source_dir="${unpacked_crd_dir}" + else + local crd_archive + crd_archive="$(find "${chart_root}/charts" -maxdepth 1 -type f -name 'crds-*.tgz' -print -quit)" + [[ -n "${crd_archive}" ]] || die "Unable to locate CRD subchart in pulled chart" + + local extract_dir="${TEMP_DIR}/crds-subchart" + mkdir -p "${extract_dir}" + tar -xzf "${crd_archive}" -C "${extract_dir}" + source_dir="${extract_dir}/crds/crds" + fi + + [[ -d "${source_dir}" ]] || die "Unable to locate CRD manifests" + cp "${source_dir}"/*.yaml "${PAYLOAD_DIR}/crds/" + compgen -G "${PAYLOAD_DIR}/crds/*.yaml" >/dev/null || die "No CRD manifests were copied" + success "Prepared bundled CRD manifests" +} + image_name_tag_from_ref() { local ref="$1" echo "${ref##*/}" @@ -193,6 +218,7 @@ build_one() { prepare_directories prepare_chart + prepare_crds prepare_images "${arch}" "${platform}" package_payload "${arch}" show_result "${arch}" From 36c3dedc5cb60390ea6394dd5929e7b57063a5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:27:49 +0800 Subject: [PATCH 13/14] fix: use published grafana renderer tag --- images/image.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/images/image.json b/images/image.json index c5edc88..71b4b98 100644 --- a/images/image.json +++ b/images/image.json @@ -72,9 +72,9 @@ { "arch": "amd64", "platform": "linux/amd64", - "pull": "docker.io/grafana/grafana-image-renderer:5.10.3", - "tag": "sealos.hub:5000/kube4/grafana-image-renderer:5.10.3", - "tar": "grafana-image-renderer-5.10.3-amd64.tar" + "pull": "docker.io/grafana/grafana-image-renderer:v5.10.3", + "tag": "sealos.hub:5000/kube4/grafana-image-renderer:v5.10.3", + "tar": "grafana-image-renderer-v5.10.3-amd64.tar" }, { "arch": "amd64", @@ -177,9 +177,9 @@ { "arch": "arm64", "platform": "linux/arm64", - "pull": "docker.io/grafana/grafana-image-renderer:5.10.3", - "tag": "sealos.hub:5000/kube4/grafana-image-renderer:5.10.3", - "tar": "grafana-image-renderer-5.10.3-arm64.tar" + "pull": "docker.io/grafana/grafana-image-renderer:v5.10.3", + "tag": "sealos.hub:5000/kube4/grafana-image-renderer:v5.10.3", + "tar": "grafana-image-renderer-v5.10.3-arm64.tar" }, { "arch": "arm64", From 8603eb210520d15b3539e4f372bee3351113845c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 14 Sep 2026 09:28:03 +0800 Subject: [PATCH 14/14] docs: correct renderer tag and CRD upgrade flow --- docs/VERSION-BOM.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/VERSION-BOM.md b/docs/VERSION-BOM.md index 84b47ae..83b77da 100644 --- a/docs/VERSION-BOM.md +++ b/docs/VERSION-BOM.md @@ -18,7 +18,7 @@ Release target: `app_prometheus-stack v0.2.0` | k8s-sidecar | 2.11.2 | | kube-rbac-proxy | 0.22.1 | | kube-webhook-certgen | 1.8.8 | -| Grafana image renderer | 5.10.3 | +| Grafana image renderer | v5.10.3 | ## Build model @@ -28,7 +28,7 @@ embeds it into the `.run` package, and verifies the chart version before packagi ## Upgrade behavior -- First install: phase 1 installs CRDs/operator, phase 2 installs the complete stack. +- First install: bundled CRDs are applied first, phase 1 installs the operator, and phase 2 installs the complete stack. - Existing Helm release: bundled CRDs are applied with server-side apply, then a single full-stack Helm upgrade is executed. - `--skip-crd-upgrade` is available for controlled exception cases.