Apps: "Bash", "Docker" & etc.
В этой заметке описан один из этапов реализации поставленной в вышестоящей публикации задачи автоматизации процедур развёртывания тестовых стендов из docker-контейнеров.
Библиотека функций управления контейнерами тестовых стендов:
# vi /usr/local/etc/devops/lib/bunch.sh.snippet
#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell).
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell).
# Функция подготовки конфигурации контейнеров тестового стенда
function bunch-preset {
# Создаём директорию для журналов событий контейнеров
mkdir -p /var/log/devops/bunch/${SITENAME}
# Зачищаем конфигурацию, данные и журналы - каждый запуск "с чистого листа"
rm -rf ${OPSROOT}/bunch/${SITENAME}
rm -f "${LOG}"
# # Создаём структуру временных директорий монтирования.
# Передаём всю файловую иерархию тестового стенда пользователю и группе "www-data"
# (за пределы контейнера и выданных ему файловых ресурсов всё равно выйти нельзя)
mkdir -p ${OPSROOT}/bunch/${SITENAME}
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --modify group::rwX ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --set default:group::rwX ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --set default:user:www-data:rwX,default:group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}
# Профилактически создаём директории, используемые в конфигурации
mkdir -p /etc/ssl/nginx
mkdir -p /etc/letsencrypt
# Создаём директории конфигурационных файлов
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/memcached
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/mongodb
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/mysql
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/nginx
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/php
# Копируем во временные директории монтирования глобальную конфигурацию
cp -a /usr/local/etc/devops/conf/bunch/etc ${OPSROOT}/bunch/${SITENAME} 2>/dev/null || true
# Пробуем скопировать во временные директории монтирования точечную конфигурацию
cp -a /usr/local/etc/devops/conf/${SITENAME}/etc ${OPSROOT}/bunch/${SITENAME} 2>/dev/null || true
# Перекрываем параметры доступа файлов настроек общепринятыми для тестовых стендов
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/etc
# Передаём директории конфигураций некоторых приложений во владение их специфичных пользователей
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/etc/mysql
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/etc/mongodb
# Создаём директории данных
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/www
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/www/log
mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/log
# Создаём директории для локальных файловых сокетов
# (переводим директории во владение пользователей, в контексте которых работают сервисы внутри контейнеров)
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/memcached
chown 11211 ${OPSROOT}/bunch/${SITENAME}/var/run/memcached
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/php
chown 33 ${OPSROOT}/bunch/${SITENAME}/var/run/php
# Запрещаем посторонним доступ в структуру временных директорий монтирования
chmod -R o-rw ${OPSROOT}/bunch/${SITENAME}
setfacl --recursive --mask --set default:other:--X ${OPSROOT}/bunch/${SITENAME}
return ${?}
}
# Функция организации SFTP-доступа к данным внутри контейнеров тестового стенда
function bunch-sftp-enable {
# Перебираем всех возможно объявленных SFTP-пользователей
for SFTP_USER_ITEM in "${SFTP_USER[@]}" ; do
# Проверяем наличие указанного пользователя в несущей ОС
id ${SFTP_USER_ITEM} > /dev/null 2>&1
if [ "${?}" -eq "0" ] ; then
# Пробуем создать директорию для "chroot"-а
[ -d "/var/opt/devops/chroot/${SFTP_USER_ITEM}" ] || mkdir -p /var/opt/devops/chroot/${SFTP_USER_ITEM}
if [ "${?}" -eq "0" ] ; then
# Профилактически приводим разрешения доступа иерархии "chroot"-а к требуемым OpenSSH-SFTP
chown root /var/opt/devops && chmod go-w /var/opt/devops
chown root /var/opt/devops/chroot && chmod go-w /var/opt/devops/chroot
chown root:developer /var/opt/devops/chroot/${SFTP_USER_ITEM}
chmod go-w /var/opt/devops/chroot/${SFTP_USER_ITEM}
# Пробуем создать директорию, служащую опорной точкой для монтирования
[ -d "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}" ] || mkdir -p /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
if [ "${?}" -eq "0" ] ; then
# Проверяем, не смонтирована ли уже файловая система
if [ "$(mount | grep -c -i /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME})" -eq "0" ] ; then
# Монтируем зеркало (посредством FUSE) файловой системы тестируемого проекта в "chroot" пользователя
chown ${SFTP_USER_ITEM}:www-data /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
bindfs --mirror-only=${SFTP_USER_ITEM} --perms=0770 --create-for-group=www-data ${OPSROOT}/bunch/${SITENAME} /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Пользователю \"${SFTP_USER_ITEM}\" успешно предоставлен SFTP-доступ к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при монтировании файловой системы проекта \"${SITENAME}\" в точку \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
fi
else
echo "$(cdate): Пользователь \"${SFTP_USER_ITEM}\" уже обладает SFTP-доступом к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при создании точки монтирования \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при создании chroot-директории \"/var/opt/devops/chroot/${SFTP_USER_ITEM}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно включить SFTP-доступ для пользователя \"${SFTP_USER_ITEM}\" - таковой отсутствует." | tee -a "${LOG}"
fi
done
unset SFTP_USER_ITEM
return ${?}
}
# Функция выключения SFTP-доступа к данным внутри контейнеров тестового стенда
function bunch-sftp-disable {
# Перебираем всех возможно объявленных SFTP-пользователей
for SFTP_USER_ITEM in "${SFTP_USER[@]}" ; do
# Проверяем наличие указанного пользователя в несущей ОС
id ${SFTP_USER_ITEM} > /dev/null 2>&1
if [ "${?}" -eq "0" ] ; then
# Проверяем, смонтирована ли файловая система
if [ "$(mount | grep -c -i /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME})" -ne "0" ] ; then
# Уничтожаем все процессы, взаимодействующие с высвобождаемыми файловыми ресурсами
fuser -km "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}"
# Демонтируем из "croot" файловую систему тестируемого проекта
umount -fl /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME} > /dev/null 2>&1
if [ "${?}" -eq "0" ]; then
echo "$(cdate): Для пользователя \"${SFTP_USER_ITEM}\" успешно деактивирован SFTP-доступ к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при демонтировании файловой системы \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
fi
# Удаляем иерархию от точки монтирования (профилактическая зачистка)
rm -rf "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}"
fi
else
FORTH=false
echo "$(cdate): Невозможно отключить SFTP-доступ для пользователя \"${SFTP_USER_ITEM}\" - таковой отсутствует." | tee -a "${LOG}"
fi
done
unset SFTP_USER_ITEM
return ${?}
}
# Функция загрузки данных от рабочего сервера к тестовому стенду
function bunch-scp-download {
# Загружаем с исходного web-сервера данные во временные директории монтирования
echo "$(cdate): Ожидаем загрузки (посредством SSH+TAR+GZip) файлов данных с исходного сервера \"${SCP_FQDN_SRC}\"..." | tee -a "${LOG}"
front-nginx-log-show
# Перебираем все возможно объявленные исходные директории
for I in "${!SCP_DIR_SRC[@]}" ; do
# Если целевая директория явно не задана, то принимаем её одинаковой с исходной
[ -z "${SCP_DIR_TGT[$I]}" ] && { SCP_DIR_TGT[$I]=${SCP_DIR_SRC[$I]}; }
# Пробуем создать целевую локальную директорию
[ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Вариант копирования с однопоточным сжатием GZip
# sudo -u "${SCP_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${SCP_FQDN_SRC} "(GZIP=-7 cd ${SCP_DIR_SRC[$I]} && tar -zcf - ./)" | tar --warning=no-timestamp --warning=no-file-removed --totals -zxf - -C ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
# Вариант копирования с многопоточным сжатием PiGZ (требуется установка пакета "pigz")
sudo -u "${SCP_SSH_USER_SRC}" ssh -q ${SCP_FQDN_SRC} "(cd ${SCP_DIR_SRC[$I]} && tar -cf - ./ | pigz -7 -p 3)" | tar --warning=no-timestamp --warning=no-file-removed --totals -zxf - -C ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Файлы данных успешно загружены в директорию \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
else
FORTH=false
echo "$(cdate): Сбой при загрузке файлов данных в директорию \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
done
unset I
return ${?}
}
# Функция синхронизации файлов данных от рабочего сервера к тестовому стенду
function bunch-scp-sync {
# Синхронизируем данные с исходного сервера в директории тестовой площадки
echo "$(cdate): Ожидаем загрузки изменений (посредством SSH+RSync) файлов данных с исходного сервера \"${SCP_FQDN_SRC}\"..." | tee -a "${LOG}"
front-nginx-log-show
# Перебираем все возможно объявленные исходные директории
for I in "${!SCP_DIR_SRC[@]}" ; do
# Если целевая директория явно не задана, то принимаем её одинаковой с исходной
[ -z "${SCP_DIR_TGT[$I]}" ] && { SCP_DIR_TGT[$I]=${SCP_DIR_SRC[$I]}; }
# Пробуем создать целевую локальную директорию
[ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Вариант однопоточной синхронизации с сжатием GZip
# (надёжнее и быстрее всего в RSync работает метод сравнения файлов посредством "MD5 checksum")
rsync --stats --checksum --recursive --links --delete --compress --compress-level=7 -e "ssh -q -o StrictHostKeyChecking=no -i /home/${SCP_SSH_USER_SRC}/.ssh/id_rsa" ${SCP_SSH_USER_SRC}@${SCP_FQDN_SRC}:${SCP_DIR_SRC[$I]}/ ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Успешно синхронизированы файлы данных в директории \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
else
FORTH=false
echo "$(cdate): Сбой при синхронизации файлов данных в директории \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
done
unset I
return ${?}
}
# Функция синхронизации файлов данных совместного использования от рабочего сервера к тестовому стенду
function bunch-scp-ext-sync {
# Перебираем все возможно объявленные исходные директории совместного использования
for I in "${!SCP_EXT_DIR_SRC[@]}" ; do
# Если целевая директория явно не задана, то принимаем её одинаковой с исходной
[ -z "${SCP_EXT_DIR_TGT[$I]}" ] && { SCP_EXT_DIR_TGT[$I]=${SCP_EXT_DIR_SRC[$I]}; }
# Пробуем создать локальную директорию хранения файлов общего пользования
[ -d "${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Пробуем создать целевую локальную директорию
[ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Вариант однопоточной синхронизации с сжатием GZip
# (надёжнее и быстрее всего в RSync работает метод сравнения файлов посредством "MD5 checksum")
rsync --stats --checksum --recursive --links --delete --compress --compress-level=7 -e "ssh -q -o StrictHostKeyChecking=no -i /home/${SCP_SSH_USER_SRC}/.ssh/id_rsa" ${SCP_SSH_USER_SRC}@${SCP_FQDN_SRC}:${SCP_EXT_DIR_SRC[$I]}/ ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Успешно синхронизированы файлы данных общего пользования в директории хранения \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}
chmod -R ug+rw ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}
# Связываем целевую директорию с директорией хранения файлов общего пользования
#if [ "$(mount | grep -c -i ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]})" -eq "0" ] ; then
if [ "$(mount | grep -c -i $(echo ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]} | sed 's#//#/#g'))" -eq "0" ] ; then
mount --bind ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]} ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Файлы данных совместного использования успешно доставлены в целевую директорию \"${SCP_EXT_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при доставке файлов данных в целевую директорию \"${SCP_EXT_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
fi
fi
else
FORTH=false
echo "$(cdate): Сбой при синхронизации файлов данных общего пользования в директории \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую локальную директорию \"${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать локальную директорию хранения файлов данных общего пользования \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
done
unset I
return ${?}
}
# Функция загрузки данных из Git-репозитория для контейнеров тестового стенда
function bunch-git-download {
# Пробуем создать временную директорию для загрузки данных
rm -rf ${OPSROOT}/bunch/${SITENAME}/var/tmp/git
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/git && chown "${GIT_SSH_USER_SRC}" ${OPSROOT}/bunch/${SITENAME}/var/tmp/git
if [ "${?}" -eq "0" ] ; then
# Загружаем с Git-репозитория данные во временные директории монтирования
echo "$(cdate): Ожидаем загрузки (посредством SSH+Git) файлов из Git-репозитория \"${GIT_REPO_SRC}\"..." | tee -a "${LOG}"
front-nginx-log-show
# Загружаем только одну ветвь без истории (по состоянию на последний коммит)
sudo -u "${GIT_SSH_USER_SRC}" XDG_CONFIG_HOME="${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}" git clone --depth 1 --no-tags --branch ${GIT_BRANCH_SRC} "${GIT_REPO_SRC}" "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}"
if [ "${?}" -eq "0" ] ; then
# Копируем полученные из репозитория файлы в директорию данных с замещением имеющихся
rm -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}/.git"
cp -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}/"* "${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}"
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Файлы данных успешно загружены в директорию \"${GIT_DIR_TGT}\"." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}
chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}
# Удаляем директорию с промежуточными данными
rm -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}"
else
FORTH=false
echo "$(cdate): Сбой при загрузке файлов данных в \"${GIT_DIR_TGT}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при загрузке данных из Git-репозитория \"${GIT_REPO_SRC}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/var/tmp/git\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция загрузки данных из MySQL для контейнеров тестового стенда
function bunch-mysql-download {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MYSQL_FQDN_SRC}" -o -z "${MYSQL_SSH_USER_SRC}" -o -z "${MYSQL_DB_SRC}" -o -z "${MYSQL_USER_SRC}" -o -z "${MYSQL_PASSWD_SRC}" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимые для загрузки в контейнер \"bunch-${SITENAME}-mysql\" данных параметры подключения." | tee -a "${LOG}"
return 1
fi
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MYSQL_DB_TGT}" ] && { MYSQL_DB_TGT=${MYSQL_DB_SRC}; }
[ -z "${MYSQL_USER_TGT}" ] && { MYSQL_USER_TGT=${MYSQL_USER_SRC}; }
[ -z "${MYSQL_PASSWD_TGT}" ] && { MYSQL_PASSWD_TGT=${MYSQL_PASSWD_SRC}; }
# Пытаемся обеспечить и проверить тем самым доступность удалённой исходной БД
sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(echo -e \"[client]\nuser=${MYSQL_USER_SRC}\npassword=${MYSQL_PASSWD_SRC}\n\" > ./.mysql-${SITENAME}.cnf && chmod go-rwx ./.mysql-${SITENAME}.cnf && mysql --defaults-file=./.mysql-${SITENAME}.cnf -h localhost -u ${MYSQL_USER_SRC} ${MYSQL_DB_SRC} -e ';' 2>/dev/null)" 2>/dev/null
if [ "${?}" -eq "0" ] ; then
# Проверяем доступность локальной тестовой БД
docker exec -i bunch-${SITENAME}-mysql mysql -h localhost -u ${MYSQL_USER_TGT} --password="${MYSQL_PASSWD_TGT}" ${MYSQL_DB_TGT} -e ";" 2>/dev/null
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Ожидаем загрузки (посредством SSH+SQL) данных с исходного MySQL-сервера \"${MYSQL_FQDN_SRC}\" и применения их на тестовой площадке..." | tee -a "${LOG}"
front-nginx-log-show
# Предварительно зачищаем применяемую базу данных, удаляя и пересоздавая её
cat "${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root mysql
# Одноэтапный простой SQL-дамп со сжатием GZip
# sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MYSQL_FQDN_SRC} "(mysqldump -h localhost -u ${MYSQL_USER_SRC} --password=${MYSQL_PASSWD_SRC} --no-tablespaces ${MYSQL_DB_SRC} | gzip -c -7)" | gzip -c -d | docker exec -i --env MYSQL_PWD="${MYSQL_ROOT_PASSWORD_TGT}" bunch-${SITENAME}-mysql mysql -h localhost -u root ${MYSQL_DB_TGT}
# Одноэтапный слегка оптимизированный SQL-дамп
# sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MYSQL_FQDN_SRC} "mysqldump --no-tablespaces --add-drop-table --quick --disable-keys --extended-insert --single-transaction --skip-lock-tables --lock-tables=false -h localhost -u ${MYSQL_USER_SRC} --password=${MYSQL_PASSWD_SRC} ${MYSQL_DB_SRC}" | docker exec -i --env MYSQL_PWD="${MYSQL_ROOT_PASSWORD_TGT}" bunch-${SITENAME}-mysql mysql -h localhost -u root ${MYSQL_DB_TGT}
# Трёхэтапный оптимизированный SQL-дамп с временным отключением индексов и сжатием GZip
echo "SET GLOBAL AUTOCOMMIT=0; SET GLOBAL UNIQUE_CHECKS=0; SET GLOBAL FOREIGN_KEY_CHECKS=0;" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT} \
&& sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(mysqldump --defaults-file=./.mysql-${SITENAME}.cnf --no-tablespaces --add-drop-database --add-drop-table --single-transaction --extended-insert --hex-blob --quick --disable-keys --skip-lock-tables --lock-tables=false -h localhost -u ${MYSQL_USER_SRC} ${MYSQL_DB_SRC} | gzip -c -5)" | gzip -c -d | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT} \
&& echo "SET GLOBAL AUTOCOMMIT=1; SET GLOBAL UNIQUE_CHECKS=1; SET GLOBAL FOREIGN_KEY_CHECKS=1; COMMIT;" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Данные MySQL успешно загружены в локальную тестовую БД \"${MYSQL_DB_TGT}\"." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при загрузке данных в локальную тестовую БД \"${MYSQL_DB_TGT}\" MySQL." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно подключение к локальной тестовой БД \"${MYSQL_DB_TGT}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно подключение к удалённой исходной БД \"${MYSQL_DB_SRC}\"." | tee -a "${LOG}"
fi
# При любом итоге пробуем зачистить следы деятельности
sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(rm -f ./.mysql-${SITENAME}.cnf)" 2>/dev/null
return ${?}
}
# Функция загрузки данных из MongoDB для контейнеров тестового стенда
function bunch-mongodb-download {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MONGODB_FQDN_SRC}" -o -z "${MONGODB_SSH_USER_SRC}" -o -z "${MONGODB_DB_SRC}" -o -z "${MONGODB_USER_SRC}" -o -z "${MONGODB_PASSWD_SRC}" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимые для загрузки в контейнер \"bunch-${SITENAME}-mongodb\" данных параметры подключения." | tee -a "${LOG}"
return 1
fi
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MONGODB_DB_TGT}" ] && { MONGODB_DB_TGT=${MONGODB_DB_SRC}; }
[ -z "${MONGODB_USER_TGT}" ] && { MONGODB_USER_TGT=${MONGODB_USER_SRC}; }
[ -z "${MONGODB_PASSWD_TGT}" ] && { MONGODB_PASSWD_TGT=${MONGODB_PASSWD_SRC}; }
# Проверяем доступность удалённой исходной БД
sudo -u "${MONGODB_SSH_USER_SRC}" ssh -q ${MONGODB_FQDN_SRC} "mongo localhost/${MONGODB_DB_SRC} --username ${MONGODB_USER_SRC} --password \"${MONGODB_PASSWD_SRC}\" --eval \"db.getCollectionNames()\" > /dev/null 2>&1"
if [ "${?}" -eq "0" ] ; then
# Проверяем доступность локальной тестовой БД
docker exec -i bunch-${SITENAME}-mongodb mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db.adminCommand('listDatabases')" 2>/dev/null | grep -i "name" | grep -i -e "\"${MONGODB_DB_TGT}\"" > /dev/null 2>&1
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Ожидаем загрузки (посредством SSH+Cat) данных с исходного MongoDB-сервера \"${MONGODB_FQDN_SRC}\" и применения их на тестовой площадке..." | tee -a "${LOG}"
front-nginx-log-show
# Предварительно зачищаем применяемую базу данных, удаляя все её "коллекции данных"
docker exec -i bunch-${SITENAME}-mongodb mongo localhost/${MONGODB_DB_TGT} --username ${MONGODB_USER_TGT} --password "${MONGODB_PASSWD_TGT}" --eval "db.getCollectionNames().forEach(function(c){db[c].drop()});" > /dev/null 2>&1
# # К сожалению, у спарки "mongodump" и "mongorestore" имеются баги, практически исключающие их работу через unix-pipe - нельзя просто выкачивать данные и сразу заливать их в целевую БД - придётся вначале делать "дамп", загружать его файлом, а в контейнере раскрывать.
# Выгрузка "дампа" локально с последующим копированием его на целевой сервер
sudo -u "${MONGODB_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MONGODB_FQDN_SRC} "( mkdir -p /var/tmp/mongodb && mongodump --host=localhost --username=\"${MONGODB_USER_SRC}\" --password=\"${MONGODB_PASSWD_SRC}\" --db=\"${MONGODB_DB_SRC}\" --gzip --archive=\"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" && cat \"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" && rm -f \"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" )" | cat > "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz"
# Применение полученного "дампа" в контейнере
docker exec -i bunch-${SITENAME}-mongodb mongorestore -v --host=localhost --username=root --password="${MONGODB_ROOT_PASSWORD_TGT}" --nsInclude "${MONGODB_DB_SRC}.*" --gzip --archive="/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz" > /dev/null 2>&1
rm -f "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz"
else
FORTH=false
echo "$(cdate): Невозможно подключение к локальной тестовой БД MongoDB \"${MONGODB_DB_TGT}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно подключение к удалённой исходной БД MongoDB \"${MONGODB_DB_SRC}\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска Shell-команд корректировки данных тестового стенда
function bunch-hook-exec {
# Перебираем всех возможно объявленных SFTP-пользователей
for HOOK_COMMAND_ITEM in "${HOOK_COMMAND[@]}" ; do
# Элементарно пытаемся исполнить команду, сохранённую в переменной
/bin/bash -c "${HOOK_COMMAND_ITEM}"
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Успешно исполнена hook-команда." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при исполнении hook-команды." | tee -a "${LOG}"
fi
done
return ${?}
}
# Функция запуска контейнера Nginx для тестирования
function bunch-nginx-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${FQDN}" -o "${#FQDN[@]}" -eq "0" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-nginx\" конфигурация." | tee -a "${LOG}"
return 1
fi
# Заранее создаём простейший HTML-файл, которым можно будет определить работоспособность тестового стенда
[[ ! -f "${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index."* ]] && {
echo "${SITENAME}" > ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index.html
chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index.html
}
# Перебираем все FQDN и каждому генерируем отдельный конфигурационный файл
for I in "${!FQDN[@]}" ; do
# Не генерируем конфигурационный файл, если в каком-то из имеющихся обнаружен текущий FQDN
if [ ! -z "${FQDN[$I]}" ] && [[ ! `grep -ril -e "server_name.*\s*${FQDN[$I]}.*;" ${OPSROOT}/bunch/${SITENAME}/etc/nginx/conf.d 2>/dev/null` ]] ; then
# Подставляем путь к файлу SSL-конфигурации, если таковой не задан индивидуально
[ -z "${FQDN_SSL_CONF[$I]}" ] && { FQDN_SSL_CONF[$I]="ssl_wildcard.conf"; }
# Подставляем путь к файлу ACL-конфигурации, если таковой не задан индивидуально
[ -z "${FQDN_ACL_CONF[$I]}" ] && { FQDN_ACL_CONF[$I]="./acl_default.conf"; }
# Пытаемся прочитать содержимое файла ACL-конфигурации
if [ -f "${FQDN_ACL_CONF[$I]}" ] ; then
FQDN_ACL_CONFIGURATION[$I]=$(cat "${FQDN_ACL_CONF[$I]}")
elif [ -f "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_ACL_CONF[$I]}" ] ; then
FQDN_ACL_CONFIGURATION[$I]=$(cat "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_ACL_CONF[$I]}")
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не обнаружен файл ACL-конфигурации \"${FQDN_ACL_CONF[$I]}\"." | tee -a "${LOG}"
fi
# Создаём конфигурационный файл по следующему шаблону
source /usr/local/etc/devops/lib/bunch-nginx-conf.sh.snippet
fi
done
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-nginx \
--network-alias bunch-${SITENAME}-nginx \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
-v /etc/ssl/nginx:/etc/ssl/nginx:ro \
-v /etc/letsencrypt:/etc/letsencrypt:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/nginx:/etc/nginx:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/php:/var/run/php:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
nginx:stable > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-nginx > /var/log/devops/bunch/${SITENAME}/output-nginx.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-nginx"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" запущен." | tee -a "${LOG}"
# Подключаем запущенный контейнер к транзитной связующей сети
docker network connect vianet bunch-${SITENAME}-nginx
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" включён в транзитную связующую сеть \"vianet\"." | tee -a "${LOG}"
# Выясняем IP-адрес, автоматически выданный Nginx-контейнеру для транзитной связующей сети "vianet"
VIAIPWOM=$(docker inspect bunch-${SITENAME}-nginx 2> /dev/null | jq -r '.[0].NetworkSettings.Networks["vianet"].IPAddress' | awk -F "/" '{print $1}')
if [ ! -z "${VIAIPWOM}" -a "${VIAIPWOM}" != "null" ] ; then
# Перебираем все FQDN и каждому генерируем отдельный конфигурационный файл для фронтального web-прокси
for I in "${!FQDN[@]}" ; do
if [ ! -z "${FQDN[$I]}" ] ; then
# Подставляем путь к файлу SSL-конфигурации, если таковой не задан индивидуально
[ -z "${FQDN_SSL_CONF[$I]}" ] && { FQDN_SSL_CONF[$I]="./ssl_wildcard.conf"; }
# Пытаемся прочитать содержимое файла SSL-конфигурации
if [ -f "${FQDN_SSL_CONF[$I]}" ] ; then
FQDN_SSL_CONFIGURATION[$I]=$(cat "${FQDN_SSL_CONF[$I]}")
elif [ -f "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_SSL_CONF[$I]}" ] ; then
FQDN_SSL_CONFIGURATION[$I]=$(cat "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_SSL_CONF[$I]}")
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не обнаружен файл SSL-конфигурации \"${FQDN_SSL_CONF[$I]}\"." | tee -a "${LOG}"
fi
# Создаём конфигурационный файл по следующему шаблону
source /usr/local/etc/devops/lib/bunch-nginx-proxying.sh.snippet
fi
done
# Указываем web-серверу фронтального контейнера принять обновлённую конфигурацию
docker exec -i ${FRONTNAME} /bin/bash -c "nginx -s reload" > /dev/null 2>/dev/null
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Проксирование от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\" успешно налажено." | tee -a "${LOG}"
# Информируем об активации и доступности FQDN
for FQDN_ITEM in "${FQDN[@]}" ; do
echo "$(cdate): Подключения к тестовой площадке принимаются по адресу \"http[s]://${FQDN_ITEM}/\"." | tee -a "${LOG}"
# Для полноты картины тестируем доступность извне доменного имени запускаемого тестового стенда
FQDN_ITEM_IP=$(dig +short "${FQDN_ITEM}")
if [ ! -z "${FQDN_ITEM_IP}" ] ; then
if [ "$(ip address | grep -i -c ${FQDN_ITEM_IP})" -eq "0" ] ; then
echo "$(cdate): IP-адрес сопоставленный с доменным именем \"${FQDN_ITEM}\" не ведёт на этот тестовый стенд!" | tee -a "${LOG}"
fi
else
echo "$(cdate): Доменному имени \"${FQDN_ITEM}\" тестового стенда в DNS не сопоставлен IP-адрес!" | tee -a "${LOG}"
fi
done
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": невозможно применить конфигурацию на фронтальном web-прокси." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не получен внутренний IP-адрес контейнера в транзитной сети \"vianet\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при включении контейнера \"bunch-${SITENAME}-nginx\" в транзитную сеть \"vianet\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-nginx\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера Nginx для тестирования
function bunch-nginx-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-nginx > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-nginx)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-nginx\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера PHP-FPM для тестирования
function bunch-php-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${PHPFPM_VER}" -o ! -f ${OPSROOT}/bunch/${SITENAME}/etc/php/${PHPFPM_VER}/fpm/php.ini ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-php\" конфигурация." | tee -a "${LOG}"
return 1
fi
# Заранее создаём простейший PHP-скрипт, которым можно будет определить работоспособность тестового стенда
[ ! -f "${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php" ] && {
echo "<?php phpinfo(); ?>" > ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php
chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php
}
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-php \
--network-alias bunch-${SITENAME}-php \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
-v ${OPSROOT}/bunch/${SITENAME}/etc/php:/etc/php:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/msmtprc:/etc/msmtprc:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/php:/var/run/php:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/var/run/memcached:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
--tmpfs /run --tmpfs /tmp \
--tmpfs /var/lib/php/sessions \
--network backnet-${SITENAME} \
--detach \
selfmade:php-${PHPFPM_VER}-fpm > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-php > /var/log/devops/bunch/${SITENAME}/output-php.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-php"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-php\" запущен." | tee -a "${LOG}"
# Для совместимости с разнородными web-проектами дублируем файловые "сокеты" разноименными ссылками
if [[ -S ${OPSROOT}/bunch/${SITENAME}/var/run/php/php-fpm.sock ]] ; then
ln -P ${OPSROOT}/bunch/${SITENAME}/var/run/php/php-fpm.sock ${OPSROOT}/bunch/${SITENAME}/var/run/php/php${PHPFPM_VER}-fpm.sock 2>/dev/null
fi
# Пробуем применить возможно заданные для контейнера crontab-записи
if [ "${#PHPFPM_CRON_COMMAND[@]}" -ge "1" ] ; then
# Получаем действующий список задач и удаляем возможно уже имеющиеся crontab-записи для этого контейнера
CRONTAB=$(crontab -u root -l)
CRONTAB=`echo "${CRONTAB}" | sed -E "s/.*docker\s*exec.*bunch-${SITENAME}-php.*//gI"`
# Перебираем возможно заданные crontab-записи и дополняем ими crontab-таблицу
for I in "${!PHPFPM_CRON_COMMAND[@]}" ; do
if [ ! -z "${PHPFPM_CRON_COMMAND[$I]}" -a ! -z "${PHPFPM_CRON_SCHEDULE[$I]}" ] ; then
CRONTAB="${CRONTAB}\n${PHPFPM_CRON_SCHEDULE[$I]} docker exec -i --user www-data bunch-${SITENAME}-php /bin/bash -c \"${PHPFPM_CRON_COMMAND[$I]}\" >> ${OPSROOT}/bunch/${SITENAME}/var/log/crontab.log 2>&1 &"
fi
done
# Сохраняем изменённый набор crontab-записей
echo -e "${CRONTAB}" | crontab -u root -
echo "$(cdate): Для контейнера \"bunch-${SITENAME}-php\" применены crontab-задачи." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-php\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера PHP-FPM для тестирования
function bunch-php-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-php > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-php)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-php\" успешно остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-php\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера NodeJS для тестирования
function bunch-nodejs-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${NODEJS_VER}" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-nodejs\" конфигурация." | tee -a "${LOG}"
return 1
fi
# Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
[ -d "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}" ] || mkdir -p "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}"
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}
# Заранее создаём простейшее NodeJS-приложение, которым можно будет определить работоспособность тестового стенда
[ ! -f "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}/package.json" ] && {
source /usr/local/etc/devops/lib/bunch-nodejs-example-app.sh.snippet
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}
}
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-nodejs \
--network-alias bunch-${SITENAME}-nodejs \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env NODE_ENV="development" \
--workdir ${NODEJS_APP_DIR} \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/var/run/memcached:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--user www-data \
--detach \
selfmade:nodejs-${NODEJS_VER} > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-nodejs > /var/log/devops/bunch/${SITENAME}/output-nodejs.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-nodejs"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nodejs\" запущен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-nodejs\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера NodeJS для тестирования
function bunch-nodejs-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-nodejs > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-nodejs)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nodejs\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-nodejs\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера Memcached для тестирования
function bunch-memcached-start {
# Пробуем подготовить строку инициализации для Memcached
while read LINE ; do
VAR=`echo "${LINE}" | grep -E "^-" | tr -d '[:blank:]'`
[ ! -z "${VAR}" ] && { MEMCACHED_VARS="${MEMCACHED_VARS} ${VAR}"; }
done < ${OPSROOT}/bunch/${SITENAME}/etc/memcached/memcached.conf
if [ "${?}" -ne "0" -o -z "${MEMCACHED_VARS}" ] ; then
FORTH=false
echo "$(cdate): Сбой при подготовке параметров инициализации для контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
return 1
fi
# Официальный контейнер Memcached может создавать файловые сокеты только в "/tmp"
# (подменяем общепринятые "/var/run" на "/tmp" с последующим перемонтированием снова в "/var/run")
MEMCACHED_VARS=`echo "${MEMCACHED_VARS}" | sed -E 's#-s\s*(\/var\/run|\/run)\/memcached#-s \/tmp\/memcached#gI'`
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-memcached \
--network-alias bunch-${SITENAME}-memcached \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/tmp/memcached:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
memcached:1.5 /bin/bash -c "mkdir -p /tmp/memcached && memcached ${MEMCACHED_VARS} logfile /dev/stdout" > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-memcached > /var/log/devops/bunch/${SITENAME}/output-memcached.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-memcached"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-memcached\" запущен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера Memcached для тестирования
function bunch-memcached-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-memcached > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-memcached)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-memcached\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера MongoDB для тестирования
function bunch-mongodb-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MONGODB_ROOT_PASSWORD_TGT}" ] ;then
FORTH=false
echo "$(cdate): Без пароля суперпользователя СУБД запуск контейнера \"bunch-${SITENAME}-mongodb\" невозможен." | tee -a "${LOG}"
return 1
fi
# Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
[ -d "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb
[ -d "${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MONGODB_DB_TGT}" ] && { MONGODB_DB_TGT=${MONGODB_DB_SRC}; }
[ -z "${MONGODB_USER_TGT}" ] && { MONGODB_USER_TGT=${MONGODB_USER_SRC}; }
[ -z "${MONGODB_PASSWD_TGT}" ] && { MONGODB_PASSWD_TGT=${MONGODB_PASSWD_SRC}; }
# При наличии параметров подключения к пользовательской БД пробуем создать Bash-скрипт для инициализации аккаунта и БД в MongoDB
if [ ! -z "${MONGODB_DB_TGT}" -a ! -z "${MONGODB_USER_TGT}" -a ! -z "${MONGODB_PASSWD_TGT}" ] ; then
# (Bash-скрипт впоследствии автоматически запускается внутри контейнера)
cat << EOS | sed "s/^[ \t]*//" > ${OPSROOT}/bunch/${SITENAME}/var/tmp/mogodb/initdb.d/1.create-db-user.sh
# Creating in MongoDB a database and user as its owner:
mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db=db.getSiblingDB('${MONGODB_DB_TGT}');db.createUser({user:'${MONGODB_USER_TGT}',pwd:'${MONGODB_PASSWD_TGT}',roles:[{role:'readWrite',db:'${MONGODB_DB_TGT}'}]});db.init.insert({sitename:'${MONGODB_DB_TGT}'})"
EOS
# (indicator strictly on beginning line)
if [ "${?}" -ne "0" ] ; then
FORTH=false
echo "$(cdate): Сбой при подготовке init-скрипта для контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
return 1
fi
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d/1.create-db-user.sh
fi
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-mongodb \
--network-alias bunch-${SITENAME}-mongodb \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env MONGO_INITDB_ROOT_USERNAME="root" \
--env MONGO_INITDB_ROOT_PASSWORD="${MONGODB_ROOT_PASSWORD_TGT}" \
-v ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d:/docker-entrypoint-initdb.d:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/mongodb:/etc/mongodb:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb:/var/lib/mongodb:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
mongo:${MONGODB_VER} --config /etc/mongodb/mongod.conf > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-mongodb > /var/log/devops/bunch/${SITENAME}/output-mongodb.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-mongodb"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mongodb\" запущен." | tee -a "${LOG}"
# Циклический тест в ожидании возможности подключения к MongoDB
echo -n "$(cdate): Ожидаем доступности сервиса \"MongoDB\"... " | tee -a "${LOG}"
front-nginx-log-show
unset FORTH_MONGODB
for try in {1..30} ; do
[[ $(docker exec -i bunch-${SITENAME}-mongodb mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db.adminCommand('listDatabases')" 2>/dev/null | grep -i "name" | grep -i -c -e "\"${MONGODB_DB_TGT}\"") -eq "1" ]] && {
FORTH_MONGODB=true
break
} || { FORTH_MONGODB=false; echo -n "#"; sleep 1; }
done ; echo | tee -a "${LOG}"
if [ "${FORTH_MONGODB}" != "true" ] ; then
FORTH=false
echo "$(cdate): Невозможно подключиться к MongoDB в контейнере \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
return 1
fi
# Разрешаем чтение и запись в файловые сокеты специфичным пользователю "www-data" и группе "www-data"
setfacl --recursive --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера MongoDB для тестирования
function bunch-mongodb-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-mongodb > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-mongodb)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mongodb\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера MySQL для тестирования
function bunch-mysql-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MYSQL_ROOT_PASSWORD_TGT}" ] ;then
FORTH=false
echo "$(cdate): Без пароля суперпользователя СУБД запуск контейнера \"bunch-${SITENAME}-mysql\" невозможен." | tee -a "${LOG}"
return 1
fi
# Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
[ -d "${OPSROOT}/bunch/${SITENAME}/var/lib/mysql" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql
[ -d "${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d
chown -R 999:33 ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql
chown -R 999:33 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d
# Укладываем пароль суперпользователя в специальном файле конфигурации, обеспечивая возможность подключения без указания пароля
echo -e "[client]\nuser=root\npassword=\"${MYSQL_ROOT_PASSWORD_TGT}\"\n" > "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
chown 999 "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
chmod go-rwx "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MYSQL_DB_TGT}" ] && { MYSQL_DB_TGT=${MYSQL_DB_SRC}; }
[ -z "${MYSQL_USER_TGT}" ] && { MYSQL_USER_TGT=${MYSQL_USER_SRC}; }
[ -z "${MYSQL_PASSWD_TGT}" ] && { MYSQL_PASSWD_TGT=${MYSQL_PASSWD_SRC}; }
# При наличии параметров подключения к пользовательской БД пробуем создать SQL-скрипт для инициализации аккаунта и БД в MySQL
if [ ! -z "${MYSQL_DB_TGT}" -a ! -z "${MYSQL_USER_TGT}" -a ! -z "${MYSQL_PASSWD_TGT}" ] ; then
# (SQL-скрипт впоследствии автоматически запускается внутри контейнера)
cat << EOS | sed "s/^[ \t]*//" > ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql
--
-- Creating in MySQL a database and user as its owner:
--
DROP DATABASE IF EXISTS ${MYSQL_DB_TGT};
CREATE DATABASE ${MYSQL_DB_TGT};
GRANT ALL PRIVILEGES ON ${MYSQL_DB_TGT}.* TO '${MYSQL_USER_TGT}'@'%' IDENTIFIED BY '${MYSQL_PASSWD_TGT}';
FLUSH PRIVILEGES;
EOS
# (indicator strictly on beginning line)
if [ "${?}" -ne "0" ] ; then
FORTH=false
echo "$(cdate): Сбой при подготовке init-скрипта для контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
return 1
fi
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql
fi
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-mysql \
--network-alias bunch-${SITENAME}-mysql \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD_TGT}" \
-v ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d:/docker-entrypoint-initdb.d:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/mysql:/etc/mysql:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql:/var/lib/mysql:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
mysql:5.7 > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-mysql > /var/log/devops/bunch/${SITENAME}/output-mysql.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-mysql"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mysql\" запущен." | tee -a "${LOG}"
# Циклический тест в ожидании возможности подключения к MySQL
# (на запуск и конфигурирование приложения внутри контейнера может понадобиться до одной-двух минут)
echo -n "$(cdate): Ожидаем доступности сервиса \"MySQL\"... " | tee -a "${LOG}"
front-nginx-log-show
unset FORTH_MYSQL
for try in {1..300} ; do
[[ $(docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root -e "show databases;" 2>/dev/null | grep -v -i "database" | grep -i "mysql") ]] && {
FORTH_MYSQL=true
break
} || { FORTH_MYSQL=false; echo -n "#"; sleep 1; }
done ; echo | tee -a "${LOG}"
if [ "${FORTH_MYSQL}" != "true" ] ; then
FORTH=false
echo "$(cdate): Невозможно подключиться к MySQL в контейнере \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
return 1
fi
# Разрешаем чтение и запись в файловые сокеты специфичным пользователю "www-data" и группе "www-data"
setfacl --recursive --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера MySQL для тестирования
function bunch-mysql-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-mysql > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-mysql)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mysql\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера "phpMyAdmin" для тестирования
function bunch-pma-start {
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-pma \
--network-alias bunch-${SITENAME}-pma \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env PMA_HOST="bunch-${SITENAME}-mysql" \
--env PMA_PORT=3306 \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
phpmyadmin/phpmyadmin:latest > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-pma > /var/log/devops/bunch/${SITENAME}/output-pma.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-pma"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-pma\" запущен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-pma\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера "phpMyAdmin" для тестирования
function bunch-pma-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-pma > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-pma)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-pma\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-pma\"." | tee -a "${LOG}"
fi
return ${?}
}
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell).
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell).
# Функция подготовки конфигурации контейнеров тестового стенда
function bunch-preset {
# Создаём директорию для журналов событий контейнеров
mkdir -p /var/log/devops/bunch/${SITENAME}
# Зачищаем конфигурацию, данные и журналы - каждый запуск "с чистого листа"
rm -rf ${OPSROOT}/bunch/${SITENAME}
rm -f "${LOG}"
# # Создаём структуру временных директорий монтирования.
# Передаём всю файловую иерархию тестового стенда пользователю и группе "www-data"
# (за пределы контейнера и выданных ему файловых ресурсов всё равно выйти нельзя)
mkdir -p ${OPSROOT}/bunch/${SITENAME}
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --modify group::rwX ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --set default:group::rwX ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}
setfacl --mask --set default:user:www-data:rwX,default:group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}
# Профилактически создаём директории, используемые в конфигурации
mkdir -p /etc/ssl/nginx
mkdir -p /etc/letsencrypt
# Создаём директории конфигурационных файлов
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/memcached
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/mongodb
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/mysql
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/nginx
mkdir -p ${OPSROOT}/bunch/${SITENAME}/etc/php
# Копируем во временные директории монтирования глобальную конфигурацию
cp -a /usr/local/etc/devops/conf/bunch/etc ${OPSROOT}/bunch/${SITENAME} 2>/dev/null || true
# Пробуем скопировать во временные директории монтирования точечную конфигурацию
cp -a /usr/local/etc/devops/conf/${SITENAME}/etc ${OPSROOT}/bunch/${SITENAME} 2>/dev/null || true
# Перекрываем параметры доступа файлов настроек общепринятыми для тестовых стендов
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/etc
# Передаём директории конфигураций некоторых приложений во владение их специфичных пользователей
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/etc/mysql
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/etc/mongodb
# Создаём директории данных
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/www
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/www/log
mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/log
# Создаём директории для локальных файловых сокетов
# (переводим директории во владение пользователей, в контексте которых работают сервисы внутри контейнеров)
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/memcached
chown 11211 ${OPSROOT}/bunch/${SITENAME}/var/run/memcached
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/run/php
chown 33 ${OPSROOT}/bunch/${SITENAME}/var/run/php
# Запрещаем посторонним доступ в структуру временных директорий монтирования
chmod -R o-rw ${OPSROOT}/bunch/${SITENAME}
setfacl --recursive --mask --set default:other:--X ${OPSROOT}/bunch/${SITENAME}
return ${?}
}
# Функция организации SFTP-доступа к данным внутри контейнеров тестового стенда
function bunch-sftp-enable {
# Перебираем всех возможно объявленных SFTP-пользователей
for SFTP_USER_ITEM in "${SFTP_USER[@]}" ; do
# Проверяем наличие указанного пользователя в несущей ОС
id ${SFTP_USER_ITEM} > /dev/null 2>&1
if [ "${?}" -eq "0" ] ; then
# Пробуем создать директорию для "chroot"-а
[ -d "/var/opt/devops/chroot/${SFTP_USER_ITEM}" ] || mkdir -p /var/opt/devops/chroot/${SFTP_USER_ITEM}
if [ "${?}" -eq "0" ] ; then
# Профилактически приводим разрешения доступа иерархии "chroot"-а к требуемым OpenSSH-SFTP
chown root /var/opt/devops && chmod go-w /var/opt/devops
chown root /var/opt/devops/chroot && chmod go-w /var/opt/devops/chroot
chown root:developer /var/opt/devops/chroot/${SFTP_USER_ITEM}
chmod go-w /var/opt/devops/chroot/${SFTP_USER_ITEM}
# Пробуем создать директорию, служащую опорной точкой для монтирования
[ -d "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}" ] || mkdir -p /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
if [ "${?}" -eq "0" ] ; then
# Проверяем, не смонтирована ли уже файловая система
if [ "$(mount | grep -c -i /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME})" -eq "0" ] ; then
# Монтируем зеркало (посредством FUSE) файловой системы тестируемого проекта в "chroot" пользователя
chown ${SFTP_USER_ITEM}:www-data /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
bindfs --mirror-only=${SFTP_USER_ITEM} --perms=0770 --create-for-group=www-data ${OPSROOT}/bunch/${SITENAME} /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Пользователю \"${SFTP_USER_ITEM}\" успешно предоставлен SFTP-доступ к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при монтировании файловой системы проекта \"${SITENAME}\" в точку \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
fi
else
echo "$(cdate): Пользователь \"${SFTP_USER_ITEM}\" уже обладает SFTP-доступом к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при создании точки монтирования \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при создании chroot-директории \"/var/opt/devops/chroot/${SFTP_USER_ITEM}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно включить SFTP-доступ для пользователя \"${SFTP_USER_ITEM}\" - таковой отсутствует." | tee -a "${LOG}"
fi
done
unset SFTP_USER_ITEM
return ${?}
}
# Функция выключения SFTP-доступа к данным внутри контейнеров тестового стенда
function bunch-sftp-disable {
# Перебираем всех возможно объявленных SFTP-пользователей
for SFTP_USER_ITEM in "${SFTP_USER[@]}" ; do
# Проверяем наличие указанного пользователя в несущей ОС
id ${SFTP_USER_ITEM} > /dev/null 2>&1
if [ "${?}" -eq "0" ] ; then
# Проверяем, смонтирована ли файловая система
if [ "$(mount | grep -c -i /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME})" -ne "0" ] ; then
# Уничтожаем все процессы, взаимодействующие с высвобождаемыми файловыми ресурсами
fuser -km "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}"
# Демонтируем из "croot" файловую систему тестируемого проекта
umount -fl /var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME} > /dev/null 2>&1
if [ "${?}" -eq "0" ]; then
echo "$(cdate): Для пользователя \"${SFTP_USER_ITEM}\" успешно деактивирован SFTP-доступ к файловой системе тестируемого проекта \"${SITENAME}\"." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при демонтировании файловой системы \"/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}\"." | tee -a "${LOG}"
fi
# Удаляем иерархию от точки монтирования (профилактическая зачистка)
rm -rf "/var/opt/devops/chroot/${SFTP_USER_ITEM}/${SITENAME}"
fi
else
FORTH=false
echo "$(cdate): Невозможно отключить SFTP-доступ для пользователя \"${SFTP_USER_ITEM}\" - таковой отсутствует." | tee -a "${LOG}"
fi
done
unset SFTP_USER_ITEM
return ${?}
}
# Функция загрузки данных от рабочего сервера к тестовому стенду
function bunch-scp-download {
# Загружаем с исходного web-сервера данные во временные директории монтирования
echo "$(cdate): Ожидаем загрузки (посредством SSH+TAR+GZip) файлов данных с исходного сервера \"${SCP_FQDN_SRC}\"..." | tee -a "${LOG}"
front-nginx-log-show
# Перебираем все возможно объявленные исходные директории
for I in "${!SCP_DIR_SRC[@]}" ; do
# Если целевая директория явно не задана, то принимаем её одинаковой с исходной
[ -z "${SCP_DIR_TGT[$I]}" ] && { SCP_DIR_TGT[$I]=${SCP_DIR_SRC[$I]}; }
# Пробуем создать целевую локальную директорию
[ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Вариант копирования с однопоточным сжатием GZip
# sudo -u "${SCP_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${SCP_FQDN_SRC} "(GZIP=-7 cd ${SCP_DIR_SRC[$I]} && tar -zcf - ./)" | tar --warning=no-timestamp --warning=no-file-removed --totals -zxf - -C ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
# Вариант копирования с многопоточным сжатием PiGZ (требуется установка пакета "pigz")
sudo -u "${SCP_SSH_USER_SRC}" ssh -q ${SCP_FQDN_SRC} "(cd ${SCP_DIR_SRC[$I]} && tar -cf - ./ | pigz -7 -p 3)" | tar --warning=no-timestamp --warning=no-file-removed --totals -zxf - -C ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Файлы данных успешно загружены в директорию \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
else
FORTH=false
echo "$(cdate): Сбой при загрузке файлов данных в директорию \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
done
unset I
return ${?}
}
# Функция синхронизации файлов данных от рабочего сервера к тестовому стенду
function bunch-scp-sync {
# Синхронизируем данные с исходного сервера в директории тестовой площадки
echo "$(cdate): Ожидаем загрузки изменений (посредством SSH+RSync) файлов данных с исходного сервера \"${SCP_FQDN_SRC}\"..." | tee -a "${LOG}"
front-nginx-log-show
# Перебираем все возможно объявленные исходные директории
for I in "${!SCP_DIR_SRC[@]}" ; do
# Если целевая директория явно не задана, то принимаем её одинаковой с исходной
[ -z "${SCP_DIR_TGT[$I]}" ] && { SCP_DIR_TGT[$I]=${SCP_DIR_SRC[$I]}; }
# Пробуем создать целевую локальную директорию
[ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Вариант однопоточной синхронизации с сжатием GZip
# (надёжнее и быстрее всего в RSync работает метод сравнения файлов посредством "MD5 checksum")
rsync --stats --checksum --recursive --links --delete --compress --compress-level=7 -e "ssh -q -o StrictHostKeyChecking=no -i /home/${SCP_SSH_USER_SRC}/.ssh/id_rsa" ${SCP_SSH_USER_SRC}@${SCP_FQDN_SRC}:${SCP_DIR_SRC[$I]}/ ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Успешно синхронизированы файлы данных в директории \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}
else
FORTH=false
echo "$(cdate): Сбой при синхронизации файлов данных в директории \"${SCP_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/${SCP_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
done
unset I
return ${?}
}
# Функция синхронизации файлов данных совместного использования от рабочего сервера к тестовому стенду
function bunch-scp-ext-sync {
# Перебираем все возможно объявленные исходные директории совместного использования
for I in "${!SCP_EXT_DIR_SRC[@]}" ; do
# Если целевая директория явно не задана, то принимаем её одинаковой с исходной
[ -z "${SCP_EXT_DIR_TGT[$I]}" ] && { SCP_EXT_DIR_TGT[$I]=${SCP_EXT_DIR_SRC[$I]}; }
# Пробуем создать локальную директорию хранения файлов общего пользования
[ -d "${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Пробуем создать целевую локальную директорию
[ -d "${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}" ] || { mkdir -p ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]} && chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}; }
if [ "${?}" -eq "0" ] ; then
# Вариант однопоточной синхронизации с сжатием GZip
# (надёжнее и быстрее всего в RSync работает метод сравнения файлов посредством "MD5 checksum")
rsync --stats --checksum --recursive --links --delete --compress --compress-level=7 -e "ssh -q -o StrictHostKeyChecking=no -i /home/${SCP_SSH_USER_SRC}/.ssh/id_rsa" ${SCP_SSH_USER_SRC}@${SCP_FQDN_SRC}:${SCP_EXT_DIR_SRC[$I]}/ ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Успешно синхронизированы файлы данных общего пользования в директории хранения \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}
chmod -R ug+rw ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]}
# Связываем целевую директорию с директорией хранения файлов общего пользования
#if [ "$(mount | grep -c -i ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]})" -eq "0" ] ; then
if [ "$(mount | grep -c -i $(echo ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]} | sed 's#//#/#g'))" -eq "0" ] ; then
mount --bind ${OPSROOT}/share/${SCP_EXT_DIR_TGT[$I]} ${OPSROOT}/bunch/${SITENAME}/${SCP_EXT_DIR_TGT[$I]}
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Файлы данных совместного использования успешно доставлены в целевую директорию \"${SCP_EXT_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при доставке файлов данных в целевую директорию \"${SCP_EXT_DIR_TGT[$I]}\" тестового стенда." | tee -a "${LOG}"
fi
fi
else
FORTH=false
echo "$(cdate): Сбой при синхронизации файлов данных общего пользования в директории \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую локальную директорию \"${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать локальную директорию хранения файлов данных общего пользования \"./share/${SCP_EXT_DIR_TGT[$I]}\"." | tee -a "${LOG}"
fi
done
unset I
return ${?}
}
# Функция загрузки данных из Git-репозитория для контейнеров тестового стенда
function bunch-git-download {
# Пробуем создать временную директорию для загрузки данных
rm -rf ${OPSROOT}/bunch/${SITENAME}/var/tmp/git
mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/git && chown "${GIT_SSH_USER_SRC}" ${OPSROOT}/bunch/${SITENAME}/var/tmp/git
if [ "${?}" -eq "0" ] ; then
# Загружаем с Git-репозитория данные во временные директории монтирования
echo "$(cdate): Ожидаем загрузки (посредством SSH+Git) файлов из Git-репозитория \"${GIT_REPO_SRC}\"..." | tee -a "${LOG}"
front-nginx-log-show
# Загружаем только одну ветвь без истории (по состоянию на последний коммит)
sudo -u "${GIT_SSH_USER_SRC}" XDG_CONFIG_HOME="${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}" git clone --depth 1 --no-tags --branch ${GIT_BRANCH_SRC} "${GIT_REPO_SRC}" "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}"
if [ "${?}" -eq "0" ] ; then
# Копируем полученные из репозитория файлы в директорию данных с замещением имеющихся
rm -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}/.git"
cp -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}/"* "${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}"
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Файлы данных успешно загружены в директорию \"${GIT_DIR_TGT}\"." | tee -a "${LOG}"
# Самым простым способом обеспечиваем доступность данных пользователю по умолчанию (www-data)
chown -R 33:33 ${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}
chmod -R ug+rw ${OPSROOT}/bunch/${SITENAME}/${GIT_DIR_TGT}
# Удаляем директорию с промежуточными данными
rm -rf "${OPSROOT}/bunch/${SITENAME}/var/tmp/git/${GIT_BRANCH_SRC}"
else
FORTH=false
echo "$(cdate): Сбой при загрузке файлов данных в \"${GIT_DIR_TGT}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при загрузке данных из Git-репозитория \"${GIT_REPO_SRC}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно создать целевую директорию \"${OPSROOT}/bunch/${SITENAME}/var/tmp/git\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция загрузки данных из MySQL для контейнеров тестового стенда
function bunch-mysql-download {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MYSQL_FQDN_SRC}" -o -z "${MYSQL_SSH_USER_SRC}" -o -z "${MYSQL_DB_SRC}" -o -z "${MYSQL_USER_SRC}" -o -z "${MYSQL_PASSWD_SRC}" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимые для загрузки в контейнер \"bunch-${SITENAME}-mysql\" данных параметры подключения." | tee -a "${LOG}"
return 1
fi
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MYSQL_DB_TGT}" ] && { MYSQL_DB_TGT=${MYSQL_DB_SRC}; }
[ -z "${MYSQL_USER_TGT}" ] && { MYSQL_USER_TGT=${MYSQL_USER_SRC}; }
[ -z "${MYSQL_PASSWD_TGT}" ] && { MYSQL_PASSWD_TGT=${MYSQL_PASSWD_SRC}; }
# Пытаемся обеспечить и проверить тем самым доступность удалённой исходной БД
sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(echo -e \"[client]\nuser=${MYSQL_USER_SRC}\npassword=${MYSQL_PASSWD_SRC}\n\" > ./.mysql-${SITENAME}.cnf && chmod go-rwx ./.mysql-${SITENAME}.cnf && mysql --defaults-file=./.mysql-${SITENAME}.cnf -h localhost -u ${MYSQL_USER_SRC} ${MYSQL_DB_SRC} -e ';' 2>/dev/null)" 2>/dev/null
if [ "${?}" -eq "0" ] ; then
# Проверяем доступность локальной тестовой БД
docker exec -i bunch-${SITENAME}-mysql mysql -h localhost -u ${MYSQL_USER_TGT} --password="${MYSQL_PASSWD_TGT}" ${MYSQL_DB_TGT} -e ";" 2>/dev/null
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Ожидаем загрузки (посредством SSH+SQL) данных с исходного MySQL-сервера \"${MYSQL_FQDN_SRC}\" и применения их на тестовой площадке..." | tee -a "${LOG}"
front-nginx-log-show
# Предварительно зачищаем применяемую базу данных, удаляя и пересоздавая её
cat "${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root mysql
# Одноэтапный простой SQL-дамп со сжатием GZip
# sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MYSQL_FQDN_SRC} "(mysqldump -h localhost -u ${MYSQL_USER_SRC} --password=${MYSQL_PASSWD_SRC} --no-tablespaces ${MYSQL_DB_SRC} | gzip -c -7)" | gzip -c -d | docker exec -i --env MYSQL_PWD="${MYSQL_ROOT_PASSWORD_TGT}" bunch-${SITENAME}-mysql mysql -h localhost -u root ${MYSQL_DB_TGT}
# Одноэтапный слегка оптимизированный SQL-дамп
# sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MYSQL_FQDN_SRC} "mysqldump --no-tablespaces --add-drop-table --quick --disable-keys --extended-insert --single-transaction --skip-lock-tables --lock-tables=false -h localhost -u ${MYSQL_USER_SRC} --password=${MYSQL_PASSWD_SRC} ${MYSQL_DB_SRC}" | docker exec -i --env MYSQL_PWD="${MYSQL_ROOT_PASSWORD_TGT}" bunch-${SITENAME}-mysql mysql -h localhost -u root ${MYSQL_DB_TGT}
# Трёхэтапный оптимизированный SQL-дамп с временным отключением индексов и сжатием GZip
echo "SET GLOBAL AUTOCOMMIT=0; SET GLOBAL UNIQUE_CHECKS=0; SET GLOBAL FOREIGN_KEY_CHECKS=0;" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT} \
&& sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(mysqldump --defaults-file=./.mysql-${SITENAME}.cnf --no-tablespaces --add-drop-database --add-drop-table --single-transaction --extended-insert --hex-blob --quick --disable-keys --skip-lock-tables --lock-tables=false -h localhost -u ${MYSQL_USER_SRC} ${MYSQL_DB_SRC} | gzip -c -5)" | gzip -c -d | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT} \
&& echo "SET GLOBAL AUTOCOMMIT=1; SET GLOBAL UNIQUE_CHECKS=1; SET GLOBAL FOREIGN_KEY_CHECKS=1; COMMIT;" | docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root ${MYSQL_DB_TGT}
# Проверяем результат и делаем отметку в журнале событий
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Данные MySQL успешно загружены в локальную тестовую БД \"${MYSQL_DB_TGT}\"." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при загрузке данных в локальную тестовую БД \"${MYSQL_DB_TGT}\" MySQL." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно подключение к локальной тестовой БД \"${MYSQL_DB_TGT}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно подключение к удалённой исходной БД \"${MYSQL_DB_SRC}\"." | tee -a "${LOG}"
fi
# При любом итоге пробуем зачистить следы деятельности
sudo -u "${MYSQL_SSH_USER_SRC}" ssh -q ${MYSQL_FQDN_SRC} "(rm -f ./.mysql-${SITENAME}.cnf)" 2>/dev/null
return ${?}
}
# Функция загрузки данных из MongoDB для контейнеров тестового стенда
function bunch-mongodb-download {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MONGODB_FQDN_SRC}" -o -z "${MONGODB_SSH_USER_SRC}" -o -z "${MONGODB_DB_SRC}" -o -z "${MONGODB_USER_SRC}" -o -z "${MONGODB_PASSWD_SRC}" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимые для загрузки в контейнер \"bunch-${SITENAME}-mongodb\" данных параметры подключения." | tee -a "${LOG}"
return 1
fi
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MONGODB_DB_TGT}" ] && { MONGODB_DB_TGT=${MONGODB_DB_SRC}; }
[ -z "${MONGODB_USER_TGT}" ] && { MONGODB_USER_TGT=${MONGODB_USER_SRC}; }
[ -z "${MONGODB_PASSWD_TGT}" ] && { MONGODB_PASSWD_TGT=${MONGODB_PASSWD_SRC}; }
# Проверяем доступность удалённой исходной БД
sudo -u "${MONGODB_SSH_USER_SRC}" ssh -q ${MONGODB_FQDN_SRC} "mongo localhost/${MONGODB_DB_SRC} --username ${MONGODB_USER_SRC} --password \"${MONGODB_PASSWD_SRC}\" --eval \"db.getCollectionNames()\" > /dev/null 2>&1"
if [ "${?}" -eq "0" ] ; then
# Проверяем доступность локальной тестовой БД
docker exec -i bunch-${SITENAME}-mongodb mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db.adminCommand('listDatabases')" 2>/dev/null | grep -i "name" | grep -i -e "\"${MONGODB_DB_TGT}\"" > /dev/null 2>&1
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Ожидаем загрузки (посредством SSH+Cat) данных с исходного MongoDB-сервера \"${MONGODB_FQDN_SRC}\" и применения их на тестовой площадке..." | tee -a "${LOG}"
front-nginx-log-show
# Предварительно зачищаем применяемую базу данных, удаляя все её "коллекции данных"
docker exec -i bunch-${SITENAME}-mongodb mongo localhost/${MONGODB_DB_TGT} --username ${MONGODB_USER_TGT} --password "${MONGODB_PASSWD_TGT}" --eval "db.getCollectionNames().forEach(function(c){db[c].drop()});" > /dev/null 2>&1
# # К сожалению, у спарки "mongodump" и "mongorestore" имеются баги, практически исключающие их работу через unix-pipe - нельзя просто выкачивать данные и сразу заливать их в целевую БД - придётся вначале делать "дамп", загружать его файлом, а в контейнере раскрывать.
# Выгрузка "дампа" локально с последующим копированием его на целевой сервер
sudo -u "${MONGODB_SSH_USER_SRC}" ssh -q -o StrictHostKeyChecking=no ${MONGODB_FQDN_SRC} "( mkdir -p /var/tmp/mongodb && mongodump --host=localhost --username=\"${MONGODB_USER_SRC}\" --password=\"${MONGODB_PASSWD_SRC}\" --db=\"${MONGODB_DB_SRC}\" --gzip --archive=\"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" && cat \"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" && rm -f \"/var/tmp/mongodb/dump.${MONGODB_DB_SRC}.gz\" )" | cat > "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz"
# Применение полученного "дампа" в контейнере
docker exec -i bunch-${SITENAME}-mongodb mongorestore -v --host=localhost --username=root --password="${MONGODB_ROOT_PASSWORD_TGT}" --nsInclude "${MONGODB_DB_SRC}.*" --gzip --archive="/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz" > /dev/null 2>&1
rm -f "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb/dump.${MONGODB_DB_SRC}.gz"
else
FORTH=false
echo "$(cdate): Невозможно подключение к локальной тестовой БД MongoDB \"${MONGODB_DB_TGT}\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Невозможно подключение к удалённой исходной БД MongoDB \"${MONGODB_DB_SRC}\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска Shell-команд корректировки данных тестового стенда
function bunch-hook-exec {
# Перебираем всех возможно объявленных SFTP-пользователей
for HOOK_COMMAND_ITEM in "${HOOK_COMMAND[@]}" ; do
# Элементарно пытаемся исполнить команду, сохранённую в переменной
/bin/bash -c "${HOOK_COMMAND_ITEM}"
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Успешно исполнена hook-команда." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при исполнении hook-команды." | tee -a "${LOG}"
fi
done
return ${?}
}
# Функция запуска контейнера Nginx для тестирования
function bunch-nginx-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${FQDN}" -o "${#FQDN[@]}" -eq "0" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-nginx\" конфигурация." | tee -a "${LOG}"
return 1
fi
# Заранее создаём простейший HTML-файл, которым можно будет определить работоспособность тестового стенда
[[ ! -f "${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index."* ]] && {
echo "${SITENAME}" > ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index.html
chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/index.html
}
# Перебираем все FQDN и каждому генерируем отдельный конфигурационный файл
for I in "${!FQDN[@]}" ; do
# Не генерируем конфигурационный файл, если в каком-то из имеющихся обнаружен текущий FQDN
if [ ! -z "${FQDN[$I]}" ] && [[ ! `grep -ril -e "server_name.*\s*${FQDN[$I]}.*;" ${OPSROOT}/bunch/${SITENAME}/etc/nginx/conf.d 2>/dev/null` ]] ; then
# Подставляем путь к файлу SSL-конфигурации, если таковой не задан индивидуально
[ -z "${FQDN_SSL_CONF[$I]}" ] && { FQDN_SSL_CONF[$I]="ssl_wildcard.conf"; }
# Подставляем путь к файлу ACL-конфигурации, если таковой не задан индивидуально
[ -z "${FQDN_ACL_CONF[$I]}" ] && { FQDN_ACL_CONF[$I]="./acl_default.conf"; }
# Пытаемся прочитать содержимое файла ACL-конфигурации
if [ -f "${FQDN_ACL_CONF[$I]}" ] ; then
FQDN_ACL_CONFIGURATION[$I]=$(cat "${FQDN_ACL_CONF[$I]}")
elif [ -f "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_ACL_CONF[$I]}" ] ; then
FQDN_ACL_CONFIGURATION[$I]=$(cat "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_ACL_CONF[$I]}")
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не обнаружен файл ACL-конфигурации \"${FQDN_ACL_CONF[$I]}\"." | tee -a "${LOG}"
fi
# Создаём конфигурационный файл по следующему шаблону
source /usr/local/etc/devops/lib/bunch-nginx-conf.sh.snippet
fi
done
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-nginx \
--network-alias bunch-${SITENAME}-nginx \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
-v /etc/ssl/nginx:/etc/ssl/nginx:ro \
-v /etc/letsencrypt:/etc/letsencrypt:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/nginx:/etc/nginx:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/php:/var/run/php:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
nginx:stable > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-nginx > /var/log/devops/bunch/${SITENAME}/output-nginx.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-nginx"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" запущен." | tee -a "${LOG}"
# Подключаем запущенный контейнер к транзитной связующей сети
docker network connect vianet bunch-${SITENAME}-nginx
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" включён в транзитную связующую сеть \"vianet\"." | tee -a "${LOG}"
# Выясняем IP-адрес, автоматически выданный Nginx-контейнеру для транзитной связующей сети "vianet"
VIAIPWOM=$(docker inspect bunch-${SITENAME}-nginx 2> /dev/null | jq -r '.[0].NetworkSettings.Networks["vianet"].IPAddress' | awk -F "/" '{print $1}')
if [ ! -z "${VIAIPWOM}" -a "${VIAIPWOM}" != "null" ] ; then
# Перебираем все FQDN и каждому генерируем отдельный конфигурационный файл для фронтального web-прокси
for I in "${!FQDN[@]}" ; do
if [ ! -z "${FQDN[$I]}" ] ; then
# Подставляем путь к файлу SSL-конфигурации, если таковой не задан индивидуально
[ -z "${FQDN_SSL_CONF[$I]}" ] && { FQDN_SSL_CONF[$I]="./ssl_wildcard.conf"; }
# Пытаемся прочитать содержимое файла SSL-конфигурации
if [ -f "${FQDN_SSL_CONF[$I]}" ] ; then
FQDN_SSL_CONFIGURATION[$I]=$(cat "${FQDN_SSL_CONF[$I]}")
elif [ -f "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_SSL_CONF[$I]}" ] ; then
FQDN_SSL_CONFIGURATION[$I]=$(cat "${OPSROOT}/bunch/${SITENAME}/etc/nginx/${FQDN_SSL_CONF[$I]}")
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не обнаружен файл SSL-конфигурации \"${FQDN_SSL_CONF[$I]}\"." | tee -a "${LOG}"
fi
# Создаём конфигурационный файл по следующему шаблону
source /usr/local/etc/devops/lib/bunch-nginx-proxying.sh.snippet
fi
done
# Указываем web-серверу фронтального контейнера принять обновлённую конфигурацию
docker exec -i ${FRONTNAME} /bin/bash -c "nginx -s reload" > /dev/null 2>/dev/null
if [ "${?}" -eq "0" ] ; then
echo "$(cdate): Проксирование от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\" успешно налажено." | tee -a "${LOG}"
# Информируем об активации и доступности FQDN
for FQDN_ITEM in "${FQDN[@]}" ; do
echo "$(cdate): Подключения к тестовой площадке принимаются по адресу \"http[s]://${FQDN_ITEM}/\"." | tee -a "${LOG}"
# Для полноты картины тестируем доступность извне доменного имени запускаемого тестового стенда
FQDN_ITEM_IP=$(dig +short "${FQDN_ITEM}")
if [ ! -z "${FQDN_ITEM_IP}" ] ; then
if [ "$(ip address | grep -i -c ${FQDN_ITEM_IP})" -eq "0" ] ; then
echo "$(cdate): IP-адрес сопоставленный с доменным именем \"${FQDN_ITEM}\" не ведёт на этот тестовый стенд!" | tee -a "${LOG}"
fi
else
echo "$(cdate): Доменному имени \"${FQDN_ITEM}\" тестового стенда в DNS не сопоставлен IP-адрес!" | tee -a "${LOG}"
fi
done
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": невозможно применить конфигурацию на фронтальном web-прокси." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при наладке проксирования от фронтального web-прокси к контейнеру \"bunch-${SITENAME}-nginx\": не получен внутренний IP-адрес контейнера в транзитной сети \"vianet\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при включении контейнера \"bunch-${SITENAME}-nginx\" в транзитную сеть \"vianet\"." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-nginx\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера Nginx для тестирования
function bunch-nginx-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-nginx > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-nginx)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nginx\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-nginx\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера PHP-FPM для тестирования
function bunch-php-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${PHPFPM_VER}" -o ! -f ${OPSROOT}/bunch/${SITENAME}/etc/php/${PHPFPM_VER}/fpm/php.ini ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-php\" конфигурация." | tee -a "${LOG}"
return 1
fi
# Заранее создаём простейший PHP-скрипт, которым можно будет определить работоспособность тестового стенда
[ ! -f "${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php" ] && {
echo "<?php phpinfo(); ?>" > ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php
chown www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${SITEROOT}/info.php
}
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-php \
--network-alias bunch-${SITENAME}-php \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
-v ${OPSROOT}/bunch/${SITENAME}/etc/php:/etc/php:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/msmtprc:/etc/msmtprc:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/php:/var/run/php:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/var/run/memcached:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
--tmpfs /run --tmpfs /tmp \
--tmpfs /var/lib/php/sessions \
--network backnet-${SITENAME} \
--detach \
selfmade:php-${PHPFPM_VER}-fpm > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-php > /var/log/devops/bunch/${SITENAME}/output-php.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-php"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-php\" запущен." | tee -a "${LOG}"
# Для совместимости с разнородными web-проектами дублируем файловые "сокеты" разноименными ссылками
if [[ -S ${OPSROOT}/bunch/${SITENAME}/var/run/php/php-fpm.sock ]] ; then
ln -P ${OPSROOT}/bunch/${SITENAME}/var/run/php/php-fpm.sock ${OPSROOT}/bunch/${SITENAME}/var/run/php/php${PHPFPM_VER}-fpm.sock 2>/dev/null
fi
# Пробуем применить возможно заданные для контейнера crontab-записи
if [ "${#PHPFPM_CRON_COMMAND[@]}" -ge "1" ] ; then
# Получаем действующий список задач и удаляем возможно уже имеющиеся crontab-записи для этого контейнера
CRONTAB=$(crontab -u root -l)
CRONTAB=`echo "${CRONTAB}" | sed -E "s/.*docker\s*exec.*bunch-${SITENAME}-php.*//gI"`
# Перебираем возможно заданные crontab-записи и дополняем ими crontab-таблицу
for I in "${!PHPFPM_CRON_COMMAND[@]}" ; do
if [ ! -z "${PHPFPM_CRON_COMMAND[$I]}" -a ! -z "${PHPFPM_CRON_SCHEDULE[$I]}" ] ; then
CRONTAB="${CRONTAB}\n${PHPFPM_CRON_SCHEDULE[$I]} docker exec -i --user www-data bunch-${SITENAME}-php /bin/bash -c \"${PHPFPM_CRON_COMMAND[$I]}\" >> ${OPSROOT}/bunch/${SITENAME}/var/log/crontab.log 2>&1 &"
fi
done
# Сохраняем изменённый набор crontab-записей
echo -e "${CRONTAB}" | crontab -u root -
echo "$(cdate): Для контейнера \"bunch-${SITENAME}-php\" применены crontab-задачи." | tee -a "${LOG}"
fi
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-php\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера PHP-FPM для тестирования
function bunch-php-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-php > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-php)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-php\" успешно остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-php\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера NodeJS для тестирования
function bunch-nodejs-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${NODEJS_VER}" ] ; then
FORTH=false
echo "$(cdate): Отсутствует необходимая для запуска контейнера \"bunch-${SITENAME}-nodejs\" конфигурация." | tee -a "${LOG}"
return 1
fi
# Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
[ -d "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}" ] || mkdir -p "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}"
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}
# Заранее создаём простейшее NodeJS-приложение, которым можно будет определить работоспособность тестового стенда
[ ! -f "${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}/package.json" ] && {
source /usr/local/etc/devops/lib/bunch-nodejs-example-app.sh.snippet
chown -R www-data:www-data ${OPSROOT}/bunch/${SITENAME}/${NODEJS_APP_DIR}
}
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-nodejs \
--network-alias bunch-${SITENAME}-nodejs \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env NODE_ENV="development" \
--workdir ${NODEJS_APP_DIR} \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/var/run/memcached:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/www:/var/www:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--user www-data \
--detach \
selfmade:nodejs-${NODEJS_VER} > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-nodejs > /var/log/devops/bunch/${SITENAME}/output-nodejs.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-nodejs"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nodejs\" запущен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-nodejs\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера NodeJS для тестирования
function bunch-nodejs-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-nodejs > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-nodejs)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-nodejs\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-nodejs\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера Memcached для тестирования
function bunch-memcached-start {
# Пробуем подготовить строку инициализации для Memcached
while read LINE ; do
VAR=`echo "${LINE}" | grep -E "^-" | tr -d '[:blank:]'`
[ ! -z "${VAR}" ] && { MEMCACHED_VARS="${MEMCACHED_VARS} ${VAR}"; }
done < ${OPSROOT}/bunch/${SITENAME}/etc/memcached/memcached.conf
if [ "${?}" -ne "0" -o -z "${MEMCACHED_VARS}" ] ; then
FORTH=false
echo "$(cdate): Сбой при подготовке параметров инициализации для контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
return 1
fi
# Официальный контейнер Memcached может создавать файловые сокеты только в "/tmp"
# (подменяем общепринятые "/var/run" на "/tmp" с последующим перемонтированием снова в "/var/run")
MEMCACHED_VARS=`echo "${MEMCACHED_VARS}" | sed -E 's#-s\s*(\/var\/run|\/run)\/memcached#-s \/tmp\/memcached#gI'`
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-memcached \
--network-alias bunch-${SITENAME}-memcached \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/memcached:/tmp/memcached:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
memcached:1.5 /bin/bash -c "mkdir -p /tmp/memcached && memcached ${MEMCACHED_VARS} logfile /dev/stdout" > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-memcached > /var/log/devops/bunch/${SITENAME}/output-memcached.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-memcached"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-memcached\" запущен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера Memcached для тестирования
function bunch-memcached-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-memcached > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-memcached)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-memcached\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-memcached\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера MongoDB для тестирования
function bunch-mongodb-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MONGODB_ROOT_PASSWORD_TGT}" ] ;then
FORTH=false
echo "$(cdate): Без пароля суперпользователя СУБД запуск контейнера \"bunch-${SITENAME}-mongodb\" невозможен." | tee -a "${LOG}"
return 1
fi
# Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
[ -d "${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb
[ -d "${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb
chown -R 999:999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MONGODB_DB_TGT}" ] && { MONGODB_DB_TGT=${MONGODB_DB_SRC}; }
[ -z "${MONGODB_USER_TGT}" ] && { MONGODB_USER_TGT=${MONGODB_USER_SRC}; }
[ -z "${MONGODB_PASSWD_TGT}" ] && { MONGODB_PASSWD_TGT=${MONGODB_PASSWD_SRC}; }
# При наличии параметров подключения к пользовательской БД пробуем создать Bash-скрипт для инициализации аккаунта и БД в MongoDB
if [ ! -z "${MONGODB_DB_TGT}" -a ! -z "${MONGODB_USER_TGT}" -a ! -z "${MONGODB_PASSWD_TGT}" ] ; then
# (Bash-скрипт впоследствии автоматически запускается внутри контейнера)
cat << EOS | sed "s/^[ \t]*//" > ${OPSROOT}/bunch/${SITENAME}/var/tmp/mogodb/initdb.d/1.create-db-user.sh
# Creating in MongoDB a database and user as its owner:
mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db=db.getSiblingDB('${MONGODB_DB_TGT}');db.createUser({user:'${MONGODB_USER_TGT}',pwd:'${MONGODB_PASSWD_TGT}',roles:[{role:'readWrite',db:'${MONGODB_DB_TGT}'}]});db.init.insert({sitename:'${MONGODB_DB_TGT}'})"
EOS
# (indicator strictly on beginning line)
if [ "${?}" -ne "0" ] ; then
FORTH=false
echo "$(cdate): Сбой при подготовке init-скрипта для контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
return 1
fi
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d/1.create-db-user.sh
fi
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-mongodb \
--network-alias bunch-${SITENAME}-mongodb \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env MONGO_INITDB_ROOT_USERNAME="root" \
--env MONGO_INITDB_ROOT_PASSWORD="${MONGODB_ROOT_PASSWORD_TGT}" \
-v ${OPSROOT}/bunch/${SITENAME}/var/tmp/mongodb/initdb.d:/docker-entrypoint-initdb.d:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/mongodb:/etc/mongodb:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/lib/mongodb:/var/lib/mongodb:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb:/var/run/mongodb:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
mongo:${MONGODB_VER} --config /etc/mongodb/mongod.conf > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-mongodb > /var/log/devops/bunch/${SITENAME}/output-mongodb.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-mongodb"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mongodb\" запущен." | tee -a "${LOG}"
# Циклический тест в ожидании возможности подключения к MongoDB
echo -n "$(cdate): Ожидаем доступности сервиса \"MongoDB\"... " | tee -a "${LOG}"
front-nginx-log-show
unset FORTH_MONGODB
for try in {1..30} ; do
[[ $(docker exec -i bunch-${SITENAME}-mongodb mongo localhost/admin --username root --password "${MONGODB_ROOT_PASSWORD_TGT}" --eval "db.adminCommand('listDatabases')" 2>/dev/null | grep -i "name" | grep -i -c -e "\"${MONGODB_DB_TGT}\"") -eq "1" ]] && {
FORTH_MONGODB=true
break
} || { FORTH_MONGODB=false; echo -n "#"; sleep 1; }
done ; echo | tee -a "${LOG}"
if [ "${FORTH_MONGODB}" != "true" ] ; then
FORTH=false
echo "$(cdate): Невозможно подключиться к MongoDB в контейнере \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
return 1
fi
# Разрешаем чтение и запись в файловые сокеты специфичным пользователю "www-data" и группе "www-data"
setfacl --recursive --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}/var/run/mongodb
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера MongoDB для тестирования
function bunch-mongodb-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-mongodb > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-mongodb)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mongodb\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-mongodb\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера MySQL для тестирования
function bunch-mysql-start {
# Проверяем наличие обязательных переменных конфигурации
if [ -z "${MYSQL_ROOT_PASSWORD_TGT}" ] ;then
FORTH=false
echo "$(cdate): Без пароля суперпользователя СУБД запуск контейнера \"bunch-${SITENAME}-mysql\" невозможен." | tee -a "${LOG}"
return 1
fi
# Создаём необходимые директории и передаём их во владение пользователя, от имени которого запущены сервисы внутри контейнера
[ -d "${OPSROOT}/bunch/${SITENAME}/var/lib/mysql" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql
[ -d "${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d" ] || mkdir -p ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d
chown -R 999:33 ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql
chown -R 999:33 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d
# Укладываем пароль суперпользователя в специальном файле конфигурации, обеспечивая возможность подключения без указания пароля
echo -e "[client]\nuser=root\npassword=\"${MYSQL_ROOT_PASSWORD_TGT}\"\n" > "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
chown 999 "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
chmod go-rwx "${OPSROOT}/bunch/${SITENAME}/etc/mysql/${SITENAME}.cnf"
# Если целевые БД, имя пользователя и пароль явно не заданы, то принимаем их одинаковыми с исходными
[ -z "${MYSQL_DB_TGT}" ] && { MYSQL_DB_TGT=${MYSQL_DB_SRC}; }
[ -z "${MYSQL_USER_TGT}" ] && { MYSQL_USER_TGT=${MYSQL_USER_SRC}; }
[ -z "${MYSQL_PASSWD_TGT}" ] && { MYSQL_PASSWD_TGT=${MYSQL_PASSWD_SRC}; }
# При наличии параметров подключения к пользовательской БД пробуем создать SQL-скрипт для инициализации аккаунта и БД в MySQL
if [ ! -z "${MYSQL_DB_TGT}" -a ! -z "${MYSQL_USER_TGT}" -a ! -z "${MYSQL_PASSWD_TGT}" ] ; then
# (SQL-скрипт впоследствии автоматически запускается внутри контейнера)
cat << EOS | sed "s/^[ \t]*//" > ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql
--
-- Creating in MySQL a database and user as its owner:
--
DROP DATABASE IF EXISTS ${MYSQL_DB_TGT};
CREATE DATABASE ${MYSQL_DB_TGT};
GRANT ALL PRIVILEGES ON ${MYSQL_DB_TGT}.* TO '${MYSQL_USER_TGT}'@'%' IDENTIFIED BY '${MYSQL_PASSWD_TGT}';
FLUSH PRIVILEGES;
EOS
# (indicator strictly on beginning line)
if [ "${?}" -ne "0" ] ; then
FORTH=false
echo "$(cdate): Сбой при подготовке init-скрипта для контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
return 1
fi
chown 999 ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d/1.create-db-user.sql
fi
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-mysql \
--network-alias bunch-${SITENAME}-mysql \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD_TGT}" \
-v ${OPSROOT}/bunch/${SITENAME}/var/tmp/mysql/initdb.d:/docker-entrypoint-initdb.d:ro \
-v ${OPSROOT}/bunch/${SITENAME}/etc/mysql:/etc/mysql:ro \
-v ${OPSROOT}/bunch/${SITENAME}/var/lib/mysql:/var/lib/mysql:rw \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
mysql:5.7 > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-mysql > /var/log/devops/bunch/${SITENAME}/output-mysql.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-mysql"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mysql\" запущен." | tee -a "${LOG}"
# Циклический тест в ожидании возможности подключения к MySQL
# (на запуск и конфигурирование приложения внутри контейнера может понадобиться до одной-двух минут)
echo -n "$(cdate): Ожидаем доступности сервиса \"MySQL\"... " | tee -a "${LOG}"
front-nginx-log-show
unset FORTH_MYSQL
for try in {1..300} ; do
[[ $(docker exec -i bunch-${SITENAME}-mysql mysql --defaults-file=/etc/mysql/${SITENAME}.cnf -h localhost -u root -e "show databases;" 2>/dev/null | grep -v -i "database" | grep -i "mysql") ]] && {
FORTH_MYSQL=true
break
} || { FORTH_MYSQL=false; echo -n "#"; sleep 1; }
done ; echo | tee -a "${LOG}"
if [ "${FORTH_MYSQL}" != "true" ] ; then
FORTH=false
echo "$(cdate): Невозможно подключиться к MySQL в контейнере \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
return 1
fi
# Разрешаем чтение и запись в файловые сокеты специфичным пользователю "www-data" и группе "www-data"
setfacl --recursive --modify user:www-data:rwX,group:www-data:rwX ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера MySQL для тестирования
function bunch-mysql-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-mysql > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-mysql)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-mysql\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-mysql\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция запуска контейнера "phpMyAdmin" для тестирования
function bunch-pma-start {
# Запускаем контейнер
docker run --rm --name bunch-${SITENAME}-pma \
--network-alias bunch-${SITENAME}-pma \
--env TZ="`cat /etc/timezone`" \
--env MODE_ENV="${MODE_ENV}" \
--env PMA_HOST="bunch-${SITENAME}-mysql" \
--env PMA_PORT=3306 \
-v ${OPSROOT}/bunch/${SITENAME}/var/run/mysqld:/var/run/mysqld:rw \
--tmpfs /run --tmpfs /tmp \
--network backnet-${SITENAME} \
--detach \
phpmyadmin/phpmyadmin:latest > /dev/null
# Направляем вывод подсистемы журналирования контейнера в текстовый файл
docker logs --follow bunch-${SITENAME}-pma > /var/log/devops/bunch/${SITENAME}/output-pma.log 2>&1 &
# Проверяем успешность выполнения процедуры запуска
if [ `docker ps | grep -c -i "bunch-${SITENAME}-pma"` -ne 0 ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-pma\" запущен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при запуске контейнера \"bunch-${SITENAME}-pma\"." | tee -a "${LOG}"
fi
return ${?}
}
# Функция остановки контейнера "phpMyAdmin" для тестирования
function bunch-pma-stop {
# Пробуем остановить контейнер
docker stop bunch-${SITENAME}-pma > /dev/null 2>&1
if [ "$(docker ps | grep -c -i bunch-${SITENAME}-pma)" -eq "0" ] ; then
echo "$(cdate): Контейнер \"bunch-${SITENAME}-pma\" остановлен." | tee -a "${LOG}"
else
FORTH=false
echo "$(cdate): Сбой при остановке контейнера \"bunch-${SITENAME}-pma\"." | tee -a "${LOG}"
fi
return ${?}
}