company_banner

Лучшие практики CI/CD с Kubernetes и GitLab (обзор и видео доклада)



    7 ноября на конференции HighLoad++ 2017, в секции «DevOps и эксплуатация» прозвучал доклад «Лучшие практики CI/CD с Kubernetes и GitLab». В нём мы делимся практическим опытом решения проблем, возникающих при построении эффективного процесса CI/CD на базе указанных Open Source-решений.

    По традиции рады представить видео с докладом (около часа, гораздо информативнее статьи) и основную выжимку в текстовом виде.

    CI/CD и ключевые требования


    Под CI/CD (Continuous Integration, Continuous Delivery, Continuous Delivery) мы понимаем все этапы доставки кода из Git-репозитория в production и последующее его обслуживание вплоть до «снятия» с production. Существующие интерпретации терминов CI/CD допускают различное отношение к этому последнему этапу (эксплуатации в production), но наш опыт говорит, что его исключение из CI/CD приводит к многочисленным проблемам.

    Перед тем, как описывать конкретные практики, мы обобщили те основные факторы, которые влияют на сложность CI/CD:

    1. Как построен основной процесс? Возможные варианты: одно или несколько окружений, динамические окружения, несколько распределённых площадок.
    2. Как осуществляется тестирование? Запуск анализатора кода, unit-тесты без окружения (т.е. без runtime-зависимостей), функциональные/интеграционные/компонентные тесты в окружении, тесты в «полном» окружении для микросервисов (end-to-end, регрессии).
    3. Какое разделение прав требуется? Простое (только писать код/выкатывать для определённых пользователей), разные права по окружениям, multi-stage approval (тимлид разрешает выкат на stage, а QA — на production), коллегиальное решение.
    4. Какова архитектура приложения? Stateless-сервис, stateful-приложение, многокомпонентное приложение, микросервисная архитектура.

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



    Есть и другие — более общие (но не менее важные!) — требования, предъявляемые к CI/CD:


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

    • они должны быть Open Source (это и наш «взгляд на жизнь», и практическая сторона вопроса: доступность кода, отсутствие ограничений в применении);
    • «разномасштабность» (продукты должны одинаково эффективно работать в компаниях и с одним разработчиком, и с пятьюдесятью);
    • интероперабельность (совместимость друг с другом, независимость от поставщика оборудования, облачного провайдера и т.п.);
    • простота эксплуатации;
    • ориентированность на будущее (мы должны быть уверены не только в том, что продукты подходят сейчас, но и имеют перспективы развития).

    Решения для CI/CD


    Стек продуктов, удовлетворяющих всем требованиям, у нас получился следующий:

    • GitLab;
    • Docker;
    • Kubernetes;
    • Helm (для управления пакетами в Kubernetes);
    • dapp (наша Open Source-утилита для упрощения/улучшения процессов сборки и деплоя).

    А общий цикл CI/CD от Git до эксплуатации при использовании перечисленных инструментов выглядит так:



    Практики


    №1. Состав Docker-образа


    Образ должен содержать всё, что требуется для работы приложения:



    №2. Один образ для всего


    Собранный однажды Docker-образ должен использоваться везде. Иначе на проверку к QA может попасть не то же самое, что будет выкачено на production (повторно собранное в другой момент времени).

    У себя мы собираем временные образы из git branch и релизные образы из git tag:



    №3. Выкат и миграции


    Работа различных компонентов, описанных в Kubernetes (бэкенд как Deployment, СУБД как StatefulSet и т.п.), может зависеть друг от друга. В случае «прямолинейного» выката K8s будет обновлять компоненты и перезапускать их (в случае неуспешного старта):



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

    Чтобы сократить время выката, нужно учитывать (и прописывать) не только явные зависимости между компонентами (бэкенд ждёт доступности СУБД), но и косвенные (бэкенд не должен обновляться до проведения миграций).



    №4. Bootstrap БД


    СУБД наполняют данными двумя путями: загрузкой готового дампа или загрузкой сидов/фикстур. Путь в Kubernetes для первого случая — это после старта СУБД запускать задание (Job), которое загружает дамп. После этого — запуск миграций (они косвенно зависят от дампа), а затем — запуск бэкенда (косвенно зависит от миграций).

    Последовательность запуска для случая с сидами меняется и выглядит так: 1) СУБД, 2) миграции, 3) задание с сидами, 4) бэкенд (можно запустить в параллель с сидами, т.к. от сидов не должен зависеть).



    Перебрав различные пути bootstrap'а БД, мы пришли к оптимальности двух: ночной дамп с seeds/fixtures из master и ночной дамп для staging.



    №5. Выкат без простоя


    Даже при правильном деплое новых версий приложения в Kubernetes не все нюансы будут автоматически учтены. Чтобы действительно гарантировать полное отсутствие простоя:

    • не забывайте про корректное завершение всех HTTP-запросов,
    • проверяйте реальную доступность приложения (открытый порт) с помощью прописанных в K8s readiness probes,
    • симулируйте нагрузку во время деплоя (проверяйте, все ли запросы выполнились),
    • предусматривайте запас производительности у подов, доступных в инфраструктуре на время деплоя.



    №6. «Атомарность» выката


    Если во время обновления какого-либо компонента инфраструктуры произошла ошибка (обновление не смогло выкатиться), в пайплайнах GitLab будет соответствующее уведомление, однако сама инфраструктура останется в переходном состоянии. Проблемный компонент не сохранит оригинальную (старую) версию, как «подумает» GitLab, а будет в состоянии не до конца выкатившейся новой версии.

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

    №7. Динамические окружения


    Встроенные в GitLab возможности позволяют при git push'е любого branch (при наличии Helm chart для полного bootstrap'а инфраструктуры) разворачивать в Kubernetes готовую к использованию инсталляцию приложения (с одноимённым пространством имён). Аналогично будет происходить удаление пространства имён при удалении branch'а.

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



    №8. Тесты


    Если с реализацией простых тестов (не нуждающихся в окружении) всё просто, то для функционального и интеграционного тестирования предлагаем создавать динамическое окружение с помощью Helm:



    Итоги


    Возвращаясь к начальному графику с требованиями к CI/CD…



    Описанные практики на базе Open Source-продуктов позволяют решать задачи, отмеченные зелёным цветом. Фиолетовым выделена область проблем, с которыми мы в компании «Флант» работаем буквально каждый день.

    Видео и слайды


    Видео с выступления (около часа) опубликовано в YouTube.

    Презентация доклада:



    P.S.


    Другие доклады в нашем блоге:


    Вероятно, вас также заинтересуют следующие публикации:

    • +21
    • 22k
    • 7
    Флант
    358,27
    Специалисты по DevOps и Kubernetes
    Поделиться публикацией

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

      0
      Спасибо за интересную статью. Очень содержательно.
      Вопрос: у нас на проекте используется webpack (react + redux). Каждый энвайронмент имеет свои URLы, что отображено в конфигах. Разработчики убеждают, что надо билдить несколько образов под каждый энвайронмент. Вы встречались с подобным? Мне не нравится идея, что приходится деплоить один образ в тестовое окружение, другой образ в пре-прод и третий образ в прод и все только потому, что webpack зашифает конфигурацию как адреса апи-сервисов, где лежат ассеты и т.д.
        0

        Вы можете модифицировать конфиги в скрипте ENTRYPOINT вашего контейнера. В случае с Kubernetes можно попробовать использовать initContainer, выполняющий примерно те же функции что и скрипт в ENTRYPOINT или даже configMap, содержащий ваши конфиг(и).

          0
          Эм, тогда в контейнере мне надо будет сохранять и секреты, чтобы можно было модифицировать под каждый энвайронмент.
          Вы считаете, это хорошая практика ребилдить приложение с webpack при инициации имиджа в контейнер?
            0

            Я последний раз касался webpack'а довольно давно и вскользь, если честно. Наш билд производил некий generic (по факту, нерабочий) config.json, который каждый раз заменялся сгенерированным при старте контейнера скриптом из ENTRYPOINT:


            #!/bin/sh
            
            cat << EOF > /app/config.json
            {
              "some_url": "${SOME_URL}",
              "some_secret": "${SOME_SECRET}",
              "key": "${VALUE:-default_value}"
            }
            EOF
            
            ...
            exec "$@" # or just nginx

            Соответственно, при деплое контейнера достаточно выставить все переменные и конфиг будет сгенерен. Вместо большого template в коде скрипта можно использовать sed и заменять параметры in-place в существующем конфиге. В любом случае, идея такая что образ всегда один и тот же, а конфиг модифицируется скриптом при старте.

              0
              Да, вот этот подход с webpack'ом как мне кажется не получится. Потому что сначала надо запустить `yarn install`, потом `yarn build`. У нас SPA, клиента надо будет загрузить на Storage, а сервер надо будет еще раз инициализировать уже с `yarn install -p`. Много телодвижений.

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

              Спасибо.
        0
        distol Привет! Как я понял, у вас «пакет» хельма лежит в папке проекта в `.helm/`. А вопрос такой — вы когда деплоите например сервис на Python, которому нужен наприемер Redis в кластере, то у вас Redis описан в этом же пакете? Или указан как зависимость? Или вообще не указан и вы его деплоите отдельным джобом?
          0

          В этом же пакете.

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

        Самое читаемое