Простая автоматизация с Bash для новичков

от автора

Это - логотип Bash оболочки. Она сама и bash скрипт - это разные вещи.

Это — логотип Bash оболочки. Она сама и bash скрипт — это разные вещи.

Приветствую, это Денис из команды BagrovChibirev, и в статье я на простом примере расскажу об автоматизации процессов в Linux с помощью bash скриптов (сценариев командной строки).

Этот материал для тех, кто только рассматривает для себя инструменты автоматизации рутинных процессов. Я не буду вдаваться в подробности работы оболочки или терминологию (на знания чего и не претендую), но я пошагово пройдусь по написанному скрипту и расскажу своё мнение почему вообще их стоит использовать. Приятного чтения 🙂

Рассматривать я буду свой минималистичный скрипт для разворачивания простого python Django проекта при помощи системных юнитов (демонов) на удалённом сервере. Для тех, кто не в курсе: демоны — это специальные системные сервисы, которые следят за состоянием сторонних процессов и поддерживают их работоспособность. В современном мире для таких целей на микросервисах применяется Docker, но когда проект небольшой и состоит из пары-тройки процессов, их намного легче, проще и дешевле для системы (в разы), развернуть при помощи встроенных в линукс демонов.

Полностью скрипт доступен здесь

Начнём с того, что баш скрипт доступен почти всегда: Bash предустановлен на большинстве машин с Linux. Я использую скрипты на удалённых VPS, где в большинстве случаев используется либо Ubuntu 18+, либо Debian 10+, и, зайдя в систему, очень удобно просто закинуть в директорию сценарий, который произведет действия по обновлению, установке необходимого ряда пакетов, настройке пользователей и ssh доступов сам, без необходимости в очередной раз лезть туда руками.

Сам по себе язык сценариев bash очень прост, и исполняется построчно, что даёт систему управления похожую на скрипты Python, или просто последовательное выполнение команд в терминале оболочки сервера, если в скрипте нет ветвления или отработки ошибок.

Чтобы запустить такой сценарий, необходимо его непосредственно написать или импортировать любым образом в нужную Вам директорию, и задать ему права на исполнение. Начинается почти любой скрипт с объявления «шебанга»: специальных символов «#!«, за которыми следует путь к «интерпретатору», который будет использоваться для выполнения этого сценария.

#!/bin/bash

В целом, для оболочки bash эта строка необязательна, но только в случаях, когда Вы явно указываете «интерпретатор», например, выполняя файл через команду ниже. Также работает, например, когда Вы выполняете программы на Python, явно вызывая его в качестве интерпретатора для сценария.

bash ./startup.sh python -m ./startup.py

Но всё же, поскольку скрипт может быть вызван из другого места, или без использования явного указания оболочки, начинается он с шебанга и указания пути до оболочки bash.

Далее, поскольку скрипт создаёт несколько текстовых файлов с переменными, которые могут разниться от клиента к клиенту, я решил сделать обработку входящих аргументов. И поскольку аргументы могут быть переданы неправильно, мне также нужна отработка ошибок, для чего я написал функцию.

Ниже сразу я объявил функцию usage(), объявлять функции можно с круглыми скобками и без, но фигурные скобки после названия обязательны. Применение этой функции делает вывод помощи в консоль и прерывает дальнейшее выполнение.Важно понимать, что, поскольку Bash читает файл построчно, то вызов функции ДО её объявления вызовет уведомление об ошибке (скрипт продолжит работать), а переопределение функции полностью перезапишет её.

#!/bin/bash   # Принт справки помощи usage() {   echo "Usage: $0 -p projectname [-s servername] [-c] [-help]"   echo "  -s servername     Set the server name (default: project)"   echo "  -p projectname    Set the project name"   echo "  -c                Include Celery service setup"   echo "  -h             Print this help message"   exit 1 }

Далее мне необходимо задать именованные параметры, которые будут использоваться ниже в коде, что я сделал через присвоение, и «встроенную» функцию getopts. Bash распознаёт различные инструменты языка при помощи пробелов и табуляции, поэтому здесь важно где Вы ставите пробел, а где нет. Например, CELERY=false — это присвоение, а вот CELERY = false — это уже сравнение.Ну а обработка аргументов происходит при помощи оператора while, который проходится по всем вариантам функции getopts из заданного фиксированного списка. getopts перебирает переданный список аргументов и передаёт аргумент с его значением (если его нет, но он указан, то попадёт true) в переменную. В данном случае, в $opt попадает сама «переменная» аргумента, а в $OPTARG его значение.Уже внутри while, когда в $opt лежит значение, можно перебрать его с помощью оператора case. В конце, для завершения цикла case ставится оператор esac (также работает и для iffi ), а для завершения while ставится оператор done .

