Selfheal at Webhosting - внешняя часть

Вступление

С почти 6000000 веб-сайтов, размещенных на более чем 15000 серверах, команда OVHcloud Webhosting SRE управляет множеством предупреждений в течение рабочего дня.

Наша инфраструктура постоянно растет, но для плавного масштабирования время, затрачиваемое на решение предупреждений, не должно увеличиваться пропорционально.

Поэтому нам нужны инструменты, которые нам помогут. В нашей команде мы называем это самоисцелением.



Что такое самоисцеление ?

Selfheal относится к автоматизации решения оповещения в производственных условиях. Автоматизированный процесс может исправить известные проблемы без вмешательства администратора.

Зачем нам это нужно?

Мы должны максимально ограничить время, которое мы тратим на устранение предупреждений. У нас есть не только время для запуска и обслуживания инфраструктуры, но и для того, чтобы оставаться в курсе последних событий.

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

Нам нужно работать эффективно, автоматизируя как можно больше производственных операций.

Оборудование

Для ежедневного обслуживания миллиардов HTTP-запросов требуется много ресурсов, поэтому мы часто используем физические серверы в наших центрах обработки данных.

Даже один физический сервер требует серьезной поддержки. Требуется много времени для диагностики, планирования простоев, запроса и управления вмешательством с группами центров обработки данных или даже для переустановки операционной системы в случае неисправности диска.

Мы не можем позволить себе тратить часы на повторяющиеся задачи, если их можно автоматизировать.

Программного обеспечения

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

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

Мы должны как можно меньше предупреждать дежурных администраторов, только когда это абсолютно необходимо.

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

Selfheal на веб — хостинг

В Webhosting мы разделяем selfheal на две части:

  1. Внешнее самовосстановление, которое обрабатывает проблемы с оборудованием или все, что не может быть решено самим хостом.
  2. Внутреннее самовосстановление, предназначенное для решения программных проблем в данной системе.

В этой статье мы обсудим внешнюю часть.

Внешнее самоисцеление

Контекст:

Как мы уже говорили ранее, внешняя часть нашего selfheal в основном предназначена для решения аппаратных проблем, которые не могут быть решены одним сервером.

Для этого мы создали небольшое приложение-микросервис, которое отслеживает и отслеживает события.

Мы могли выбрать существующий инструмент (например, StackStorm), но мы этого не сделали. Вот почему:

  • Создание микросервисов в OVH действительно просто и быстро.
  • Структурированные, подробные и простые журналы с уникальным идентификатором uuid для отслеживания каждой задачи самовосстановления в нашей внутренней системе журналов (что позволяет нам легко строить графики). 
  • Простая интеграция с нашими существующими инструментами и экосистемой 
  • Быстрое и простое развертывание во всех наших регионах
  • Простой CI / CD (модульное тестирование и т. Д.)
  • Пользовательские уведомления, например чат-бот
  • Интеллект и история



Как это устроено

Все начинается с нашего мониторинга, который отбрасывает пробы серверов и отправляет все предупреждения в теме Kafka.

Приложение использует события Kafka, а затем мгновенно реагирует на правильный рабочий процесс, в зависимости от проблемы.

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

Все выполненные действия сохраняются. Это избавляет от необходимости выполнять одно и то же исправление несколько раз на данном сервере и выявлять сложные проблемы.

Конкретный пример замены неисправного диска

Одним из самых трудоемких предупреждений, которые нам пришлось решить, была замена неисправного жесткого диска, обнаруженного при проверке SMART.

Поскольку у нас нет состояния, многие наши серверы используют один диск без настройки рейда. Это также означает замену диска для переустановки хоста; но, надеюсь, это можно сделать с помощью одного вызова API.

Чтобы управлять этим предупреждением, администратор должен был выполнить следующие действия:

  1. Поставьте сервер на обслуживание, чтобы истощить запросы клиентов
  2. Создайте запрос центра обработки данных на замену жесткого диска
  3. Переустановите сервер

Весь этот процесс может занять до 3 часов и его сложно выполнить вручную (управление несколькими проблемами одновременно).

Первое, что мы сделали, — автоматизировали проверку зондом.

Затем мы решили автоматизировать все это с помощью простого рабочего процесса в нашем приложении с самовосстановлением, а затем организовать вызов API.



Благодаря этому процессу мы можем заменять диски каждый день без каких-либо ручных действий со стороны администратора.

Заключить

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

Мы победили по реактивности. Больше нет задержки между обнаружением предупреждения и его обработкой.

Оповещения обрабатываются мгновенно при обнаружении системой мониторинга. Это помогает нам вести чистый список невыполненных работ по мониторингу и избегать «пакетных» решений по оповещениям, которые были сложными как для нас, так и для DC.

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

Делаем БОЛЬШУЮ автоматизацию с помощью Celery

Вступление

TL; DR: вы можете пропустить вступление и сразу перейти к «Celery — Distributed Task Queue».



Здравствуйте! Меня зовут Бартош Рабьега, я работаю в команде R & D / DevOps в OVHcloud. В рамках нашей повседневной работы мы разрабатываем и поддерживаем проект Ceph-as-a-Service, чтобы обеспечить надежное распределенное хранилище высокой доступности для различных приложений. Мы имеем дело с более чем 60 ПБ данных в 10 регионах, поэтому, как вы можете себе представить, у нас впереди довольно много работы с точки зрения замены вышедшего из строя оборудования, обработки естественного роста, предоставления новых регионов и центров обработки данных, оценки нового оборудования, оптимизация конфигураций программного и аппаратного обеспечения, поиск новых решений для хранения данных и многое другое!



Из-за большого объема нашей работы нам нужно разгрузить как можно больше повторяющихся задач. И мы делаем это с помощью автоматизации.

Автоматизация вашей работы

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



Хм… Что может помочь нам сделать это автоматически? Разве компьютер не кажется идеальным?

Сельдерей — распределенная очередь задач

Celery — это хорошо известное и широко используемое программное обеспечение, которое позволяет нам обрабатывать задачи асинхронно. Описание проекта на его главной странице ( www.celeryproject.org/ ) может показаться немного загадочным, но мы можем сузить его базовую функциональность до примерно следующего:



Такой механизм идеально подходит для таких задач, как асинхронная отправка электронных писем (например, «запустил и забыл»), но его также можно использовать для различных целей. Так с какими еще задачами он справился? В принципе, любые задачи можно реализовать на Python (основном языке Celery)! Я не буду вдаваться в подробности, поскольку они доступны в документации по Celery. Важно то, что, поскольку мы можем реализовать любую задачу, которую захотим, мы можем использовать ее для создания строительных блоков для нашей автоматизации.

Есть еще одна важная вещь… Celery изначально поддерживает объединение таких задач в рабочие процессы (примитивы Celery: цепочки, группы, аккорды и т. Д.). Итак, давайте рассмотрим несколько примеров…

Мы будем использовать следующие определения задач — одиночная задача, печать аргументов и kwargs:

@celery_app.task
def noop(*args, **kwargs):
    # Task accepts any arguments and does nothing
    print(args, kwargs)
    return True


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

task = noop.s(777)
task.apply_async()


Элементарные задачи можно параметризовать и объединить в сложный рабочий процесс с использованием методов сельдерея, то есть «цепочки», «группы» и «хорды». См. Примеры ниже. В каждом из них левая сторона показывает визуальное представление рабочего процесса, а правая — фрагмент кода, который его генерирует. Зеленая рамка — это отправная точка, после которой выполнение рабочего процесса продвигается по вертикали.

Цепочка — набор задач, обрабатываемых последовательно



workflow = (
    chain([noop.s(i) for i in range(3)])
)


Группа — набор параллельно обрабатываемых задач.



workflow = (
    group([noop.s(i) for i in range(5)])
)


Аккорд — группа задач, связанных со следующей задачей



workflow = chord(
        [noop.s(i) for i in range(5)],
        noop.s(i)
)

# Equivalent:
workflow = chain([
        group([noop.s(i) for i in range(5)]),
        noop.s(i)
])


Важный момент: выполнение рабочего процесса всегда останавливается в случае сбоя задачи. В результате цепочка не будет продолжена, если какая-то задача не удастся в ее середине. Это дает нам довольно мощный фреймворк для реализации аккуратной автоматизации, и именно это мы используем для Ceph-as-a-Service в OVHcloud! Мы реализовали множество небольших, гибких, параметризуемых задач, которые мы объединяем для достижения общей цели. Вот несколько реальных примеров элементарных задач, используемых для автоматического удаления старого оборудования:

  • Изменить вес узла Ceph (используется для увеличения / уменьшения количества данных на узле. Запускает ребалансировку данных)
  • Установите время простоя службы (перебалансировка данных запускает зонды мониторинга, но это ожидается, поэтому установите время простоя для этой конкретной записи мониторинга)
  • Подождите, пока Ceph не станет здоровым (дождитесь завершения ребалансировки данных — повторяющаяся задача)
  • Удалите узел Ceph из кластера (узел пуст, поэтому его можно просто удалить)
  • Отправьте информацию техническим специалистам в DC (оборудование готово к замене)
  • Добавить новый узел Ceph в кластер (установить новый пустой узел)

