2015-06-28

Об InfluxDB

Оказывается, 11 июня отрелизился InfluxDB версии 0.9.0. Обещали релиз аж в апреле, но получилось только сейчас. И то хорошо.
InfluxDB
InfluxDB — это такая хипстерская БД для хранения временных рядов. Тех самых наших любимых метрик, и прочих данных, тесно привязанных ко времени их возникновения. Сравнивать Инфлюксину следует с Graphite иOpenTSDB.
InfluxDB полностью написана на Go. Даже движок хранения данных (в версии 0.9) — BoltDB на Go. Go очень очень сильно упрощает развертывание. У InfluxDB в deb пакете — лишь один исполняемый файл демона, да конфиг. Для сравнения Graphite — это нехилая кучка кода на Python и даже Django приложение. Ну а OpenTSDB вообще работает поверх Hadoop.
Инфлюксина еще не добралась до версии 1.0. И это заметно. Даже в версии 0.9 явно не все еще доделали. Но пользоваться вполне можно. Помните только, что Influx 0.8 и Influx 0.9 — это две совсем большие разницы.
Ничего не могу сказать про их работу в кластере, тут много чего поменяли, не пробовал. Меня больше интересует смена представляения данных.
InfluxDB 0.8 ближе к Graphite. Здесь каждая метрика представлена иерархическим именем, значением и таймстампом. Впрочем, все же можно группировать независимые наборы метрик в базы данных. А также, кроме собственно числового значения метрики, с каждым отчетом можно хранить произвольный набор дополнительных значений-колонок. Это позволяет использовать InfluxDB не только для хранения метрик, но и, например, для хранения логов, да и любых данных, изменяющихся во времени. Для запросов нескольких метрик, их группировки, предлагается использовать регулярные выражения, применяемые к имени метрики, почти как в Graphite.
В версии 0.9 все изменилось. Появились теги, по аналогии с OpenTSDB. Теперь нет нужды кодировать, например, имя хоста и другие параметры группировки метрик, в названии самой метрики, как это принято в Graphite. Теперь можно с каждой метрикой связать конкретные теги, host=localhost. И именно конкретные значения тегов использовать в выборке и группировке конкретных метрик. Вместо не столь удобных для этого регулярных выражений.
Теги — это прекрасно. Они действительно сильно упрощают понимание структуры БД и ускоряют запросы. Если бы не один неприятный атавизм. Собиралки метрик, например, мой по-прежнему сильно любимый Diamond, понятия не имеют о тегах. Diamond создавался под Graphite. И представление метрики в нем — графитовое: длинное имя-путь и значение. Нет тегов.
В попытке решить проблему, в InfluxDB, при приеме метрик по протоколу Graphite, городят очаровательные костыли. Пользователю предлагается написать набор шаблонов, которые будут сообщать, что, допустим, в метрике с названием servers.localhost.cpu.loadavg.10 'localhost' — это значение тега 'host', а 'loadavg.10' — это, собственно название метрики (средняя загрузка системы за последние 10 минут). Этот костылик не вошел в InfluxDB 0.9.0, его замержили буквально совсем недавно в 0.9.1-rc1.
Конечно, правильнее научить Diamond тегам. Но что-то на Гитхабе не видно шевеления на эту тему. Это ж надо переписать весь Diamond, ибо теги надо добавить в самую середину, в класс Metric. И не только сам Diamond перетрясти нужно, но и кучу сторонних коллекторов, совместимых с ним. Кто знает, есть ли альтернатива Diamond, но с тегами?
InfluxDB по умолчанию хранит данные в том виде, как они пришли, вечно. Тут он ближе к OpenTSDB. Однако можно настроить правила удаления старых данных (называются Retention Policies). А также запросы по свертке старых данных. Да, тут правила сворачивания старых данных не зашиты строго, а могут полностью определяться пользователем. Нужно создать Continuous Queries, которые постоянно будут читать одни метрики, сворачивать их и складывать в другие метрики.
У InfluxDB есть веб админка. Не самая выдающаяся, но через неё вполне можно разобраться с базами данных и выяснить, что за метрики там хранятся.
Гораздо интереснее API InfluxDB. Чтобы вставить данные в версии 0.8 предлагалось постить JSON. В версии 0.9 перешли на текстовый протокол, сильно похожий на Graphite. (Таймстамп Инфлюксина может подставить сама).
% curl -i -XPOST 'http://localhost:8086/write?db=test' \
    -d 'test_metric,host=localhost,test=alpha value=1'
% curl -i -XPOST 'http://localhost:8086/write?db=test' \
    -d 'test_metric,host=localhost,test=alpha value=2
        test_metric,host=localhost,test=beta value=3'
При выборке возвращается JSON.
% curl -G 'http://localhost:8086/query' \
    --data-urlencode "db=test" \
    --data-urlencode "pretty=true" \
    --data-urlencode "q=SELECT * FROM test_metric"      
{
    "results": [
        {
            "series": [
                {
                    "name": "test_metric",
                    "tags": {
                        "host": "localhost",
                        "test": "alpha"
                    },
                    "columns": [
                        "time",
                        "value"
                    ],
                    "values": [
                        [
                            "2015-06-28T16:31:47.795210017Z",
                            1
                        ],
                        [
                            "2015-06-28T16:33:43.423490806Z",
                            2
                        ]
                    ]
                },
                {
                    "name": "test_metric",
                    "tags": {
                        "host": "localhost",
                        "test": "beta"
                    },
                    "columns": [
                        "time",
                        "value"
                    ],
                    "values": [
                        [
                            "2015-06-28T16:33:43.423490806Z",
                            3
                        ]
                    ]
                }
            ]
        }
    ]
}
Да, все верно, язык запросов в InfluxDB — SQL-подобное нечто.
Теги можно использовать как условие выборки.
% curl -G 'http://localhost:8086/query' \
        --data-urlencode "db=test" \
        --data-urlencode "pretty=true" \
        --data-urlencode "q=SELECT * FROM test_metric WHERE test='alpha'"    
{
    "results": [
        {
            "series": [
                {
                    "name": "test_metric",
                    "tags": {
                        "host": "localhost",
                        "test": "alpha"
                    },
                    "columns": [
                        "time",
                        "value"
                    ],
                    "values": [
                        [
                            "2015-06-28T16:31:47.795210017Z",
                            1
                        ],
                        [
                            "2015-06-28T16:33:43.423490806Z",
                            2
                        ]
                    ]
                }
            ]
        }
    ]
}
Можно агрегировать результаты как по времени, так и по тегам.
% curl -G 'http://localhost:8086/query' \
            --data-urlencode "db=test" \
            --data-urlencode "pretty=true" \
            --data-urlencode "q=SELECT sum(value) FROM test_metric \
                                WHERE time > '2015-06-28T16:31:00Z' AND time < '2015-06-28T16:34:00Z' \
                                GROUP BY time(1m), test"  
{
    "results": [
        {
            "series": [
                {
                    "name": "test_metric",
                    "tags": {
                        "test": "alpha"
                    },
                    "columns": [
                        "time",
                        "sum"
                    ],
                    "values": [
                        [
                            "2015-06-28T16:31:00Z",
                            1
                        ],
                        [
                            "2015-06-28T16:32:00Z",
                            null
                        ],
                        [
                            "2015-06-28T16:33:00Z",
                            2
                        ]
                    ]
                },
                {
                    "name": "test_metric",
                    "tags": {
                        "test": "beta"
                    },
                    "columns": [
                        "time",
                        "sum"
                    ],
                    "values": [
                        [
                            "2015-06-28T16:31:00Z",
                            null
                        ],
                        [
                            "2015-06-28T16:32:00Z",
                            null
                        ],
                        [
                            "2015-06-28T16:33:00Z",
                            3
                        ]
                    ]
                }
            ]
        }
    ]
}
Группировка по тегам порождает несколько серий в результате. Группировка по времени нужна для построения графиков. В этом случае обязательно нужно указать интервал времени, и все отчеты за указанный период будут сгруппированы по интервалам указанной длины с использованием указанной функции агрегирования. Функций этих довольно много. Но все же меньше, чем у Graphite. Впрочем, это несколько компенсируется возможностью использовать выражения в запросах, ну как в SQL. И, вроде как, можно делать запросы к нескольним метрикам сразу, джойнить "таблицы". Кстати, складывается ощущение, что не все функции и фичи по джойну из версии 0.8 перенесли в 0.9. Но работа идет.
В отличие от Graphite, у InfluxDB нет встроенного средства рисования графиков. Но с InfluxDB классно работает мощнющий и красивый дашбоард Grafana. Причем даже поддерживается версия 0.9.
Grafana
Однако дашбоарды хороши, если вам нужно глазками постоянно наблюдать обновляющиеся данные. Если же нужно просто показать исторические данные, совсем не трудно нарисовать свой собственный дашбоард. В конце концов, InfluxDB возвращает JSON, который легко обработать в JavaScript прямо в браузере и нарисовать график с помощью какого-нибудь Richshaw. Правда правда, это просто.

2015-06-13

О джедайской технике

Прошло чуть больше месяца с момента начала применения джедайской техники Дорофеи. Полет нормальный.
Jedi
Техника работает. Задался целью купить новые зимние шины для Гетца этим летом. И выполнил эту цель. За три итерации.
Задача номер раз — узнать, какие шины продаются и почем. Интернет в помощь, яндексмаркеты и каталоги местных магазинов. Магазин я особо не выбирал, остановился на том, где уже брал шины. Цены на почти все модели меня устроили. Выбрал пару моделек с хорошими отзывами, еще не факт, что они в наличии окажутся.
Задача номер два — съездить и купить. Выбрал денёк, когда в машине можно будет освободить место для шин и сразу отвезти их к месту хранения, чтобы не воняли. Точнее денёк выбрался совершенно случайно. Но, так как задача была у меня в списке, она тут же нашлась и соединилась с этим деньком. Съездить пришлось в два денька, сначала заказать, чтобы шины пришли со склада, а на следующий день, собственно, купить. Ну тут все успешно.
Задача номер три — перебортовать. Снять старую резину со старых дисков, надеть новую резину. Попутно взять диск с запаски вместо слегка погнутого диска с одного из колес. Вот тут пришлось приложить усилие воли, чтобы с утречка поехать на шиномонтажку и все уладить.
Зато цель достигнута. Причем, как по учебнику, каждая последующая задача создавалась по окончанию предыдущей.
Впрочем, не все цели так хорошо выполняются. Многие продолжают продалбываться. Но продалбываются теперь совершенно сознательно. Не потому, что забыл. Ну не всё всегда хочется делать. Поэтому, точно зная, какой следующий шаг, и что, если этого не сделать, никто не умрет, можно спокойно откладывать на послезавтра.
Тут же все просто. Если ты этого не делаешь сейчас, значит нет острой необходимости делать это сейчас. Значит, желание сделать это не превысило желание сделать что-то еще. В конце концов, я же не лежу на диване, уставившись в ~~потолок~~ телевизор. Всегда есть что сделать. Проклятие айтишника: что бы я не захотел сделать, со стороны это всё выглядит одинаково — сижу за компом.
Programmer's day
Натренировался в формулировке задач. Надо заменить лампочку в ванной. Нет. Как её заменить? Там же какая-то хитрая лампочка на 12 вольт с отражателем. Блин. Выяснить тип лампочки в ванной. Стоп. Как его выяснить? Это ж надо... Точно! Извлечь лампочку в ванной для последующей замены. Задача сформулирована. Прокрастинируется уже вторую неделю :)
Всё верно. Это не тайм менеджмент. Со временем всё равно ничего сделать нельзя. Пока вы не обзавелись машиной времени или не обратились за помощью к Хакермэну. Всё равно надо спать более 8 часов в сутки. Всё равно надо работать хотя бы по 8 часов каждый рабочий день. Но вот мозги потренировать можно. И все записывать. Больше дел делать я не стал. А вот забывать что-то сделать стал гораздо меньше.
Получается сделать от трех до восьми задач в день. И в корзинках на сегодня-неделю-потом валяется от семи до двадцати пяти задач. Общее количество задачек очень медленно, но увеличивается. Не потому, что растет ком несделанных дел. А потому, что вспоминаются какие-то цели, про которые давным давно забыл.
Еженедельный обзор задач оказался не таким уж и страшным. Главное — завести чеклист. Инбоксы в почте подчистил? Ок. Инбокс в Микромильках тоже? Ок. Список задач просмотрел? Список целей просмотрел? Удалил? Добавил? Ок. В календарик заглянул? Ок. Ну и все. Спокойно чекаешь, что сделал еще одно дело. И ЧСВ растет.
Стал активным пользователем Микромилек. Задачки в них вести действительно хорошо. Есть кучка мелких багов, что в веб версии, что под Андроид. Но тут главное не лениться, и слать багрепорты. Мелочи довольно быстро исправляются. А глобальных косяков не замечено.
Но вот вести совместные списки покупок, или набрасывать черновики и планы в Микромильках не очень. Потому считаю перепил своей софтинки всё еще актуальным. Попрокрастинирую над этим еще...
Не определился окончательно, что делать с задачами по проектам. Есть как бы работа, которую я работаю на работе, проектная, где задачи ведутся в Жире. Есть с полтора десятка личных программистских проектов, которые я еще сопровождаю, и где задачи ведутся либо в Битбакете, либо вообще нигде.
Пока складывается соотношение между задачами в жирах и задачами в Микромильках согласно заветам Макса. Задачи в жирах — это нечто большее, чем задачи на каждый день. Это План. Если я знаю, какую часть этого Плана я собираюсь делать следующей в ближайшее время, я создаю такую задачку в Микромильках. Если не знаю, можно создать задачку навроде: сделать пару тикетов по проекту за полчаса. Я так пока не делал, потому что знаю :) А по работе часто возникают задачки вообще не связанные с конкретным жиротикетом. Что-то куда-то посмотреть, пофиксить, проверить. Самое место их создать в Микромильках сразу по поступлению, чтобы не забыть. И точно будешь знать, что нужно сделать сегодня и завтра. Ну а отчитаться потом в жире за что-то более общее, имплементацию какой-нибудь фичи по проекту.
Plan
Продолжаем эксперименты над собой...

