[[ Доступ readonly через MySQL-Proxy ]]

MySQL

Доступ readonly через MySQL-Proxy

Ubuntu 14.04
mysql-proxy 0.8.1

Используем MySQL-Proxy для следующей задачи: Нужно поднять зеркало сайта на CMS Drupal.
Т.к. при работе CMS вносит изменения в БД, репликация постоянно ломается, как решение - игнорировать все запросы кроме SELECT.
Таким образом получаем readonly доступ к базе, но не вызывая ошибок, как если бы ограничили права.

Установка

Установка

sudo apt-get install mysql-proxy

Настройка

/etc/default/mysql-proxy

ENABLED="true"
OPTIONS="
--log-file=/var/log/mysql-proxy.log
--log-level=message
--user=nobody
--event-threads=8
--keepalive

--admin-username=admin
--admin-password=Pa$sWoRd
--admin-lua-script=/usr/lib/mysql-proxy/lua/admin.lua

--proxy-lua-script=/etc/mysql/mysql-proxy.lua
--proxy-skip-profiling=true
"

Полный список опций

По умолчанию порт для подключения 4040, админский порт 4041.

По умолчанию proxy-backend-addresses = 127.0.0.1:3306

Скрипт admin-lua-script указываем из установочного пакета, без него не запустится

(critical) admin-plugin.c:579: --admin-lua-script needs to be set, <install-dir>/lib/mysql-proxy/lua/admin.lua may be a good value

Стоит отметить так же опции proxy-skip-profiling и event-threads, для нагруженных систем актуально, т.к. прокси заметно будет тормозит работу с БД.

Ротация лога. Из пакета конфига logrotate нет, поэтому создадим свой

/etc/logrotate.d/mysql-proxy

/var/log/mysql-proxy.log {
        size 10M
        missingok
        notifempty
        create 640 root root
        postrotate
                /bin/kill -HUP `cat /var/run/mysql-proxy.pid 2>/dev/null` 2>/dev/null || true
        endscript
}

Создадим пока пустой файл

sudo touch /etc/mysql/mysql-proxy.lua
sudo chmon 640 /etc/mysql/mysql-proxy.lua

Запускаем

/etc/init.d/mysql-proxy start

Проверяем что приложение запустилось

ps aux | grep mysql-proxy

LUA

Все манипуляции с запросами выполняет скрипт на языке LUA. Его придется написать самостоятельно или найти что-то готовое. Из пакета можно взять парочку примеров и библиотек

dpkg -L mysql-proxy | grep lua

В скрипте используются встроенные функции, которые вызываются на разных этапах, см. документацию

Для моей задачи интересна только одна функция read_query - вызывается на этапе чтения запроса от клиента и может возвращать:

  • <ничего не возвращаем> - Передаем запрос в MySQL как есть
  • proxy.PROXY_SEND_QUERY - Передает запросы в MySQL из очереди proxy.queries
  • proxy.PROXY_SEND_RESULT - Прерывает выполнение и возвращает клиенту сразу ответ (resultset) без запроса в MySQL

/etc/mysql/mysql-proxy.lua

function read_query( packet )
        if packet:byte() == proxy.COM_QUERY then
                local tokenizer = require("proxy.tokenizer")
                local tokens = tokens or assert(tokenizer.tokenize(packet:sub(2)))
                local stmt = tokenizer.first_stmt_token(tokens)
                -- print("Query:" .. string.sub(packet, 2))
                -- print("stmt_name:" .. stmt.token_name)
                -- print("stmt_text:" .. stmt.text)

                if stmt.token_name ~= "TK_SQL_SELECT" and stmt.token_name ~= "TK_SQL_SHOW" and stmt.token_name ~= "TK_SQL_SET" then
                        -- print("intercepting: " .. string.sub(packet, 2))
                        proxy.response.type = proxy.MYSQLD_PACKET_RAW
                        proxy.response.packets = {
                                "\0" ..        -- field-count 0
                                "\0" ..        -- affected rows
                                "\0" ..        -- insert-id
                                "\002\0" ..    -- server-status
                                "\0\0" ..      -- warning-count
                                string.char(27) .. "mysql-proxy fake response"
                        }
                        return proxy.PROXY_SEND_RESULT
                end
        end
end

Разберем:

  • if packet:byte() == proxy.COM_QUERY then - Первый байт пакета определяет его тип, см. подробнее. Мне нужны только COM_QUERY
  • tokens - Вся команда разбивается на токены с помощью библиотеки tokenizer из пакета, см. объяснение

For example, the query SELECT 1 FROM dual will be returned as the following tokens:
  1:
   text          select
   token_name    TK_SQL_SELECT'
   token_id      204
  2: 
   text          1
   token_name    TK_INTEGER
   token_id      11
  3:
   text          from
   token_name    TK_SQL_FROM
   token_id      105
  4: 
   text          dual
   token_name    TK_SQL_DUAL
   token_id      87
 

  • stmt - Только первый токен
  • proxy.response.type - Формируем ответ (resultset) для клиента. Бывает следующих типов
    • MYSQLD_PACKET_OK - Обычный ответ, в ответе таблица. т.е. обязательно нужно вернуть таблицу, можно даже без записей. Пример:

            proxy.response.resultset = {
                    fields = {
                            { type = proxy.MYSQL_TYPE_LONG, name = "numbers", },
                    },
                    rows = {
                            { 0, 1, 2, 3}
                    }
            }

  • MYSQLD_PACKET_ERR - Ошибка. Код и текст ошибки указать
  • MYSQLD_PACKET_RAW - Формируем ответ самостоятельно по байтам, соответственно протоколу MySQL

Для отладки можно вставить print вывод на stdout, поэтому нужно запустить mysql-proxy из консоли руками со всеми опциями.

tokens

Проверка

mysql -h 127.0.0.1 -P4040

mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
|      110 |
+----------+
1 row in set (0.01 sec)
 
mysql> bla bla bla;
Query OK, 0 rows affected (0.00 sec)
mysql-proxy fake response











Обсуждение

Ваш комментарий. Вики-синтаксис разрешён:
157​ -7 =
 
howto/mysql/mysql-proxy.txt · Последнее изменение: 2022/08/31 16:47 — lexa
Gentoo Linux Gentoo Linux Driven by DokuWiki