Мы параметризируем эти задачи и связываем их вместе, используя цепочки, группы и аккорды сельдерея для создания желаемого рабочего процесса. Затем Celery сделает все остальное, асинхронно выполняя рабочий процесс.

Большие рабочие процессы и сельдерей

По мере роста нашей инфраструктуры растут и наши автоматизированные рабочие процессы, с увеличением количества задач на рабочий процесс, более высокой сложностью рабочих процессов… Что мы понимаем под большим рабочим процессом? Рабочий процесс, состоящий из 1000-10 000 задач. Чтобы визуализировать это, взгляните на следующие примеры:

Несколько аккордов, связанных вместе (всего 57 заданий)



workflow = chain([
    noop.s(0),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    noop.s()
])


Более сложная структура графа, построенная из цепочек и групп (всего 23 задачи)



# | is ‘chain’ operator in celery
workflow = (
    group(
        group(
            group([noop.s() for i in range(5)]),
            chain([noop.s() for i in range(5)])
        ) |
        noop.s() |
        group([noop.s() for i in range(5)]) |
        noop.s(),
        chain([noop.s() for i in range(5)])
    ) |
    noop.s()
)


Как вы, наверное, догадались, когда задействовано 1000 задач, визуализация становится довольно большой и беспорядочной! Celery — мощный инструмент, обладающий множеством функций, которые хорошо подходят для автоматизации, но он по-прежнему испытывает трудности, когда дело доходит до обработки больших, сложных и длительных рабочих процессов. Организовать выполнение 10 000 задач с различными зависимостями — нетривиальная вещь. Когда наша автоматизация стала слишком большой, мы столкнулись с несколькими проблемами:

  • Проблемы с памятью во время построения рабочего процесса (на стороне клиента)
  • Проблемы с сериализацией (клиент -> бэкэнд-передача сельдерея)
  • Недетерминированное, прерывистое выполнение рабочих процессов
  • Проблемы с памятью у рабочих Celery (серверная часть Celery)
  • Исчезающие задачи
  • И более…

Взгляните на GitHub:


Использование сельдерея для нашего конкретного случая стало трудным и ненадежным. Встроенная поддержка рабочих процессов в Celery не кажется правильным выбором для обработки 100/1000/10 000 задач. В нынешнем состоянии этого просто недостаточно. Итак, вот мы стоим перед прочной бетонной стеной… Либо мы как-то исправляем Celery, либо переписываем нашу автоматизацию, используя другую структуру.

Сельдерей — исправить… или исправить?

Переписать всю нашу автоматизацию можно, хотя и относительно болезненно. Поскольку я довольно ленив, возможно, попытка исправить Celery была не совсем плохой идеей? Так что я потратил некоторое время, чтобы покопаться в коде Celery, и мне удалось найти части, отвечающие за построение рабочих процессов и выполнение цепочек и аккордов. Мне все еще было немного сложно понять все различные пути кода, обрабатывающие широкий спектр вариантов использования, но я понял, что можно реализовать чистую, прямую оркестровку, которая будет обрабатывать все задачи и их комбинации в одном путь. Более того, я понял, что интегрировать его в нашу автоматизацию не потребует слишком больших усилий (давайте не будем забывать о главной цели!).

К сожалению, внедрение новой оркестровки в проект Celery, вероятно, будет довольно сложным и, скорее всего, нарушит некоторую обратную совместимость. Поэтому я решил использовать другой подход — написать расширение или плагин, которые не требовали бы изменений в Celery. Что-то подключаемое и максимально неинвазивное. Так появился Celery Dyrygent…

Сельдерей Dyrygent

github.com/ovh/celery-dyrygent

Как представить рабочий процесс

Вы можете представить себе рабочий процесс как направленный ациклический граф (DAG), где каждая задача является отдельным узлом графа. Когда дело доходит до ациклических графов, относительно легко хранить и разрешать зависимости между узлами, что приводит к простой оркестровке. Celery Dyrygent был реализован на основе этих функций. Каждая задача в рабочем процессе имеет уникальный идентификатор (Celery уже назначает идентификаторы задач, когда задача отправляется на выполнение), и каждая из них заключена в узел рабочего процесса. Каждый узел рабочего процесса состоит из сигнатуры задачи (простой сигнатуры сельдерея) и списка идентификаторов задач, от которых он зависит. См. Пример ниже:



Как обработать рабочий процесс

Итак, мы знаем, как сохранить рабочий процесс простым и понятным способом. Теперь нам просто нужно его выполнить. Как насчет… сельдерея? Почему бы нет? Для этого Celery Dyrygent вводит задачу процессора рабочего процесса (обычная задача Celery ). Эта задача охватывает весь рабочий процесс и планирует выполнение примитивных задач в соответствии с их зависимостями. После того, как часть планирования завершена, задача повторяется («тикает» с некоторой задержкой).

На протяжении всего цикла обработки процессор рабочих процессов сохраняет внутреннее состояние всего рабочего процесса. В результате он обновляет состояние при каждом повторении. Вы можете увидеть пример оркестровки ниже:







В частности, процессор рабочего процесса останавливает свое выполнение в двух случаях:

  • После завершения всего рабочего процесса и успешного выполнения всех задач
  • Когда он не может продолжить работу из-за сбоя задачи

Как интегрировать

Итак, как нам это использовать? К счастью, мне довольно легко удалось найти способ использовать Celery Dyrygent. Прежде всего, вам необходимо внедрить определение задачи процессора рабочего процесса в ваше приложение CeleryP:

from celery_dyrygent.tasks import register_workflow_processor
app = Celery() #  your celery application instance
workflow_processor = register_workflow_processor(app)


Затем вам нужно преобразовать рабочий процесс, определенный для Celery, в рабочий процесс Celery Dyrygent:

from celery_dyrygent.workflows import Workflow

celery_workflow = chain([
    noop.s(0),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    chord([noop.s(i) for i in range(10)], noop.s()),
    noop.s()
])

workflow = Workflow()
workflow.add_celery_canvas(celery_workflow)


Наконец, просто выполните рабочий процесс, как обычную задачу Celery:

workflow.apply_async()


Это оно! Вы всегда можете вернуться, если хотите, так как небольшие изменения очень легко отменить.

Попробуйте!

Celery Dyrygent можно использовать бесплатно, а его исходный код доступен на Github ( github.com/ovh/celery-dyrygent ). Не стесняйтесь использовать его, улучшать, запрашивать функции и сообщать о любых ошибках! У него есть несколько дополнительных функций, не описанных здесь, поэтому я рекомендую вам взглянуть на файл readme проекта. Для наших требований к автоматизации это уже надежное, проверенное на практике решение. Мы используем его с конца 2018 года, и он обработал тысячи рабочих процессов, состоящих из сотен тысяч задач. Вот некоторая статистика производства за период с июня 2019 года по февраль 2020 года:

  • Выполнено 936 248 элементарных задач
  • Обработано 11 170 рабочих процессов
  • 4098 задач в самом большом рабочем процессе
  • ~ 84 задачи на рабочий процесс, в среднем

Автоматизация — это всегда хорошая идея!

Представляющий директор - инструмент для построения рабочих процессов Celery

Нам как разработчикам часто приходится выполнять задачи в фоновом режиме. К счастью, для этого уже существуют некоторые инструменты. Например, в экосистеме Python самая известная библиотека — это Celery. Если вы уже использовали это, вы знаете, насколько это здорово! Но вы также, вероятно, обнаружите, насколько сложно может быть отслеживание состояния сложного рабочего процесса.



Celery Director — это инструмент, который мы создали в OVHcloud для решения этой проблемы. Код теперь имеет открытый исходный код и доступен на Github.



После нашего выступления во время FOSDEM 2020, этот пост направлен на представление инструмента. Мы подробно рассмотрим, что такое Celery, почему мы создали Director и как его использовать.

Что такое «сельдерей»?

Вот официальное описание сельдерея:

Celery — это асинхронная очередь задач / очередь заданий, основанная на распределенной передаче сообщений. Он ориентирован на работу в реальном времени, но также поддерживает планирование.

Важные слова здесь — «очередь задач». Это механизм, используемый для распределения работы по пулу машин или потоков.



Очередь в середине приведенной выше диаграммы хранит сообщения, отправленные производителями (например, API). С другой стороны, потребители постоянно читают очередь, чтобы отображать новые сообщения и выполнять задачи.