2015-06-08

Об Ansible

Промастерклассили про Docker. И на одном маленьком проекте тоже используем Docker. Но сегодня хочу поделиться про Ansible. Пришлось и там, и там весьма хорошо ковырнуть этот самый Ansible. И впечатления неоднозначные.
Ansible + Docker
С одной стороны — хорошо. Не использовал я большие штуки вроде Chef или Puppet. За ними стоят серьезные и даже вроде научные концепции, где важным является не выполнение какой-то команды на сервере, а именно приведение сервера в какое-то описанное состояние.
Я пользовался Fabric, который представляет собой лишь питоновую библиотечку для одновременного удаленного выполнения команд на туевой хуче серверов. Ansible не далеко ушел от этой концепции. Всё, что ему нужно, это ssh до машины, да Python 2.7 на той стороне. Ну иногда некоторые дополнительные штуки. И да, Ansible выполняет команды, точнее питоновые скрипты, на удаленной стороне. И у него уже искаропки имеется куча мегаполезных скриптов — модулей.
В другой стороны — как-то не очень. Сыроват наш Ansible. Иногда ведет себя совсем не так, как хотелось бы. Иногда капризничает на ровном месте.
В подражение Chefу да Puppetу многие модули приводят удаленную систему в некоторое состояние. Таким образом, выполнение этих модулей является идемпонентным. Скажем, запуск сервиса - service: name=httpd state=started требует, чтобы httpd был запущен. И, если он действительно запущен, ничего предприниматься не будет.
Но, в отличие от автора Bash Booster, создатели Ansible не заморачивались обязательностью идемпотентности. Вполне можно запустить любую команду на сервере - command: /usr/bin/make_database.sh arg1 arg2 и она будет выполняться всегда, ибо модуль command будет не в состоянии понять, в правильное ли состояние перешла система при выполнении команды, или же надо повторить. Можно даже попросить сделать - service: name=httpd state=restartedrestarted — это такое "состояние", когда сервис был рестартован, т.е. рестарт делается всегда.
Ansible настолько сырой, что, поставив свеженькую версию 1.9.1 из PPA, я тут же поймал баг. Дело в том, что в Ubuntu 14.04 таки уже еще используется Upstart, но и инит скрипты, что в /etc/init.d/ лежат, все еще сохранились. Вот только в новомодных пакетах, в том числе и Dockerа, эти скрипты ничего не делают, а лишь вежливо ругаются, что, мол, пользуйтесь командой service вместо прямого вызова скрипта. А вот свежий Ansible внезапно решил, что инит скрипты важнее команды service и пытается использовать именно их. И в результате не может ни запустить, ни перезапустить сервисы. Тупой косяк, который лечится правкой одной строчки. Но он есть.
Ansible настолько быстро развивается, что функционал и набор параметров у docker модуля вот прям сильно отличается от версии к версии. И, кажется, даже не всегда с сохранением обратной совместимости. Но доставляет боли больше всего зависимость от docker-py.
Вроде как Ansible требует на удаленном хосте лишь возможность подключения по ssh, да наличие Python. Но это не всегда так. Иногда встречаются зависимости — какие-то программки или питоновые модули, которые должны быть установлены на сервере. В данном случае ансибловый модуль docker использует питоновый модуль docker-py для общения c docker демоном (и управления контейнерами) на удаленном хосте. И вот эту зависимость надо поставить самому, например, выполнив отдельный ансибловый таск. Почему Ansible не заботится об установке всех нужных зависимостей самостоятельно?
Случай с docker-py усугубляется еще и тем, что конкретная версия Ansible гвоздями прибита к конкретной версии docker-py. Более новые или более старые версии не годятся. Ибо их использование приводит к невнятным питоновым стектрейсам, тупо об отсутствии каких-то атрибутов у объектов. И какая именно версия нужна — нигде не указано. Приходится вбивать стектрейс в Гугль, и находить в комментариях на каком-нибудь форуме, что "я откатился до версии 1.1.0 и это помогло".
assimoolated
Сам модуль docker действительно очень сырой. У меня к нему есть несколько замечаний. Может, доберусь, и сам запулреквестю фиксы.
Есть тупая недоработка. Можно работать только с именем контейнера, тем самым, которое по умолчанию Docker назначает веселенькими вроде 'tender_heisenberg', но нельзя с его id, которое хэш или часть хэша. Хотя все консольные команды Dockerа прекрасно воспринимают и то, и то.
Ansible в очередной раз старается изобразить идемпотентность задачи. Можно указать, сколько именно контейнеров из данного образа нужно запустить. И он запустит именно столько, сколько укажешь. Ни больше, и ни меньше. И вот совсем нельзя сказать, что нужен еще один контейнер из этого образа, +1 к общему количеству. Ну да, это же непредсказуемое изменение состояния, которое есть количество запущенных контейнеров.
Плоховата, точнее не совпадает с моими ожиданиями, проверка изменений в реестре Docker. Если я говорю, что хочу запустить контейнеры из образа, и точно знаю, что образ обновился, я все же хочу чтобы контейнеры были перезапущены/пересозданы из нового образа. Но Ansible не будет трогать/обновлять контейнеры, если они уже запущены.
Может, я продолжаю думать о запуске команд на сервере, а не о приведении его в некоторое состояние? Но в таком случае, я не могу выразить желаемое состояние теми средствами, что сейчас есть в docker модуле. Да вот, например, говорю Ansible, что хочу видеть запущенными меньше контейнеров данного образа, чем запущено сейчас. Я, в ожидании лучшего, хочу видеть убитыми более старые контейнеры, чтобы более новые, возможно, с более новой версией моего кода, остались жить. Но нет, Ansible прибивает первые контейнеры из списка, т.е. самые новые. Нет, не понимаем мы друг друга.
Ну ладно, Docker, хипстота и все такое. А вот посмотрим на вопрос копирования файлов. Есть модуль copy, в чьей документации честно написано: когда файлов много, я буду медленным, используйте вместо меня модуль synchronize. Ноу проблем. По синтаксису вызова модули похожи. Меняем слово 'copy' на слово 'synchronize'. Не работает.
Почему не работает? Потому что copy копирует файлы через то же ssh соединение, через которое Ansible взаимодействует с удаленным хостом. А synchronize запускает чудесный rsync. А rsynс создает свое собственное ssh подключение к удаленному узлу. А разработчики Ansible не приложили никаких усилий к тому, чтобы ssh, запущенный rsync, заполучил те же параметры подключения, что использует сам Ansible. Ну разве что имя ssh юзера задает правильно.
Может, это и хорошо. Может, это и правильно. Может, это и секурно. В конце концов, правильные настройки в ~/.ssh/ и запущенный ssh-agent — это вопрос личной гигиены каждого. Но в случае Vagrant окружения это не решается так просто. Ну и, в конце концов, можно же было типовые случаи хотя бы в документации описать?
Automate!
Но всё-равно, мне Ansible нравится. Чтобы начать его использовать, нужно просто установить его и написать первый playbook. После этого возникает стойкое желание описывать конфигурацию любых серверов исключительно через Ansible. Никакого ручного тюнинга, ни-ни-ни. Чтобы всегда можно было всё проиграть сначала.
А посмотрите, как Ansible собирает факты. Это переменные со сведениями о каждом сервере. Тут сведения о CPU, памяти, сети, дисках, и тонне всякого прочего. Их можно использовать в выполняемых задачах, в шаблонах файлов, которые надо сгенерить и закинуть на удаленный хост, повсюду. И столько всего уже искаропки известно Ansible. И это уже запаковано в красивый JSON.
Вот только сведений о Docker контейнерах явно не хватает. Но ассортимент сборщиков фактов легко пополнить. Мне вот, пришлось написать башевые скрипты, которые перечисляют docker контейнеры, которые нужно балансировать. Да, баш скрипт, который выводит JSON. Костыльно, но работает. Можно будет модуль Ansible по мотивам наваять.
Good Luck
Cсылки: