diff --git a/cmd/Dockerfile b/cmd/Dockerfile index 68ed73a1..701c5b1f 100644 --- a/cmd/Dockerfile +++ b/cmd/Dockerfile @@ -3,6 +3,7 @@ FROM busybox # Add user and group to support run it with non-root. ARG PINGCAP_UID=1000 ARG PINGCAP_GID=2000 +ENV RELOADER_UID=${PINGCAP_UID} RELOADER_GID=${PINGCAP_GID} RUN addgroup -g ${PINGCAP_GID} pingcap && \ adduser -u ${PINGCAP_UID} -G pingcap -h /home/pingcap -D pingcap diff --git a/cmd/datasource/k8s-datasource.yaml b/cmd/datasource/k8s-datasource.yaml index 9b8f9694..ea99ca8f 100644 --- a/cmd/datasource/k8s-datasource.yaml +++ b/cmd/datasource/k8s-datasource.yaml @@ -1,7 +1,7 @@ apiVersion: 1 datasources: - access: proxy - editable: true + editable: false name: k8s-cluster orgId: 1 type: prometheus diff --git a/cmd/dm_rules_test.go b/cmd/dm_rules_test.go index 6a913de9..1313be2b 100644 --- a/cmd/dm_rules_test.go +++ b/cmd/dm_rules_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io" "net/http" "os" @@ -17,6 +18,77 @@ import ( type dmTransport func(*http.Request) (*http.Response, error) +func TestInitializerPreparesRuleAccess(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("initializer requires POSIX permissions") + } + for _, source := range []string{"init.sh", "../platform-monitoring/operator/init.sh", "../monitor-snapshot/master/operator/init.sh"} { + t.Run(source, func(t *testing.T) { + script, err := os.ReadFile(source) + if err != nil { + t.Fatal(err) + } + prefix, _, ok := strings.Cut(string(script), "# Multiline replacements") + if !ok || !strings.Contains(prefix, "prepare_reloader_rules()") { + t.Fatal("initializer does not prepare reloader access") + } + dir := t.TempDir() + active := filepath.Join(dir, "active rules", "rules") + data := filepath.Join(dir, "data") + store := filepath.Join(data, "latest-rules", "tidb:test") + for _, path := range []string{active, store} { + if err := os.MkdirAll(path, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(path, "test.yml"), []byte("groups: []"), 0444); err != nil { + t.Fatal(err) + } + } + unrelated := filepath.Join(data, "prometheus") + if err := os.Mkdir(unrelated, 0700); err != nil { + t.Fatal(err) + } + run := func() error { + cmd := exec.Command("sh", "-c", prefix+"\nprepare_reloader_rules tidb:test") + cmd.Env = append(os.Environ(), "PROM_CONFIG_PATH="+filepath.Dir(active), "PROM_PERSISTENT_DIR="+data, + fmt.Sprintf("RELOADER_UID=%d", os.Geteuid()), fmt.Sprintf("RELOADER_GID=%d", os.Getegid())) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%w: %s", err, output) + } + return nil + } + if err := run(); err != nil { + t.Fatal(err) + } + for _, path := range []string{active, store} { + info, err := os.Stat(filepath.Join(path, "test.yml")) + if err != nil || info.Mode().Perm() != 0644 { + t.Fatalf("rule permissions: %v, %v", info, err) + } + } + info, err := os.Stat(unrelated) + if err != nil || info.Mode().Perm() != 0700 { + t.Fatal("unrelated data directory permissions changed", err) + } + outside := filepath.Join(dir, "outside.yml") + if err := os.WriteFile(outside, []byte("private"), 0400); err != nil { + t.Fatal(err) + } + if err := os.Symlink(outside, filepath.Join(active, "alias.yml")); err != nil { + t.Fatal(err) + } + if err := run(); err == nil { + t.Fatal("symbolic-link rule accepted") + } + info, err = os.Stat(outside) + if err != nil || info.Mode().Perm() != 0400 { + t.Fatal("permissions changed through symlink", err) + } + }) + } +} + func (f dmTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } func TestDMRulesAreGenerated(t *testing.T) { @@ -120,7 +192,7 @@ func TestDMInitializerRejectsMissingRules(t *testing.T) { } } cmd := exec.Command("sh", scriptPath) - cmd.Env = append(os.Environ(), "DM_CLUSTER_NAME=test", "GF_PROVISIONING_PATH="+filepath.Join(root, "grafana space"), "PROM_CONFIG_PATH="+filepath.Join(root, "prometheus space"), "PROM_PERSISTENT_DIR=", "GF_DATASOURCE_PATH="+datasource, "GF_DM_PROMETHEUS_URL=http://example.test/?a=1&b=2", "PATH="+extraPath+os.Getenv("PATH")) + cmd.Env = append(os.Environ(), fmt.Sprintf("RELOADER_UID=%d", os.Geteuid()), fmt.Sprintf("RELOADER_GID=%d", os.Getegid()), "DM_CLUSTER_NAME=test", "GF_PROVISIONING_PATH="+filepath.Join(root, "grafana space"), "PROM_CONFIG_PATH="+filepath.Join(root, "prometheus space"), "PROM_PERSISTENT_DIR=", "GF_DATASOURCE_PATH="+datasource, "GF_DM_PROMETHEUS_URL=http://example.test/?a=1&b=2", "PATH="+extraPath+os.Getenv("PATH")) out, err := cmd.CombinedOutput() if !hasRules && err == nil { t.Errorf("missing rules reported success: %s", out) @@ -148,7 +220,7 @@ func TestInitializerRejectsMultilineReplacement(t *testing.T) { t.Run(source, func(t *testing.T) { root := t.TempDir() cmd := exec.Command("sh", source) - cmd.Env = append(os.Environ(), "DM_CLUSTER_NAME=test", "GF_PROVISIONING_PATH="+filepath.Join(root, "grafana"), "PROM_CONFIG_PATH="+filepath.Join(root, "prometheus"), "GF_DM_PROMETHEUS_URL=bad\nreplacement") + cmd.Env = append(os.Environ(), fmt.Sprintf("RELOADER_UID=%d", os.Geteuid()), fmt.Sprintf("RELOADER_GID=%d", os.Getegid()), "DM_CLUSTER_NAME=test", "GF_PROVISIONING_PATH="+filepath.Join(root, "grafana"), "PROM_CONFIG_PATH="+filepath.Join(root, "prometheus"), "GF_DM_PROMETHEUS_URL=bad\nreplacement") out, err := cmd.CombinedOutput() if err == nil || !strings.Contains(string(out), "must be single-line") { t.Fatalf("multiline value not rejected: %v %s", err, out) @@ -224,7 +296,7 @@ func TestTiDBInitializerPreservesDatasourceURLs(t *testing.T) { cmd.Dir = root k8sURL := "http://example.test/?a=1&b=2,tail" tidbURL := "http://example.test/tidb?a=1&b=2" - cmd.Env = append(os.Environ(), "DM_CLUSTER_NAME=", "TIDB_CLUSTER_NAME=test", "TIDB_CLUSTER_NAMESPACE=default", "GF_PROVISIONING_PATH="+filepath.Join(root, "grafana space"), "PROM_CONFIG_PATH="+filepath.Join(root, "prometheus space"), "PROM_PERSISTENT_DIR=", "GF_DATASOURCE_PATH="+datasource, "GF_K8S_PROMETHEUS_URL="+k8sURL, "GF_TIDB_PROMETHEUS_URL="+tidbURL, "PATH="+extraPath+os.Getenv("PATH")) + cmd.Env = append(os.Environ(), fmt.Sprintf("RELOADER_UID=%d", os.Geteuid()), fmt.Sprintf("RELOADER_GID=%d", os.Getegid()), "DM_CLUSTER_NAME=", "TIDB_CLUSTER_NAME=test", "TIDB_CLUSTER_NAMESPACE=default", "GF_PROVISIONING_PATH="+filepath.Join(root, "grafana space"), "PROM_CONFIG_PATH="+filepath.Join(root, "prometheus space"), "PROM_PERSISTENT_DIR=", "GF_DATASOURCE_PATH="+datasource, "GF_K8S_PROMETHEUS_URL="+k8sURL, "GF_TIDB_PROMETHEUS_URL="+tidbURL, "PATH="+extraPath+os.Getenv("PATH")) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("TiDB initializer failed: %v %s", err, out) } diff --git a/cmd/init.sh b/cmd/init.sh index b15e7f47..7282c8d6 100644 --- a/cmd/init.sh +++ b/cmd/init.sh @@ -6,6 +6,48 @@ replace_template() { sed -i "s|$1|$replacement|g" -- "$3" } +# Run after copying/restoring rules on every Pod initialization. Do not chown /data. +prepare_reloader_rules() { + reloader_uid=${RELOADER_UID:-1000} + reloader_gid=${RELOADER_GID:-2000} + for reloader_id in "$reloader_uid" "$reloader_gid"; do + case "$reloader_id" in + ''|*[!0-9]*) echo "Reloader UID/GID must be numeric" >&2; return 1;; + esac + done + reloader_version=$1 + reloader_store= + set -- "$PROM_CONFIG_PATH/rules" + if [ -n "$PROM_PERSISTENT_DIR" ]; then + case "$reloader_version" in + ''|.|..) echo "Invalid persistent rules version" >&2; return 1;; + esac + reloader_store="$PROM_PERSISTENT_DIR/latest-rules/$reloader_version" + set -- "$@" "$PROM_PERSISTENT_DIR/latest-rules" "$reloader_store" + fi + for reloader_dir do + if [ -L "$reloader_dir" ] || { [ -e "$reloader_dir" ] && [ ! -d "$reloader_dir" ]; }; then + echo "Rules path must be a directory, not a symlink: $reloader_dir" >&2 + return 1 + fi + mkdir -p -- "$reloader_dir" && + chown "$reloader_uid:$reloader_gid" "$reloader_dir" && + chmod u+rwx,go-w "$reloader_dir" || return 1 + done + for reloader_dir in "$PROM_CONFIG_PATH/rules" "$reloader_store"; do + [ -n "$reloader_dir" ] || continue + for reloader_file in "$reloader_dir"/*.yml; do + [ -e "$reloader_file" ] || [ -L "$reloader_file" ] || continue + if [ -L "$reloader_file" ] || [ ! -f "$reloader_file" ]; then + echo "Rule must be a regular file: $reloader_file" >&2 + return 1 + fi + chown "$reloader_uid:$reloader_gid" "$reloader_file" && + chmod u+rw,go-w,a-x "$reloader_file" || return 1 + done + done +} + # Multiline replacements would become new sed commands or malformed configuration. for value in "$DM_CLUSTER_NAME" "$TIDB_CLUSTER_NAME" "$TIDB_CLUSTER_NAMESPACE" \ "$GF_DM_PROMETHEUS_URL" "$GF_K8S_PROMETHEUS_URL" "$GF_TIDB_PROMETHEUS_URL"; do @@ -61,6 +103,7 @@ if [ -n "$DM_CLUSTER_NAME" ]; then fi cp -f -- /tmp/dm-cluster-datasource.yaml "$GF_DATASOURCE_PATH/" fi + prepare_reloader_rules "${DM_VERSION##*/}" || exit 1 echo "running for DM done" exit fi @@ -176,6 +219,8 @@ if [ -n "$PROM_PERSISTENT_DIR" ]; then fi # Datasources +prepare_reloader_rules "${TIDB_VERSION##*/}" || exit 1 + if [ -n "$GF_DATASOURCE_PATH" ]; then if [ -n "$GF_K8S_PROMETHEUS_URL" ]; then replace_template 'http://prometheus-k8s.monitoring.svc:9090' "${GF_K8S_PROMETHEUS_URL}" /tmp/k8s-datasource.yaml diff --git a/monitor-snapshot/master/operator/Dockerfile b/monitor-snapshot/master/operator/Dockerfile index 08314550..57bb4729 100644 --- a/monitor-snapshot/master/operator/Dockerfile +++ b/monitor-snapshot/master/operator/Dockerfile @@ -3,6 +3,7 @@ FROM busybox:1.37.0 # Add user and group to support run it with non-root. ARG PINGCAP_UID=1000 ARG PINGCAP_GID=2000 +ENV RELOADER_UID=${PINGCAP_UID} RELOADER_GID=${PINGCAP_GID} RUN addgroup -g ${PINGCAP_GID} pingcap && \ adduser -u ${PINGCAP_UID} -G pingcap -h /home/pingcap -D pingcap diff --git a/monitor-snapshot/master/operator/datasources/k8s-datasource.yaml b/monitor-snapshot/master/operator/datasources/k8s-datasource.yaml index 9b8f9694..ea99ca8f 100644 --- a/monitor-snapshot/master/operator/datasources/k8s-datasource.yaml +++ b/monitor-snapshot/master/operator/datasources/k8s-datasource.yaml @@ -1,7 +1,7 @@ apiVersion: 1 datasources: - access: proxy - editable: true + editable: false name: k8s-cluster orgId: 1 type: prometheus diff --git a/monitor-snapshot/master/operator/init.sh b/monitor-snapshot/master/operator/init.sh index a9774e9f..b10a4b35 100755 --- a/monitor-snapshot/master/operator/init.sh +++ b/monitor-snapshot/master/operator/init.sh @@ -6,6 +6,48 @@ replace_template() { sed -i "s|$1|$replacement|g" -- "$3" } +# Run after copying/restoring rules on every Pod initialization. Do not chown /data. +prepare_reloader_rules() { + reloader_uid=${RELOADER_UID:-1000} + reloader_gid=${RELOADER_GID:-2000} + for reloader_id in "$reloader_uid" "$reloader_gid"; do + case "$reloader_id" in + ''|*[!0-9]*) echo "Reloader UID/GID must be numeric" >&2; return 1;; + esac + done + reloader_version=$1 + reloader_store= + set -- "$PROM_CONFIG_PATH/rules" + if [ -n "$PROM_PERSISTENT_DIR" ]; then + case "$reloader_version" in + ''|.|..) echo "Invalid persistent rules version" >&2; return 1;; + esac + reloader_store="$PROM_PERSISTENT_DIR/latest-rules/$reloader_version" + set -- "$@" "$PROM_PERSISTENT_DIR/latest-rules" "$reloader_store" + fi + for reloader_dir do + if [ -L "$reloader_dir" ] || { [ -e "$reloader_dir" ] && [ ! -d "$reloader_dir" ]; }; then + echo "Rules path must be a directory, not a symlink: $reloader_dir" >&2 + return 1 + fi + mkdir -p -- "$reloader_dir" && + chown "$reloader_uid:$reloader_gid" "$reloader_dir" && + chmod u+rwx,go-w "$reloader_dir" || return 1 + done + for reloader_dir in "$PROM_CONFIG_PATH/rules" "$reloader_store"; do + [ -n "$reloader_dir" ] || continue + for reloader_file in "$reloader_dir"/*.yml; do + [ -e "$reloader_file" ] || [ -L "$reloader_file" ] || continue + if [ -L "$reloader_file" ] || [ ! -f "$reloader_file" ]; then + echo "Rule must be a regular file: $reloader_file" >&2 + return 1 + fi + chown "$reloader_uid:$reloader_gid" "$reloader_file" && + chmod u+rw,go-w,a-x "$reloader_file" || return 1 + done + done +} + # Multiline replacements would become new sed commands or malformed configuration. for value in "$DM_CLUSTER_NAME" "$TIDB_CLUSTER_NAME" "$TIDB_CLUSTER_NAMESPACE" \ "$GF_DM_PROMETHEUS_URL" "$GF_K8S_PROMETHEUS_URL" "$GF_TIDB_PROMETHEUS_URL"; do @@ -61,6 +103,7 @@ if [ -n "$DM_CLUSTER_NAME" ]; then fi cp -f -- /tmp/dm-cluster-datasource.yaml "$GF_DATASOURCE_PATH/" fi + prepare_reloader_rules "${DM_VERSION##*/}" || exit 1 echo "running for DM done" exit fi @@ -184,6 +227,8 @@ if [ -n "$PROM_PERSISTENT_DIR" ]; then fi # Datasources +prepare_reloader_rules "${TIDB_VERSION##*/}" || exit 1 + if [ -n "$GF_DATASOURCE_PATH" ]; then if [ -n "$GF_K8S_PROMETHEUS_URL" ]; then replace_template 'http://prometheus-k8s.monitoring.svc:9090' "${GF_K8S_PROMETHEUS_URL}" /tmp/k8s-datasource.yaml diff --git a/platform-monitoring/operator/Dockerfile b/platform-monitoring/operator/Dockerfile index 08314550..57bb4729 100644 --- a/platform-monitoring/operator/Dockerfile +++ b/platform-monitoring/operator/Dockerfile @@ -3,6 +3,7 @@ FROM busybox:1.37.0 # Add user and group to support run it with non-root. ARG PINGCAP_UID=1000 ARG PINGCAP_GID=2000 +ENV RELOADER_UID=${PINGCAP_UID} RELOADER_GID=${PINGCAP_GID} RUN addgroup -g ${PINGCAP_GID} pingcap && \ adduser -u ${PINGCAP_UID} -G pingcap -h /home/pingcap -D pingcap diff --git a/platform-monitoring/operator/Dockerfile.compat b/platform-monitoring/operator/Dockerfile.compat new file mode 100644 index 00000000..0a07173c --- /dev/null +++ b/platform-monitoring/operator/Dockerfile.compat @@ -0,0 +1,28 @@ +# Build from the exact deployed initializer digest, preserving its asset lineage. +ARG INITIALIZER_IMAGE +FROM ${INITIALIZER_IMAGE} + +USER 0:0 +ARG PINGCAP_UID=1000 +ARG PINGCAP_GID=2000 +ENV RELOADER_UID=${PINGCAP_UID} RELOADER_GID=${PINGCAP_GID} + +# Keep the original initializer logic paired with its original rule/dashboard assets. +RUN cp /usr/bin/init.sh /usr/bin/init-before-nonroot.sh +COPY platform-monitoring/operator/init.sh /tmp/nonroot-init.sh +RUN grep -q '^# Multiline replacements' /tmp/nonroot-init.sh && \ + awk '/^# Multiline replacements/ {exit} {print}' /tmp/nonroot-init.sh > /usr/bin/init.sh && \ + printf '%s\n' \ + '/usr/bin/init-before-nonroot.sh "$@" || exit $?' \ + 'if [ -n "${DM_CLUSTER_NAME:-}" ]; then' \ + ' prepare_reloader_rules "${DM_VERSION##*/}"' \ + 'else' \ + ' prepare_reloader_rules "${TIDB_VERSION##*/}"' \ + 'fi' >> /usr/bin/init.sh && \ + chmod 0755 /usr/bin/init.sh && \ + rm /tmp/nonroot-init.sh + +# Apply the datasource change without replacing version-specific rules/dashboards. +RUN if [ -f /tmp/k8s-datasource.yaml ]; then \ + sed -i 's/^\([[:space:]]*editable:[[:space:]]*\)true[[:space:]]*$/\1false/' /tmp/k8s-datasource.yaml; \ + fi diff --git a/platform-monitoring/operator/datasource/k8s-datasource.yaml b/platform-monitoring/operator/datasource/k8s-datasource.yaml index 9b8f9694..ea99ca8f 100644 --- a/platform-monitoring/operator/datasource/k8s-datasource.yaml +++ b/platform-monitoring/operator/datasource/k8s-datasource.yaml @@ -1,7 +1,7 @@ apiVersion: 1 datasources: - access: proxy - editable: true + editable: false name: k8s-cluster orgId: 1 type: prometheus diff --git a/platform-monitoring/operator/init.sh b/platform-monitoring/operator/init.sh index a9774e9f..b10a4b35 100755 --- a/platform-monitoring/operator/init.sh +++ b/platform-monitoring/operator/init.sh @@ -6,6 +6,48 @@ replace_template() { sed -i "s|$1|$replacement|g" -- "$3" } +# Run after copying/restoring rules on every Pod initialization. Do not chown /data. +prepare_reloader_rules() { + reloader_uid=${RELOADER_UID:-1000} + reloader_gid=${RELOADER_GID:-2000} + for reloader_id in "$reloader_uid" "$reloader_gid"; do + case "$reloader_id" in + ''|*[!0-9]*) echo "Reloader UID/GID must be numeric" >&2; return 1;; + esac + done + reloader_version=$1 + reloader_store= + set -- "$PROM_CONFIG_PATH/rules" + if [ -n "$PROM_PERSISTENT_DIR" ]; then + case "$reloader_version" in + ''|.|..) echo "Invalid persistent rules version" >&2; return 1;; + esac + reloader_store="$PROM_PERSISTENT_DIR/latest-rules/$reloader_version" + set -- "$@" "$PROM_PERSISTENT_DIR/latest-rules" "$reloader_store" + fi + for reloader_dir do + if [ -L "$reloader_dir" ] || { [ -e "$reloader_dir" ] && [ ! -d "$reloader_dir" ]; }; then + echo "Rules path must be a directory, not a symlink: $reloader_dir" >&2 + return 1 + fi + mkdir -p -- "$reloader_dir" && + chown "$reloader_uid:$reloader_gid" "$reloader_dir" && + chmod u+rwx,go-w "$reloader_dir" || return 1 + done + for reloader_dir in "$PROM_CONFIG_PATH/rules" "$reloader_store"; do + [ -n "$reloader_dir" ] || continue + for reloader_file in "$reloader_dir"/*.yml; do + [ -e "$reloader_file" ] || [ -L "$reloader_file" ] || continue + if [ -L "$reloader_file" ] || [ ! -f "$reloader_file" ]; then + echo "Rule must be a regular file: $reloader_file" >&2 + return 1 + fi + chown "$reloader_uid:$reloader_gid" "$reloader_file" && + chmod u+rw,go-w,a-x "$reloader_file" || return 1 + done + done +} + # Multiline replacements would become new sed commands or malformed configuration. for value in "$DM_CLUSTER_NAME" "$TIDB_CLUSTER_NAME" "$TIDB_CLUSTER_NAMESPACE" \ "$GF_DM_PROMETHEUS_URL" "$GF_K8S_PROMETHEUS_URL" "$GF_TIDB_PROMETHEUS_URL"; do @@ -61,6 +103,7 @@ if [ -n "$DM_CLUSTER_NAME" ]; then fi cp -f -- /tmp/dm-cluster-datasource.yaml "$GF_DATASOURCE_PATH/" fi + prepare_reloader_rules "${DM_VERSION##*/}" || exit 1 echo "running for DM done" exit fi @@ -184,6 +227,8 @@ if [ -n "$PROM_PERSISTENT_DIR" ]; then fi # Datasources +prepare_reloader_rules "${TIDB_VERSION##*/}" || exit 1 + if [ -n "$GF_DATASOURCE_PATH" ]; then if [ -n "$GF_K8S_PROMETHEUS_URL" ]; then replace_template 'http://prometheus-k8s.monitoring.svc:9090' "${GF_K8S_PROMETHEUS_URL}" /tmp/k8s-datasource.yaml diff --git a/reload/CONTAINER_SECURITY.md b/reload/CONTAINER_SECURITY.md new file mode 100644 index 00000000..9a5cf11a --- /dev/null +++ b/reload/CONTAINER_SECURITY.md @@ -0,0 +1,111 @@ +# Reloader container identity and migration + +Both reloader image recipes run the service as the existing `pingcap` user, +UID 1000 and GID 2000 by default (build arguments can select different numeric +IDs). The initializer remains a separate root init container so it can prepare +the shared rule volumes before the non-root service starts. + +**Upgrade the initializer and reloader images together.** Merely replacing the +reloader image leaves root-owned files from the old initializer unwritable, +including on fresh volumes. In the same `TidbMonitor` update, select images built +from these changes in both `spec.initializer` and `spec.reloader`. For DM, also +update `spec.dm.initializer`. Retain the +authentication/TLS configuration required by the secured reloader. + +## Preserve the existing rules version when upgrading + +An unchanged Operator defaults the saved-rules directory to +`tidb:`. Changing that tag can select an empty directory +and stop restoring custom rules. For an existing deployment, keep the initializer +version unchanged and use a new image repository containing its compatible build: + +```yaml +spec: + initializer: + baseImage: /tidb-monitor-initializer-compatible + version: v8.5.7 # Keep the deployment's original value; this is an example. +``` + +Use `platform-monitoring/operator/Dockerfile.compat` to build from the exact +deployed initializer digest. It preserves that image's rules, dashboards and +original initialization logic, then prepares their ownership after initialization +succeeds. Do not rebuild an old version from current master assets or overwrite +an already-published image tag. Publish under a new repository and the original +tag so both the image reference and its version lineage are unambiguous: + +```bash +docker build --file platform-monitoring/operator/Dockerfile.compat \ + --build-arg INITIALIZER_IMAGE='@sha256:' \ + --tag '/tidb-monitor-initializer-compatible:v8.5.7' . +``` + +Build and publish each required original version separately. The command above +only builds locally. For DM, apply the corresponding compatible image to +`spec.dm.initializer` and retain its original version as well; the shared rules +directory still follows the top-level initializer version. Keep any explicitly +configured `spec.alertManagerRulesVersion` unchanged. This path requires no +Operator change, PVC directory scan, rule copying between versions or automatic +selection among historical directories. + +If changing `spec.initializer.version` intentionally, preserve the current rule +directory by retaining the existing explicit `alertManagerRulesVersion`, or set +it to the old top-level initializer version when it was previously unset. Do not +include the `tidb:` prefix in that field. Back up rules before changing versions. + +## Runtime permissions + +After generating rules and restoring saved rules, both the TiDB and DM +initializer paths prepare the active rules directory and the current version's +`/data/latest-rules/` directory and files for the reloader UID/GID. +This runs on every Pod initialization, so a rebuilt rules `emptyDir` remains +editable and existing root-owned persisted rules are migrated. Only the rules +directories and their regular `.yml` files are changed, not `/data/prometheus`, +`/data/grafana` or the entire data volume. Symbolic-link rule files and dedicated +rules directories are rejected; ownership or permission errors fail initialization. +Existing group/other read bits are retained, while group/other write and file +execute bits are removed. Back up custom rules before upgrading. + +For TiDB Operator v1, the existing `spec.podSecurityContext` and +`spec.reloader.securityContext` can configure this without replacing generated +commands: + +```yaml +spec: + initializer: + baseImage: /tidb-monitor-initializer-compatible + version: v8.5.7 # Keep the existing initializer version. + envs: + RELOADER_UID: "1000" + RELOADER_GID: "2000" + reloader: + baseImage: /tidb-monitor-reloader + version: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 2000 + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] +``` + +Merge this fragment with the complete existing CR. The initializer environment +defaults to UID 1000/GID 2000 (or its build arguments); set these two variables +to match any custom reloader `runAsUser`/`runAsGroup`. DM's initializer image and +`envs` belong under `spec.dm.initializer`, not `spec.initializer`; when monitoring +both TiDB and DM, configure both initializers to match the shared reloader. +An `fsGroup` alone does +not prepare files created later by the initializer and is not a substitute. + +Storage must allow the initializer to set ownership of these dedicated paths. +Storage that denies these operations (for example, root-squashed NFS) requires +a storage-specific initialization strategy; this initializer fails rather than +starting an unwritable reloader. Verify initialization before upgrading. +Do not make files world writable or recursively change unrelated data as a +workaround. Verify a real save/reload and persisted content after deleting and +recreating a monitor Pod, in addition to checking readiness. This image change +does not modify already-running workloads or override an explicit runtime identity. + +Current k8s datasource provisioning is also non-editable in Grafana, consistent +with the sibling datasource definitions. Change the reviewed provisioning source +and redeploy to update its URL or credentials. diff --git a/reload/Dockerfile b/reload/Dockerfile index 13c7e065..35c0c845 100644 --- a/reload/Dockerfile +++ b/reload/Dockerfile @@ -7,5 +7,7 @@ RUN addgroup -g ${PINGCAP_GID} pingcap && \ adduser -u ${PINGCAP_UID} -G pingcap -h /home/pingcap -D pingcap COPY --chmod=755 ./build/linux/reload /bin/reload +USER ${PINGCAP_UID}:${PINGCAP_GID} + ENTRYPOINT ["/bin/reload"] CMD ["--watch-path=/etc/prometheus/rules", "--prometheus-url=http://127.0.0.1:9090"] diff --git a/reload/Dockerfile_buildx b/reload/Dockerfile_buildx index 46318749..4adf122e 100644 --- a/reload/Dockerfile_buildx +++ b/reload/Dockerfile_buildx @@ -13,6 +13,8 @@ RUN addgroup -g ${PINGCAP_GID} pingcap && \ adduser -u ${PINGCAP_UID} -G pingcap -h /home/pingcap -D pingcap COPY --from=builder --chmod=755 /app/main /bin/reload +USER ${PINGCAP_UID}:${PINGCAP_GID} + ENTRYPOINT [ "/bin/reload" ] CMD [ "--watch-path=/etc/prometheus/rules", \ "--prometheus-url=http://127.0.0.1:9090" ]