Ми}{@лbI4

Блог хеллоуворлдщика

Зачистка директории со старыми билдами

27.03.2022 linux, bash, deploy

Когда на сервер попадает уже собранный билд веб-приложения, то со временем они накапливаются, что приводит к некоторым проблемам. Рассмотрим следующий случай и его решение.

На продакшен сервер попадает уже собранное веб-приложение. Новый билд всегда "монтируется" по адресу ./www/current и перезапускает Nginx. Со временем билдов ставилось всё больше, кончалась память и свободные индексные дескрипторы доходили до критичного значения. Чтобы такое не происходило - старые билды нужно удалять. В этом случае не все, т.к. может потребоваться откат на предыдущий билд в аварийных случаях (а такое, к сожалению, случается).

Структура директории с билдами имела следующий вид:

- www
- - build_202203011010300
- - build_202203051536856
- - current
  • www - директория с билдами;
  • current - симлинк на актуальный билд, который указан как точка входа для Nginx. Когда появляется свежий билд, то симлинк пересоздается, а Nginx перезапускается;
  • build_* - наши билды.

Задача предельно простая: нужно удалить всё ненужное, и оставить всё нужное. Для этого был использован командный язык Bash для написания скрипта зачистки и настроен вызов этого скрипта через Cron по удобному графику. Скрипт оставляет N-последних билдов, билд используемый для симлинка current и сам симлинк. В константе BUILD_DIR указывается абсолютный путь до директории с билдами, а в константе PRESERVE_BUILDS_COUNT указывается необходимое количество билдов, которое не будет затронуто. По результату выполнения скрипта будет выведены удаленные имена директорий и имена файлов/директорий, которые остались в директории с билдами после зачистки.

#!/usr/bin/env bash

# Скрипт предназначен для очистки директории с билдами от старых билдов, которые занимают память и
# и не требуются для работы.
# По умолчанию будет не затронуто 7 последних билдов включая билд, который используется для симлинка "current".

set -euo pipefail

# директория с билдами
BUILD_DIR=/home/user/www
# количество последних билдов на хранение
PRESERVE_BUILDS_COUNT=7
# символ новой строки
EOL='\n'
if [ ! -h ${BUILD_DIR}/current ]; then
  echo 'ERROR: symlink "current" is not exists'
  exit 1
fi
# имя билда используемого в симлинке "current"
CURRENT_BUILD_NAME=$(basename $(readlink ${BUILD_DIR}/current))

echo 'BUILD DIR:' $BUILD_DIR
echo 'CURRENT BUILD: ' $CURRENT_BUILD_NAME
echo ''

filtered_builds=''
# В результирующую выборку на удаление не попадет симлинк "current", т.к. он не является директорией,
# будут отброшены директории не удовлетворяющие шаблону "^build_[\d]+$", и будет отброшена директория используемая
# для симлинка "current".
for build_name in ${BUILD_DIR}/*; do
  build_name=$(basename $build_name)

  if [ ! -d ${BUILD_DIR}/${build_name} ]; then
    continue
  fi

  if ! echo -n $build_name | grep -qP '^build_[\d]+$'; then
    continue
  fi

  if [ $build_name = $CURRENT_BUILD_NAME ]; then
    continue
  fi

  filtered_builds+=${build_name}${EOL}
done

# оставляем $PRESERVE_BUILDS_COUNT последних билдов
start_from_line=$(expr $PRESERVE_BUILDS_COUNT + 1)
filtered_builds=$(echo -ne $filtered_builds | sort -fr | tail -n +$start_from_line)

# удаление старых билдов
echo 'DELETED BUILDS:'
for build_name in $filtered_builds; do
  rm -rf ${BUILD_DIR}/${build_name}
  echo "> ${build_name}"
done
if [ -z "${filtered_builds}" ]; then
  echo '> NONE'
fi
echo ''

# список сохраненных файлов/директорий после "зачистки"
echo 'PRESERVED FILES/DIRECTORIES:'
for build_name in ${BUILD_DIR}/*; do
  build_name=$(basename $build_name)

  echo -n "> ${build_name}"
  if [ -h ${BUILD_DIR}/${build_name} ]; then
    echo -n ' -> ' $(readlink ${BUILD_DIR}/${build_name})
  fi
  echo ''
done

exit 0