MySQL в контейнере Docker - часть II

В первой части мы рассмотрели то, как можно создать контейнер Docker с работающей в нем MySQL, сделали основу для контейнера, обновили адресную привязку и сделали данные постоянными. Однако, осталась еще пара штрихов, которые необходимо сделать.

1. Складываем все вместе

Не хотите следовать всем пошаговым инструкциям, чтобы получить ваши файлы правильно? Вот готовый продукт (плюс некоторые вспомогательные сценарии для сборки, запуска сервера и подключения к клиенту cli.)Также эти файлы можно найти на гитхабе.
Dockerfile
FROM ubuntu
RUN dpkg-divert --local --rename --add /sbin/initctl
RUN ln -s /bin/true /sbin/initctl
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install mysql-client mysql-server
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf
ADD ./startup.sh /opt/startup.sh
EXPOSE 3306
CMD ["/bin/bash", "/opt/startup.sh"]
<h5>startup.sh</h5>
#!/bin/bash
if [ ! -f /var/lib/mysql/ibdata1 ]; then
mysql_install_db
/usr/bin/mysqld_safe &
sleep 10s
echo "GRANT ALL ON *.* TO admin@'%' IDENTIFIED BY 'changeme' WITH GRANT OPTION; FLUSH PRIVILEGES" | mysql
killall mysqld
sleep 10s
fi
/usr/bin/mysqld_safe

2. Вспомогательные скрипты

build.sh
#!/bin/sh
docker build -t mysql .
run-server.sh
#!/bin/sh
docker run -d -p 3306:3306 -v /data/mysql:/var/lib/mysql mysql
run-client.sh
#!/bin/sh
TAG="mysql"
CONTAINER_ID=$(docker ps | grep $TAG | awk '{print $1}')
IP=$(docker inspect $CONTAINER_ID | python -c 'import json,sys;obj=json.load(sys.stdin);print obj[0]["NetworkSettings"]["IPAddress"]')
mysql -u admin -p -h $IP

MySQL в контейнере Docker - часть I

Тема MySQL казалось бы, уже сто раз обсосана, но все-таки она является одной из важных, поскольку MySQL хранит все наши данные уже не один десяток лет, она одна
из ведущих систем управления базами данных, и я думаю что вам понравится тема, которую я хочу предложить.

На первый взгляд, создание контейнера MySQL для Docker довольно легко, но если вы хотите подключиться (не уверены, что сервер mysql, который не позволил бы этого, был бы хорош) и отделить ваши базы данных от вашего контейнера (я предполагаю, что Вы не хотите, чтобы они уходили с вашим контейнером), тогда есть несколько проблем.

Я начинаю эту статью с упрощенного примера (с эфемерным хранилищем базы данных и без возможности подключения). Вы можете перейти на gist (в котором есть файлы для сборки контейнера, а также некоторые скрипты для его сборки и запуска), если все станет слишком скучным или запутанным.

1. Делаем основу

Сейчас мы наметим Dockerfile на основе Ubuntu.
FROM ubuntu
RUN dpkg-divert --local --rename --add /sbin/initctl
RUN ln -s /bin/true /sbin/initctl
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install mysql-server
EXPOSE 3306
CMD ["/usr/bin/mysqld_safe"]
затем создадим и пометим его:
docker build -t mysql-ubu .
Теперь у нас есть полностью функционирующий контейнер, который мы можем запустить так:
docker run -d -p 3306:3306 mysql-ubu
Он будет работать, но такой подход не полезен, потому что:

mysql слушает на адресе 127.0.0.1, таким образом, мы можем только соединиться изнутри контейнера;
у нас есть только пользователь root, и ему разрешено входить только из контейнера;
поскольку наши данные записываются внутри контейнера, если мы теряем контейнер или нам нужно что-то изменить (например, применить Обновление безопасности), мы теряем наши данные.

2. Обновим адресную привязку

Первый шаг должен заставить наш сервер mysql слушать больше, чем localhost так, чтобы мы могли соединиться извне с нашим контейнером.