В Celery сообщение, отправленное производителем, является подписью функции Python: send_email(«john.doe»)например.

Очередь (именуемая посредником в Celery) хранит эту подпись до тех пор, пока рабочий не прочитает ее и не выполнит функцию в пределах данного параметра.

Но зачем выполнять функцию Python где-то еще? Основная причина — быстрое возвращение ответа в случаях, когда функции долго выполняются. В самом деле, это не вариант заставлять пользователей ждать ответа несколько секунд или минут.

Точно так же, как мы можем представить производителей без достаточного количества ресурсов, с задачей, связанной с процессором, более надежный работник мог бы справиться с ее выполнением.

Как использовать «сельдерей»

Итак, Celery — это библиотека, используемая для выполнения кода Python где-то еще, но как она это делает? На самом деле это действительно просто! Чтобы проиллюстрировать это, мы будем использовать некоторые из доступных методов для отправки задач брокеру, а затем запустим воркер для их использования.

Вот код для создания задачи Celery:

# tasks.py
from celery import Celery

app = Celery("tasks", broker="redis://127.0.0.1:6379/0")

@app.task
def add(x, y):
    return x + y


Как видите, задача Celery — это просто функция Python, преобразованная для отправки в брокер. Обратите внимание, что мы передали соединение redis приложению Celery (названному app), чтобы сообщить брокеру, где хранить сообщения.

Это означает, что теперь можно отправить задачу в брокере:

>>> from tasks import add
>>> add.delay(2, 3)


Вот и все! Мы использовали этот .delay()метод, поэтому наш производитель не выполнял код Python, а вместо этого отправлял подпись задачи брокеру.

Теперь пора употребить его вместе с сельдереем:

$ celery worker -A tasks --loglevel=INFO
[...]
[2020-02-14 17:13:38,947: INFO/MainProcess] Received task: tasks.add[0e9b6ff2-7aec-46c3-b810-b62a32188000]
[2020-02-14 17:13:38,954: INFO/ForkPoolWorker-2] Task tasks.add[0e9b6ff2-7aec-46c3-b810-b62a32188000] succeeded in 0.0024250600254163146s: 5


Можно даже комбинировать задачи Celery с некоторыми примитивами (полный список здесь ):

  • Цепочка: будет выполнять задачи одну за другой.
  • Группа: будет выполнять задачи параллельно, направляя их нескольким рабочим.

Например, следующий код сделает два добавления параллельно, а затем суммирует результаты:

from celery import chain, group

# Create the canvas
canvas = chain(
    group(
        add.si(1, 2),
        add.si(3, 4)
    ),
    sum_numbers.s()
)

# Execute it
canvas.delay()


Вы, наверное, заметили, что мы не использовали здесь метод .delay () . Вместо этого мы создали холст , используемый для объединения набора задач.

.si()
Метод используется для создания неизменяемой подписи (т. Е. Такой , которая не получает данные от предыдущей задачи), в то время как 
.s()
полагается на данные, возвращенные двумя предыдущими задачами.

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

Как разработчик я хочу…

Я часть команды, целью которой является развертывание и мониторинг внутренней инфраструктуры. В рамках этого нам нужно было запустить некоторые фоновые задачи, и, как разработчики Python, мы естественным образом выбрали Celery. Но из коробки Celery не поддерживал определенные требования для наших проектов:

  • Отслеживание развития задач и их зависимостей в WebUI.
  • Выполнение рабочих процессов с помощью вызовов API или просто с помощью интерфейса командной строки.
  • Объединение задач для создания рабочих процессов в формате YAML.
  • Периодическое выполнение всего рабочего процесса.

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

И поскольку нам действительно были нужны эти функции, мы решили создать Celery Director.



Как использовать Директор

Установку можно произвести с помощью pip-команды:

$ pip install celery-director


Директор предоставляет простую команду для создания новой папки рабочего пространства:

$ director init workflows
[*] Project created in /home/ncrocfer/workflows
[*] Do not forget to initialize the database
You can now export the DIRECTOR_HOME environment variable


Ниже для вас создана новая папка задач и пример рабочего процесса:

$ tree -a workflows/
├── .env
├── tasks
│   └── etl.py
└── workflows.yml


