Модель «Инфраструктура как код» (IaC), которую иногда называют программируемой инфраструктурой, — это модель, в которой процесс настройки инфраструктуры аналогичен процессу программирования программного обеспечения.
По сути, он начал разрушать границы между написанием приложений и созданием сред для этих приложений.
Приложения могут содержать сценарии, которые создают собственные виртуальные машины и управляют ими.
Это основа облачных вычислений и неотъемлемая часть DevOps. Инфраструктура как код позволяет управлять виртуальными машинами на программном уровне.
Это устраняет необходимость ручной настройки и обновлений отдельных аппаратных компонентов.
Инфраструктура становится чрезвычайно «эластичной», то есть воспроизводимой и масштабируемой.
Один оператор может развертывать и управлять одной или 1000 машинами, используя один и тот же набор кода.
Гарантированные преимущества инфраструктуры как кода включают скорость, экономическую эффективность и снижение рисков.
Именно об этом и идет расшифровка доклада Кирилла Ветчинкина на DevOpsDays Москва 2018. В отчете: повторное использование модулей Ansible, сохранение в Git, обзор, пересборка, финансовая выгода, горизонтальное масштабирование в 1 клик.
Кому интересно, смотрите кат. Всем привет. Как уже было сказано, я Ветчинкин Кирилл.
Я работаю в TYME, и сегодня мы поговорим об инфраструктуре как коде.
Мы также поговорим о том, как мы научились экономить на этой практике, ведь она довольно дорогая.
Написание большого количества кода для настройки инфраструктуры обходится довольно дорого.
Расскажу коротко о компании.
Я работаю в Тайме.
У нас произошел ребрендинг.
Теперь мы называемся PaySystem — как следует из названия, мы занимаемся платежными системами.
У нас есть как свои решения – это переработка, так и заказная разработка.
Заказная разработка включает в себя электронный банкинг, выставление счетов и тому подобное.
И как вы понимаете, если это заказная разработка, то это большое количество проектов каждый год. Проект следует за проектом.
Чем больше проектов, тем больше однотипной инфраструктуры приходится создавать.
Поскольку проекты зачастую сильно загружены, мы используем микросервисную архитектуру.
Поэтому один проект содержит еще много-много мелких подпроектов.
Соответственно, управлять всем этим зоопарком без полноценного DevOps очень сложно.
Поэтому наша компания внедрила различные практики DevOps. Естественно, мы работаем по канбану, по SCRUM и храним всё в git. После фиксации происходит непрерывная интеграция и запускаются тесты.
Тестировщики пишут сквозные тесты на PyTest, которые запускаются каждую ночь.
Модульные тесты запускаются после каждого коммита.
Мы используем отдельный процесс сборки и развертывания: мы собираем его, а затем многократно развертываем в разных средах.
Мы были на окнах.
Мы выполнили развертывание в Windows с помощью Octopus Deploy. В этом году мы разрабатываем DotNet Core. Таким образом, теперь мы можем запускать программное обеспечение в системах Linux. Мы покинули Octopus и пришли к Ansible. Сегодня мы поговорим об этой части — новой практике, которую мы разработали в этом году, чего у нас раньше не было.
Когда у тебя есть тесты, когда ты умеешь хорошо собрать приложение, где-то его развернуть — всё хорошо.
Но если ваши две среды настроены по-разному, вы все равно потерпите неудачу, и вы потерпите неудачу в производстве.
Поэтому управление конфигурацией является очень важной практикой.
Именно об этом мы и поговорим сегодня.
Коротко расскажу, как вообще строится экономика продукта, исходя из трудозатрат: 60 процент, который мы тратим на разработка , аналитика занимает где-то 10 процент, контроль качества (тестирование) занимает ок.
20 проценты и все остальное отводится на конфигурацию.
Когда системы приходят целым потоком, они содержат много стороннего ПО, сами операционные системы настроены практически одинаково.
Мы тратим на это лишнее время, делая по сути то же самое.
Возникла идея все автоматизировать и сократить затраты на настройку инфраструктуры.
Подобные задачи автоматизированы, хорошо отлажены и не содержат ручных операций.
Каждое приложение работает в определенной среде.
Давайте разберемся, из чего все это состоит. Мы должны иметь хотя бы Операционная система , его нужно настроить, есть некоторые сторонние приложения , который также необходимо настроить, само приложение должны получать конфигурации, но для работы всего продукта должно запуститься само приложение, которое функционирует во всей этой системе.
Есть ли еще сеть , которую тоже нужно настроить, но о сети мы сегодня говорить не будем, потому что у нас разные клиенты, разные сетевые устройства.
Также мы попытались автоматизировать настройку сети, но поскольку устройства разные, особого смысла в этом не было; мы потратили на это больше ресурсов.
Но мы автоматизировали операционные системы, сторонние приложения и передачу параметров конфигурации в сами приложения.
Есть два подхода к тому, как можно настроить серверы: вручную - если вы настраиваете их вручную, то может получиться ситуация, что у вас продакшн настроен одним образом, тест - другим, на тесте все зеленое, тесты - зеленый.
Вы деплоите в продакшн, но там не установлен фреймворк — у вас ничего не работает. Другой пример: три сервера приложений настраиваются вручную.
Один сервер приложений был настроен одним образом, другой — другим.
Серверы могут работать по-другому.
Другой пример: произошла ситуация, когда один из наших серверов Stage полностью перестал работать.
Мы начали создавать новый сервер с помощью и через 30 минут сервер был готов.
Другой пример: сервер просто перестал работать.
Если настраивать вручную, то нужно искать человека, который умеет это настраивать, нужно получить документацию.
Как мы знаем, документация вряд ли будет актуальной.
Это большие проблемы.
И, самое главное, это аудит, то есть, грубо говоря, у вас десять администраторов, каждый из них что-то настраивает вручную, не особо понятно, правильно или неправильно они это настроили, и как вообще понять, так ли это надо делать что угодно, тогда в настройках могло быть установлено что-то ненужное, открыты какие-то ненужные порты.
Есть альтернативный вариант — именно об этом мы сегодня и говорим — настройка из кода.
То есть у нас есть git-репозиторий, в котором хранится вся инфраструктура.
Там хранятся все скрипты, с помощью которых мы это будем настраивать.
Так как это все в git, мы получаем все преимущества управления кодом как при разработке, то есть можем делать ревью, аудиты, историю изменений, кто их сделал, почему они их сделали, комментарии, можем откатиться.
Для работы с кодом нужно использовать конвейер непрерывной сборки — конвейер развертывания.
Чтобы какая-то система вносила изменения в серверы, то есть не человек что-то делал руками, а исключительно система.
Мы используем Ansible как систему, которая вносит изменения.
Поскольку у нас нет огромного количества серверов, нас это вполне устраивает. Если у вас там 100-200 серверов, то у вас будут небольшие проблемы, потому что он (т. е.
Ansible) все равно подключается к каждому и настраивает их по одному — это проблема.
Лучше использовать другие средства, которые не толкают, а тянут. Но для нашей истории, когда у нас много проектов, но не более 20 серверов, нам это вполне подходит. Большим преимуществом Ansible является низкий порог входа.
То есть буквально любой IT-специалист может полностью освоить его за три недели.
Имеет множество модулей.
То есть вы можете управлять облаками, сетями, файлами, установкой программ, развертыванием — абсолютно всем.
Если нет модулей, которые вы могли бы написать самостоятельно, вам может потребоваться написать что-то с использованием оболочки Ansible или командного модуля.
В общем, давайте кратко рассмотрим, как на самом деле выглядит этот инструмент. В Ansible есть модули, о которых я уже говорил.
То есть они могут быть поставлены, написаны сами собой, которые что-то делают. Есть инвентари — здесь мы будем выкатывать свои изменения, то есть это хосты, их IP-адреса, специфичные для этих хостов переменные.
И, соответственно, роли.
Роли — это то, что мы будем накатывать на эти сервера.
А еще группируем хосты по группам, то есть в данном случае мы видим, что у нас есть две группы: серверы баз данных и серверы приложений.
У нас по три машины в каждой группе.
Они подключаются через ssh. Таким образом мы решаем те проблемы, о которых говорили ранее, что, во-первых, наши сервера настроены идентично, так как на сервера накатывается одна и та же роль.
И точно так же, если мы запустим эту роль на нескольких машинах, то на каждой она будет работать одинаково.
Если мы более подробно рассмотрим, как структурирован проект Ansible, то здесь мы увидим, что хосты-инвентари допускаются к производству.
Эта группа указана в списке, и в ней есть два сервера.
Если мы зайдем на конкретный сервер, то увидим, что здесь указан IP-адрес этой машины.
Там же могут быть указаны и другие параметры — переменные, специфичные для данной среды.
Если мы посмотрим на роли.
Роль содержит несколько задач, которые будут выполняться.
В данном случае это роль установки PostgreSQL. То есть устанавливаем необходимое приложение и создаем базы данных.
Здесь мы используем цикл.
Будет создано несколько из них (баз данных).
Затем устанавливаем необходимое соединение — IP-адреса, которые могут авторизоваться в этой базе данных.
И, соответственно, в самом конце настраиваем фаервол.
Настройки будут применены ко всем серверам группы.
Мы только подходим к проблеме: мы научились настраивать серверы с помощью Ansible и все было нормально.
Но, как я уже сказал, у нас много проектов.
Они почти все одинаковы.
В каждом проекте задействованы примерно следующие системы (k8s, RabbitMQ, Vault, ELK, PostgreSQL, HAProxy).
Для каждого мы написали свою роль.
Мы можем свернуть его с кнопки.
Но проектов у нас много и в каждом они по сути пересекаются.
То есть в одном такой набор, во втором такой набор, в третьем такой набор.
Мы получаем точки пересечения, где в разных проектах играют одни и те же роли.
У нас есть репозиторий с приложением, и у нас есть репозиторий с инфраструктурой проекта.
Второй проект точно такой же.
Продолжение инфраструктуры.
И третий.
Если мы реализуем то же самое, по сути это будет копипаст. Мы сделаем одну и ту же роль в 10 местах.
Потом, если будет какая-то ошибка, исправим ее в 10 местах.
Что мы сделали: мы вынесли каждую общую для всех проектов роль и все ее конфигурации, поступающие извне, в отдельный репозиторий и поместили их в Git в отдельную папку — под названием TYME Infrastructure. Там у нас есть роль для PostgreSQL, для ELK, для развертывания кластеров Kubernetes. Если нам нужно установить тот же PostgreSQL в какой-то проект, то мы просто включаем его как подмодуль, переписываем инвентаризации, то есть, грубо говоря, конфигурацию, куда выкатить эту роль.
Саму роль мы не переписываем: она уже существует. И одним нажатием кнопки PostgreSQL появляется во всех новых проектах.
Если вам нужно поднять кластер Kubernetes, применяется то же самое.
Таким образом, удалось сократить затраты на написание ролей.
То есть написано один раз, использовано 10 раз.
Когда проект следует за проектом, это очень удобно.
Но поскольку теперь мы работаем с инфраструктурой как с кодом, нам, естественно, понадобятся конвейеры, о которых мы говорили.
Люди коммитят на git, могут допустить какую-то некорректность — нам нужно всё это отслеживать.
Вот почему мы построили этот трубопровод. То есть разработчик коммитит скрипты Ansible в git. TeamCity отслеживает их и передает в Ansible. Teamcity здесь нужен только по одной причине: во-первых, у него визуальный интерфейс (есть бесплатная версия Ansible Tower — AWX, которая решает ту же проблему — прим.
ред.) в отличие от Ansible, который бесплатен и в принципе у нас есть Teamcity как единый CI. Итак, в принципе, в Ansible есть модуль, который сам можно отслеживать с помощью git. Но в данном случае сделали это просто по образу и подобию.
И как только он это отследил, он переносит весь код в Ansible и Ansible соответственно запускает их на сервере интеграции и меняет конфигурацию.
Если этот процесс нарушен, то разбираемся, что не так и почему скрипты были написаны плохо.
Второй момент — есть конкретная инфраструктура, вот у нас инфраструктура развернута отдельно, приложение развернуто отдельно.
Но для каждого приложения существует определенная инфраструктура, то есть которую нужно развернуть до того, как мы его запустим.
Здесь, соответственно, перенести это в другой конвейер нельзя.
Вы должны развернуть его в том же контейнере, что и само приложение.
То есть, скажем так, фреймворки — это популярная вещь, когда под новое приложение нужно установить один фреймворк, а под другое другое.
Вот так и в этой ситуации.
Либо кэши нужно очистить.
Например, Ansible также может войти и очистить кеш.
Но здесь мы используем Docker в сочетании с Ansible. То есть наша конкретная инфраструктура находится в Docker, неспецифическая в Ansible. И таким образом мы как бы разделяем эту маленькую дельту в Docker, всё остальное, фундаментальное, в Ansible.
Очень важный момент — если вы запускаете инфраструктуру через какие-то скрипты, через код, то если у вас еще есть ручное манипулирование серверами, то это потенциальная уязвимость.
Допустим, вы установили Java на тестовый сервер, написали роль ELK и развернули ее.
Развертывание для тестирования прошло успешно.
Развертываю в продакшен, но там нет Java. А в скрипте вы не указали java — развертывание в продакшен не удалось.
Поэтому надо отобрать у администраторов права на все сервера, чтобы они не лезли в него руками и не вносили все изменения через git. Мы прошли весь этот конвейер сами.
Но здесь есть одно обстоятельство – не нужно слишком сильно затягивать гайки.
То есть такой процесс нужно внедрять постепенно.
Потому что это еще не проверено.
В нашем случае мы оставили доступ ко всем системам самому старшему администратору на случай непредвиденных инцидентов.
Доступ был предоставлен при условии, что он не будет ничего настраивать вручную.
Как происходит разработка? Внедрение в промежуточную и производственную среду должно быть безошибочным.
У нас что-то может сломаться.
Если выкатка в среду интеграции будет постоянно проваливаться с ошибками, это будет плохо.
Это похоже на отладку приложений на удаленном компьютере.
Когда разработчик сначала разрабатывает все на машине, оно компилируется.
Если все компилируется, то отправляется в репозиторий.
Здесь используется тот же подход. Разработчики используют код Visual Studio с плагинами Ansible, Vagrant, Docker и т. д. Разработчики тестируют свой код инфраструктуры на локальном бродяге.
Там установлена чистая операционная система.
Сами скрипты для поднятия этой машины также находятся в этом репозитории с той инфраструктурой, о которой мы говорили.
Разработчик начинает устанавливать на него FTP-сервер.
Если что-то идет не так, он просто удаляет его, скачивает заново и снова пытается установить на него необходимое ПО с помощью скриптов развертывания.
После отладки скриптов развертывания он делает Merge Request в основную ветку.
После слияния CI Merge Request распространяет эти изменения на сервер интеграции.
Поскольку все скрипты представляют собой код, мы можем писать тесты.
Допустим, мы установили PostgreSQL. Мы хотим проверить, работает это или нет. Для этого мы используем модуль Assert Ansible. Сравниваем установленную версию PostgreSQL с версией в скриптах.
Таким образом мы понимаем, что он установлен, даже запущен, это именно та версия, которую мы ожидали.
Мы видим, что тест пройден.
Это означает, что наша пьеса работала правильно.
Вы можете написать столько тестов, сколько захотите.
Они идемпотентны.
Идемпотентность (операция, которая, если применить к какому-либо значению несколько раз, всегда дает то же значение, что и при однократном применении).
Если вы пишете собственный скрипт для установки и настройки, то убедитесь, что ваши скрипты всегда получают одно и то же значение, если вы запускаете их несколько раз.
Есть еще один тип тестов, не имеющий прямого отношения к тестированию инфраструктуры.
Но они, кажется, косвенно влияют на него.
Это сквозные тесты.
Наша инфраструктура и сами приложения устанавливаются на один и тот же сервер, который тестируют тестировщики.
Если мы развернули какую-то неправильную инфраструктуру, то наши сложные тесты просто не пройдут. То есть наше приложение будет работать как-то некорректно.
В этом примере мы установили новую версию на продакшн — приложение работает. Потом был сделан коммит на git и сквозные тесты, которые проходят ночью, отследили, что вот у нас на фтп отсутствует файл.
Давайте разберем этот случай и увидим, что проблема в настройках ftp. Исправляем скрипты в коде, деплоим заново и все становится зеленым.
Та же история с кодом.
Код инфраструктуры и инфраструктура тем или иным образом тестируются косвенно.
Затем мы можем развернуть его в производство.
Когда мы реализовали этот подход, CI (Teamcity), которая выкатывала изменения на сервер интеграции, падала в 8 случаях из 10. На это никто не обращал внимания, потому что не было обратной связи.
Разработчики уже давно реализовали эти процессы, но до ОПС (системных администраторов) сообщения не дошли.
Поэтому мы добавили Дашборд со сборками этого проекта на большом мониторе на самом видном месте в офисе.
На нем выделены различные проекты зеленый - это значит, что с ним все в порядке.
Если выбрано красный значит, у него все плохо.
Мы видим, что некоторые тесты провалились.
В презентации слева, второй сверху, мы видим результат развертывания инфраструктуры задачи.
Задачи по развертыванию инфраструктуры отмечены зеленым цветом.
Это значит, что все испытания прошли, дефектов нет, собрали успешно.
Предупреждение: допустим, ИТ-специалист запускает сценарий и уходит. Если коммит сломал все.
Он получает уведомление в своем Slack-канале о том, что такой-то айтишник сломал наш проект таким-то коммитом.
Хорошо, мы только что говорили о том, как мы разрабатываем, как мы фиксируем некоторые тестовые среды, как мы обычно распространяем это в других средах.
Мы используем подход на основе транка.
Поэтому здесь ветка Master является основной веткой разработки.
Когда вы выполняете фиксацию в основной ветке, сервер CI (Teamcity) передает изменения на сервер интеграции.
Если задача на CI-сервере зелёная, то пишем тестировщикам, что они могут протестировать продукт на сервере интеграции.
Формируем релиз-кандидата.
Эта конфигурация появится на тестовом сервере.
Его тестировщики могут тестировать вместе с самими приложениями.
Если всё ок, если сквозные тесты пройдены, то уже можно выкатывать саму конфигурацию и приложение в промежуточную среду.
Затем мы сможем внедрить его в производство.
Благодаря таким многоуровневым барьерам мы добиваемся того, чтобы наша промежуточная среда всегда была зеленой.
Давайте сравним управление инфраструктурой из кода и настройку инфраструктуры вручную.
Какая экономика? На этом графике показано, как мы установили PostgreSQL. Управление инфраструктурой из кода на раннем этапе оказалось в 5 раз дороже.
Все скрипты должны быть написаны и отлажены вручную.
Это займет 1-2 часа.
Со временем вы приобретете опыт написания этих сценариев.
Давайте сравним установку и настройку PostgreSQL вручную и с помощью скрипта развертывания.
Поскольку сценарии развертывания и настройки PostgreSQL уже написаны, развертывание в промежуточной и рабочей среде занимает 4 минуты.
Чем больше сред, тем больше машин, тогда вы начинаете выигрывать по трудозатратам при настройке инфраструктуры.
А если у вас один проект и одна база данных, то вам дешевле сделать это вручную.
То есть интересно только тогда, когда у тебя есть какие-то масштабные проекты.
Мы реализовали подмодуль git, который позволяет вам использовать роль Ansible несколько раз.
Второй раз писать не надо, оно уже есть.
Добавляем в проект git-подмодуль с ролью Ansible. Пишем в инвентари сервера, где развернуть эту роль.
Это занимает 30 минут. В случае использования подмодуля git трудозатраты на написание скриптов развертывания значительно превосходят ручные операции.
По поводу идентичности окружений: здесь я не готов сказать, сколько ошибок у нас было раньше и сколько потом.
Но я хочу сказать, что при соблюдении всех правил, о которых мы сегодня говорили, наш стейджинг настроен точно так же, как и тест. Потому что если тест разваливается, мы уничтожаем машину, пытаемся настроить ее заново, и только когда она становится зеленой, все дело катится в стадирование.
Сколько ручных ошибок допускает ваша команда — вы можете подумать сами, посчитать, у каждого, наверное, свой порог.
На графике показаны результаты за 6 месяцев.
Затраты на оплату труда – сложность по 10-бальной шкале.
Первые два месяца мы просто очень мучительно и долго писали эти роли.
Потому что это была новая система.
Когда мы их написали, график пошёл вниз.
Где-то посередине мы ввели подмодуль git, когда начали повторно использовать уже написанные роли в разных проектах.
Это привело к резкому снижению затрат на рабочую силу.
Если сравнить с ручной настройкой проекта, для которого проводилась эта оценка, то мы настраиваем его вручную за три дня; при использовании инфраструктурного подхода на настройку кода ушло около месяца.
В долгосрочной перспективе использование инфраструктуры как кода более эффективно, чем настройка серверов вручную.
Выводы.
Во-первых, мы получили документацию, то есть когда ко мне приходит менеджер и спрашивает: на каком порту слушает наша база данных, я открываю Ansible-скрипт в git-репозитории, говорю: «Смотрите, на таком-то порту».
Какие у нас здесь настройки? Все это видно в репозитории git. Это можно проверить, показать менеджерам и так далее.
Это 100% достоверная информация.
Документация устаревает. И в этом случае ничего не устаревает. Важный момент, о котором мы не говорили.
О нем я вам скажу косвенно.
Есть команды разработчиков, которым также необходимо установить RabbitMQ и ELK локально, чтобы протестировать различные идеи.
Если делать это вручную, подъем ЕЛК может занять больше часа.
При использовании подхода «инфраструктура как код» разработчик берет виртуальную машину, нажимает кнопку, и ELK устанавливается.
Если разработчик сломает ELK, то он может удалить и перезапустить установку ELK на виртуальной машине.
Как мы уже говорили, это дешевле только тогда, когда у вас много проектов, или много сред, или много машин.
Если у вас этого нет, то, соответственно, не дешевле, а дороже.
На наш взгляд, в наших проектах получилось, что со временем оно дешевеет. Поэтому есть как плюсы, так и минусы.
Быстрое восстановление после аварии.
Если вы упали, нужно искать человека, который сможет настроить эту машину, который умеет ее настраивать, который помнит, как ее настраивать.
В этом случае все настройки и скрипты развертывания находятся в git. Даже если человек увольняется или переходит в другой проект, все есть в гите, все есть в комментариях, все видно: почему он открыл такой порт, почему выставил такие-то настройки и так далее.
Вы можете легко все восстановить.
Количество ошибок уменьшилось, потому что у нас теперь разные барьеры, локальные среды для отслеживания, для разработки и плюс ревью кода.
Это также важно, потому что разработчики проверяют то, что они делают. Таким образом, они продолжают учиться.
Сложность возросла, потому что это, соответственно, новая система, это конвейеры, людей надо обучать.
Это не то, к чему мы обычно привыкли: читай статью и делай по статье.
Здесь немного по-другому.
Но тем не менее, со временем это довольно легко освоить.
Если посмотреть на разработку полностью, то это где-то три-четыре месяца.
Вопрос задается через приложение, но вопрос не слышен.
Ответ: Мы используем последнюю версию ролей, то есть выделяем роль в отдельный git-подмодуль только тогда, когда она уникальна абсолютно для всех систем.
Если на нем произошли какие-то коммиты, то переходим к последнему коммиту.
И вся конфигурация берется из инвентарей.
То есть роль — это просто то, что будет выполняться.
Имя базы данных, логин, пароль и т.д. приходят извне.
Роль — это просто алгоритм, который выполняется.
Вся конфигурация исходит из самого проекта.
Вопрос: Если у вас есть какие-то транзитивные зависимости между ролями Ansible, как вы их выдергиваете при развертывании приложения (роль А зависит от Б, Б от С, а при развертывании роли А она сама вытягивает зависимость)? Какие инструменты использовать, если они у вас есть? Ответ: В этой парадигме таких зависимостей нет. У нас есть зависимости конфигурации.
То есть мы устанавливаем какую-то систему и знаем IP-адреса серверов, и при этом есть балансировщик, который теперь стал, должен как бы сам себя гонять, чтобы балансировать на этих настроенных машинах.
Это вроде как решается настройками.
Но, если у вас есть зависимость от модулей, то тут ничего нет, у нас есть один бесплатный, он ни от чего не зависит, он самодостаточен.
И мы не помещаем в общие роли ничего, что имеет хоть какую-то не общую структуру, то есть, допустим, RabbitMQ может иметь и RabbitMQ в Африке, это ни от чего не зависит. Некоторую специфику мы храним в Docker, те же фреймворки.
P.S. Приглашаю всех желающих совместно перевести интересные доклады с конференции в текст на github. Вы можете создать группу на github для перевода.
Сейчас я перевожу в текст у себя учетная запись GitHub — вы также можете отправить туда запрос на исправление статьи.
Теги: #ИТ-инфраструктура #Системное администрирование #Администрирование серверов #DevOps #ansible #инфраструктура как код #проверка кода #teamcity
-
Вольф, Рудольф
19 Oct, 24 -
Мотивенчер 2009
19 Oct, 24