# Парсит переданные аргументы CELERY=false while getopts "cs:p:" opt; do   case $opt in     c)       CELERY=true       ;;     s)       SERVERNAME=$OPTARG       ;;     p)       PROJECTNAME=$OPTARG       ;;     h)       usage       ;;     ?)       usage       ;;   esac done 

Далее, распарсив аргументы, необходимо удостовериться, что они не пусты, а если и пусты, то передать в них стандартное значение. С полученными итоговыми значениями, скрипт наконец может спокойно заняться основным делом — созданием конфигурационных файлов. А по пути я еще задаю русскую локаль для сервера.

# Объявление переменных для подстановки PROJECTFOLDER=$(pwd) PROJECTNAME=${PROJECTNAME:-project} SERVERNAME=${SERVERNAME:-_}   # Шаг 0: Конфигурация русской локали if ! grep -q '^ru_RU.UTF-8' /etc/locale.gen; then     echo "ru_RU locale is not configured. Configuring..."     echo 'ru_RU.UTF-8 UTF-8' | sudo tee -a /etc/locale.gen     sudo locale-gen     echo "ru_RU locale configured successfully." else     echo "ru_RU locale is already configured." fi

Поскольку мы не знаем пуст ли аргумент, то мы можем задать стандартное значение для него, произведя манипуляцию, похожую на тернарный оператор в Python. Здесь в его качестве выступает конструкция :- , стандартная при назначении значений переменных: если параметр не задан, задать ему указанное после оператора значение. Далее, при конфигурации локали и создании файла, используется конструкция if then fi: в общем случае, указанного перед этим синтаксиса достаточно для того, чтобы выполнять простые условия, но в качестве условий могут быть как выполнены команды (как в случае с локалью), так и произведены вычисления и сравнения.

После объявления условий выполнения хорошим тоном будет поставить ; , чтобы облегчить интерпретатору понимание кода, и гарантировать отсутствие ошибок типа пропущенного синтаксиса. Существует несколько разных способов объявления условий, и, в примере, использованы два: без скобок и с двойными скобками ( подробнее здесь ). Скобки можно не отбрасывать вообще, но можно и отбросить, если условие — это выполнение сторонней команды или результат работы функции. Двойные же квадратные скобки служат для того, чтобы обеспечить, простыми словами, более «буквальное» выполнение написанного внутри кода и обойтись без употребления кавычек вокруг переменных.Здесь же, в локали, используется оператор ! , обозначающий эквивалент not для дальнейшего условия, а команда grep -q производит поиск заданной строки в заданном затем файле в «тихом» (-q = --quiet = --silent) режиме — без вывода информации в консоль. Соответственно, если результат выполнения команды — провал, то мы записываем в файл конфигурации локалей строку с необходимой локалью с помщью команды tee -a, и вызываем их генерацию через sudo .Чуть больше про tee . Здесь можно было бы воспользоваться стандартным echo >> file , но tee позволяет увидеть в терминале что было записано в файл, поэтому стандартно я пользуюсь ей, хотя в данном случае вывод в консоль перекрыт параметром > /dev/null, который «утилизирует» вывод — если он Вам нужен, этот параметр необходимо убрать. Параметр служит для добавления информации в конец файла без полной перезаписи. Ну а параметр <<EOL говорит команде о том, что она должна записать в файл всё, что находится до символов «EOL»

# Шаг 2: Создание юнита для запуска селери if [[ $CELERY = true ]]; then   sudo tee /etc/systemd/system/celery_$PROJECTNAME.service > /dev/null <<EOL [Unit] Description=Celery instance to serve $PROJECTNAME After=network.target After=$SERVICE_NAME.service  [Service] User=$USER WorkingDirectory=$PROJECTFOLDER/src ExecStart=$PROJECTFOLDER/bin/start_celery.sh Restart=always RestartSec=5s  [Install] WantedBy=multi-user.target EOL fi

После записи новых юнитов, необходимо перечитать их как указано в 3м шаге, за чем следует простая команда на установку nginx (здесь apt-get сам отработает ситуацию, когда nginx уже установлен), и также через tee для него прописывается конфиг. Если у вас все ещё стандартный nginx.conf, и порт 80 не занят, всё будет работать. Обратить внимание здесь необходимо на proxy_pass конфигурации nginx. Он рассчитан на конфигурацию запуска gunicorn, которая у Вас может отличаться. Nginx, также, требует перезапуска.

# Шаг 3: Обновить конфигурацию системного демона sudo systemctl daemon-reload  # Шаг 4: Установить и обновить конфигурацию Nginx sudo apt-get install -y nginx  sudo tee /etc/nginx/sites-enabled/$PROJECTNAME > /dev/null <<EOL server {     listen 80;     listen [::]:80;      root /var/www/html;     server_name $SERVERNAME;      location /static/ {         autoindex on;         root $PROJECTFOLDER/src;     }      location /media/ {         autoindex on;         root $PROJECTFOLDER/src;     }          location / {         proxy_pass http://127.0.0.1:8005;         proxy_set_header X-Forwarded-Host \$server_name;         proxy_set_header X-Real-IP \$remote_addr;         add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';         add_header Access-Control-Allow-Origin *;     } } EOL  sudo systemctl restart nginx

Далее всё просто. Если Вы скопируете мой код из репозитория, то в папке «bin» у Вас будут лежать два скрипта, которые будут запускаться юнитами, поэтому мы должны выдать им права на выполнение chmod +x. Затем, устанавливается редис как очередь для селери, и python-venv. (Сейчас вместо стандартного python-venv я рекомендую использовать современный uv или, хотя бы, pip-tools, но поскольку скрипт сделан не только для себя, но и для клиента, и для минимальной реализации, используем python-venv: он всем прост и понятен в установке.)

# Шаг 5: Задание прав на выполненияе для скриптов запуска селери и гуникорна chmod +x ./bin/*  # Шаг 6: Установка сервера редис (очереди для задач) sudo apt-get update sudo apt-get install -y redis-server python3-venv

После этого, скрипт создаёт, активирует, и устанавливает зависимости в окружение. Как можно увидеть, этого я добиваюсь просто прописав одна за другой команды, как я писал в начале, как будто сам нахожусь в терминале оболочки.Шаг 9 нужен для того, чтобы сделать этот скрипт универсальным. Здесь я подставляю полученные в начале переменные в код других скриптов в папках «/bin» и «/src». С помощью команды sed -i я подставляю значения $PROJECTFOLDER вместо прямо в потоке чтения (в данном случае, sed читает из файла) с аттрибутом --in-place . После чего я просто выполняю пошагово миграции и загружаю фикстуры, а также загружаю и запускаю работу демонов через утилиту systemctl .

# Шаг 7: создание и запуск виртуального окружения python python3 -m venv ./venv source ./venv/bin/activate  # Шаг 8: Устоновка необходимых зависимостей python -m pip install -r requirements.txt  # Шаг 9: Подстановка пользователя и директории в исполняемые файлы sed -i "s|<projectfolder>|$PROJECTFOLDER|g" ./bin/start_gunicorn.sh sed -i "s|<projectfolder>|$PROJECTFOLDER|g" ./bin/start_celery.sh sed -i "s|<projectfolder>|$PROJECTFOLDER|g" ./src/gunicorn_config.py sed -i "s|<user>|$USER|g" ./src/gunicorn_config.py  # Шаг 10: Запуск миграций джанго на пустую базу данных cd ./src python manage.py makemigrations python manage.py migrate python manage.py loaddata fixtures/initial.json python manage.py collectstatic --noinput  # Шаг 11: Запуск проекта с помощью юнита sudo systemctl enable celery_$PROJECTNAME sudo systemctl enable $PROJECTNAME sudo systemctl start $PROJECTNAME sudo systemctl start celery_$PROJECTNAME

Для корректной работы этого скрипта, ему самому необходимо выдать права на выполнение от пользователя, которым вы являетесь, или от суперпользователя через команду chmod +x ./startup.sh , и запустить его ./startup.sh .

Рассчитывается, что этот файл будет лежать на уровень выше корневой директории Вашего Django проекта, на уровне с виртуальным окружением и папкой /bin.

В данной статье я затрагиваю тему системных юнитов (демонов), но не распространяюсь. На этот счет на Хабре есть статьи, я лично читал эту. А целиком принцип именования и расположения файлов, а также способ развертывания позаимствован у неповторимого Алексея Голобурдина, конкретнее из этого видео.

Спасибо за внимание, надеюсь, статья была для Вас полезна 🙂 Оставляйте отзывы, критику и пожелания в комментариях. Приятного дня 🙂


ссылка на оригинал статьи https://habr.com/ru/articles/834684/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *