refactor(smoke): ограничен по времени опрос keeper, уточнены формулировки

- Зачем:
  - горячее ревью: перевезённая проверка keeper потеряла привычку файла
    ограничивать обращения к контейнерам по времени и глушить их ошибки.
- Что:
  - опрос keeper идёт через keeper_exec с timeout 20s и тихим stderr,
    в отчёте об отказе пустой ответ назван словами.
  - комментарий к проверке и абзац README переписаны на проверяемое
    утверждение: настройки объявлены в compose.yaml, смоук спрашивает,
    дошли ли они до процесса.
  - в пробнике ClickHouse ожидаемой строке возвращено имя expected_rows.
- Проверка:
  - make config-test, make smoke, make smoke-cluster; отдельно проверено,
    что на паузе keeper проверка краснеет, а не виснет.
This commit is contained in:
2026-08-06 14:01:45 +03:00
parent 66490d124e
commit a2f5b27d89
3 changed files with 29 additions and 22 deletions
+10 -9
View File
@@ -64,8 +64,8 @@ Superset, создаёт администратора и импортирует
`make smoke` проверяет зависимости машины, здоровье контейнеров, устройство `make smoke` проверяет зависимости машины, здоровье контейнеров, устройство
keeper, Kafka через порт машины, три цели Prometheus, источник Grafana, keeper, Kafka через порт машины, три цели Prometheus, источник Grafana,
компоненты Airflow, ручной запуск пробников `test_clickhouse` компоненты Airflow, ручной запуск пробников `test_clickhouse` и `test_kafka`,
и `test_kafka`, метаданные и подключение Superset. Первый пробник создаёт метаданные и подключение Superset. Первый пробник создаёт
таблицы на обеих нодах и читает через `Distributed` на ноде 2 строку из таблицы на обеих нодах и читает через `Distributed` на ноде 2 строку из
локальной таблицы ноды 1. Второй пишет в Kafka и читает свой маркер. В конце локальной таблицы ноды 1. Второй пишет в Kafka и читает свой маркер. В конце
проверка спрашивает у Docker, не убивало ли ядро что-нибудь в долгоживущих проверка спрашивает у Docker, не убивало ли ядро что-нибудь в долгоживущих
@@ -74,13 +74,14 @@ keeper, Kafka через порт машины, три цели Prometheus, ис
Временный топик проверки с машины и запуски DAG удаляются; Временный топик проверки с машины и запуски DAG удаляются;
постоянный топик пробника сохраняется, а старые записи чистит Kafka. постоянный топик пробника сохраняется, а старые записи чистит Kafka.
Про здоровье контейнеров честно будет сказать так: сразу после `make up --wait` Одиннадцать проверок здоровья сразу после `make up --wait` повторяют то, чего
эти одиннадцать проверок повторяют то, чего Compose уже дождался, — у каждой Compose уже дождался: у каждой долгоживущей службы есть своя `healthcheck`.
долгоживущей службы есть своя `healthcheck`. Оставлены они потому, что первый Оставлены они потому, что первый вопрос к стенду всё равно «всё ли живо», а
вопрос к стенду всё равно «всё ли живо», и ответ на него стоит меньше секунды. ответ на него стоит меньше секунды. Устройство keeper — другое дело: он должен
Устройство keeper — другое дело: он работает от пользователя `clickhouse`, с работать от пользователя `clickhouse`, с пределом в 262144 открытых файла и со
пределом в 262144 открытых файла и своим каталогом координации. Здоровым он своим томом под данные. Всё это объявлено в `compose.yaml`, но здоровым keeper
выглядит и без этого, а грабли тут настоящие. выглядит и без этого, поэтому смоук спрашивает у живого контейнера, дошли ли
объявленные настройки до процесса.
`make smoke-cluster` запускает отдельную глубокую проверку ClickHouse: описание `make smoke-cluster` запускает отдельную глубокую проверку ClickHouse: описание
кластера, макросы, связь с keeper, `ReplicatedMergeTree`, `Distributed`, очередь кластера, макросы, связь с keeper, `ReplicatedMergeTree`, `Distributed`, очередь
+4 -3
View File
@@ -83,8 +83,8 @@ def _drop_tables(client) -> None:
) )
# Единственная проверка, вынесенная из задач: её делают обе, до создания таблиц # Отсутствие таблиц проверяют обе задачи — перед созданием и после уборки,
# и после уборки. # поэтому у этой проверки своё имя, а остальные живут прямо в теле задач.
def _assert_tables_absent(client) -> None: def _assert_tables_absent(client) -> None:
for node_name, source in NODES: for node_name, source in NODES:
remaining = _table_engines(client, source) remaining = _table_engines(client, source)
@@ -197,7 +197,8 @@ def test_clickhouse():
raise RuntimeError( raise RuntimeError(
"запись и чтение маркера должны выполняться с разных нод" "запись и чтение маркера должны выполняться с разных нод"
) )
if distributed_rows != [(1, written["hostname"], written["marker"])]: expected_rows = [(1, written["hostname"], written["marker"])]
if distributed_rows != expected_rows:
raise RuntimeError( raise RuntimeError(
"нода 2 не прочитала маркер первого шарда через Distributed: " "нода 2 не прочитала маркер первого шарда через Distributed: "
f"{written['marker']}, получено {distributed_rows}" f"{written['marker']}, получено {distributed_rows}"
+15 -10
View File
@@ -74,26 +74,31 @@ check_container_health() {
fi fi
} }
# Keeper — единственная служба, которой мало быть здоровой: она пишет журнал keeper_exec() {
# координации, и если запустить её от root или с чужим каталогом данных, файлы timeout 20s "${COMPOSE_CMD[@]}" --project-directory "$ROOT_DIR" \
# останутся с неверным владельцем и следующий запуск их не откроет. Предел на exec -T clickhouse-keeper "$@" 2>/dev/null
# открытые файлы у неё свой: соединений много, и стандартной тысячи не хватает. }
# Три настройки keeper объявлены в compose.yaml: свой пользователь, свой предел
# на открытые файлы и свой том под /var/lib/clickhouse. Проверка спрашивает у
# живого контейнера, дошли ли они до процесса — здоровым keeper выглядит и без
# них, а каталог координации, однажды созданный от чужого пользователя,
# следующий запуск уже не откроет.
check_keeper_runtime() { check_keeper_runtime() {
local keeper_user
local keeper_nofile local keeper_nofile
local keeper_owner local keeper_owner
local keeper_user
keeper_user="$(compose exec -T clickhouse-keeper id -un)" keeper_user="$(keeper_exec id -un)"
keeper_nofile="$(compose exec -T clickhouse-keeper \ keeper_nofile="$(keeper_exec \
awk '$1 == "Max" && $2 == "open" && $3 == "files" {print $4}' /proc/1/limits)" awk '$1 == "Max" && $2 == "open" && $3 == "files" {print $4}' /proc/1/limits)"
keeper_owner="$(compose exec -T clickhouse-keeper \ keeper_owner="$(keeper_exec stat -c '%U:%G' /var/lib/clickhouse/coordination)"
stat -c '%U:%G' /var/lib/clickhouse/coordination)"
if [[ "$keeper_user" == 'clickhouse' ]] && \ if [[ "$keeper_user" == 'clickhouse' ]] && \
[[ "$keeper_nofile" -ge 262144 ]] && \ [[ "$keeper_nofile" -ge 262144 ]] && \
[[ "$keeper_owner" == 'clickhouse:clickhouse' ]]; then [[ "$keeper_owner" == 'clickhouse:clickhouse' ]]; then
pass 'keeper работает от clickhouse с nofile 262144 и своим каталогом данных' pass 'keeper работает от clickhouse с nofile 262144 и своим каталогом данных'
else else
fail "неверное окружение keeper: пользователь=${keeper_user}, nofile=${keeper_nofile}, владелец каталога=${keeper_owner}" fail "неверное окружение keeper: пользователь=${keeper_user:-нет ответа}, nofile=${keeper_nofile:-нет ответа}, владелец каталога=${keeper_owner:-нет ответа}"
fi fi
} }