Для этого нам нужно обновить bind-адрес в /etc/mysql/my.cnf от 127.0.0.1 до 0.0.0.0 (то есть сделаем привязку mysqld к каждой доступной сети вместо просто localhost.)

Мы могли бы просто добавить поддержку файлика /etc/mysql/my.cnf и добавить его в наш контейнер. а именно в Dockerfile:
ADD ./my.cnf /etc/mysql/my.cnf
Или мы можем обновить это свойство. Я предпочитаю этот способ, потому что я получаю самую последнюю конфигурацию из своей установки и просто обновляю то, что мне нужно. Мы можем добавить соответствующую команду sed в наш Dockerfile после установки mysql-server.
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf
Даже при том, что mysqld сейчас слушает все, мы все еще не можем войти, потому что пользователь root только имеет доступ от localhost.

Виновник торжества — тот самый дельфин :)
Нам нужно добавить учетную запись администратора для администрирования баз из-за пределов контейнера. Для того, чтобы добавить учетную запись, нам нужно, чтобы сервер mysql работал. Так как отдельные строки в Dockerfile создают различные коммиты, и коммиты сохраняют только состояние файловой системы (не состояние памяти), нам нужно втиснуть обе команды в один коммит:
RUN /usr/sbin/mysqld & \
sleep 10s &&\
echo "GRANT ALL ON *.* TO admin@'%' IDENTIFIED BY 'changeme' WITH GRANT OPTION; FLUSH PRIVILEGES" | mysql
EXPOSE 3306
CMD ["/usr/bin/mysqld_safe"]
А теперь давайте все соберем и запустим.
docker build -t mysql-ubu .
docker run -d -p 3306:3306 mysql-ubu
Теперь попробуем подключиться. Для того, чтобы сделать это, нам нужно выяснить ip контейнера, и чтобы найти это, нам нужен идентификатор нашего контейнера. Это достаточно легко сделать вручную с помощью docker ps и docker inspect, но вы также можете написать сценарий:
CONTAINER_ID=$(docker ps | grep mysql | awk '{print $1}')
IP=$(docker inspect $CONTAINER_ID | python -c 'import json,sys;obj=json.load(sys.stdin);print obj[0]["NetworkSettings"]["IPAddress"]')
mysql -u admin -p -h $IP
Теперь у нас есть полнофункциональный контейнер mysql! Это здорово, но мы вкладываем много доверия в этот контейнер, полагаясь на него, чтобы отслеживать наши базы данных, не говоря уже о том, что мы бы облажались, если мы когда-нибудь захотели обновить что-нибудь. Почему? Да потому, что данные непостоянны. Как их сделать постоянными — читайте далее.

3. Делаем данные постоянными

Нам нужно удалить нашу зависимость от этого конкретного контейнера, и для этого нам нужно экстернализировать (попросту говоря, сделать внешним) наш каталог данных. Это легко, но вызывает проблемы. При запуске нашего контейнера мы просто посылаем в контейнер команду -v /host/path:/container/path, и поставляемый каталог на вашем хост-компьютере используется в контейнере везде, где мы указываем.

Таким образом, чтобы сохранить базы данных из нашего контейнера в /data/mysql на вашем хост-компьютере, мы обновляем нашу команду run:
docker run -d -p 3306:3306 -v /data/mysql:/var/lib/mysql mysql-ubu
Проблема в том, что мы просто уничтожим наши системные таблицы, когда мы заменим /var/lib/mysql нашим пустым каталогом. Это также означает, что мы потеряли нашего администратора. Это сложно объяснить, потому что мы не можем инициализировать каталог (или добавить нашего администратора), пока каталог данных не будет виден контейнеру (во время выполнения), но мы не хотим инициализировать каталог каждый раз, когда мы запускаем либо. Весь смысл экстернализации каталога данных заключается в том, чтобы контейнер мог «приходить» и «уходить» без потери данных.

Чтобы решить эту проблему, давайте создадим скрипт, который для замены просто вызывает /usr/bin/mysqld_safe.

Во-первых, давайте напишем наш сценарий ( startup.sh) для инициализации только в том случае, если каталог данных еще не заполнен.
#!/bin/bash
if [ ! -f /var/lib/mysql/ibdata1 ]; then
mysql_install_db
fi
/usr/bin/mysqld_safe
Это будет искать файл «ibdata1» в наших данных как способ определить, нужно ли нам инициализировать каталог или нет. После того, как каталог данных был инициализирован (или определен уже инициализирован), мы можем продолжить запуск сервера.

И теперь мы обновим Dockerfile, чтобы добавить startup.sh к контейнеру и вызвать его вместо mysqld_safe:
ADD ./startup.sh /opt/startup.sh
CMD ["/bin/bash", "/opt/startup.sh"]
Мы также можем добавить в нашего admin пользователя со скриптом следующее:
#!/bin/bash
if [ ! -f /var/lib/mysql/ibdata1 ]; then
mysql_install_db
/usr/bin/mysqld_safe &
sleep 10s
echo "GRANT ALL ON *.* TO admin@'%' IDENTIFIED BY 'changeme' WITH GRANT OPTION; FLUSH PRIVILEGES" | mysql
killall mysqld
sleep 10s
fi
/usr/bin/mysqld_safe
И, конечно, мы должны также удалить строку запуска из Dockerfile, который делал то же самое, но получал отмену как только мы расширили каталог данных.

Possibly tempdb out of space or a system table is inconsistent

Если вы видите у себя ошибку
A failure was detected while collecting facts. Possibly tempdb out of space or a system table is inconsistent
то ее причины в следующем:
  • Места на диске не хватает.
  • Приказал долго жить жесткий диск.
  • 3апущена виртуальная машина и может быть такое, что она жестко ограничена по ресурсам.
Поэтому для исправления ошибки рекомендуется:
  • проверить диск на ошибки.
  • почистить диск, если места мало или подготовить новый, более вместительный.
  • увеличить лимит виртуальной машины (тот же виртуальный жесткий диск например).

Как проверить подключение к MySQL

Ниже приведен простой сценарий PHP для проверки возможности подключения к базе данных MySQL. Результат этого сценария отображает имена таблиц, присутствующих в указанной базе данных.
<?
$connect=mysql_connect("dbserver","dbuser","dbpassword") or die("Unable to Connect");
mysql_select_db("dbname") or die("Could not open the db");
$showtablequery="SHOW TABLES FROM dbname";
$query_result=mysql_query($showtablequery);
while($showtablerow = mysql_fetch_array($query_result))
{
echo $showtablerow[0]." ";
} 
?>
Пояснение к используемым в скрипте переменным:
  • dbserver: Укажите значение localhost для хостинга Linux. Для хостинга Windows необходимо указать IP-адрес сервера MySQL.
  • dbname: Укажите полное имя базы данных, включая префикс. Пример: user_base.
  • dbpassword: Укажите пароль для указанного выше пользователя базы данных.

Работа с БД MySQL через SSH

Импорт и экспорт БД в систему с использованием терминала Linux.

При переносе сайта с одно хостинга на другой встает вопрос по переносы базы данных. Часто вебмастер сталкивается с проблемой экспорта/импорта базы данных большого размера, если эту процедуру проделывать с помощью phpMyAdmin, то в большинстве случаев можно столкнуться с ошибками следующего рода – привешен максимальный размер файла для загрузки, превышено максимальное время выполнение скрипта.

Разумеется, эти параметры можно отредактировать в php.ini в большую сторону, но порой даже максимальные значение не гарантируют успешность импорта/экспорта (допустим ваша БД в архиве весит 2 ГБ). В таком случае следует воспользоваться командами SSH, и работать с базой непосредственно из командной строки (использовать терминал). Для этого соединяемся с сервером на 22 порт с помощью putty.
Для импорта файла базы данных:
mysql -u пользовательБД -p имяБД < файлдампаБД
Для экспорта
mysqldump -u пользовательБД -p имяБД > имя_файла
Внимание! Эти команды работают с файлом в формате .sql, т.е. не с архивом.
Таким способом можно делать бэкап и затем восстанавливать БД большого размера, причем с быстрой скоростью.

