Пример копирования из старого VMetrics-кластера в новый с изменением номеров тенантов

1. Заметки

  1. В старом кластере метрики различных команд хранились в тенантах без учёта номера проекта, то есть одна команда могла занимать несколько номеров vm_account_id. В парадигме VMetrics, где в случае пропуска номера проекта в номере тенанта, считается, что номер проекта равен нулю.

    Предположим, что «пятая» команда занимает своими метриками в «старом» VMetrics-кластере тенанты с номерами 12, 13, 14. Это означает, что команда занимает тенанты 12:0, 13:0, 14:0.

  2. В новом кластере «пятой» команде выдлелен лишь один номер vm_account-id, предположим, что это номер 5. В пределах этого номера аккаунта, команда «пять» может занять все 32 бита vm_project_id. То есть для технический метрик выделить тенант 5:1. Для каких-либо бизнес-метрик тенанты 5:130, 5:131, 5:132. И т.д.

2. Подготовка к запуску скрипта

  1. В переменных скрипта START_MONTH и END_MONTH поменяйте даты начального месяца и последнего месяца копируемого диапазона. При желании скрипт можно переделать на использование в диапазоне дней, часов, и т.д.

  2. В переменной ARGS_LIST отредактируйте записи в массиве в виде:
    «номер исходного тенанта в старом кластере»
    «номер целевого аккаунта»
    «номер целевого проекта»

  3. Помните, что метрики храняться чанками, поэтому при попадании какой-либо метрики в, например, последнюю минуту желаемого диапазона, будет скопирован весь чанк, то есть в новом кластере можно будет увидеть метрики, дата которых выходят за границы скопированного диапазона. Я наблюдал +несколько часов.

  4. В результате копирования, в новом VMetrics-кластере могут появиться дублирующие записи. Для решения этой проблемы включите в новом VMetrics-кластере опцию дедупликации равной 1ms и на vmstorage’ах, и на vmselect’ах.

3. Запуск скрипта

Я выполнял запуск с помощью команды:

unbuffer ./vmctl-copying-16.sh | tee script.$(date +%y%m%d-%H%M%S).log

4. Содержимое скрипта

Details
#!/bin/bash
exec 3>script.$(date +%y%m%d-%H%M%S).stderr.log
BASH_XTRACEFD=3

set -xeuo pipefail

# -------------------------------
# Constants
# -------------------------------

DST_ADDRESS='https://mon-vmauth-vip.example.org'
DST_BEARER_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxx'

START_MONTH="2024-09"
END_MONTH="2024-12"
DEFAULT_CHUNK="month"

LOG_FILE="vmctl-migration.$(date +%y%m%d-%H%M%S).log"
DRY_RUN="${DRY_RUN:-false}"

ARGS_LIST=(
  "0 0 1"
  "1 2 1"
  "2 2 2"
  "3 2 3"
  "4 2 4"
  "5 13 1"
  "6 7 1"
  "7 3 1000"
  "8 1 1"
  "9 9 1"
  "21 7 0"
)

# -------------------------------
# Logging
# -------------------------------

log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_FILE"
}

log_hr() {
  echo "--------------------------------------------------------------------------------" | tee -a "$LOG_FILE"
}

log_section() {
  echo "================================================================================" | tee -a "$LOG_FILE"
}

log_err() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}

# -------------------------------
# HTTP helper
# -------------------------------

safe_curl() {
  local url="$1"
  log_err "CMD: curl -sf \"$url\""
  local result
  if ! result=$(curl -sf "$url"); then
    log_err "❌ curl error for: $url"
    return 1
  fi
  log_err "RESP: $result"
  printf '%s' "$result"
}

# -------------------------------
# Count check (no filters allowed)
# -------------------------------

check_series_count() {
  local src_address=$1
  local url="${src_address}/api/v1/series/count"

  log "Checking series count: $url"
  local response count
  if ! response=$(safe_curl "$url"); then
    log "⚠ Cannot fetch series count, skipping full-tenant copy"
    return 1
  fi

  if ! count=$(echo "$response" | jq -r '.data[]' 2>/dev/null); then
    log "⚠ Cannot parse series count, skipping full-tenant copy"
    return 1
  fi

  if [[ -z "$count" ]]; then
    log "⚠ Empty series count, skipping full-tenant copy"
    return 1
  fi

  log "Series count: $count"
  if (( count >= 1000000 )); then
    log "⚠ Too many series ($count), skipping full-tenant copy"
    return 1
  fi

  return 0
}

# -------------------------------
# Run vmctl
# -------------------------------

run_vmctl() {
  local src=$1 dst=$2 tok=$3 acc=$4 prj=$5
  local ts=$6 te=$7 filt=$8 step=$9

  log_hr
  log "▶ vmctl: step=${step}, filter=${filt:-<none>}"
  if [ "$DRY_RUN" = "true" ]; then
    log "(dry-run) skipping vmctl"
    return 0
  fi

  if ! vmctl-prod vm-native -s \
       -vm-native-backoff-retries 1 \
       -vm-native-src-addr "$src" \
       -vm-native-dst-addr "$dst" \
       -vm-native-dst-bearer-token "$tok" \
       -vm-extra-label "vm_account_id=${acc}" \
       -vm-extra-label "vm_project_id=${prj}" \
       -vm-native-filter-time-start "$ts" \
       -vm-native-filter-time-end "$te" \
       -vm-native-step-interval="$step" \
       ${filt:+-vm-native-filter-match "$filt"}; then
    log "❌ vmctl failed"
    return 1
  fi

  log "✔ vmctl succeeded"
  return 0
}

# -------------------------------
# Metadata fetcher: metrics only
# -------------------------------

fetch_metric_names_for_range() {
  local src=$1 start_ts=$2 end_ts=$3
  local url="${src}/api/v1/label/__name__/values?start=${start_ts}&end=${end_ts}"
  log_err "Fetch metrics via: $url"
  local resp
  resp=$(safe_curl "$url") || return 1
  echo "$resp" | jq -r '.data[]' 2>/dev/null | sort -u
}

# -------------------------------
# Main
# -------------------------------

for entry in "${ARGS_LIST[@]}"; do
  read -r SRC_ID DST_ID PRJ_ID <<< "$entry"
  SRC="http://127.0.0.1:8000/select/${SRC_ID}/prometheus"

  TIME_START="${START_MONTH}-01T00:00:00+03:00"
  TIME_END=$(date -d "$(date -d "${END_MONTH}-01 +1 month" +%Y-%m-%d) -1 sec" +%Y-%m-%dT%H:%M:%S%:z)
  TS_START=$(date -d "$TIME_START" +%s)
  TS_END=$(date -d "$TIME_END" +%s)

  log_section
  log "TENANT ${SRC_ID} → ${DST_ID}:${PRJ_ID}"

  # 1) Full-tenant copy
  if check_series_count "$SRC"; then
    run_vmctl "$SRC" "$DST_ADDRESS" "$DST_BEARER_TOKEN" "$DST_ID" "$PRJ_ID" \
      "$TIME_START" "$TIME_END" "" "month" && continue
  else
    log "⏭ skip full-tenant"
  fi

  log_hr
  log "▶ per-metric copy"

  # 2) Fetch metrics list
  METRICS=$(fetch_metric_names_for_range "$SRC" "$TS_START" "$TS_END") || continue

  for METRIC in $METRICS; do
    FILTER="{__name__=\"$METRIC\"}"
    log_hr
    log "Metric: $METRIC"

    # 3) Try steps in sequence
    for STEP in month week day hour minute; do
      if run_vmctl "$SRC" "$DST_ADDRESS" "$DST_BEARER_TOKEN" "$DST_ID" "$PRJ_ID" \
          "$TIME_START" "$TIME_END" "$FILTER" "$STEP"; then
        # success at this granularity, go to next metric
        continue 2
      fi
    done

    # 4) All steps failed
    log "❌ Failed to migrate: $METRIC"
  done
done

set +x
exec 3>&-