diff --git a/.github/workflows/build-offline-installer.yml b/.github/workflows/build-offline-installer.yml index 99f5cac..0a14468 100644 --- a/.github/workflows/build-offline-installer.yml +++ b/.github/workflows/build-offline-installer.yml @@ -12,25 +12,54 @@ on: - ".github/workflows/build-offline-installer.yml" - "build.sh" - "install.sh" + - "install-v2.sh" + - "versions.env" - "README.md" - "docs/**" - "images/**" - - "charts/**" pull_request: paths: - ".github/workflows/build-offline-installer.yml" - "build.sh" - "install.sh" + - "install-v2.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 install-v2.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-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 runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -49,12 +78,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 + run: chmod +x build.sh install.sh install-v2.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: diff --git a/build.sh b/build.sh index 7a8b284..e69d6c2 100644 --- a/build.sh +++ b/build.sh @@ -3,15 +3,18 @@ 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" 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_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 "${INSTALLER_TEMPLATE}" ]] || die "install-v2.sh is missing" [[ -f "${IMAGE_JSON}" ]] || die "images/image.json is missing" - [[ -d "${CHART_SRC_DIR}" ]] || die "charts/kube-prometheus-stack is missing" - grep -q '^__PAYLOAD_BELOW__$' "${INSTALLER_TEMPLATE}" || die "install.sh is missing __PAYLOAD_BELOW__ marker" + [[ -f "${ROOT_DIR}/versions.env" ]] || die "versions.env is missing" + grep -q '^__PAYLOAD_BELOW__$' "${INSTALLER_TEMPLATE}" || die "install-v2.sh is missing __PAYLOAD_BELOW__ marker" } 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() { + 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 '/^version:/ {print $2; exit}' "${chart_file}")" + [[ "${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() { @@ -162,9 +183,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 +191,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 +217,8 @@ build_one() { PLATFORM="${platform}" prepare_directories + prepare_chart + prepare_crds prepare_images "${arch}" "${platform}" package_payload "${arch}" show_result "${arch}" diff --git a/docs/VERSION-BOM.md b/docs/VERSION-BOM.md new file mode 100644 index 0000000..83b77da --- /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 | v5.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: 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. + +## 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. diff --git a/images/image.json b/images/image.json index 4a7626e..71b4b98 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:v5.10.3", + "tag": "sealos.hub:5000/kube4/grafana-image-renderer:v5.10.3", + "tar": "grafana-image-renderer-v5.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:v5.10.3", + "tag": "sealos.hub:5000/kube4/grafana-image-renderer:v5.10.3", + "tar": "grafana-image-renderer-v5.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" } ] diff --git a/install-v2.sh b/install-v2.sh new file mode 100644 index 0000000..e2c46ce --- /dev/null +++ b/install-v2.sh @@ -0,0 +1,802 @@ +#!/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" +CRD_DIR="${WORKDIR}/crds" +IMAGE_DIR="${WORKDIR}/images" +IMAGE_INDEX="${IMAGE_DIR}/image-index.tsv" +VALUES_FILE="${WORKDIR}/values-generated.yaml" +PHASE1_FILE="${WORKDIR}/values-phase1.yaml" + +ACTION="help" +RELEASE="prometheus-stack" +NAMESPACE="monitoring" +WAIT_TIMEOUT="10m" +AUTO_YES="false" + +REGISTRY="sealos.hub:5000/kube4" +REGISTRY_EXPLICIT="false" +REGISTRY_USER="admin" +REGISTRY_PASSWORD="passw0rd" +REGISTRY_SECRET="" +IMAGE_PULL_POLICY="IfNotPresent" +SKIP_IMAGE_PREPARE="false" +SKIP_CRD_UPGRADE="false" +DELETE_CRDS="false" + +PROMETHEUS_STORAGE_CLASS="nfs" +PROMETHEUS_STORAGE_SIZE="200Gi" +PROMETHEUS_STORAGE_ACCESS_MODE="ReadWriteOnce" +PROMETHEUS_RETENTION="14d" +PROMETHEUS_RETENTION_SIZE="30GiB" +PROMETHEUS_REPLICAS="1" +PROMETHEUS_SERVICE_TYPE="NodePort" +PROMETHEUS_NODE_PORT="30091" + +ALERTMANAGER_STORAGE_CLASS="nfs" +ALERTMANAGER_STORAGE_SIZE="10Gi" +ALERTMANAGER_STORAGE_ACCESS_MODE="ReadWriteOnce" +ALERTMANAGER_REPLICAS="1" +ALERTMANAGER_CONFIG_FILE="" +ENABLE_ALERTMANAGER="true" + +GRAFANA_STORAGE_CLASS="nfs" +GRAFANA_STORAGE_SIZE="10Gi" +GRAFANA_STORAGE_ACCESS_MODE="ReadWriteOnce" +GRAFANA_REPLICAS="1" +GRAFANA_ADMIN_USER="admin" +GRAFANA_ADMIN_PASSWORD="admin@passw0rd" +GRAFANA_SERVICE_TYPE="NodePort" +GRAFANA_NODE_PORT="30090" +ENABLE_GRAFANA="true" + +ENABLE_DEFAULT_RULES="true" +ENABLE_KUBE_STATE_METRICS="true" +ENABLE_NODE_EXPORTER="true" +STACK_LABEL_KEY="monitoring.archinfra.io/stack" +STACK_LABEL_VALUE="default" +DASHBOARD_LABEL_KEY="grafana_dashboard" +DASHBOARD_LABEL_VALUE="1" +DASHBOARD_FOLDER_ANNOTATION="grafana_folder" +DASHBOARD_SEARCH_NAMESPACE="ALL" + +EXTRA_VALUES_FILES=() +HELM_ARGS=() +PAYLOAD_OFFSET="" + +declare -A IMAGES=() + +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 +) + +log() { echo "[INFO] $*"; } +ok() { echo "[OK] $*"; } +warn() { echo "[WARN] $*" >&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} -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: ${NAMESPACE}] + --release-name [default: ${RELEASE}] + --wait-timeout [default: ${WAIT_TIMEOUT}] + --values-file Extra Helm values file; repeatable [default: none] + +Prometheus: + --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: ${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_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: ${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 Do not apply bundled CRDs before upgrade [default: false] + --delete-crds Delete monitoring CRDs during uninstall [default: false] + +Other: + -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 +} + +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) 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_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" +} + +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}" +} + +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 +} + +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" +} + +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 +} + +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_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}" +} + +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_pull_secret_values() { + if [[ -n "$REGISTRY_SECRET" ]]; then + cat < "$VALUES_FILE" < "$PHASE1_FILE" </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_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_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 Helm upgrade" + apply_bundled_crds + wait_for_crds + helm_run upgrade + else + 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 "$NAMESPACE" -l "app.kubernetes.io/instance=${RELEASE}" || true + ok "Prometheus Stack ${STACK_VERSION} install/upgrade completed" +} + +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 +} + +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 +} + +cleanup() { rm -rf "$WORKDIR"; } +trap cleanup EXIT + +main() { + parse_args "$@" + validate_args + + case "$ACTION" in + help) usage ;; + install) check_deps; confirm; install_stack ;; + uninstall) check_deps; confirm; uninstall_stack ;; + status) check_deps; show_status ;; + *) die "Unsupported action: ${ACTION}" ;; + esac +} + +main "$@" +exit 0 + +__PAYLOAD_BELOW__ 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" "$@" 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