Host 'hostname' is blocked

Если Вы получаете сообщение об ошибке:
Host 'hostname' is blocked because of many connection errors. Unblock with 'mysqladmin flush-hosts'
Это означает, что mysqld получил много (max_connect_errors) запросов подключения с компьютера hostname, которые были прерваны в середине. После max_connect_errors потерпевших неудачу запросов mysqld считает, что что-то неправильно (подобно нападению хакеров) и блокирует дальнейшие соединения с этой машины, пока кто-то не выполняет команду mysqladmin flush-hosts.
По умолчанию mysqld блокирует компьютер после 10 ошибок подключения. Вы можете легко скорректировать это, запуская сервер так:
SET GLOBAL max_connect_errors=10000
Обратите внимание, что, если Вы получаете это сообщение об ошибках для данного компьютера, Вы должны сначала проверить, что не имеется чего-нибудь неправильного с TCP/IP подключениями. Если Ваши TCP/IP подключения не работают увеличение переменной max_connect_errors не поможет!
Для Вашего виртуального или выделенного сервера: Разблокировать все заблокированные хосты можно командой
mysqladmin flush-hosts -u root -p
После чего Вас попросят ввести пароль пользователя root MySQL.
Если вы используете веб-хостинг — обратитесь к администрации с просьбой очистить список заблокированных хостов. В случае повторных ошибок рекомендуется проверить конфигурацию ваших скриптов сайта.

Что из себя физически представляет база данных MySQL

Давайте разберемся, что именно из себя представляет наша база.

Наверняка все вы когда-то работали (или работаете сейчас) с БД MySQL. И когда мы ее открываем в phpmyadmin, такая база представляет собой красивую табличку.


И конечно, большинство пользователей полагает, что базы данных не хранятся в файлах.

Но кто из вас по настоящему желает познать вид MySQL? Давайте же узнаем, насколько же глубока кроличья нора.
Для этого мы перейдем туда, где базы хранятся.

В MacOS, на которой работает тестовый стенд, путь будет таким:
mac⁩ ▸ ⁨Программы⁩ ▸ ⁨MAMP⁩ ▸ ⁨db⁩ ▸ ⁨mysql57⁩
Более расширенный путь здесь публиковать не станем, чтобы не забивать мозг лишней сейчас информацией.
Папка mysql в которой хранятся базы данных из примера выглядит так.

Что мы тут видим?
Во-первых, непонятные файлы с расширениями MYI, FRM, MYD. Давайте поставим их на свои места.

Файлы MYI

Файлы MYI для таблицы MyISAM содержит индексы таблицы.

Заголовок MYI

Файл .MYI начинается с заголовка, содержащего информацию о параметрах, размерах файлов и ключей. В терминологии MySQL ключ — это то, что вы создаете с помощью запроса CREATE [UNIQUE] INDEX.

Программные файлы, которые читают и записывают заголовки .MYI, находятся в каталоге ./myisam: mi_open.c содержит подпрограммы, которые записывают каждый раздел заголовка, mi_create.c имеет подпрограмму, которая вызывает подпрограммы mi_open.c по порядку, а myisamdef.h имеет определения структуры, соответствующие тому, что мы собираемся описать.

Ключевые значения .MYI

Ключевые значения находятся в блоках (термин MySQL для страниц). Блок содержит значения только из одного индекса.
Каждый ключ содержит все содержимое всех столбцов, включая конечные пробелы в столбцах CHAR. Переднего усечения нет. Обратного усечения нет. (Может произойти усечение пробела, если keyseg-> flag флаг HA_SPACE_PACK включен.)

Для таблиц с фиксированной строкой: указатель представляет собой номер фиксированного размера (4 байта), который содержит порядковый номер строки. Первая строка — это запись № 0000. Этот элемент аналогичен ROWID или RID (идентификатор строки), который используют другие СУБД. Для таблиц с динамическими строками: указатель — это смещение в файле .MYD.
Нормальная длина блока составляет 0x0400 (1024) байтов.

Файл MYD

Файлы MYD — это файл, содержащий в себе данные базы. Проще сказать красивую табличку из примера выше и не только ее.

Файл FRM

Файлы FRM — это файлы форм, используемые в качестве файлов формата базы данных MySQL. Когда в MySQL создается новая таблица, появляется файл FRM. Эти файлы форм используются для определения полей в таблице. Он также используется для представления информации о форматировании структуры таблицы.

Файлы FRM хранятся в той же папке, в которой хранится база данных этой таблицы файлов FRM. Файлы FRM — это двоичные файлы. Существует множество программ, которые используют этот тип расширения файла и, следовательно, могут использоваться для открытия файла FRM. Некоторые из них — Microsoft Visual Basic, MySQL, iBlaze, Photoframe, Corel WorldPerfect, Corel Painter и MYOB. Среди них Photoframe использует файл .frm для файла изображения изображения и Corel Painter для файлов стека кадров, а остальные используются для файлов форм.

Как обновить MySQL 5.5 до 5.6/5.7 или MariaDB 5.5 до 10.x в Linux

Примечание: Обновление можно выполнить в интерфейсе командной строки с приведенными ниже инструкциями на свой страх и риск. Эта задача должна выполняться системным администратором или опытным человеком. Если вы к таковым не относитесь, лучше обновление самостоятельно не делать.
Чтобы узнать свой дистрибутив Linux, выполните:
cat /etc/*-release
Предупреждение: Прямое обновление с MySQL 5.1 до MySQL 5.6/5.7 приведет к нарушению структуры таблиц.
Настоятельно рекомендуется создать моментальный снимок сервера (полную резервную копию) перед обновлением. Выполните обновление MySQL на свой страх и риск.
Перед началом обновления MySQL остановите службу WatchDog, если она используется на сервере. Проверьте его состояние с помощью команды service watchdog status.
Примечание: MySQL не будет обновлен в операционных системах на базе CentOS/RHEL, если он был установлен из репозитория Webtatic Yum:
rpm -qa | grep -i mysql
mysql55w-server-5.5.57-1.w6.x86_64
Чтобы начать обновление MySQL/MariaDB, подключитесь к серверу через SSH от имени root/суперпользователя и следуйте инструкциям для вашей операционной системы:

CentOS 8

По умолчанию CentOS 8 поставляется с MariaDB 10.3.
Сохраняем все базы для того, чтобы не потерять их
mysqldump -uroot --verbose --all-databases --routines --triggers > /tmp/all-databases.sql
Останавливаем MariaDB
service mariadb stop
Удаляем дополнительные конфликтующие пакеты
yum remove mariadb-gssapi-server -y
На всякий случай копируем папку с базами
cp -v -a /var/lib/mysql/ /var/lib/mysql_backup
Настройка репозитория MariaDB: откройте страницу Настройка репозиториев MariaDB, выберите дистрибутив ОС, выпуск и желаемую версию MariaDB. После этого конфигурация, которая должна быть добавлена в /etc/yum.repos.d/MariaDB.repo появится в файле.
Пример для MariaDB 10.4:
Откройте/создайте файл MariaDB.repo в любом текстовом редакторе. В этом примере мы используем редактор vi:
vi /etc/yum.repos.d/MariaDB.repo
Добавьте в файл содержимое, приведенное ниже:
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.4/centos8-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
priority=1
module_hotfixes=1
Элемент конфигурации module_hotfixes=1 — это обходной путь для того, что MariaDB иногда сообщает об ошибке dnf. См. MDEV-20673 для получения более подробной информации.
Сохраните изменения и закройте файл.
Сбрасываем кэш:
yum clean all
Начинаем обновление:
yum install -y MariaDB-server galera-4 MariaDB-client MariaDB-shared MariaDB-backup MariaDB-common
После завершения обновления перезапускаем MariaDB:
systemctl restart mariadb
Обновляем все базы:
mysql_upgrade -uroot
Рестартуем сервис
systemctl restart mariadb
Восстанавливаем selinux (если selinux отключен — делать не надо)
restorecon -v /var/lib/mysql/*