В tasks/*.pyфайлы будут содержать ваши задачи Сельдерей, в то время как workflows.ymlфайл будет объединить их:

$ cat workflows.yml
---
ovh.SIMPLE_ETL:
  tasks:
    - EXTRACT
    - TRANSFORM
    - LOAD


В этом примере с именем ovh.SIMPLE_ETL будут выполняться три задачи, одна за другой. Вы можете найти больше примеров в документации.

После экспорта DIRECTOR_HOMEпеременной и инициализации базы данных с помощью director db upgradeвы можете выполнить следующий рабочий процесс:

$ director workflow list
+----------------+----------+-----------+
| Workflows (1)  | Periodic | Tasks     |
+----------------+----------+-----------+
| ovh.SIMPLE_ETL |    --    | EXTRACT   |
|                |          | TRANSFORM |
|                |          | LOAD      |
+----------------+----------+-----------+
$ director workflow run ovh.SIMPLE_ETL


Брокер получил задачи, поэтому теперь вы можете запустить Celery worker для их выполнения:

$ director celery worker --loglevel=INFO


А затем отобразите результаты с помощью команды веб-сервера ( director webserver):



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

Вывод

Наши команды регулярно используют Director для запуска наших рабочих процессов. Больше нет шаблонов и нет необходимости в продвинутых знаниях Celery… Новый коллега может легко создавать свои задачи на Python и комбинировать их в YAML, не используя примитивы Celery, описанные ранее.

Иногда нам нужно периодически выполнять рабочий процесс (например, для заполнения кеша), а иногда нам нужно вручную вызывать его из другой веб-службы (обратите внимание, что рабочий процесс также может быть выполнен через вызов API ). Теперь это возможно с помощью нашего единственного экземпляра Director.

Мы приглашаем вас попробовать Director на себе и оставить свой отзыв через Github, чтобы мы могли продолжать его улучшать. Исходный код можно найти на Github, а презентация FOSDEM 2020 доступна здесь.

Упростите свои исследовательские эксперименты с Kubernetes

Абстрактно

Мне как исследователю необходимо проводить эксперименты, чтобы подтвердить свои гипотезы. Когда речь идет о компьютерных науках, хорошо известно, что специалисты-практики обычно проводят эксперименты в различных средах (на аппаратном уровне: x86 / arm /…, частота процессора, доступная память или на уровне программного обеспечения: операционная система, версии библиотек). Проблема с этими различными средами заключается в сложности точного воспроизведения эксперимента, как он был представлен в исследовательской статье.

В этом посте мы предлагаем способ проведения экспериментов, который можно воспроизвести с помощью Kubernetes-as-a-service, управляемой платформы для выполнения распределенных вычислений вместе с другими инструментами (Argo, MinIO), которые учитывают преимущества платформы.



Статья организована следующим образом: сначала мы вспомним контекст и проблему, с которой сталкивается исследователь, которому необходимо провести эксперименты. Затем мы объясняем, как решить проблему с Kubernetes и почему мы не выбрали другие решения (например, программное обеспечение HPC). Наконец, дадим несколько советов по улучшению настройки.

Вступление

Когда я начал свою докторскую диссертацию, я прочитал кучу статей, относящихся к той области, в которой я работаю, то есть к AutoML. Из этого исследования я понял, насколько важно хорошо проводить эксперименты, чтобы они были достоверными и проверяемыми. Я начал спрашивать своих коллег, как они проводят свои эксперименты, и у них был общий шаблон: разработайте свое решение, посмотрите на другие решения, связанные с той же проблемой, запустите каждое решение 30 раз, если оно стохастическое. с эквивалентными ресурсами и сравните свои результаты с другими решениями с помощью статистических тестов: Вилкоксона-Манна-Уитни при сравнении двух алгоритмов или теста Фридмана. Поскольку это не основная тема данной статьи, я не буду подробно останавливаться на статистических тестах.

Как опытный DevOps, у меня был один вопрос по автоматизации: как мне узнать, как воспроизвести эксперимент, особенно другого решения? Угадай ответ? Внимательно прочтите статью или найдите хранилище со всей информацией.

Либо вам повезло и исходный код доступен, либо в публикации указан псевдокод. В этом случае вам необходимо повторно реализовать решение, чтобы можно было его протестировать и сравнить. Даже если вам повезло и есть доступный исходный код, часто отсутствует вся среда (например, точная версия пакетов, сама версия python, версия JDK и т. Д.…). Отсутствие нужной информации влияет на производительность и может потенциально искажать эксперименты. Например, новые версии пакетов, языков и т. Д. Обычно имеют лучшую оптимизацию, которую может использовать ваша реализация. Иногда бывает сложно найти версии, которые использовались практикующими.

Другая проблема — сложность настройки кластера с помощью программного обеспечения HPC (например, Slurm, Torque). Действительно, для управления таким решением требуются технические знания: настройка сети, проверка того, что каждый узел имеет зависимости, необходимые для установленных запусков, проверка того, что узлы имеют одинаковые версии библиотек и т. Д. Эти технические шаги отнимают время у исследователей, таким образом отвлеките их от основной работы. Более того, чтобы извлечь результаты, исследователи обычно делают это вручную, они извлекают различные файлы (через FTP или NFS), а затем выполняют статистические тесты, которые сохраняют вручную. Следовательно, рабочий процесс проведения эксперимента относительно дорог и ненадежен.

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

Решение

OVH предлагает Kubernetes-as-a-service, платформу управляемого кластера, где вам не нужно беспокоиться о том, как настроить кластер (добавить узел, настроить сеть и т. Д.), Поэтому я начал исследовать, как я мог бы аналогичным образом проводить эксперименты. к решениям для высокопроизводительных вычислений. Argo Workflows вышла из коробки. Этот инструмент позволяет вам определять рабочий процесс шагов, которые вы можете выполнять в своем кластере Kubernetes, когда каждый шаг заключен в контейнер, который в общих чертах называется образом. Контейнер позволяет запускать программу в определенной среде программного обеспечения (языковая версия, библиотеки, сторонние программы), дополнительно ограничивая ресурсы (время процессора, память), используемые программой.

Решение связано с нашей большой проблемой: убедитесь, что вы можете воспроизвести эксперимент, который эквивалентен запустить рабочий процесс, состоящий из этапов в соответствии с конкретной средой.



Пример использования: оценка решения AutoML

Вариант использования, который мы используем в нашем исследовании, будет связан с измерением сходимости байесовской оптимизации (SMAC) по проблеме AutoML.

Для этого варианта использования мы указали рабочий процесс Argo в следующем файле yaml

Настроить инфраструктуру

Сначала мы настроим кластер Kubernetes, во-вторых, мы установим службы в нашем кластере и, наконец, проведем эксперимент.

Кластер Kubernetes

Установка кластера Kubernetes с OVH — это детская игра. Подключитесь к панели управления OVH, перейдите к Public Cloud > Managed Kubernetes Service, затем Create a Kubernetes clusterследуйте инструкциям в зависимости от ваших потребностей.

  • После создания кластера:
    • Примите во внимание политику обновления изменений. Если вы исследователь и для выполнения вашего эксперимента требуется некоторое время, вам следует избегать обновления, которое отключило бы вашу инфраструктуру во время выполнения. Чтобы избежать такой ситуации, лучше выбрать «Минимальная недоступность» или «Не обновлять».
    • Загрузите 
      kubeconfig
      файл, он будет использоваться позже 
      kubectl
      для подключения к нашему кластеру.
    • Добавьте хотя бы один узел в свой кластер.
  • После установки вам понадобится kubectl  — инструмент, позволяющий управлять кластером.Если все настроено правильно, должно получиться примерно следующее:
    kubectl top nodes
    NAME      CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
    node01   64m          3%     594Mi           11%
    Установка Арго
    Как мы упоминали ранее, Argo позволяет нам запускать рабочий процесс, состоящий из шагов. Этот учебник вдохновил нас на установку клиента и службы в кластере.Сначала скачиваем и устанавливаем Арго (клиент):
    curl -sSL -o /usr/local/bin/argo https://github.com/argoproj/argo/releases/download/v2.3.0/argo-linux-amd64
    chmod +x /usr/local/bin/argo
    Настройте учетную запись службы:
    kubectl create rolebinding default-admin --clusterrole=admin --serviceaccount=default:default
    Затем с клиентом попробуйте простой рабочий процесс hello-world, чтобы убедиться, что стек работает (статус: успешно):
    argo submit --watch https://raw.githubusercontent.com/argoproj/argo/master/examples/hello-world.yaml
    Name:                hello-world-2lx9d
    Namespace:           default
    ServiceAccount:      default
    Status:              Succeeded
    Created:             Tue Aug 13 16:51:32 +0200 (24 seconds ago)
    Started:             Tue Aug 13 16:51:32 +0200 (24 seconds ago)
    Finished:            Tue Aug 13 16:51:56 +0200 (now)
    Duration:            24 seconds
    
    STEP                  PODNAME            DURATION  MESSAGE
     ✔ hello-world-2lx9d  hello-world-2lx9d  23s
    Вы также можете получить доступ к панели управления пользовательского интерфейса через localhost:8001:

    kubectl port-forward -n argo service/argo-ui 8001:80
    Настроить репозиторий артефактов (MinIO)
    Артефакт — это термин, используемый Argo, он представляет собой архив, содержащий файлы, возвращаемые шагом. В нашем случае мы будем использовать эту функцию, чтобы возвращать окончательные результаты и делиться промежуточными результатами между шагами.Чтобы Artifact работал, нам нужно хранилище объектов. Если он у вас уже есть, вы можете пропустить часть установки, но все равно нужно его настроить.Как указано в руководстве, мы использовали MinIO, вот манифест для его установки ( minio-argo-artifact.install.yml):
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      # This name uniquely identifies the PVC. Will be used in deployment below.
      name: minio-pv-claim
      labels:
        app: minio-storage-claim
    spec:
      # Read more about access modes here: https://kubernetes.io/docs/user-guide/persistent-volumes/#access-modes
      accessModes:
        - ReadWriteOnce
      resources:
        # This is the request for storage. Should be available in the cluster.
        requests:
          storage: 10
      # Uncomment and add storageClass specific to your requirements below. Read more https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class-1
      #storageClassName:
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      # This name uniquely identifies the Deployment
      name: minio-deployment
    spec:
      strategy:
        type: Recreate
      template:
        metadata:
          labels:
            # Label is used as selector in the service.
            app: minio
        spec:
          # Refer to the PVC created earlier
          volumes:
          - name: storage
            persistentVolumeClaim:
              # Name of the PVC created earlier
              claimName: minio-pv-claim
          containers:
          - name: minio
            # Pulls the default MinIO image from Docker Hub
            image: minio/minio
            args:
            - server
            - /storage
            env:
            # MinIO access key and secret key
            - name: MINIO_ACCESS_KEY
              value: "TemporaryAccessKey"
            - name: MINIO_SECRET_KEY
              value: "TemporarySecretKey"
            ports:
            - containerPort: 9000
            # Mount the volume into the pod
            volumeMounts:
            - name: storage # must match the volume name, above
              mountPath: "/storage"
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: minio-service
    spec:
      ports:
        - port: 9000
          targetPort: 9000
          protocol: TCP
      selector:
        app: minio
    • Примечание . Измените следующие пары «ключ-значение»:
      • spec > resources > requests > storage > 10
         соответствуют 10 ГБ хранилища, запрошенного MinIO для кластера
      • TemporaryAccessKey
      • TemporarySecretKey
    kubectl create ns minio
    kubectl apply -n minio -f minio-argo-artifact.install.yml
    Примечание: в качестве альтернативы вы можете установить MinIO с Helm.Теперь нам нужно настроить Argo, чтобы использовать наше хранилище объектов MinIO:
    kubectl edit cm -n argo workflow-controller-configmap
    ...
    data:
      config: |
        artifactRepository:
          s3:
            bucket: my-bucket
            endpoint: minio-service.minio:9000
            insecure: true
            # accessKeySecret and secretKeySecret are secret selectors.
            # It references the k8s secret named 'argo-artifacts'
            # which was created during the minio helm install. The keys,
            # 'accesskey' and 'secretkey', inside that secret are where the
            # actual minio credentials are stored.
            accessKeySecret:
              name: argo-artifacts
              key: accesskey
            secretKeySecret:
              name: argo-artifacts
              key: secretkey
    Добавьте учетные данные:
    kubectl create secret generic argo-artifacts --from-literal=accesskey="TemporaryAccessKey" --from-literal=secretkey="TemporarySecretKey"
    
    Примечание. Используйте правильные учетные данные, указанные выше.Создайте ведро my-bucketс правами Read and writeподключившись к интерфейсу localhost:9000:

    kubectl port-forward -n minio service/minio-service 9000
    Убедитесь, что Argo может использовать Artifact с хранилищем объектов:
    argo submit --watch https://raw.githubusercontent.com/argoproj/argo/master/examples/artifact-passing.yaml
    Name:                artifact-passing-qzgxj
    Namespace:           default
    ServiceAccount:      default
    Status:              Succeeded
    Created:             Wed Aug 14 15:36:03 +0200 (13 seconds ago)
    Started:             Wed Aug 14 15:36:03 +0200 (13 seconds ago)
    Finished:            Wed Aug 14 15:36:16 +0200 (now)
    Duration:            13 seconds
    
    STEP                       PODNAME                            DURATION  MESSAGE
     ✔ artifact-passing-qzgxj
     ├---✔ generate-artifact   artifact-passing-qzgxj-4183565942  5s
     └---✔ consume-artifact    artifact-passing-qzgxj-3706021078  7s
    Примечание: если вы застряли с сообщением ContainerCreating, есть большая вероятность, что Argo не сможет получить доступ к MinIO, например, из-за неверных учетных данных.
    Установить частный реестр
    Теперь, когда у нас есть способ запустить рабочий процесс, мы хотим, чтобы каждый шаг представлял конкретную программную среду (то есть изображение). Мы определили эту среду в Dockerfile.Поскольку каждый шаг может выполняться на разных узлах в нашем кластере, образ необходимо где-то хранить, в случае Docker нам требуется частный реестр.Получить приватный реестр можно разными способами:В нашем случае мы использовали частный реестр OVH.
    # First we clone the repository
    git clone git@gitlab.com:automl/automl-smac-vanilla.git
    cd automl-smac-vanilla
    
    # We build the image locally
    docker build -t asv-environment:latest .
    
    # We push the image to our private registry
    docker login REGISTRY_SERVER -u REGISTRY_USERNAME
    docker tag asv-environment:latest REGISTRY_IMAGE_PATH:latest
    docker push REGISTRY_IMAGE_PATH:latest
    Разрешите нашему кластеру извлекать образы из реестра:
    kubectl create secret docker-registry docker-credentials --docker-server=REGISTRY_SERVER --docker-username=REGISTRY_USERNAME --docker-password=REGISTRY_PWD
    Попробуйте наш эксперимент на инфраструктуре
    git clone git@gitlab.com:automl/automl-smac-vanilla.git
    cd automl-smac-vanilla
    
    argo submit --watch misc/workflow-argo -p image=REGISTRY_IMAGE_PATH:latest -p git_ref=master -p dataset=iris
    Name:                automl-benchmark-xlbbg
    Namespace:           default
    ServiceAccount:      default
    Status:              Succeeded
    Created:             Tue Aug 20 12:25:40 +0000 (13 minutes ago)
    Started:             Tue Aug 20 12:25:40 +0000 (13 minutes ago)
    Finished:            Tue Aug 20 12:39:29 +0000 (now)
    Duration:            13 minutes 49 seconds
    Parameters:
      image:             m1uuklj3.gra5.container-registry.ovh.net/automl/asv-environment:latest
      dataset:           iris
      git_ref:           master
      cutoff_time:       300
      number_of_evaluations: 100
      train_size_ratio:  0.75
      number_of_candidates_per_group: 10
    
    STEP                       PODNAME                            DURATION  MESSAGE
     ✔ automl-benchmark-xlbbg
     ├---✔ pre-run             automl-benchmark-xlbbg-692822110   2m
     ├-·-✔ run(0:42)           automl-benchmark-xlbbg-1485809288  11m
     | └-✔ run(1:24)           automl-benchmark-xlbbg-2740257143  9m
     ├---✔ merge               automl-benchmark-xlbbg-232293281   9s
     └---✔ plot                automl-benchmark-xlbbg-1373531915  10s
    Примечание :Затем мы просто получаем результаты через веб-интерфейс пользователя MinIO localhost:9000(вы также можете сделать это с помощью клиента ).

    Результаты находятся в каталоге с тем же именем, что и имя рабочего процесса argo, в нашем примере это так my-bucket > automl-benchmark-xlbbg.

    Ограничение нашего решения
    Решение не может выполнять параллельные шаги на нескольких узлах. Это ограничение связано с тем, как мы объединяем наши результаты от параллельных шагов к шагу слияния. Мы используем volumeClaimTemplates, то есть монтируем том, и это невозможно сделать между разными узлами. Проблему можно решить двумя способами:
    • Использование параллельных артефактов и их агрегирование, однако это постоянная проблема с Argo
    • Непосредственно реализовать в коде вашего запуска способ сохранить результат в доступном хранилище (например, MinIO SDK )
    Первый способ предпочтительнее, это означает, что вам не нужно изменять и настраивать код для конкретной файловой системы хранилища.
    Советы по улучшению решения
    Если вы хотите продолжить настройку, вам следует изучить следующие темы:
    • Контроль доступа : чтобы ограничить пользователей в разных пространствах (по соображениям безопасности или для контроля ресурсов).
    • Исследуя селектор Арго и селектор Kubernetes : в случае , если у вас есть кластер состоит из узлов , которые имеют различное оборудование , и что вам требуется эксперимент с использованием конкретного оборудования (например, конкретный процессор, графический процессор).
    • Настройте распределенный MinIO : он гарантирует, что ваши данные будут реплицированы на несколько узлов и останутся доступными в случае отказа узла.
    • Мониторинг вашего кластера .
    Вывод
    Не нуждаясь в глубоких технических знаниях, мы показали, что можем легко настроить сложный кластер для проведения исследовательских экспериментов и убедиться, что они могут быть воспроизведены.

Частное облако OVH и HashiCorp Terraform - Часть 1

При обсуждении концепций DevOps и Infrastructure-as-a-Code быстро всплывают инструменты, разработанные HashiCorp. С Terraform HashiCorp предлагает простой способ автоматизации подготовки инфраструктуры как в общедоступных облаках, так и в локальной среде. Terraform имеет долгую историю развертывания и управления ресурсами публичного облака OVH. Например, вы можете найти полное руководство на GitHub. В этой статье мы сосредоточимся на использовании Terraform для взаимодействия с другим решением OVH: частным облаком.



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

Установка

Terraform доступен на веб-сайте HashiCorp практически для всех ОС в виде простого двоичного файла. Просто загрузите его и скопируйте в каталог в PATH вашей операционной системы. Чтобы проверить, что все работает правильно, запустите команду terraform.

$ terraform
Usage: terraform [-version] [-help] <command> [args]

The available commands for execution are listed below.
The most common, useful commands are shown first, followed by
less common or more advanced commands. If you're just getting
started with Terraform, stick with the common commands. For the
other commands, please read the help and docs before usage.

Common commands:
    apply              Builds or changes infrastructure
    console            Interactive console for Terraform interpolations
    destroy            Destroy Terraform-managed infrastructure


Папки и файлы

Как и другие инструменты «Инфраструктура как код», Terraform использует простые файлы для определения целевой конфигурации. Для начала мы создадим каталог и поместим файл с именем main.tf. По умолчанию Terraform будет читать все файлы в рабочем каталоге с .tfрасширением, но для упрощения мы начнем с одного файла. В следующей статье мы увидим, как организовать данные в несколько файлов.

Точно так же, чтобы было легче понять операции Terraform, мы укажем всю необходимую информацию прямо в файлах. Сюда входят имена пользователей, пароли и имена различных ресурсов (vCenter, кластер и т. Д.). Совершенно очевидно, что делать это не рекомендуется для использования Terraform в производстве. Вторая статья также даст возможность улучшить эту часть кода. Но пока давайте будем простыми!

Провайдеры

Провайдеры позволяют указать, как Terraform будет взаимодействовать с внешним миром. В нашем примере провайдер vSphere будет отвечать за подключение к vCenter вашего частного облака. Мы декларируем поставщика следующим образом:

provider "vsphere" {
    user = "admin"
    password = "MyAwesomePassword"
    vsphere_server = "pcc-XXX-XXX-XXX-XXX.ovh.com"
}


Здесь мы видим, что Terraform использует свой собственный способ структурирования данных (также можно записать все в JSON, чтобы облегчить автоматическое создание файлов! ). Данные сгруппированы в блоки (здесь блок с именем vsphere, который относится к типу провайдера ), а данные, относящиеся к блоку, представлены в форме ключей / значений.

Данные

Теперь, когда Terraform может подключаться к vCenter, нам нужно получить информацию об инфраструктуре vSphere. Поскольку мы хотим развернуть виртуальную машину, нам нужно знать центр обработки данных, кластер, шаблон и т. Д. И где мы собираемся его создать. Для этого мы будем использовать блоки типа данных:

data "vsphere_datacenter" "dc" {
  name = "pcc-XXX-XXX-XXX-XXX_datacenter3113"
}

data "vsphere_datastore" "datastore" {
  name          = "pcc-001234"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_virtual_machine" "template" {
  name          = "UBUNTU"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}


В приведенном выше примере, мы пытаемся получить информацию о центре обработки данных с именем pcc-XXX-XXX-XXX-XXX_datacenter3113и получить информацию из хранилища данных с именем pcc-001234и шаблона, имя которого является UBUNTU. Здесь мы видим, что мы используем идентификатор центра обработки данных, чтобы получить информацию об объекте, связанном с ним.

Ресурсы

Ресурсы будут использоваться для создания и / или управления элементами инфраструктуры. В нашем примере мы будем использовать ресурс типа virtual_machine, который, как следует из названия, поможет нам создать виртуальную машину.

resource "vsphere_virtual_machine" "vm" {
  name             = "vm01"
  resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
  datastore_id     = "${data.vsphere_datastore.datastore.id}"
  guest_id         = "${data.vsphere_virtual_machine.template.guest_id}"
  scsi_type        = "${data.vsphere_virtual_machine.template.scsi_type}"

  network_interface {
    network_id = "${data.vsphere_network.network.id}"
  }

  disk {
    label = "disk0"
    size  = "${data.vsphere_virtual_machine.template.disks.0.size}"
  }

  clone {
    template_uuid = "${data.vsphere_virtual_machine.template.id}"

    customize {
      linux_options {
        host_name = "vm01"
        domain     = "example.com"
      }

      network_interface {
        ipv4_address = "192.168.1.2"
        ipv4_netmask = 24
      }

      ipv4_gateway    = "192.168.1.254"
      dns_suffix_list = ["example.com"]
      dns_server_list = ["192.168.1.1"]
    }
  }
}


Структура этого ресурса немного сложнее, потому что она состоит из нескольких подблоков. Мы видим, что сначала мы определим имя виртуальной машины. Затем мы предоставляем информацию о его конфигурации (пул ресурсов, хранилище данных и т. Д.). И блоки используются, чтобы определить конфигурацию своих виртуальных устройств. Субблок позволит вам указать, какой шаблон вы хотите использовать для создания виртуальной машины, а также указать информацию о конфигурации операционной системы, установленной на виртуальной машине. Субблок является специфическим для типа ОС, которую вы хотите клонировать. На всех уровнях мы используем информацию, полученную ранее в блоках.network_interfacedisk clone customize data

Полный пример

provider "vsphere" {
    user = "admin"
    password = "MyAwesomePassword"
    vsphere_server = "pcc-XXX-XXX-XXX-XXX.ovh.com"
}

data "vsphere_datacenter" "dc" {
  name = "pcc-XXX-XXX-XXX-XXX_datacenter3113"
}

data "vsphere_datastore" "datastore" {
  name          = "pcc-001234"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_compute_cluster" "cluster" {
  name          = "Cluster1"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_network" "network" {
  name          = "vxw-dvs-57-virtualwire-2-sid-5001-Dc3113_5001"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_virtual_machine" "template" {
  name          = "UBUNTU"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

resource "vsphere_virtual_machine" "vm" {
  name             = "vm01"
  resource_pool_id = "${data.vsphere_compute_cluster.cluster.resource_pool_id}"
  datastore_id     = "${data.vsphere_datastore.datastore.id}"
  guest_id         = "${data.vsphere_virtual_machine.template.guest_id}"
  scsi_type        = "${data.vsphere_virtual_machine.template.scsi_type}"

  network_interface {
    network_id = "${data.vsphere_network.network.id}"
  }

  disk {
    label = "disk0"
    size  = "${data.vsphere_virtual_machine.template.disks.0.size}"
  }

  clone {
    template_uuid = "${data.vsphere_virtual_machine.template.id}"

    customize {
      linux_options {
        host_name = "vm01"
        domain     = "example.com"
      }

      network_interface {
        ipv4_address = "192.168.1.2"
        ipv4_netmask = 24
      }

      ipv4_gateway    = "192.168.1.254"
      dns_suffix_list = ["example.com"]
      dns_server_list = ["192.168.1.1"]
    }
  }
}


3… 2… 1… Зажигание

Давайте посмотрим, как использовать наш новый файл конфигурации с Terraform…



Инициализация

Теперь, когда наш файл конфигурации готов, мы сможем использовать его для создания нашей виртуальной машины. Начнем с инициализации рабочей среды с помощью terraform initкоманды. Это позаботится о загрузке поставщика vSphere и создании различных файлов, необходимых Terraform для работы.

$ terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "vsphere" (1.10.0)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

...

* provider.vsphere: version = "~> 1.10"

Terraform has been successfully initialized!
...


Строить планы

Следующим шагом является выполнение terraform planкоманды, чтобы убедиться, что наш файл конфигурации не содержит ошибок, и визуализировать все действия, которые будет выполнять Terraform.

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.vsphere_datacenter.dc: Refreshing state...
data.vsphere_compute_cluster.cluster: Refreshing state...
data.vsphere_network.network: Refreshing state...
data.vsphere_datastore.datastore: Refreshing state...
data.vsphere_virtual_machine.template: Refreshing state...

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + vsphere_virtual_machine.vm
      id:                                                   <computed>
      boot_retry_delay:                                     "10000"
      change_version:                                       <computed>
      clone.#:                                              "1"
      clone.0.customize.#:                                  "1"
      clone.0.customize.0.dns_server_list.#:                "1"
      clone.0.customize.0.dns_server_list.0:                "192.168.1.1"
      clone.0.customize.0.dns_suffix_list.#:                "1"
      clone.0.customize.0.dns_suffix_list.0:                "example.com"
      clone.0.customize.0.ipv4_gateway:                     "172.16.0.1"
      clone.0.customize.0.linux_options.#:                  "1"
      clone.0.customize.0.linux_options.0.domain:           "example.com"
      clone.0.customize.0.linux_options.0.host_name:        "vm01"
      clone.0.customize.0.linux_options.0.hw_clock_utc:     "true"
      clone.0.customize.0.network_interface.#:              "1"
      clone.0.customize.0.network_interface.0.ipv4_address: "192.168.1.2"
      clone.0.customize.0.network_interface.0.ipv4_netmask: "16"
      clone.0.customize.0.timeout:                          "10"
      clone.0.template_uuid:                                "42061bc5-fdec-03f3-67fd-b709ec06c7f2"
      clone.0.timeout:                                      "30"
      cpu_limit:                                            "-1"
      cpu_share_count:                                      <computed>
      cpu_share_level:                                      "normal"
      datastore_id:                                         "datastore-93"
      default_ip_address:                                   <computed>
      disk.#:                                               "1"
      disk.0.attach:                                        "false"
      disk.0.datastore_id:                                  "<computed>"
      disk.0.device_address:                                <computed>
      ...

Plan: 1 to add, 0 to change, 0 to destroy.


Перед продолжением важно уделить время проверке всей информации, возвращаемой командой. Было бы беспорядком удалять виртуальные машины в производственной среде из-за ошибки в файле конфигурации… В приведенном ниже примере мы видим, что Terraform создаст новый ресурс (здесь виртуальная машина), а не изменит и не удалит ничего, что в точности Цель!

Применять

На последнем этапе terraform applyкоманда фактически настроит инфраструктуру в соответствии с информацией, содержащейся в файле конфигурации. В качестве первого шага planкоманда будет выполнена, и Terraform попросит вас подтвердить ее, набрав yes.

$ terraform apply
...

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

vsphere_virtual_machine.vm: Creating...
  boot_retry_delay:                                     "" => "10000"
  change_version:                                       "" => "<computed>"
  clone.#:                                              "" => "1"
  clone.0.customize.#:                                  "" => "1"
  clone.0.customize.0.dns_server_list.#:                "" => "1"
  clone.0.customize.0.dns_server_list.0:                "" => "192.168.1.1"
  clone.0.customize.0.dns_suffix_list.#:                "" => "1"
  clone.0.customize.0.dns_suffix_list.0:                "" => "example.com"
  clone.0.customize.0.ipv4_gateway:                     "" => "192.168.1.254"
  clone.0.customize.0.linux_options.#:                  "" => "1"
  clone.0.customize.0.linux_options.0.domain:           "" => "example.com"
  clone.0.customize.0.linux_options.0.host_name:        "" => "terraform-test"
  clone.0.customize.0.linux_options.0.hw_clock_utc:     "" => "true"
  clone.0.customize.0.network_interface.#:              "" => "1"
  clone.0.customize.0.network_interface.0.ipv4_address: "" => "192.168.1.2"
  clone.0.customize.0.network_interface.0.ipv4_netmask: "" => "16"
  clone.0.customize.0.timeout:                          "" => "10"
  clone.0.template_uuid:                                "" => "42061bc5-fdec-03f3-67fd-b709ec06c7f2"
  clone.0.timeout:                                      "" => "30"
  cpu_limit:                                            "" => "-1"
  cpu_share_count:                                      "" => "<computed>"
  cpu_share_level:                                      "" => "normal"
  datastore_id:                                         "" => "datastore-93"
  default_ip_address:                                   "" => "<computed>"
  disk.#:                                               "" => "1"
...
vsphere_virtual_machine.vm: Still creating... (10s elapsed)
vsphere_virtual_machine.vm: Still creating... (20s elapsed)
vsphere_virtual_machine.vm: Still creating... (30s elapsed)
...
vsphere_virtual_machine.vm: Still creating... (1m50s elapsed)
vsphere_virtual_machine.vm: Creation complete after 1m55s (ID: 42068313-d169-03ff-9c55-a23e66a44b48)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.


Когда вы подключаетесь к vCenter вашего частного облака, вы должны увидеть новую виртуальную машину в инвентаре!

Следующие шаги

Теперь, когда мы увидели стандартный рабочий процесс Terraform, вы можете протестировать некоторые изменения в вашем файле конфигурации. Например, вы можете добавить еще один виртуальный диск к своей виртуальной машине, изменив блок ресурса virtual_machine следующим образом:

disk {
  label = "disk0"
  size  = "${data.vsphere_virtual_machine.template.disks.0.size}"
}

disk {
  label = "disk1"
  size  = "${data.vsphere_virtual_machine.template.disks.0.size}"
  unit_number = 1
}


Затем запустите, terraform planчтобы увидеть, что Terraform собирается сделать, чтобы согласовать состояние инфраструктуры с вашим файлом конфигурации.

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.vsphere_datacenter.dc: Refreshing state...
data.vsphere_datastore.datastore: Refreshing state...
data.vsphere_network.network: Refreshing state...
data.vsphere_compute_cluster.cluster: Refreshing state...
data.vsphere_virtual_machine.template: Refreshing state...
vsphere_virtual_machine.vm: Refreshing state... (ID: 4206be6f-f462-c424-d386-7bd0a0d2cfae)

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  ~ vsphere_virtual_machine.vm
      disk.#:                  "1" => "2"
      disk.1.attach:           "" => "false"
      disk.1.datastore_id:     "" => "<computed>"
      ...


Plan: 0 to add, 1 to change, 0 to destroy.


Если вы согласны с предложением действия terraform, вы можете запустить его повторно terraform apply, чтобы добавить новый виртуальный диск к вашей виртуальной машине.

Приберись

Когда вы закончили свои тесты, и вы больше не требуется полезность инфраструктуры, у НУ можете просто запустить terraform destroyкоманду, чтобы удалить все ранее созданные ресурсы. Будьте осторожны с этой командой, так как после этого нет возможности вернуть ваши данные!

$ terraform destroy

data.vsphere_datacenter.dc: Refreshing state...
data.vsphere_compute_cluster.cluster: Refreshing state...
data.vsphere_datastore.datastore: Refreshing state...
data.vsphere_network.network: Refreshing state...
data.vsphere_virtual_machine.template: Refreshing state...
vsphere_virtual_machine.vm: Refreshing state... (ID: 42068313-d169-03ff-9c55-a23e66a44b48)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  - vsphere_virtual_machine.vm


Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

vsphere_virtual_machine.vm: Destroying... (ID: 42068313-d169-03ff-9c55-a23e66a44b48)
vsphere_virtual_machine.vm: Destruction complete after 3s

Destroy complete! Resources: 1 destroyed.


В этой статье мы увидели, как развернуть виртуальную машину с файлом конфигурации Terraform. Это позволило нам узнать основные команды plan, applyи destroy, а также понятия provider, dataи resource. В следующей статье мы разработаем этот пример, изменив его, чтобы сделать его более адаптируемым и универсальным.

Выделенные серверы: вдвое больше пропускной способности по той же цене

Мы объявили об этом на OVH Summit 2018… Мы собирались удвоить общедоступную пропускную способность на выделенных серверах OVH, не меняя цены.

Обещание есть обещание, поэтому несколько недель назад мы его выполнили: теперь ваши серверы имеют вдвое большую пропускную способность по той же цене!

www.ovh.com/blog/wp-content/uploads/2019/03/IMG_0185-300x218.jpg

Мы с самого начала знали, что это обновление возможно, поскольку наше сетевое ядро ​​20 Тбит / с определенно может справиться с дополнительной нагрузкой! Мы ежедневно работаем, чтобы вам было приятно пользоваться нашей сетью, которая является одной из крупнейших в мире среди хостинг-провайдеров.

Действительно, наша сеть постоянно развивается, и наши команды неустанно работают над оптимизацией планирования мощностей и предвидением нагрузки, создаваемой всеми нашими клиентами, распределенной по нашим 28 центрам обработки данных.

Он также более чем способен управлять волнами DDoS-атак, которые прибывают почти ежедневно, отправляя миллионы запросов на размещенные серверы в попытке сделать их недоступными. Они поглощаются нашей внутренней защитой от DDoS-атак без какого-либо воздействия на клиента! Напоминаем, что несколько лет назад мы пострадали от одной из самых крупных атак, в результате которой был генерирован трафик более 1 Тбит / с, но, тем не менее, он был поглощен нашей инфраструктурой без какого-либо ущерба для наших клиентов.

Чтобы гарантировать эту дополнительную общедоступную полосу пропускания, наши команды по работе с сетями и Bare Metal тесно сотрудничали, чтобы быть все более и более БЕРЕМЕННЫМИ, когда дело касается нашей инфраструктуры. В результате тысячи активных устройств (маршрутизаторы, коммутаторы, серверы и т. Д.) Были обновлены полностью прозрачным образом!

Общий процесс развертывания занял некоторое время, так как мы выполнили последовательное обновление с использованием подхода QoS и изоляции для предотвращения возможных всплесков трафика. Ассортимент продуктов по ассортименту, центр обработки данных по центру обработки данных… Само развертывание было быстрым и безболезненным, поскольку оно было полностью автоматизировано. Потенциальным узким местом было убедиться, что все работает, как задумано, что включало тщательный мониторинг всей нашей серверной фермы, поскольку удвоение пропускной способности может иметь огромное влияние, особенно на OVH, где (позвольте мне еще раз упомянуть!) Исходящий трафик действительно неограничен !

Вот краткий обзор новой полосы пропускания для каждого диапазона серверов:



Даже если удвоение пропускной способности еще не покрывает весь диапазон наших диапазонов или серверов So you Start и Kimsufi, мы не забыли наших клиентов, которые используют эти серверы. Мы также обновили наши параметры пропускной способности, чтобы предложить всем нашим клиентам еще лучший сервис по еще более выгодной цене.

Но мы не собираемся останавливаться на достигнутом! Вскоре мы объявим о некоторых приятных новых функциях в сетевой части. И, конечно же, в ближайшие месяцы появится множество других инноваций. Но это другие истории, которые будут рассказаны в других сообщениях блога…

Рабочие процессы непрерывной доставки и развертывания с CDS

Рабочий процесс CDS — ключевая особенность платформы OVH CI / CD. Этот структурный выбор добавляет дополнительную концепцию к конвейерам и заданиям CI / CD и после более чем трех лет интенсивного использования, безусловно, является важной особенностью.



Прежде чем углубляться в полное объяснение рабочих процессов CDS, давайте рассмотрим некоторые ключевые концепции конвейеров и заданий. Эти концепции взяты из справочника 8 принципов непрерывной доставки.

Основной элемент: «Работа».


Задание состоит из шагов, которые будут выполняться последовательно. J О.Б. выполняется в специальной рабочей области (т.е. файловая система). Новое рабочее пространство назначается для каждого нового запуска задания.



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



Вы можете использовать «встроенные» действия, такие как checkoutApplication, скрипт, jUnit, загрузка / скачивание артефактов.

  • Действие heckoutApplication клонирует ваш репозиторий Git
  • Действие сценария выполняет вашу команду сборки как «make build».
  • Действие artifactUpload загружает ранее созданные двоичные файлы
  • Действие jUnit анализирует данный XML-файл в формате Junit, чтобы извлечь результаты его тестирования.

Конвейер: как организовать работу с этапами

С CDS конвейер — это не поток заданий. Трубопровод представляет собой последовательность этапов, каждый из которых содержит один или несколько рабочих мест.



Этап   представляет собой набор заданий , которые будут выполняться параллельно . Этапы выполняются последовательно, если предыдущий этап прошел успешно. 

Возьмем реальный вариант использования: конвейер, на котором построена CDS. Этот конвейер состоит из четырех этапов: 



  • Этап «Build Minimal» запущен для всех веток Git. Основная цель этого этапа — скомпилировать Linux-версию двоичных файлов CDS. 
  • Этап «Сборка другой ОС / Arch» запускается только  в основной ветке. На этом этапе компилируются все бинарные файлы, поддерживаемые os / arch: linux, openbsd, freebsd, darwin, windows-386, amd64 и arm.  
  • Этап «Пакет» запущен для всех веток Git. На этом этапе готовится образ докера и пакет Debian.
  • Наконец, запускается этап «Публикация» независимо от ветки Git. 

По возможности большинство задач выполняются параллельно. Это приводит к очень быстрой обратной связи, поэтому мы быстро узнаем, в порядке ли компиляция или нет. 

Рабочие процессы CDS: как организовать ваши конвейеры


Концепция рабочего процесса — это ключевая функция, которая широко считается встроенной, управляемой и многофункциональной сущностью в CDS. Рабочий процесс CDS позволяет связывать конвейеры с ручными или автоматическими шлюзами, используя условное ветвление. Рабочий процесс может быть сохранен в виде кода, созданного в пользовательском интерфейсе CDS, или в обоих случаях, в зависимости от того, что вам больше подходит. 

Возьмем пример. Один рабочий процесс для создания и развертывания трех микросервисов:  

  • Создайте каждый микросервис 
  • Разверните их на подготовительной стадии 
  • Запустите интеграционные тесты в тестовой среде 
  • Разверните их в производственной среде, а затем повторно запустите интеграционные тесты в производственной среде.



Для строительной части существует только один конвейер для управления, который используется три раза в рабочем процессе с разными контекстами приложения / среды каждый раз. Это называется «контекст конвейера».

Любое условное ветвление рабочего процесса (например, «автоматическое развертывание в промежуточной среде, только если текущая ветвь Git является главной») может быть выполнено с помощью «условного выполнения», установленного в конвейере.



Давайте посмотрим на реальный вариант использования. Это рабочий процесс, который создает, тестирует и развертывает CDS в производственной среде в OVH ( да, CDS создает и развертывает себя! ):



  1. Для каждого коммита Git запускается рабочий процесс
  2. Пользовательский интерфейс упакован, все двоичные файлы подготовлены, а образы докеров созданы. Задание «UT» запускает модульные тесты. Задание «ИТ» устанавливает CDS в эфемерной среде и запускает в ней интеграционные тесты. Часть 2 автоматически запускается при всех коммитах Git.
  3. В части 3 развертывается CDS в нашей подготовительной среде, а затем запускаются интеграционные тесты. Он запускается автоматически, когда текущая ветвь является главной.
  4. И последнее, но не менее важное: часть 4 развертывает CDS в нашей производственной среде.

Я е есть сбой на трубопроводе, он может выглядеть следующим образом:





Но, конечно же, с помощью CDS Workflows вы не ограничены самыми сложными задачами! Эти два примера демонстрируют тот факт, что рабочие процессы позволяют создавать и развертывать согласованный набор микросервисов. Я ж вам есть потребности более простые, ваши рабочие процессы, конечно, проще.



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

Больше чем “Pipeline as Code”… “Workflow as Code”

С CDS нет никаких компромиссов. Некоторые пользователи предпочитают рисовать рабочие процессы через веб-интерфейс, другие предпочитают писать код на yaml. CDS позволяет делать и то, и другое!

Есть два способа хранить рабочие процессы: либо в базе данных CDS, либо в репозитории Git с исходным кодом. Мы называем это «Рабочий процесс как код».

Это позволяет иметь рабочий процесс в одной ветке, а затем развивать его в другой ветке. CDS создаст экземпляр рабочего процесса на лету на основе кода yaml, присутствующего в текущей ветке.

CDS — это программное обеспечение с открытым исходным кодом OVH, которое можно найти на github.com/ovh/cds, с документацией на ovh.github.io/cds.

Как OVH управляет CI / CD в масштабе?

Процесс доставки — это набор шагов — от git commit до производства — которые выполняются для предоставления ваших услуг вашим клиентам. Опираясь на ценности Agile, непрерывная интеграция и непрерывная доставка (CI / CD) — это практики, которые нацелены на максимальную автоматизацию этого процесса.

Команда непрерывной доставки в OVH выполняет одну основную задачу: помочь разработчикам OVH индустриализировать и автоматизировать их процессы доставки. Команда CD здесь, чтобы отстаивать передовой опыт CI / CD и поддерживать наши инструменты экосистемы, уделяя максимальное внимание решениям как услуги.



Центром этой экосистемы является инструмент CDS, разработанный в OVH.
CDS — это программное решение с открытым исходным кодом, которое можно найти по адресу https://github.com/ovh/cds, с документацией по адресу https://ovh.github.io/cds .

CDS — это третье поколение инструментов CI / CD в OVH, следующее за двумя предыдущими решениями, основанными на Bash, Jenkins, Gitlab и Bamboo. Это результат 12-летнего опыта в области CI / CD. Ознакомившись с большинством стандартных инструментов отрасли, мы обнаружили, что ни один из них полностью не соответствовал нашим ожиданиям в отношении четырех выявленных нами ключевых аспектов. Вот что пытается решить CDS.

Вот эти четыре аспекта:

Эластичный

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

Расширяемый

В CDS любые действия (развертывание Kubernetes и OpenStack, отправка в Kafka, тестирование CVE…) могут быть записаны в подключаемых модулях высокого уровня , которые будут использоваться пользователями в качестве строительных блоков . Эти плагины просты в написании и использовании, поэтому легко и эффективно удовлетворить самые экзотические потребности..

Гибко, но легко

CDS может запускать сложные рабочие процессы со всевозможными промежуточными этапами, включая сборку, тестирование, развертывание 1/10/100, ручные или автоматические шлюзы, откат, условные переходы… Эти рабочие процессы могут храниться в виде кода в репозитории git. CDS предоставляет базовые шаблоны рабочих процессов для наиболее распространенных сценариев основной группы, чтобы упростить процесс внедрения. Таким образом, создание функциональной цепочки CI / CD из ничего может быть быстрым и легким.

Самообслуживание

Наконец, ключевым аспектом является идея самообслуживания . После того, как проект CDS создается пользователями, они становятся полностью автономными в этом пространстве, с возможностью управлять конвейерами, делегировать права доступа и т. Д. Все пользователи могут свободно настраивать свое пространство по своему усмотрению и использовать то, что предоставляется. из коробки. Персональная настройка шаблонов рабочих процессов, подключаемых модулей, запуск сборки и тестов на настраиваемых вариантах виртуальных машин или настраиваемом оборудовании… все это можно сделать без какого-либо вмешательства со стороны администраторов CDS.

CI / CD в 2018 году — пять миллионов сотрудников!

  • Около 5,7 млн ​​работников запускались и удалялись по запросу.
  • 3,7 млн ​​контейнеров
  • 2M виртуальных машин

Как это возможно?

Одной из первоначальных задач CDS в OVH было создание и развертывание 150 приложений в виде контейнера менее чем за семь минут. Это стало реальностью с 2015 года. Так в чем же секрет? Автоматическое масштабирование по запросу!

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



Инкубаторий похож на инкубатор: он рождает рабочих CDS и поддерживает над ними власть жизни и смерти.



Каждый инкубаторий предназначен для оркестратора. Кроме того, один инстанс CDS может создавать рабочих на многих облачных платформах:
— Инкубаторий Kubernetes запускает рабочих в контейнерах
— Инкубаторий OpenStack запускает виртуальные машины
— Инкубаторий Swarm запускает докерные контейнеры
— Инкубаторий Marathon запускает докерные контейнеры
— Инкубаторий VSphere запускает виртуальные машины
— местный инкубатор запускает процесс на хосте



Что дальше?

Это всего лишь предварительный просмотр CDS … Нам есть о чем вам рассказать! Инструмент CI / CD предлагает широкий спектр функций, которые мы подробно рассмотрим в наших  следующих статьях . Мы обещаем, что до завершения 2019 года вы больше не будете смотреть на свой инструмент CI / CD таким же образом ...

Понимание CI / CD для больших данных и машинного обучения

На этой неделе команда OVH Integration and Continuous Deployment была приглашена на подкаст DataBuzzWord .



Вместе мы исследовали тему непрерывного развертывания в контексте машинного обучения и больших данных. Мы также обсудили непрерывное развертывание для таких сред , как Kubernetes , Docker, OpenStack и VMware VSphere .

Если вы его пропустили или хотите пересмотреть все, что обсуждалось, вы можете прослушать его еще раз здесь . Мы надеемся скоро вернуться, чтобы продолжить разделять нашу страсть к тестированию, интеграции и непрерывному развертыванию.

Хотя подкаст был записан на французском языке, начиная с завтрашнего дня мы углубимся в ключевые моменты нашего обсуждения в серии статей в этом блоге.

Найдите CDS на GitHub:


…. и подписывайтесь на нас в Twitter:


Обсудите эти темы с нами на нашем канале Gitter: https://gitter.im/ovh-cds/