Русский
Русский
English
Статистика
Реклама

Deployment

Перевод Используем Ansible вместе с Terraform

30.10.2020 18:08:58 | Автор: admin


Недавно я начал применять Terraform для создания облачной лабы для тестов, и это довольно круто. Буквально за несколько дней я поднялся с никогда не использовал AWS до я умею декларативно создавать изолированную инфраструктуру в облаке. Я поставил парочку серверов в выделенной сети в VPC с security group и отдельными ключами SSH, все это заняло у меня несколько сотен строк кода.


Все приятно и прельстиво, но после создания сервера из некоторого базового AMI мне надо его развернуть. Мой типовой инструмент для этого Ansible, но, к сожалению, у Terraform нет встроенного модуля для Ansible (есть обратный, начиная с Ansible 2.5, прим. переводчика), в отличие от Chef и Salt. Это не похоже на Packer, имеющий ansible (удаленный) и ansible-local, который я использовал для сборки образов Docker.


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


Нужно ли развертывание в облаке?


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


Далее если делаете свои AMI, вам все равно нужно как-то запускать развертывание, так что опять появляются такие вещи, как Ansible. Ну и опять же, я рекомендую использовать Packer вместе с Ansible.


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


Как использовать Ansible вместе с Terraform


Возвращаемся к задаче развертывания: я нашел три способа использовать Ansible совместно с Terraform после прочтения этого обсуждения. Читайте дальше, чтобы выбрать подходящий вам.


Встроенный inventory с IP сервера


Наиболее очевидное и хакерское решение запускать Ansible с помощью local-exec, например так:


provisioner "local-exec" {    command = "ansible-playbook -i '${self.public_ip},' --private-key ${var.ssh_key_private} provision.yml"}

Просто и приятно, но тут сразу же есть нюанс. local-exec запускается без ожидания запуска сервера, так что в большинстве случаев это не сработает, потому что на момент подключения еще некуда подключаться.


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


В результате у меня получилась следующая вещь, запускающая роль Ansible provisioner:


provisioner "remote-exec" {    inline = ["sudo dnf -y install python"]    connection {      type        = "ssh"      user        = "fedora"      private_key = "${file(var.ssh_key_private)}"    }  }  provisioner "local-exec" {    command = "ansible-playbook -u fedora -i '${self.public_ip},' --private-key ${var.ssh_key_private} provision.yml"  }

Чтобы ansible-playbook работал, вам надо иметь код для Ansible рядом с кодом для Terraform:


$ ll infradrwxrwxr-x. 3 avd avd 4.0K Mar  5 15:54 roles/-rw-rw-r--. 1 avd avd  367 Mar  5 15:19 ansible.cfg-rw-rw-r--. 1 avd avd 2.5K Mar  7 18:54 main.tf-rw-rw-r--. 1 avd avd  454 Mar  5 15:27 variables.tf-rw-rw-r--. 1 avd avd   38 Mar  5 15:54 provision.yml

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


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


Динамический inventory после работы Terraform


Еще одно простое решение для раскатки инфраструктуры, созданной Terraform не связывать вместе Terraform и Ansible. Создаем инфраструктуру с помощью Terraform, а затем используем Ansible с динамическим inventory без привязки к тому, как сервера были созданы.


Так что вы сначала создаете инфраструктуру с помощью terraform apply, затем запускаете ansible-playbook -i inventory site.yml, где inventory каталог, содержащий скрипты динамического inventory.


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


А это то, что я использую в дополнение к предыдущему способу.


Inventory, создаваемая из состояния Terraform


Есть еще одна интересная штука, которая, возможно, будет у вас работать создание статического inventory из состояния Terraform.


Terraform при работе поддерживает состояние инфраструктуры, содержащее все, включая ваши сервера. При использовании local backend это состояние сохраняется в файле JSON, который можно потом легко разобрать и сконвертировать в inventory для Ansible.


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


Первый


[all]52.51.215.84[all:vars][server]52.51.215.84[server.0]52.51.215.84[type_aws_instance]52.51.215.84[name_c10k server]52.51.215.84[%_1]52.51.215.84

Второй


$ ~/soft/terraform.py --root . --hostfile## begin hosts generated by terraform.py ##52.51.215.84        C10K Server## end hosts generated by terraform.py ##

Дополнение Ansible для Terraform, которое у меня не заработало


Наконец, есть несколько проектов, которые пытаются внедрить поддержку Ansible в Terraform, как это уже, например, сделано для Chef.


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


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


...provisioner "ansible" {    plays {        playbook = "./provision.yml"        hosts = ["${self.public_ip}"]    }    become = "yes"    local = "yes"}...

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


Заключение


Terraform и Ansible мощная связка, которую я использую для развертывания облачной инфраструктуры. Для типовых облачных серверов я запускаю Ansible через local-exec, позднее я запускаю Ansible отдельно с динамическим inventory.


Примеры можно найти здесь.


Благодарю за внимание и до новых встреч!

Подробнее..

Фронт без релиз-инженера, или Как я перестал бояться и полюбил деплой

08.02.2021 14:10:06 | Автор: admin

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

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

  • Разделение ответственности в ряде случаев ответственность за деплой оказывается высокой.

  • Необходимость в дополнительных навыках обычно нужно неплохо знать платформу, через которую происходит деплой, или хотя бы разбираться в скриптах вроде Capistrano.

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

Как это происходит у нас

В Учи.ру был создан собственный инструмент для выкатки сервисов и настройки стейджей под названием Шаман по сути кастомный интерфейс к HashiCorp Nomad. Несколько лет назад он помог уйти от исполнения скриптов на локальных машинах, автоматизировать процесс деплоя и позволил разработчикам создавать тестовые конфигурации максимально быстро. Об этом мои коллеги из команды инфраструктуры написали отдельную статью.

Немного истории

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

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

Да придет спаситель

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

А что делать, если тебе нужно поднять копию среды для тестирования второго релиза? Не создавать же всю связку целиком заново? А если ее незначительно видоизменить? Для подобного в Шамане есть возможность создать шаблон конфигурации, в котором с помощью YAML описана вся россыпь машин, сервисов и эндпоинтов. В результате, если ваша команда поддерживает свой шаблон, поднятие нового инстанса развесистой связки сервисов становится тривиальной задачей.

И, кажется, коллеги со мной согласны:

Лично для меня в деплое нет ничего сложного. И учитывая, что иногда мне приходится делать больше десяти тестовых выкаток в день, а также развертывания совершенно новых приложений, я очень рад, что не нужно каждый раз подключать кого-то из отдела инфраструктуры. Приложений у нас много, и возможность самостоятельной выкатки сильно упрощает жизнь и ускоряет работу, Роман Литвинов, ROR разработчик.

Схема связи Шамана с кодом

На данный момент почти все наши GitHub-репозитории собираются при помощи GitHub Actions, в результате чего докер-образ пушится в общую приватную докер-репу. Этот workflow можно триггерить как автоматически (пуш в релизную ветку), так и руками, если хочется потестить какой-то сиюминутный фикс. Дальше Шаман подцепляет свежий образ и по кнопке раскатывает его на стейдж. Ну не красота, а?

И так как этот процесс находится в ведении разработчиков чуть более чем полностью, у нас есть возможность его упрощать. Меня, например, все-таки расстраивает необходимость сначала идти в GitHub, чтобы посмотреть статус билда, а потом в Шаман чтобы нажать на заветную зеленую кнопку для выкатки образа. После незначительной встряски коллег из инфраструктуры выяснилось, что последний предоставляет API, ручку которого можно дернуть из Github Actions с адресом для деплоя и идентификатором образа для деплоя. А это значит, что деплоить код можно полностью автоматически!

Когда что то идёт не так

Понятное дело, что после выкатки кода на прод надо убедится, что он ведёт себя так, как задумано. И тут у нас чуть хуже с автоматизацией, но тем не менее всё под контролем. Для мониторинга мы используем сервис Sentry, который нотифицирует как о новых индивидуальных ошибках, так и об изменениях общего фона. Ну и продуктовые дашборды с метриками никто не отменял. А Шаман, в свою очередь, быстро и непринужденно позволяет откатить бажную версию фронтового приложения в случае серьезных проблем.

Так нужно ли деплоить разработчику?

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

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

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

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

  • Стоит держать в голове, что мы работаем с in-house-решением со всеми вытекающими: оно может сломаться. Поэтому здесь нужно быть достаточно открытым и помогать коллегам чинить проблему, а не бранить решение.

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

Как происходит деплой в вашей компании? Считаете ли вы наш подход с одной кнопкой оптимальным или вам нравятся другие варианты выкатки новых сервисов и обновлений? Поделитесь мнением в комментариях.

Подробнее..

Перевод Как использовать HashiCorp Waypoint для совместной работы с GitLab CICD

22.10.2020 06:11:39 | Автор: admin


HashiCorp показала новый проект Waypoint на HashiCorp Digital. Он использует файл на основе HCL для описания сборки, поставки и выпуска приложений для различных облачных платформ, начиная от Kubernetes и заканчивая AWS и Google Cloud Run. Можно представить, что Waypoint это сложенные вместе Terraform и Vagrant для описания процесса сборки, поставки и выпуска ваших приложений.


Не изменяя себе, HashiCorp выпустила Waypoint с открытым исходным кодом, также к нему прилагается множество примеров. Уровень оркестратора остается за вами, Waypoint поставляется в виде исполняемого файла, который вы можете запустить прямиком на вашем ноутбуке или из выбранного вами инструмента оркестрации CI/CD. Цель для развертывания приложений также выбирается вами, поскольку Waypoint поддерживает Kubernetes, Docker, Google Cloud Run, AWS ECS и другие.


Почитав улетную документацию и шикарнейшие примеры приложений, предоставленные HashiCorp, мы решили взглянуть поближе на оркестровку Waypoint с помощью GitLab CI/CD. Чтобы это сделать, мы возьмем простое приложение Node.js, запускаемое на AWS ECS, из репозитория примеров.


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



Как вы могли заметить, в этом проекте нет Dockerfile. Они не добавлены в примере, поскольку они в принципе и не нужны нам, ведь Waypoint позаботится о них за нас. Давайте рассмотрим детальнее файл waypoint.hcl, чтобы понять, что он будет делать:


project = "example-nodejs"app "example-nodejs" {  labels = {    "service" = "example-nodejs",    "env" = "dev"  }  build {    use "pack" {}    registry {    use "aws-ecr" {        region = "us-east-1"        repository = "waypoint-gitlab"        tag = "latest"    }    }  }  deploy {    use "aws-ecs" {    region = "us-east-1"    memory = "512"    }  }}

На этапе сборки Waypoint использует Cloud Native Buildpacks (CNB), чтобы определить язык программирования проекта и создать образ для Docker без использования Dockerfile. В принципе, это та же самая технология, которая используется GitLab в части Auto DevOps на шаге Auto Build. Приятно видеть, что CNB от CNCF получает все большее распространение у пользователей из отрасли.


Как только образ собран, Waypoint автоматически выгрузит его в нашу AWS ECR registry, чтобы он был готов к поставке. По окончанию сборки шаг поставки использует дополнение AWS ECS для развертывания нашего приложения в нашу учетную запись AWS.


С моего ноутбука все просто. Я ставлю Waypoint, который уже аутентифицирован в моей учетной записи AWS, и оно просто работает. Но что будет, если я захочу выйти за пределы моего ноутбука? Или вдруг я хочу автоматизировать это развертывание в виде части моего общего конвейера CI/CD, где запускаются мои текущие интеграционные тесты, тесты безопасности и прочие? Это та часть рассказа, где появляется GitLab CI/CD!


N.B. Если вы еще только планируете внедрение CI/CD или хотите начать применять лучшие практики построения пайплайнов, обратите внимание на новый курс Слёрма CI/CD на примере Gitlab CI. Сейчас он доступен по цене предзаказа.

Waypoint в GitLab CI/CD


Для оркестровки всего этого в GitLab CI/CD давайте посмотри, что нам понадобится в нашем файле .gitlab-ci.yml:


  • В первую очередь, нужен базовый образ для запуска внутри него. Waypoint работает на любом дистрибутиве Linux, ему нужен только Docker, так что мы может запускаться с generic образа Docker.
  • Далее надо установить Waypoint в этот образ. В будущем мы можем собрать образ meta build и контейнеризировать этот процесс для себя.
  • Наконец мы запустим команды Waypoint

Выше расписано все, что понадобится нашему конвейеру для запуска нужных для выполнения развертывания скриптов, но для развертывания в AWS нам понадобится еще одна вещь: мы должны авторизоваться в нашей учетной записи AWS. В описании Waypoint есть планы об аутентификации и авторизации. HashiCorp на этой неделе также выпустила впечатляющий проект Boundary. Но на данный момент мы можем просто взять и самостоятельно обработать аутентификацию и авторизацию.


Для аутентификации GitLab CI\CD в AWS есть несколько вариантов. Первый вариант использование встроенного HashiCorp Vault. Он подойдет, если ваша команда уже пользуется Vault для управления учетными данными. Еще один способ, который подходит, если ваша команда управляет авторизацией с помощью AWS IAM проверьте, что задачи поставки запускаются через GitLab Runner, авторизованный для запуска развертывания через IAM. Но если вы просто хотите ознакомиться с Waypoint и хотите это сделать побыстрее, есть последний вариант добавить ваши ключи AWS API и Secret в переменные окружения GitLab CI/CD AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY.


Собираем все вместе


Как только мы разобрались с аутентификацией, можно начинать! Наш окончательный .gitlab-ci.yml выглядит так:


waypoint:  image: docker:latest  stage: build  services:    - docker:dind  # Define environment variables, e.g. `WAYPOINT_VERSION: '0.1.1'`  variables:    WAYPOINT_VERSION: ''    WAYPOINT_SERVER_ADDR: ''    WAYPOINT_SERVER_TOKEN: ''    WAYPOINT_SERVER_TLS: '1'    WAYPOINT_SERVER_TLS_SKIP_VERIFY: '1'  script:    - wget -q -O /tmp/waypoint.zip https://releases.hashicorp.com/waypoint/${WAYPOINT_VERSION}/waypoint_${WAYPOINT_VERSION}_linux_amd64.zip    - unzip -d /usr/local/bin /tmp/waypoint.zip    - rm -rf /tmp/waypoint*    - waypoint init    - waypoint build    - waypoint deploy    - waypoint release

Вы видите, что мы начинаем с образа docker:latest и устанавливаем несколько переменных окружения, требуемых для Waypoint. В разделе script мы скачиваем последнюю версию исполняемого файла Waypoint и ставим его в /usr/local/bin. Поскольку наш runner уже авторизован в AWS, далее мы просто запускаем waypoint init, build, deploy и release.


Вывод задачи по сборке покажет нам endpoint, куда мы раскатили приложение:



Waypoint одно из многочисленных решений HashiCorp, отлично работающих с GitLab. Например, в дополнение к поставке приложения мы можем оркестрировать нижележащую инфраструктуру с помощью Terraform в GitLab. Для стандартизации безопасности SDLC, мы можем также внедрить GitLab с Vault для управления секретами и токенами в конвейерах CI/CD, предоставляя целостное решение для разработчиков и администраторов, полагающихся на управление секретами при разработке, тестировании, а также промышленном использовании.


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


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

Подробнее..

Перевод Что такое Waypoint и какие возможности дает его использование

26.10.2020 16:12:51 | Автор: admin


Пару недель назад я посмотрела демонстрацию Waypoint нового инструмента, который представила 15 октября 2020 года компания Hashicorp. Инструмента, который предназначен для создания легкого, интуитивного и настраиваемого под пользователя рабочего процесса сборки, развертывания и релиза.


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


Чем Waypoint не является


Наверное, будет проще в начале пояснить, чем Waypoint не является.


Waypoint не:


  • автоматическая система сборки программ (как make, npm, maven и т.д.)
  • диспетчер пакетов (как helm, pip и т.д.)
  • система непрерывной интеграции (как Jenkins, GitHub Actions и т.д.)
  • registry артефактов (как Artifactory, Docker Registry и т.д.)
  • среда выполнения (как OCI images, Docker containers, buildpacks, машинный код, архивы и т.д.)
  • кластерный оркестратор или кластерная платформа (как Kubernetes, EC2, EKS и т.д.)

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


Что же такое Waypoint?


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


Разработчику не нужно делать каких-либо правок или писать дополнительную конфигурацию. Как говорится в документации:


Waypoint был построен для простоты использования. Больше не надо писать Dockerfiles, YAML и т.д.У нас есть дополнения для автоматического определения вашего языка, построения образа и развертывания. Да, вам придется немного настроить его, но мы сейчас говорим примерно о пятнадцати сроках текста для одного инструмента в сравнении с сотнями строк в разных инструментах на разных языках программирования.

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


Довольно много слов, и изрядное их количество требует объяснения. Остаток статьи как раз будет посвящен расшифровке того, что все это значит.


Переосмысление схемы работы разработкатестированиеразвертываниевыпуск


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


Разработка: включает в себя сборку исполняемых файлов и библиотек, содержащих изменения кода. Большая часть языков программирования имеют собственный набор системы сборки кода. Большинство вычислительных рабочих окружений имеют собственные поддерживаемые форматы артефактов, как Docker/OCI images, ELF binaries, ZIP archives, платформенно-ориентированные артефакты (как AWS Lambda Layers), и это далеко не полный перечень. Любая схема работы с комплексом инструментальных средств, которая имеет целью обслуживать максимально обширное сообщество разработчиков, вынуждена приспосабливаться к сборке кода и предпочтительного формата артефакта, по возможности используя уже существующие ориентированные на конкретный язык встроенные инструментальные средства.


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


Развертывание: чтобы начать обслуживать новый трафик, новые артефакты должны внедряться в эксплуатационную (или тестовую) среду. Часто для этого артефакты устанавливаются на сервера, где запускаются соответствующие исполняемые файлы. В своей карьере я работала с инструментами развертывания, которые охватывали весь спектр: начиная от простых развертываний на основе git (Heroku), до лабиринтообразных скриптов оболочки, до безагентных инструментов удаленного выполнения на основе ssh, как Fabric/Capistrano/Ansible, до систем, запускающих агентов на узлах, которые следят за новыми развертываниями, до (после 2015) сервисов кластерных оркестраторов, как Nomad или Kubernetes. Новому инструменту нецелесообразно заново изобретать платформу/runtime/механизмы развертывания, уже имеющиеся у поставщика облачных услуг, поскольку поставщики облачных услуг уже предоставляют собственные безапелляционные API специальные инструменты и рабочие процессы для развертывания кода.


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


Hashicorp (я никак с ней не связана) с самого начала была компанией с особым сверхъестественным умением выпускать инфраструктурные проекты, соответствующие (за редким исключением) рынку. Это позволило компании процветать, не ввязываясь в схемы с повторным лицензированием, чем массово занимались другие компании, выпускающие инфраструктуры с открытым исходным кодом, а особенно с open-core.


Специалисты, идущие в ногу со временем и использующие продукты Hashicorp так же долго, как и я, возможно, заметят, что Waypoint не первый заход Hashicorp в сферу развертывания или рабочих процессов. Одной из предыдущих попыток был проект Otto, который должен был стать абстракцией высокого уровня для разработки и развертывания приложений. Atlas также был экспериментом для предоставления интегрированных инструментов и общего рабочего пространства как для разработчиков, так и для операторов; от внедрения кода приложения до создания артефакта развертывания, конфигурирования инфраструктуры и в конечном итоге управления жизненным циклом приложения в процессе его работы.


Теперь, по прошествии времени, становится понятно, почему ни один из инструментов не справился и оба были в итоге свернуты или стали частью других предложений. На мой взгляд, и Otto, и Atlas должны были одни махом решить слишком много разношерстных задач. Хотя такой подход мог сработать в случае Consul, который может использоваться в качестве распределенного хранилища данных типа ключ-значение, сервиса распределенных блокировок, сервисной сетки, системы обнаружения сервисов и многого другого (и я за годы работы действительно использовала Consul для многих из этих разнообразных целей), рабочий процесс разработчика или даже рабочий процесс развертывания, требующий использования полного набора инструментов Hashicorp, был не совсем подходящим продуктом для решения такой серьезной проблемы как рабочий процесс, и особенно не в то время (начиная с 2015 года), когда индустрия видела невообразимый всплеск в количестве специализированных инструментов инфраструктуры, систем, платформ, API, парадигм и экосистем. Когда у пользователей есть выбор из множества вариантов, продуктам, нацеленным на объединение разрозненных проблем, пожалуй, труднее найти соответствие продукта и рынка без интеграции с широким спектром доступных решений.


Здесь напрашивается вопрос, чем же третья попытка одолеть эту проблемную задачу отличается от предыдущих?
Начнем с того, что Waypoint намного более узко сфокусирован, чем были Otto и Atlas за все время своего существования. Waypoint призван унифицировать опыт разработчика, не давая предписаний или обязуя пользователей прибегать к другим продуктам Hashicorp, таким как Vagrant, Terraform или Packer.


Во-вторых, складывается ощущение, что Waypoint перенял кое-что у Terraform. Подобно тому как Terraform успешно абстрагировался от различных облачных провайдеров, облачных API, платформ, систем и инструментов с открытым кодом, предоставил пользователям язык (HCL) для определения, настройки, обновления, поддержки и развития исходных конфигураций инфраструктуры и для интеграции, Waypoint стремится предоставить пользователям ориентированную на HCL декларативную спецификацию и простой CLI-интерфейс для управления и развития рабочего процесса сборки и развертывания. Как и проводники в Terraform, так и плагины в Waypoint позволяют проводить интеграцию с внешними и API системами.


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


Наконец, несмотря на то, что доказательство универсальности идет вишенкой на торте, архитектура Waypoint теоретически достаточно гибка, чтобы быть масштабированной до службы целой команде инженеров (создав удаленный сервер Waypoint и удаленных раннеров).


Заключение


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


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


Ни один из существующих инструментов в сфере развертывания не является тем, что я бы назвала инструментами с обычного CD. В сущности, инструменты из разряда Spinnaker настолько сложны, что гарантируют потребность в обучающем курсе. Более того, вы можете возразить, что большому количеству компаний просто никогда не потребуется такой сложный инструмент для развертывания, как Spinnaker.


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


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

Подробнее..

Как готовить Helm правильно несколько полезных рецептов

16.05.2021 18:12:28 | Автор: admin
(источник фото -https://unsplash.com/photos/XWNbUhUINB8(источник фото -https://unsplash.com/photos/XWNbUhUINB8

Когда-то давно, когда ножей не знали,х@#$ говядину рубили... ой нет, это другая сказка, простите. Вот, правильная эта. Когда-то давно, деды сказывают, для обновления сервиса в среде инженерам приходилось самим писать скрипты для деплоймента, а некоторые даже запускали их сами из консоли, руками. Были даже причудливые инструменты вроде ClusterSSH, чтобы делать сеанс одновременной игры на всех нодах кластера.

Но тотальная контейнеризация, ввод в обиход универсального понятия "workload", и оркестризация привели к появлению Kubernetes, на котором сейчас работает примерно 3/4 мировых production сред.

Kubernetes ввёл в обиход концепцию управления конфигурацией при помощи дескрипторов - kubectl apply что в переводе означает "я не хочу объяснять что делать, я говорю как хочу чтобы в среде всё стало - а ты делай что должен, блестящий металлический зад!".

Как с любой инновацией, выход на базовый уровень оркестрации привёл к осознанию того, что дескрипторы должен делать не человек руками, а другой "металлический зад", которому задачу ставить можно на ещё более высоком уровне - "накати обновление сервиса на среду Ъ, согласно правилам подготовки конфигурации среды".

Поэтому Helm стал де-факто стандартом в автоматизированных деплойментах Kubernetes, добавляя новые интересные возможности, которые идут гораздо дальше простой альтернативы выполнению kubectl apply -f ...

Ниже мы расссмотрим несколько примеров того, что можно получить от Helm по сравнению с простой командой kubectl apply, с чем разработчик сталкивается в реальной жизни. Мы покажем, как организовать доставку новых версий сервисов в разные среды, описывая параметры так, что не требуется копипастных повторений, которые ведут к ошибкам. Мы также увидим несколько других полезных приёмов - для удобного управления секретами и организации надёжных пайплайнов.

Итак, напомним, что Helm в принципе может делать три основные вещи:

  • сделать дескрипторы по шаблонам

  • накатить дескрипторы на кластер

  • использовать репозитории charts - взять шаблоны дескрипторов из центральной среды

В данном тексте мы фокусируемся на первых двух моментах, шаблонах и состоянии кластера, не затрагивая репозитории.

Шаблонизатор Helm работает примерно так:

Другими словами, Helm берёт исходники:

  • файлы, текстовые и бинарные

  • шаблоны .yaml

  • значения из одного или нескольких .yaml файлов, и, возможно, командной строки

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

При этом файлы вставляются в Secrets и ConfigMaps, а значения занимают свои места в шаблонах.

Получившиеся шаблоны можно скормить в кластер непосредственно через kubectl, или же (что открывает новые возможности) наложить их через Helm. Helm имеет встроенные средства управления состоянием кластера - несколько последних сделанных деплойментов сохраняются в отдельном Secret, что даёт возможность полного и точного отката при ошибке - то есть обеспечивает атомарность операции и целостность кластера.

Рассмотрим некоторые полезные техники Helm для решения типичных задач, с которыми девопс инженер сталкивается за пределами простого, понятного, нежно-розового мира hello world:

  • управление конфигурацией множества сред

  • использование секретов

  • повышение стабильности пайплайнов

Конфигурация множества сред

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

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

Production-среды имеют общие моменты, характерные для production, например реальный секретный ключ для платёжной системы. Но, тем не менее, могут различаться в других аспектах: например по региону, среда для "канареечных" релизов для тестов на малой группе, или по регионам. Или же может быть несколько production сред для каждого клиента или их группы, если ваш сервис больше похож на SaaS.

12 заповедей

Возможно, вы слышали о 12 заповедях - наборе "золотых" правил для организации сервисов, которым контейнерные оркестрации типа Kubernetes соответствуют чуть менее чем полностью. Под номером 3 сформулирована важность сохранения настроек и конфигураций в самой среде, а не в приложении.

Однако в скрижалях с 12 заповедями не сообщается подробностей - как именно передать эти значения в среды прозрачным, безопасным и контролируемым образом и где все это хранить. Особенно когда вам нужно поддерживать много сред, похожих по одним аспектам, но разных по другим. И как быть с некоторыми значениями конфигурации, которые лучше держать в секрете? Чтобы успешно справиться со всей этой сложностью с помощью Helm, необходимо внести некоторые улучшения в стандартную компоновку chart.

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

Код с конфигурацией - как мясное с молочным? Код с конфигурацией - как мясное с молочным?

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

Каждый новый билд порождает неизменяемые (immutable) контейнер(ы) с кодом, и Helm chart, который можно применить для любой среды - от локального docker-desktop до production кластера в AWS.

При этом код конфигурации, в нашем случае - шаблоны и файлы Helm chart, с полным набором настроек для всех сред, за исключением секретных значений, проще хранить вместе с кодом сервиса. В особенности, если вам всё-таки удалось сделать гармоничную (микро)сервисную архитектуру, и у каждого сервиса есть возможность обновляться независимо. Если такой возможности нет - не льстите себе, у вас распределённый монолит, а не (микро)сервисы.

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

Следовательно:

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

  • ... ведь в подавляющем большинстве случаев все, что нужно сервису - это просто набор из нескольких файлов конфигурации в одном, доступном ему каталоге (conf или properties)

  • а ещё больше мы не хотим ничего копипастить - это ведёт к визуальному мусору, глупым ошибкам, повышению затрат на поддержку - поэтому

    • каждое значение конфигурации и/или файл должны храниться в одном и только в одном месте,

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

    • добавление файла в конфигурацию должно требовать именно этого - добавление файла: никаких изменений в пицот других yaml-ов!

Мы будем хранить конфигурации всех наших сред в диаграмме Helm как часть chart.

Пример ниже, дополнительные каталоги выделены (+).

/env             // (+) значения для сред /<chart-name>    // chart    /files         // (+) конфигурационные файлы и их шаблоны     /templates     // шаблоны Helm   Chart.yaml     // заглавный файл chart  values.yaml    // значения по умолчанию

Файлы со значениями для сред

Давайте посмотрим подробнее на структуру ваших сред.

Допустим у вас есть два типа, TEST и PROD для тестовых и продакшен сред, соответственно. тип TEST делится на две разновидности -STABLE и -PR (пул реквест, нестабильная среда), а для типа PROD у вас разновидности это регионы, EU и US.

Получается примерно такая структура для /env (значения для сред) и /files (файлы конфигурации):

/env                  TEST.yaml            // общие значения для всех тестовых сред  TEST-PR.yaml         // только для PR/нестабильной  TEST-STABLE.yaml     // только для стабильной     PROD.yaml            // общие значения для всех продакшен сред  PROD-EU.yaml         // продакшен EU     PROD-US.yaml         // продакшен US /files                     /TEST                // общие файлы для всех тестовых сред  /TEST-PR             // ...  /TEST-STABLE         // ...    /PROD                // общие файоы для всех продакшен сред   /PROD-EU             // ...  /PROD-US             // ...  /shared              // файлы общие для всех средvalues.yaml             // значения общие для всех сред

Теперь посмотрим что внутри /files - текстовые и бинарные файлы конфигурации.

.../PROD    /binary    /text/PROD-EU    /binary    /text .../shared    /binary    /text    /secret-text

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

Не все типы файлов представлены на всех уровнях, но всему есть причина, давайте подробнее рассмотрим.

1. Мы предполагаем что секретные файлы только текстовые а не бинарные. Почему? Потому что так проще. Секретный двоичный файл (например .p12 сертификат) становится несекретным, если зашифровать его секретным ключом с достаточной (скажем 120+ бит) энтропией - и можно хранить его просто в гите, даже если он размеров в несколько (десятков) килобайт. С точки зрения безопасности - это просто рандомный набор битов. А вот ключ, отпирающий этот бинарник, мы будем хранить в строгом секрете - как и все другие-остальные пароли.

2. С другой стороны, вряд ли будут секретные текстовые файлы, которые при этом не общие (shared) для всех сред. Обычно такой файл - это шаблон (например конфигурация пула соединений к базе данных), в который надо вставить несколько секретных значений, например пароль пользователя. Ниже мы чуть подробнее раскроем тему секретов в шаблонах.

Итак, когда структура выше подготовлена, всё работает примерно так - значения из нескольких файлов сначала превращаются в один плоский yaml, накладываясь друг на друга, в порядке уменьшающегося приоритета:

  • среда - разновидность (TEST-STABLE.yaml)

  • среда - тип (TEST.yaml)

  • общие значения (values.yaml)

Helm позволяет это сделать, указав файлы со значениями как последовательность опций --values:

helm upgrade --install <chart> path/to/<chart> --strict \     --values env/<env>.yaml --values env/<env>-<flavour>.yaml

В .yaml-файлах разновидности и типа среды содержатся атрибуты, например TEST-STABLE.yaml среди прочего содержит:

envFlavour: STABLE

а TEST.yaml содержит

envClass: TEST

В плоском финальном наборе значений всё это объединяется, для того чтобы, как мы увидим в следующем разделе, можно было удобно используем эти значения, чтобы взять нужные файлы из /files.

Один ConfigMap и один Secret для всех файлов

Смотрим внимательно - вот одна, единая конфигурация для одного единственного ConfigMap и одного Secret, куда поместятся все ваши файлы. Бинарные файлы и текстовые несекретные файлы пойдут в ConfigMap, а секретные текстовые файлы в Secret.

# это нужно для вложенных range{{- $self := . -}} # список директорий, откуда берутся файлы - среда-разновидность, среда-тип и потом общие файлы из shared{{ $sources := (list "shared" .Values.envClass (printf "%s-%s" .Values.envFlavour .Values.envClass ) }}apiVersion: v1kind: ConfigMapmetadata:  name: myapp# вставить несекретные текстовые файлы как шаблоныdata:{{ range $env := $sources }}{{ range $path, $bytes := $self.Files.Glob (printf "files/%s/text/*" $env) }}  {{ base $path }}: {{ tpl ($self.Files.Get $path) $ | quote }}{{ end }}{{ end }}# вставить двоичные файлыbinaryData:{{ range $env := $sources }}{{ range $path, $bytes := $self.Files.Glob (printf "files/%s/binary/*" $env) }}  {{ base $path }}: {{ $self.Files.Get $path | b64enc | quote }}{{ end }}{{ end }}---apiVersion: v1kind: Secretmetadata:  name: myapp  labels:type: Opaquedata:# вставить секретные текстовые файлы как шаблоны{{ range $env := $sources }}{{ range $path, $bytes := $self.Files.Glob (printf "files/%s/secret-text/*" $env) }}  {{ base $path }}: {{ tpl ($self.Files.Get $path) $ | b64enc | quote }}{{ end }}{{ end }}

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

Тотальная шаблонизация

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

А вот почему. Заметили маленькую функцию tpl, применяемую на каждый файл? Правильно, именно поэтому она нужна, чтобы каждый текстовый файл обрабатывался как отдельный шаблон - вы можете вставлять ссылки на значения как {{ .Values.myValue }}в любое место ваших текстовых файлов конфигурации.

Шаблон может использоваться для любого типа конфигурации, .properties, yaml, HOCON, например:

akka {  persistence {    cassandra {        keyspace = {{ .Values.cassandra.keyspace | quote }}        table = "{{ .Values.cassandra.tablePrefix }}messages"

Коварные кавычки

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

Каким-то значениям, содержащим скобки, запятые и прочее, что может смутить парсер конфигурации, полезно быть взятым в кавычки:

databasePassword: {{ .Values.databasePassword | quote }}

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

param/username={{ .Values.username | trimAll "\"" }}

Проецируемые тома (projected volumes)

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

Для этого в Kubernetes удачно завезли projected volumes - проецируемые тома, которые можно собирать из нескольких ConfigMaps и Secrets.

volumes:  - name: properties    projected:      defaultMode: 0640      sources:        - configMap:            name: myapp        - secret:            name: myapp

Очень просто смонтировать такой "сборный" том в директорию/confвашего приложения.

Линтер

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

У Helm есть для этого команды lint и template:

helm lint --debug path/to/<chart> --strict --values env/<env>.yaml \  --values env/<env>-<flavour>.yaml  helm template <chart> path/to/<chart> --strict --values env/<env>.yaml \  --values env/<env>-<flavour>.yaml

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

Но совершенству нет пределов (почти) - можно пойти ещё на шаг дальше и использовать yamllint для валидации yaml-дескрипторов. Это позволит отловить проблемы, которые иначе не получается ловить - например два файла с одним и тем же именем, которые оказались в PROD и PROD-EU, будут дважды вставлены в ConfigMap, что приведёт к ошибке при деплойменте.

Управление секретами

Теперь немного про то, как обращаться с секретами - например ключами платных API, паролями для баз данных и паролями от TLS-сертификатов, которые мы не хотим показывать никому, кроме самого сервиса.

Обычно секретные значения хранятся в отдельном сервисе, типа Heroku Vault, Azure Vault, Google Cloud KMS и подобных. В Helm даже есть плагин для управления секретами,но в большинстве компаний управление секретами централизовано, и инженерам не позволено, да и не нужно, трогать секреты из production.

Когда пайплайн запускает обновление сервиса в подобной среде, то сам пайплайн имеет возможность считать секреты и вставить их в конфигурационные файлы. В большинстве случаев (Gitlab, Circle, Azure, ...) секреты становятся переменными среды, и всё что требуется - это вставить их в шаблон в самом начале.

Для этого мы добавим секцию в values.yaml

# secretsdatabase_username: "${UserNameSecret}"database_password: "${DatabasePasswordSecret}"

И в нашем пайплайне испольуемenvsubstили нечто похожее:

cat <chart>/values.yaml | envsubst > <chart>/values-injected.yamlmv <chart>/values-injected.yaml <chart>/values.yaml

Такой подход позволяет ссылаться на секреты просто как {{ .Value.xxx }} в любом шаблоне, вставляя их туда куда им положено быть.

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

Чтобы решить этот вопрос, можно ввести конвенцию называть секреты как XXXSecret, и добавить что-то типа такого:

EXPOSED_SECRETS=$(grep Secret <chart>/files | grep -v secret-files | wc -l)if [ $EXPOSED_SECRETS -gt 0 ]; then fail "Secrets are exposed"; fi

Это добавит уровень защиты против случайной потери секрета.

Атомарные обновления сред

Теперь последний раздел - как использовать Helm hooks для того, чтобы повысить надёжность ваших пайплайнов.

Предположим, вы только что развернули новую версию приложения или новый набор значений конфигурации. Теперь вы определенно хотите знать, не поломалось ли что-то? Если все в порядке, вы можете продолжить работу над другими задачами вашего спринта, но если что-то не так - то вы скорее всего очень хотите, чтобы среда была автоматически восстановлена до последнего известного исправного состояния, без вашего вмешательства и всех этих надоедливых сообщений фрустрированных коллег: Почему я снова получаю ошибку 5xx?.

Это может показаться сложным - особенно если вы хотите реализовать это самостоятельно, вооружившись простым kubectl apply -f ... Но, к счастью, Helm многое делает "прямо из коробки".

Флаг --atomic

Просто добавьте флаг --atomicдля автоматической отмены текущего обновления, если поды не запустились по любой причине:

helm upgrade --install my-chart some/path/to/my-chart \    --atomic --debug --timeout 300s

Helm откатит обновление, если проверки проб health/readiness не дали положительного результата в указанный срок. Хорошо, но можно лучше.

Hooks

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

И разумеется, сигнализировать Helm откатить обновление, если тест не прошёл.

apiVersion: batch/v1kind: Jobmetadata:  name: myapp  labels:  annotations:     "helm.sh/hook": post-install,post-upgrade     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeededspec:  template:    metadata:      name: myapp-smoke-test    spec:      restartPolicy: Never      containers:        - name: tests          image: test-image:           command: ['/bin/sh',                    '-c',                    '/test/run-test.sh']

Если вы используете--atomicфлаг и post-upgrade/post-install hook для тестов в пайплайне, вы можете со значительной долей уверенности считать пайплайн надёжным. Он будет иметь "зелёный" статус тогда, и только тогда, когда сервис был успешно обновлён, запущен и прошёл базовые тесты.

А если что-то пошло не так - Helm возвратит среду в то состояние, которое было до этого обновление, включая все конфигурации и дескрипторы.

Автоматически!

Заключение

Helm это бесплатный инструмент с открытым кодом для управления обновлениями сервисов и их конфигураций в кластерах Kubernetes.

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

***

Этот текст является авторским переводом, оригинал статьи этого же автора
https://jacum.medium.com/advanced-helm-practices-for-perfect-kubernetes-deployments-7fc4e00cc41c

Подробно об авторе - https://www.linkedin.com/in/tim-evdokimov/

Подробнее..

Blue-Green Deployment на минималках

24.08.2020 22:08:53 | Автор: admin

В этой статье мы с помощью bash, ssh, docker и nginx организуем бесшовную выкладку веб-приложения. Blue-green deployment это техника, позволяющая мгновенно обновлять приложение, не отклоняя ни одного запроса. Она является одной из стратегий zero downtime deployment и лучше всего подходит для приложений с одним инстансом, но возможностью загрузить рядом второй, готовый к работе инстанс.


Допустим, у Вас есть веб-приложение, с которым активно работает множество клиентов, и ему совершенно никак нельзя на пару секунд прилечь. А Вам очень нужно выкатить обновление библиотеки, фикс бага или новую крутую фичу. В обычной ситуации, потребуется остановить приложение, заменить его и снова запустить. В случае докера, можно сначала заменить, потом перезапустить, но всё равно будет период, в котором запросы к приложению не обработаются, ведь обычно приложению требуется некоторое время на первоначальную загрузку. А если оно запустится, но окажется неработоспособным? Вот такая задача, давайте её решать минимальными средствами и максимально элегантно.


DISCLAIMER: Большая часть статьи представлена в экспериментальном формате в виде записи консольной сессии. Надеюсь, это будет не очень сложно воспринимать, и этот код сам себя документирует в достаточном объёме. Для атмосферности, представьте, что это не просто кодсниппеты, а бумага из "железного" телетайпа.



Интересные техники, которые сложно нагуглить просто читая код описаны в начале каждого раздела. Если будет непонятно что-то ещё гуглите и проверяйте в explainshell (благо, он снова работает, в связи с разблокировкой телеграма). Что не гуглится спрашивайте в комментах. С удовольствием дополню соответствующий раздел "Интересные техники".


Приступим.


$ mkdir blue-green-deployment && cd $_

Сервис


Сделаем подопытный сервис и поместим его в контейнер.


Интересные техники


  • cat << EOF > file-name (Here Document + I/O Redirection) способ создать многострочный файл одной командой.
  • wget -qO- URL (explainshell) вывести полученный по HTTP документ в /dev/stdout (аналог curl URL).

Распечатка


Я специально разрываю сниппет, чтобы включить подсветку для Python. В конце будет ещё один такой кусок. Считайте, что в этих местах бумага порвалась и была склеена.

$ cat << EOF > uptimer.py

from http.server import BaseHTTPRequestHandler, HTTPServerfrom time import monotonicapp_version = 1app_name = f'Uptimer v{app_version}.0'loading_seconds = 15 - app_version * 5class Handler(BaseHTTPRequestHandler):    def do_GET(self):        if self.path == '/':            try:                t = monotonic() - server_start                if t < loading_seconds:                    self.send_error(503)                else:                    self.send_response(200)                    self.send_header('Content-Type', 'text/html')                    self.end_headers()                    response = f'<h2>{app_name} is running for {t:3.1f} seconds.</h2>\n'                    self.wfile.write(response.encode('utf-8'))            except Exception:                self.send_error(500)        else:            self.send_error(404)httpd = HTTPServer(('', 8080), Handler)server_start = monotonic()print(f'{app_name} (loads in {loading_seconds} sec.) started.')httpd.serve_forever()

EOF$ cat << EOF > DockerfileFROM python:alpineEXPOSE 8080COPY uptimer.py app.pyCMD [ "python", "-u", "./app.py" ]EOF$ docker build --tag uptimer .Sending build context to Docker daemon  39.42kBStep 1/4 : FROM python:alpine ---> 8ecf5a48c789Step 2/4 : EXPOSE 8080 ---> Using cache ---> cf92d174c9d3Step 3/4 : COPY uptimer.py app.py ---> a7fbb33d6b7eStep 4/4 : CMD [ "python", "-u", "./app.py" ] ---> Running in 1906b4bd9fdfRemoving intermediate container 1906b4bd9fdf ---> c1655b996fe8Successfully built c1655b996fe8Successfully tagged uptimer:latest$ docker run --rm --detach --name uptimer --publish 8080:8080 uptimer8f88c944b8bf78974a5727070a94c76aa0b9bb2b3ecf6324b784e782614b2fbf$ docker psCONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES8f88c944b8bf        uptimer             "python -u ./app.py"   3 seconds ago       Up 5 seconds        0.0.0.0:8080->8080/tcp   uptimer$ docker logs uptimerUptimer v1.0 (loads in 10 sec.) started.$ wget -qSO- http://localhost:8080  HTTP/1.0 503 Service Unavailable  Server: BaseHTTP/0.6 Python/3.8.3  Date: Sat, 22 Aug 2020 19:52:40 GMT  Connection: close  Content-Type: text/html;charset=utf-8  Content-Length: 484$ wget -qSO- http://localhost:8080  HTTP/1.0 200 OK  Server: BaseHTTP/0.6 Python/3.8.3  Date: Sat, 22 Aug 2020 19:52:45 GMT  Content-Type: text/html<h2>Uptimer v1.0 is running for 15.4 seconds.</h2>$ docker rm --force uptimeruptimer

Реверс-прокси


Чтобы наше приложение имело возможность незаметно поменяться, необходимо, чтобы перед ним была ещё какая-то сущность, которая скроет его подмену. Это может быть веб-сервер nginx в режиме реверс-прокси. Реверс-прокси устанавливается между клиентом и приложением. Он принимает запросы от клиентов и перенаправляет их в приложение а ответы приложения направляет клиентам.


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


Если реверс-прокси будет жить на другом хосте, придётся отказаться от docker network и связать приложение с реверс-прокси через сеть хоста, пробросив порт приложения параметром --publish, как при первом запуске и как у реверс-прокси.


Реверс-прокси будем запускать на порту 80, ибо это именно та сущность, которой следует слушать внешку. Если 80-й порт у Вас на тестовом хосте занят, поменяйте параметр --publish 80:80 на --publish ANY_FREE_PORT:80.


Интересные техники



Распечатка


$ docker network create web-gateway5dba128fb3b255b02ac012ded1906b7b4970b728fb7db3dbbeccc9a77a5dd7bd$ docker run --detach --rm --name uptimer --network web-gateway uptimera1105f1b583dead9415e99864718cc807cc1db1c763870f40ea38bc026e2d67f$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer:8080<h2>Uptimer v1.0 is running for 11.5 seconds.</h2>$ docker run --detach --publish 80:80 --network web-gateway --name reverse-proxy nginx:alpine80695a822c19051260c66bf60605dcb4ea66802c754037704968bc42527bf120$ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES80695a822c19        nginx:alpine        "/docker-entrypoint."   27 seconds ago       Up 25 seconds       0.0.0.0:80->80/tcp   reverse-proxya1105f1b583d        uptimer             "python -u ./app.py"     About a minute ago   Up About a minute   8080/tcp             uptimer$ cat << EOF > uptimer.confserver {    listen 80;    location / {        proxy_pass http://uptimer:8080;    }}EOF$ docker cp ./uptimer.conf reverse-proxy:/etc/nginx/conf.d/default.conf$ docker exec reverse-proxy nginx -s reload2020/06/23 20:51:03 [notice] 31#31: signal process started$ wget -qSO- http://localhost  HTTP/1.1 200 OK  Server: nginx/1.19.0  Date: Sat, 22 Aug 2020 19:56:24 GMT  Content-Type: text/html  Transfer-Encoding: chunked  Connection: keep-alive<h2>Uptimer v1.0 is running for 104.1 seconds.</h2>

Бесшовный деплоймент


Выкатим новую версию приложения (с двухкратным бустом startup performance) и попробуем бесшовно её задеплоить.


Интересные техники


  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' Записать текст my text в файл /my-file.txt внутри контейнера my-container.
  • cat > /my-file.txt Записать в файл содержимое стандартного входа /dev/stdin.

Распечатка


$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py$ docker build --tag uptimer .Sending build context to Docker daemon  39.94kBStep 1/4 : FROM python:alpine ---> 8ecf5a48c789Step 2/4 : EXPOSE 8080 ---> Using cache ---> cf92d174c9d3Step 3/4 : COPY uptimer.py app.py ---> 3eca6a51cb2dStep 4/4 : CMD [ "python", "-u", "./app.py" ] ---> Running in 8f13c6d3d9e7Removing intermediate container 8f13c6d3d9e7 ---> 1d56897841ecSuccessfully built 1d56897841ecSuccessfully tagged uptimer:latest$ docker run --detach --rm --name uptimer_BLUE --network web-gateway uptimer96932d4ca97a25b1b42d1b5f0ede993b43f95fac3c064262c5c527e16c119e02$ docker logs uptimer_BLUEUptimer v2.0 (loads in 5 sec.) started.$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080<h2>Uptimer v2.0 is running for 23.9 seconds.</h2>$ sed s/uptimer/uptimer_BLUE/ uptimer.conf | docker exec --interactive reverse-proxy sh -c 'cat > /etc/nginx/conf.d/default.conf'$ docker exec reverse-proxy cat /etc/nginx/conf.d/default.confserver {    listen 80;    location / {        proxy_pass http://uptimer_BLUE:8080;    }}$ docker exec reverse-proxy nginx -s reload2020/06/25 21:22:23 [notice] 68#68: signal process started$ wget -qO- http://localhost<h2>Uptimer v2.0 is running for 63.4 seconds.</h2>$ docker rm -f uptimeruptimer$ wget -qO- http://localhost<h2>Uptimer v2.0 is running for 84.8 seconds.</h2>$ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES96932d4ca97a        uptimer             "python -u ./app.py"     About a minute ago   Up About a minute   8080/tcp             uptimer_BLUE80695a822c19        nginx:alpine        "/docker-entrypoint."   8 minutes ago        Up 8 minutes        0.0.0.0:80->80/tcp   reverse-proxy

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


Перекачка образов


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


$ ssh production-server docker image lsREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE$ docker image save uptimer | ssh production-server 'docker image load'Loaded image: uptimer:latest$ ssh production-server docker image lsREPOSITORY          TAG                 IMAGE ID            CREATED             SIZEuptimer             latest              1d56897841ec        5 minutes ago       78.9MB

Команда docker save сохраняет данные образа в .tar архив, то есть он весит примерно в 1.5 раза больше, чем мог бы весить в сжатом виде. Так пожмём же его во имя экономии времени и трафика:


$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'Loaded image: uptimer:latest

А ещё, можно наблюдать за процессом перекачки (правда, для этого нужна сторонняя утилита):


$ docker image save uptimer | gzip | pv | ssh production-server 'zcat | docker image load'25,7MiB 0:01:01 [ 425KiB/s] [                   <=>    ]Loaded image: uptimer:latest

Совет: Если Вам для соединения с сервером по SSH требуется куча параметров, возможно вы не используете файл ~/.ssh/config.

Передача образа через docker image save/load это наиболее минималистичный метод, но не единственный. Есть и другие:


  1. Container Registry (стандарт отрасли).
  2. Подключиться к docker daemon сервера с другого хоста:
    1. Переменная среды DOCKER_HOST.
    2. Параметр командной строки -H или --host инструмента docker-compose.
    3. docker context

Второй способ (с тремя вариантами его реализации) хорошо описан в статье How to deploy on remote Docker hosts with docker-compose.


deploy.sh


Теперь соберём всё, что мы делали вручную в один скрипт. Начнём с top-level функции, а потом посмотрим на остальные, используемые в ней.


Интересные техники


  • ${parameter?err_msg} одно из заклинаний bash-магии (aka parameter substitution). Если parameter не задан, вывести err_msg и выйти с кодом 1.
  • docker --log-driver journald по-умолчанию, драйвером логирования докера является текстовый файл без какой-либо ротации. С таким подходом логи быстро забивают весь диск, поэтому для production-окружения необходимо менять драйвер на более умный.

Скрипт деплоймента


deploy() {    local usage_msg="Usage: ${FUNCNAME[0]} image_name"    local image_name=${1?$usage_msg}    ensure-reverse-proxy || return 2    if get-active-slot $image_name    then        local OLD=${image_name}_BLUE        local new_slot=GREEN    else        local OLD=${image_name}_GREEN        local new_slot=BLUE    fi    local NEW=${image_name}_${new_slot}    echo "Deploying '$NEW' in place of '$OLD'..."    docker run \        --detach \        --restart always \        --log-driver journald \        --name $NEW \        --network web-gateway \        $image_name || return 3    echo "Container started. Checking health..."    for i in {1..20}    do        sleep 1        if get-service-status $image_name $new_slot        then            echo "New '$NEW' service seems OK. Switching heads..."            sleep 2  # Ensure service is ready            set-active-slot $image_name $new_slot || return 4            echo "'$NEW' service is live!"            sleep 2  # Ensure all requests were processed            echo "Killing '$OLD'..."            docker rm -f $OLD            docker image prune -f            echo "Deployment successful!"            return 0        fi        echo "New '$NEW' service is not ready yet. Waiting ($i)..."    done    echo "New '$NEW' service did not raise, killing it. Failed to deploy T_T"    docker rm -f $NEW    return 5}

Использованные функции:


  • ensure-reverse-proxy Убеждается, что реверс-прокси работает (полезно для первого деплоя)
  • get-active-slot service_name Определяет какой сейчас слот активен для заданного сервиса (BLUE или GREEN)
  • get-service-status service_name deployment_slot Определяет готов ли сервис к обработке входящих запросов
  • set-active-slot service_name deployment_slot Меняет конфиг nginx в контейнере реверс-прокси

По порядку:


ensure-reverse-proxy() {    is-container-up reverse-proxy && return 0    echo "Deploying reverse-proxy..."    docker network create web-gateway    docker run \        --detach \        --restart always \        --log-driver journald \        --name reverse-proxy \        --network web-gateway \        --publish 80:80 \        nginx:alpine || return 1    docker exec --interactive reverse-proxy sh -c "> /etc/nginx/conf.d/default.conf"    docker exec reverse-proxy nginx -s reload}is-container-up() {    local container=${1?"Usage: ${FUNCNAME[0]} container_name"}    [ -n "$(docker ps -f name=${container} -q)" ]    return $?}get-active-slot() {    local service=${1?"Usage: ${FUNCNAME[0]} service_name"}    if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then        echo "Collision detected! Stopping ${service}_GREEN..."        docker rm -f ${service}_GREEN        return 0  # BLUE    fi    if is-container-up ${service}_BLUE && ! is-container-up ${service}_GREEN; then        return 0  # BLUE    fi    if ! is-container-up ${service}_BLUE; then        return 1  # GREEN    fi}get-service-status() {    local usage_msg="Usage: ${FUNCNAME[0]} service_name deployment_slot"    local service=${1?usage_msg}    local slot=${2?$usage_msg}    case $service in        # Add specific healthcheck paths for your services here        *) local health_check_port_path=":8080/" ;;    esac    local health_check_address="http://personeltest.ru/away/${service}_${slot}${health_check_port_path}"    echo "Requesting '$health_check_address' within the 'web-gateway' docker network:"    docker run --rm --network web-gateway alpine \        wget --timeout=1 --quiet --server-response $health_check_address    return $?}set-active-slot() {    local usage_msg="Usage: ${FUNCNAME[0]} service_name deployment_slot"    local service=${1?$usage_msg}    local slot=${2?$usage_msg}    [ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1    get-nginx-config $service $slot | docker exec --interactive reverse-proxy sh -c "cat > /etc/nginx/conf.d/$service.conf"    docker exec reverse-proxy nginx -t || return 2    docker exec reverse-proxy nginx -s reload}

Функция get-active-slot требует небольших пояснений:


Почему она возвращает число, а не выводит строку?

Всё равно в вызывающей функции мы проверяем результат её работы, а проверять exit code средствами bash намного проще, чем строку. К тому же, получить из неё строку очень просто:
get-active-slot service && echo BLUE || echo GREEN.


А трёх условий точно хватает, чтобы различить все состояния?


Даже двух хватит, последнее тут просто для полноты, чтобы не писать else.


Осталась неопределённой только функция, возвращающая конфиги nginx: get-nginx-config service_name deployment_slot. По аналогии с хелсчеком, тут можно задать любой конфиг для любого сервиса. Из интересного только cat <<- EOF, что позволяет убрать все табы в начале. Правда, цена благовидного форматирования смешанные табы с пробелами, что сегодня считается очень дурным тоном. Но bash форсит табы, а в конфиге nginx тоже было бы неплохо иметь нормальное форматирование. Короче, тут смешение табов с пробелами кажется действительно лучшим решением из худших. Однако, в сниппете ниже Вы этого не увидите, так как хабр "делает хорошо", меняя все табы на 4 пробела и делая невалидным EOF. А вот тут заметно.


Чтоб два раза не вставать, сразу расскажу про cat << 'EOF', который ещё встретится далее. Если писать просто cat << EOF, то внутри heredoc производится интерполяция строки (раскрываются переменные ($foo), вызовы команд ($(bar)) и т.д.), а если заключить признак конца документа в одинарные ковычки, то интерполяция отключается и символ $ выводится как есть. То что надо для вставки скрипта внутрь другого скрипта.

get-nginx-config() {    local usage_msg="Usage: ${FUNCNAME[0]} service_name deployment_slot"    local service=${1?$usage_msg}    local slot=${2?$usage_msg}    [ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1    local container_name=${service}_${slot}    case $service in        # Add specific nginx configs for your services here        *) nginx-config-simple-service $container_name:8080 ;;    esac}nginx-config-simple-service() {    local usage_msg="Usage: ${FUNCNAME[0]} proxy_pass"    local proxy_pass=${1?$usage_msg}cat << EOFserver {    listen 80;    location / {        proxy_pass http://$proxy_pass;    }}EOF}

Это и есть весь скрипт. И вот гист с этим скриптом для скачки через wget или curl.


Выполнение параметризированных скриптов на удалённом сервере


Пришло время стучаться на целевой сервер. В этот раз localhost вполне подойдёт:


$ ssh-copy-id localhost/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keyshimura@localhost's password: Number of key(s) added: 1Now try logging into the machine, with:   "ssh 'localhost'"and check to make sure that only the key(s) you wanted were added.

Мы написали скрипт деплоймента, который перекачивает предварительно собранный образ на целевой сервер и бесшовно подменяет контейнер сервиса, но как его выполнить на удалённой машине? У скрипта есть аргументы, так как он универсален и может деплоить сразу несколько сервисов под один реверс-прокси (конфигами nginx можно разрулить по какому url какой будет сервис). Скрипт нельзя хранить на сервере, так как в этом случае мы не сможем его автоматически обновлять (с целью багфиксов и добавления новых сервисоы), да и вообще, стэйт = зло.


Решение 1: Таки хранить скрипт на сервере, но копировать его каждый раз через scp. Затем подключиться по ssh и выполнить скрипт с необходимыми аргументами.


Минусы:


  • Два действия вместо одного
  • Места куда вы копируете может не быть, или не быть к нему доступа, или скрипт может выполняться в момент подмены.
  • Желательно убрать за собой (удалить скрипт).
  • Уже три действия.

Решение 2:


  • В скрипте держать только определения функций и вообще ничего запускать
  • С помощью sed дописывать в конец вызов функции
  • Отправлять всё это прямо в shh через pipe (|)

Плюсы:


  • Truely stateless
  • No boilerplate entities
  • Feeling cool

Вот давайте только без Ansible. Да, всё уже придумано. Да, велосипед. Смотрите, какой простой, элегантный и минималистичный велосипед:


$ cat << 'EOF' > deploy.sh

#!/bin/bashusage_msg="Usage: ${FUNCNAME[0]} ssh_address image_name"ssh_address=${1?$usage_msg}image_name=${2?$usage_msg}echo "Connecting to '$ssh_address' via ssh to seamlessly deploy '$image_name'..."( sed "\$a deploy $image_name" | ssh -T $ssh_address ) << 'END_OF_SCRIPT'deploy() {    echo "Yay! The '${FUNCNAME[0]}' function is executing on '$(hostname)' with argument '$1'"}END_OF_SCRIPT

EOF$ chmod +x deploy.sh$ ./deploy.sh localhost magic-porridge-potConnecting to localhost...Yay! The 'deploy' function is executing on 'hut' with argument 'magic-porridge-pot'

Однако, мы не можем быть уверены, что на удалённом хосте есть адекватный bash, так что добавим в начало небольшую проверочку (это вместо shellbang):


if [ "$SHELL" != "/bin/bash" ]then    echo "The '$SHELL' shell is not supported by 'deploy.sh'. Set a '/bin/bash' shell for '$USER@$HOSTNAME'."    exit 1fi

А теперь всё по-настоящему:


$ docker exec reverse-proxy rm /etc/nginx/conf.d/default.conf$ wget -qO deploy.sh https://git.io/JUJq1$ chmod +x deploy.sh$ ./deploy.sh localhost uptimerSending gzipped image 'uptimer' to 'localhost' via ssh...Loaded image: uptimer:latestConnecting to 'localhost' via ssh to seamlessly deploy 'uptimer'...Deploying 'uptimer_GREEN' in place of 'uptimer_BLUE'...06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18aContainer started. Checking health...Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:  HTTP/1.0 503 Service Unavailablewget: server returned error: HTTP/1.0 503 Service UnavailableNew 'uptimer_GREEN' service is not ready yet. Waiting (1)...Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:  HTTP/1.0 503 Service Unavailablewget: server returned error: HTTP/1.0 503 Service UnavailableNew 'uptimer_GREEN' service is not ready yet. Waiting (2)...Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:  HTTP/1.0 200 OK  Server: BaseHTTP/0.6 Python/3.8.3  Date: Sat, 22 Aug 2020 20:15:50 GMT  Content-Type: text/htmlNew 'uptimer_GREEN' service seems OK. Switching heads...nginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successful2020/08/22 20:15:54 [notice] 97#97: signal process started'uptimer_GREEN' service is live!Killing 'uptimer_BLUE'...uptimer_BLUETotal reclaimed space: 0BDeployment successful!

Теперь можно открыть http://localhost/ в браузере, запустить деплоймент ещё раз и убедиться, что он проходит бесшовно путём обновления страницы по КД во время выкладки.


Не забываем убираться после работы :3


$ docker rm -f uptimer_GREEN reverse-proxy uptimer_GREENreverse-proxy$ docker network rm web-gateway web-gateway$ cd ..$ rm -r blue-green-deployment
Подробнее..

Обновление процесса CICD год спустя

05.04.2021 02:07:14 | Автор: admin

Это четвертая и заключительная часть цикла об обновлении CI/CD процессов. Кстати, вот оглавление:
Часть 1: что есть, почему оно не нравится, планирование, немного bash. Я бы назвал эту часть околотехнической.
Часть 2: teamcity.
Часть 3: octopus deploy.
Часть 4: внезапно вполне себе техническая. Что произошло за прошедший год.

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

Осуществлялся переезд CI/CD системы с CruiseControl.NET + git deploy на Teamcity + octopus. Будем честны, CD там и не пахло. Об этом, возможно, будет отдельная статья, но не в этом цикле.
С момента выхода первой статьи цикла прошло чуть больше года, с момента начала работы системы в проде - примерно полтора. Процесс разработки во время внедрения новой системы практически не прерывался. Было два раза, когда делали code freeze: один раз в момент перехода с mercurial репозитория в git (чтобы не потерять коммиты во время конвертации), и второй раз во время перехода билда production окружения с ccnet на teamcity (просто так, на всякий случай).
В результате мы получили систему которая способна наиболее оптимально (с минимальными время- и ресурсозатратами, а также с минимальными рисками) доставлять обновления во все существующие окружения.

С момента выхода 3 части статьи в конфигурации произошли некоторые изменения, о которых, наверное, стоит рассказать.

Что произошло за этот год

  1. Мы практически полностью отказались от конфигураций вида Build + deploy. Теперь используем отдельно Build и отдельно Deploy. Последние всё также вызываются из teamcity, но это сделано исключительно для упрощения жизни всем менее причастным. На самом деле, для того чтобы обезопасить Octopus от вмешательства любопытных.

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

  3. Появился опыт настройки деплоя на сервере без каких-либо доступов, только с установленным octopus агентом. Не то, чтобы его не было, просто он был забыт как страшный сон за много лет. А тут пришлось вспоминать. Приятного мало, но на удивление, терпимо.

  4. Перешли с Visual studio (sln) раннера на .net msbuild ввиду окончания поддержки первого (Teamcity).

  5. Для Special Module (см часть 1) появился интересный вызов билда из деплоя с пробросом параметров через reverse.dep

  6. Появился какой-никакой роллбек.

  7. Переработали variable setы в octopus, используем tenant variables.

  8. Практически везде перешли от хранения connection string в репозитории на хранение в Octopus и подстановкой при деплое. К сожалению, раньше хранили именно в репозитории.

  9. Для деплоя особо важных модов добавили защиту от тестировщика (подробнее чуть позже).

  10. Выросли на 7 новых тенантов (клиентов).

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

Build-chain наоборот (пункт 4)

Для Special module, как и для любых других есть несколько окружений. Список всех пайплайнов для этого модуля в teamcity выглядит так:

Build конфигурация используется одна, с вот таким параметром ветки:

Также в данной конфигурации используются две prompted переменные типа Select: env.Environment и env.buildBranch. Выглядят они примерно одинаково, отличаются только Items. Для каждого env ставится в соответствие ветка репозитория.

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

В каждой Deploy конфигурации, есть зависимость от актуальности конфигурации build и параметры типа reverse.dep, которые при запуске Build устанавливают для него env.Environment и env.buildBranch. Например, для development это выглядит так:

Как всё это работает вместе: при нажатии кнопки deploy соответствующего окружения проверяется наличие изменений в репозитории. Если изменения есть - запускается конфигурация Build с установленными через reverse.dep переменными. По завершению билда, запускается ожидавший всё это время деплой новой версии пакета.

Rollback (пункт 6)

Rollback построен на следующем алгоритме:

  1. Определить номер текущего и предыдущего релизов в octopus для Core и Module.

  2. Откатить Core (задеплоить предыдущий релиз)

  3. Откатить Module.

Octopus хранит 3 предыдущих релиза так, на всякий случай. Rollback из teamcity работает только с предыдущим релизом. Откат на более давний релиз надо делать вручную, но такой необходимости ни разу не возникало. Так выглядят определение версий:

$packageRelease = ((%env.octoExe% list-deployments --server="%env.octoUrl%" --apikey="%env.octoApiKey%" --project="ProjectName.%env.modName%" --environment="%env.Environment%" --outputFormat=json) | ConvertFrom-Json).Version[0..1]$coreRelease = (((%env.octoExe% list-deployments --server="%env.octoUrl%" --apikey="%env.octoApiKey%" --project="%env.coreProjectName%" --environment="%env.Environment%" --outputFormat=json) | ConvertFrom-Json).Version | Get-Unique)[0..1]$OctopusPackageCurrentRelease = $packageRelease[0]$OctopusPackagePreviousRelease = $packageRelease[1]$corePreviousVersion = $OctopusPackagePreviousRelease | %{ $_.Split('-')[0]; }$coreEnv = $OctopusPackagePreviousRelease | %{ $_.Split('-')[1]; } |  %{ $_.Split('+')[0]; }$OctopusCoreCurrentRelease = $coreRelease[0]$OctopusCorePreviousRelease = "$corePreviousVersion-$coreEnv"Write-Host "##teamcity[setParameter name='OctopusPackageCurrentRelease' value='$OctopusPackageCurrentRelease']"Write-Host "##teamcity[setParameter name='OctopusPackagePreviousRelease' value='$OctopusPackagePreviousRelease']"Write-Host "##teamcity[setParameter name='OctopusCoreCurrentRelease' value='$OctopusCoreCurrentRelease']"Write-Host "##teamcity[setParameter name='OctopusCorePreviousRelease' value='$OctopusCorePreviousRelease']"

Откат является деплоем соответствующей версии, поэтому глобально ничем не отличается от шага Deploy.2 описанного в части 2. Меняется только Release Number. Вместо latest используется %OctopusCorePreviousRelease% и %OctopusPackagePreviousRelease% соответственно.

Переработка variable sets

Раньше все переменные тенантов хранились в конфигурациях проектов и разруливались расстановкой скоупов. Вот хороший пример из части 3:

При количестве тенантов больше 3 это оказывается неудобно. Поэтому, перешли к хранению переменных клиентов в предназначенном для этого месте - tenant variables - common variables.
Так списки переменных проектов стали чище, и там больше нет каши.

Защита от тестировщика (пункт 9)

В список задач тестировщика входит также деплой на некоторые окружения. Туда, куда деплои не попадают автоматически из за ограничений. Зачастую это выглядит как клик клик клик клик по кнопкам Run не задумываясь. Исключение составляет prod окружение, но это не точно. Пару раз были прецеденты деплоя модов, которые помечены как secure. Это особая категория модов, которыми пользуются особые люди. Они очень любят стабильность и все релизы у них планируются, а набор новых фич обсуждается. В общем, для этих модов пришлось добавить элементарную защиту в виде всплывающего Are you sure и требованием ввести ответ буквами.

Реализовано это с помощью prompted переменной и regexp.

Заключение

В данный момент я работаю над этим проектом по минимуму. Саппорт практически не требуется, всё работает практически без моего участия. Где-то есть continuous deployment, где-то пришлось ограничиться delivery. Там где надо нажимать кнопки вручную - справляются тестировщик и главный девелопер. Время добавления новых конфигураций (по факту нового клиента) вместе с проверкой работоспособности - час с чайком и без напряга. С CCNet такой результат показался бы фантастикой при условии отcутствия дичайшего оверхеда со стороны ресурсов сервера. Да и удобства никакого. Пропала проблема бесконечной нехватки места, так как на сервере не хранятся лишние копии одного и того же. И даже rollback показал себя с хорошей стороны, и на удивление работает.Всё работает классно шустро, и самое главное - стабильно и прогнозируемо.

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

Статья получилось вполне технической. На описание неприятных моментов и места то не осталось. Могу сказать, что все неприятные моменты связаны исключительно с впечатлением от windows, а не с проектом. Это всё таки был мой первый проект на оконном стеке. Впечатления непосредственно от проекта только самые лучшие. Хоть это и древнее легаси-зло, проект на самом деле очень крутой и интересный. И опыт поддержки и постановки таких проектов на новые рельсы без преувеличения можно назвать бесценным. В общем, в мире стало на один хороший проект с devops методологией больше.

Подробнее..

Развертывание нескольких моделей машинного обучения на одном сервере

03.02.2021 00:17:16 | Автор: admin

Знакомство с проблемой

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

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

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

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

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

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

Решение

Решение предполагает, что обучение модели машинного обучения выполняется отдельно от обслуживания данной модели. Например, задание Airflow выполняет обучение модели и сохраняет ее в S3, в таком случае единственная ответственность ML сервера это прогнозы.

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

Компоненты решения

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

  • Model абстрактная модель, предоставляющая прогноз; его реализация может быть SklearnModel, TensorFlowModel, MyCustomModel и т. д.

  • ModelInfoRepository абстрактный репозиторий, который предоставляет сопоставления userid -> modelid. Например, он может быть реализован как SQAlchemyModelInfoRepository.

  • ModelRepository абстрактный репозиторий, который может возвращать модель по ее ID. Это может быть FileSystemRepository, S3Repository или любая другая реализация репозитория.

from abc import ABCclass Model(ABC):    @abstractmethod    def predict(self, data: pd.DataFrame) -> np.ndarray:        raise NotImplementedError class ModelInfoRepository(ABC):    @abstractmethod    def get_model_id_by_user_id(self, user_id: str) -> str:        raise NotImplementedError class ModelRepository(ABC):    @abstractmethod    def get_model(self, model_id: str) -> Model:        raise NotImplementedError

Реализация

Теперь предположим, что мы обучили модель sklearn, которая хранится в Amazon S3 с сопоставлениями userid -> modelid, определенными в базе данных.

class SklearnModel(Model):    def __init__(self, model):        self.model = model     def predict(self, data: pd.DataFrame):        return self.model.predict(data) class SQAlchemyModelInfoRepository(ModelInfoRepository):    def __init__(self, sqalchemy_session: Session):        self.session = sqalchemy_session     def get_model_id_by_user_id(user_id: str) -> str:        # implementation goes here, query a table in any Database      class S3ModelRepository(ModelRepository):    def __init__(self, s3_client):        self.s3_client = s3_client     def get_model(self, model_id: str) -> Model:        # load and deserialize pickle from S3, implementation goes here

Это делает реализацию сервера чрезвычайно простой:

def make_app(model_info_repository: ModelInfoRepository,     model_repsitory: ModelRepository) -> Flask:    app = Flask("multi-model-server")        @app.predict("/predict/<user_id>")    def predict(user_id):        model_id = model_info_repository.get_model_id_by_user_id(user_id)         model = model_repsitory.get_model(model_id)         data = pd.DataFrame(request.json())         predictions = model.predict(data)         return jsonify(predictions.tolist())     return app

Обратите внимание, что благодаря абстракциям сервер Flask полностью независим от конкретной модели и реализации хранилища; мы можем заменить sklearn на TensorFlow и S3 на локальную папку, при этом в коде сервера Flask не меняются строки.

Замечание о кешировании

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

from cachetools import Cache class CachedModelRepository(ModelRepository):    def __init__(self, model_repository: ModelRepository, cache: Cache):        self.model_repository = model_repository        self.cache = cache     @abstractmethod    def get_model(self, model_id: str) -> Model:        if model_id not in self.cache:            self.cache[model_id] = self.model_repository.get_model(model_id)        return self.cache[model_id]

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

from cachetools import LRUCache model_repository = CachedModelRepository(    S3ModelRepository(s3_client),    LRUCache(max_size=10))

Перед выходом в продакшен

Такой многомодельный сервер - одна из многих частей, необходимых для запуска приложений производственного уровня с возможностями машинного обучения. Безопасность корпоративного уровня, масштабируемость, MLOps и т. д. могут быть даже более важными для успеха и надежности проекта, чем немного более точная модель машинного обучения. Всегда помните о гениальном правиле 4 от Google: пусть первая модель будет простой, а инфраструктура - правильной.

Подробнее..

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

27.02.2021 14:17:14 | Автор: admin
Количество установок приложения IntellectoKids Classroom & Learning games.Количество установок приложения IntellectoKids Classroom & Learning games.

Привет, Хабр! Меня зовут Андрей Романенков, я работаю ведущим программистом в IntellectoKids. Мы создаем образовательные приложения для дошкольников.

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

Но есть одно но.

В определенный момент ваше простенькое приложение может превратиться в перспективный проект с десятками мини-игр внутри и еженедельными обновлениями. Собственно, это произошло с нами. И тут начинается самое интересное. Представьте, что геймдизайнеры постоянно добавляют в игры новые уровни, художники новые текстуры, а локализаторы локализацию на волапюк. Репозиторий растет, приложение раздувается. Как результат увеличивается время скачивания. А это уже может повлиять на популярность продукта и на его продвижение.

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

Саб-модульность, многорепозиторность, подход к общему коду

Всего у IntellectoKids 4 приложения. Поскольку сервисы у них идентичны (например, логика работы с сервером, аналитика, покупки) и много одинакового кода, мы выделили общий функционал в отдельный репозиторий, который каждый конкретный проект подключает через git submodules. Выскажу довольно очевидную мысль, что когда ваши проекты с общим кодом множатся, код нужно скорее выделить в единую библиотеку. Вроде бы все это понимают, но часто откладывают на потом, а чем дальше вы откладываете, тем тяжелее будет процесс слияния.Помимо выделения репозитория для общих сервисов, мы создали также другой общий репозиторий для более базовой библиотеки утилит и вспомогательных классов.Второй вариант подключения дополнительного функционала появился у нас с добавлением Package Manager в Unity. Так можно делать, когда ваша библиотека устоялась и если вам необходимо подключить какую-то стороннюю библиотеку с репозитория как package.Когда проект длится давно, а количество контента увеличивается с каждым днем, то из-за раздувшейся истории и обилия больших файлов рано или поздно вы столкнетесь с проблемой размера репозитория. У нас текущий репозиторий перевалил за 10Гб (сейчас 14 гб), с чем справляются не все хостинги (большая часть из них ограничивает размеры хранилища).В борьбе за производительность нам помогают чистка истории и использование git lfs, а также внимательное отношение к размеру и формату импортируемых в проект ассетов. Например, импортирование mp3 и ogg файлов вместо wav; и отсекание слишком больших текстур.

Локализация, в том числе RTL-языки

Наши приложения локализованы более чем на 40 языков, включая RTL языки (предполагающие чтение справа налево). Система локализации самописная, но в целом она похожа на типовые решения из Asset Store (такие, как I2 Localization). В Google-таблицах хранятся ключи и значения. Есть базовая таблица для всех игр, и дополнительные таблицы для каждой конкретной игры. Каждая таблица в Google-документах скриптами собирается из других вспомогательных таблиц, которые редактируют локализаторы.

Данные из вспомогательных таблиц(цветные закладки внизу) попадают в финальные таблицы локализаций.Данные из вспомогательных таблиц(цветные закладки внизу) попадают в финальные таблицы локализаций.

На клиенте наши скрипты MonoBehaviour выцепляют нужные значения и выставляют их в TextMesh Pro компоненты. Клиент может обновлять таблицы как в режиме редактора, так и рантайма. У клиента таблицы хранятся в csv формате, и в память грузится только нужный язык, так как количество ключей для каждого языка превышает уже пять сотен!

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

Пример стандартной вёрстки на английском.Пример стандартной вёрстки на английском.То же окно, но на иврите.То же окно, но на иврите.

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

Когда волна больше определённого значения у Animator кролика включается параметр IsTalking.Когда волна больше определённого значения у Animator кролика включается параметр IsTalking.

Бандловость: 2 подхода. Эволюция в работе с бандлами

По мере развития проекта (добавления новых уровней и другого контента, увеличения количества локализаций) постоянно рос общий размер содержимого. С самого начала нам было понятно, что необходимо использовать бандлы, иначе размер клиента был бы огромен и сейчас составлял бы 3 ГБ. Да, не Modern Warfare, но всё же для еженедельной скачки это неприемлемо.

В какой-то момент мы, правда, провели эксперимент с выпуском таких больших релизов (тогда размер был примерно под два гигабайта), но это сразу заметно отразилось на общей статистике приложения. Сейчас у нас зашиты в билд только небольшие бандлы, необходимые для ускорения старта. В нашем основном приложении IntellectoKids Classroom&Learning Games больше тысячи бандлов общим размером 2.5 гигабайта. Может показаться, что это слишком много, но если умножить количество встроенных игр на количество языков, и добавить к этому, что у каждой игры есть множество уровней с насыщенным контентом, то всё сразу станет понятно.Из-за особенности геймплея каждая игра имеет свои нюансы объединения ресурсов в бандлы. Где-то можно поместить все локализованные фразы в один бандл, так как их общий размер мал, а где-то необходимо разделить и поместить каждый язык в отдельный бандл. В каких-то играх несколько уровней объединены в один бандл, а в других должен быть бандл у каждого уровня. При формировании бандлов мы создаём manifest файл, описывающий имена и хэши бандлов.

Загрузчик при обновлении версии качает этот manifest и проверяет, какие бандлы нужно закачать заново, а какие удалить. Изначально игра поддерживала фоновую загрузку бандлов во время игры, но позже мы отказались от такого подхода, так как он таил в себе много скрытых проблем (сценарии обновления бандла во время использования его игрой, баги Unity в выгрузке бандлов и т.п.) Сейчас мы перешли на более классический вариант, когда игроку в нужные моменты показывается экран загрузки бандлов.Самый маленький большой нюанс нашего проекта, это, безусловно, релиз и деплой более чем тысячи бандлов. На первоначальном этапе, когда бандлов было меньше, мы размещали их в Google репозитории и даже использовали веб-интерфейс Chrome для их загрузки, но довольно быстро перешли на собственный билдер-загрузчик, выполненный в виде инструментария в Unity. Он позволяет загрузить нужные бандлы, задать версионность билда и настроить другую рутину.

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

Первоначально бандлы лежали в Google Cloud Storage, затем мы перешли на Amazon Web Services. Основная статья затрат у бандлов это скачивание. С переходом на AWS и CloudFront нам удалось оптимизировать издержки. Хоть это, а также переход на новый API с доработкой инструментов деплоя занял некоторое время, но оно того стоило.

Переход на новые версии Unity

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

Если вы растите приложение, будьте готовы, что требования к нему будут довольно часто меняться. Нам много раз приходилось перерабатывать фреймворки аналитики (один Facebook включался и выключался больше пяти раз).

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

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

Что можно добавить к сказанному в статье?

Пожалуй, стоит понимать, что ваш проект всегда будет далек от того идеала, который вы себе намечтали. Но это не значит, что нужно откладывать очевидные улучшения архитектуры. Особенно в условиях, когда требования к функционалу приложения постоянно меняются. Ведь неделька на рефакторинг может сэкономить вам месяцы мучительной разработки, но помните, что "Real artists ship" и если ваш рефакторинг слишком большой, а бизнес не стоит на месте, то и рефакторинг может подождать. Тут дам совет, особенно скромным программистам: более четко доносить до менеджмента такие критичные вещи, чтобы ваш технический долг не рос.

Dixi

Подробнее..

Перевод Разворачиваем модель машинного обучения с Docker Часть 1

04.08.2020 18:23:50 | Автор: admin
Перевод статьи подготовлен в преддверии старта базового и продвинутого курсов по машинному обучению.

Расширяем возможности для наших студентов. Теперь в OTUS есть целых два курса по Machine Learning: базовый и продвинутый. Оба курса стартуют в августе, в связи с чем мы приглашаем всех желающих на день открытых дверей в онлайн-формате, в рамках которого наш эксперт Дмитрий Сергеев (Senior Data Scientist в Oura) подробно расскажет о курсах, а также ответит на интересующие вопросы.


Наша основная задача как специалистов по Data Science это обработка данных, а также разработка и совершенствование моделей машинного обучения. Бытует мнение, что обработка данных самый трудоемкий этап во всем проекте, а точность модели определяет успех информационного продукта. Однако отрасль сейчас находится на переходном этапе от эпохи открытий к эпохе внедрения (Сверхдержавы в области искусственного интеллекта: Китая, Силиконовая Долина, а новый мировой порядок в этой сфере диктует Ли Кайфу). Сейчас картина становится шире, и фокус смещается с построения модели на предоставление модели пользователям в качестве сервиса и с производительности модели к ее бизнес-ценности. Самый известный пример здесь Netflix, который никогда не использовал модели победителей их соревнования на 1$ млн, несмотря на обещанное значительное повышение производительности, обеспечиваемое этими движками (Netflix Never Used Its $1 Million Algorithm Due To Engineering Costs WIRED).


От понимания к реальности (слайды с конференции Strata Data Kubeflow explained: Portable machine learning on Kubernetes)

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

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

С сайта Docker

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

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

Вопрос первый Почему Docker?


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

Когда я только начинал свою работу в банке, мне поручили проект, который был связан с обработкой данных, и первый MVP (минимальный жизнеспособный продукт) нужно было доставить за месяц. Звучит напряжно, но мы в команде используем методологию Agile в процессе разработки всех основных продуктов, и основной целью этого MVP было проверить гипотезу о практичности и эффективности продукта (более подробную информацию про методологию Agile вы найдете в книге Эрика Риса Lean Startup). Мой менеджер хотел, чтобы я развернул свою модель на его ноутбуке, то есть запустил ее и использовал для прогнозирования.

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

  • На какой операционной системе нужно будет запускать модель, ведь он пользуется Macbook и ThinkPad? Я мог бы, конечно, спросить его об этом, но предположим, что в тот момент жизни мой босс был очень противным, и не хотел, чтобы я знал эту информацию. (Эта мысль здесь для того, чтобы вы осознали проблему зависимости от операционной системы, а так мой босс правда хороший человек.)
  • Второй вопрос: Установлен ли у него Python?. Если да, то какая версия, 2 или 3? Какая именно: 2.6, 2.7 или 3.7?
  • А как насчет необходимых пакетов, таких как scikit-learn, pandas и numpy? Установлены ли у него те же версии, что и у меня на машине?

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

  1. Установить Python.
  2. Установить все пакеты.
  3. Настроить переменные среды.
  4. Перенести код на машину.
  5. Запустить код с необходимыми параметрами.

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

Итак, если у вас уже установлен и запущен Docker, вы можете открыть терминал и выполнить следующую команду:

docker run --rm -p 5000:5000 datascienceexplorer/classifier

Через пару минут вы должны увидеть нечто похожее в своем терминале:

* Serving Flask app "main" (lazy loading)* Environment: production   WARNING: Do not use the development server in a production environment.   Use a production WSGI server instead.* Debug mode: off* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Теперь откройте свой любимый браузер и перейдите по этому адресу:

http://localhost:5000/apidocs/

Нажмите на строчку predict из API, а затем на кнопку try-out справа, интерфейс будет выглядеть следующим образом:


Страница Swagger для API на бэкенде

Помните стандартный набор данных Цветы Ириса, с которым вы игрались? Это небольшое приложение поможет вам спрогнозировать вид цветка на основе информации о некоторых измерениях на основе классификационной модели. На самом деле вы уже используете мою модель машинного обучения как сервис, и все, что вы установили это лишь Docker, и мне не понадобилось ставить Python или какие-то пакеты на вашу машину.

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

DevOps Data Science


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

Что же такое DevOps?


Из Википедии
DevOps это совокупность практик, сочетающих в себе разработку программного обеспечения и информационно-технологическое обслуживание, целью которых является сокращение жизненного цикла разработки систем и обеспечение непрерывной поставки высококачественного программного обеспечения.
Целью разработчиков программного обеспечения является своевременная доставка кода со всем необходимым функционалом, в то время как удобство использования, надежность, масштабируемость, сетевая часть, брандмауэр, инфраструктура и т.д. часто остаются операционными проблемами. Из-за разнящихся конечных целей и, вероятно, ключевых показателях эффективности, эти команды обычно не уживаются под одной крышей. Поэтому DevOps-специалист мог бы сыграть роль связующего звена и помочь этим командам работать сообща, или даже взять на себя ответственность обеих сторон, чтобы в итоге получилась одна команда, ведущая разработку от начала до конца. В конце концов, вы ведь не можете просто отдать свой компьютер клиенту, поскольку на нем-то код работает как надо.
Но с Jupyter notebook я счастлив!!!
У специалистов по Data Science история аналогичная, поскольку опять же вы не можете просто взять и отдать свой ноутбук с запущенным Jupyter Notebook, чтобы клиент просто пользовался им. Нам нужен способ использовать модель так, чтобы она могла обслуживать большое количество пользователей в любое время в любом месте и подниматься с минимальным временем простоя (удобство использования, надежность, масштабируемость).

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

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

Хранение обученной модели


Еще в университете мы узнали, что проект Data Science состоит из шести этапов, как показано на картинке ниже. Если автоматизация и развертывание модели на продакшене является нашей конечной целью, то как же отправить модель на стадию развертывания?


Шесть этапов Data Science-проекта

Самый простой способ, который только можно придумать это скопировать все из нашего notebook, вставить в файл с расширение .py и запустить его. Однако каждый раз, когда нам нужно будет сделать прогноз, мы будем запускать этот файл и заново на тех же данных обучать модель. Если такой подход хоть как-то применим для простых моделей с небольшим обучающим набором данных, он не будет эффективен для сложных моделей с большим количеством обучающих данных (подумайте о том, сколько времени вам понадобится для обучения модели ANN или CNN). Здесь имеется в виду, что, когда пользователь отправляет запрос модели на прогнозирование, ему придется ждать от нескольких минут до нескольких часов, чтобы получить результат, поскольку много времени уйдет на выполнения этапа обучения модели.

Как хранить модель сразу после того, как она обучится?


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

В Jupyter notebook вы можете с легкостью сохранить объект модели (в моем случае knn) в файл pkl, который располагается в том же каталоге, что и код:

import picklewith open('./model.pkl', 'wb') as model_pkl:  pickle.dump(knn, model_pkl)

Сохранение модели в текущую директорию

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

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



Узнать подробнее о:





Читать ещё


Подробнее..

Перевод Разворачиваем модель машинного обучения с Docker Часть 2

17.08.2020 18:04:48 | Автор: admin


Расширяем возможности для наших студентов. Теперь в OTUS есть целых два курса по Machine Learning: базовый и продвинутый. Оба курса стартуют в августе, в связи с чем мы предлагаем вам посмотреть запись дня открытых дверей в онлайн-формате, а также приглашаем записаться на бесплатные уроки: Пайплайн работы с задачей ML и Поиск аномалий в данных.


В первой части этого руководства мы успешно сохранили вашу классификационную модель в локальном каталоге и завершили все работы по разработке модели, связанные с Jupyter Notebook. С этого момента основное внимание будет уделяться развертыванию нашей модели. Чтобы повторно использовать модель для прогнозирования, вы можете просто загрузить ее и вызвать метод predict(), как вы обычно это делаете в Jupyter Notebook.

Для того чтобы протестировать модель, в той же папке, где находится файл model.pkl, создайте файл main.py с этим кодом:

import pickle# Импортируем все пакеты, которые необходимы для вашей моделиimport numpy as npfrom sklearn.neighbors import KNeighborsClassifier# Загружаем модель в памятьwith open('./model.pkl', 'rb') as model_pkl:   knn = pickle.load(model_pkl)# Неизвестные данные (создаем новое наблюдение для тестирования)unseen = np.array([[3.2, 1.1, 1.5, 2.1]])result = knn.predict(unseen)# Выводим результаты на консольprint('Predicted result for observation ' + str(unseen) + ' is: ' + str(result))


Повторное использование модели для прогнозирования.

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

Traceback (most recent call last): File "main.py", line 4, in <module>   from sklearn.neighbors import KNeighborsClassifierImportError: No module named sklearn.neighbors


Это связано с тем, что используемый нами пакет недоступен в среде, в которой вы запускаете файл. Это значит, что среда, используемая для разработки модели (conda), не идентична среде выполнения (среда python за пределами conda), и это можно рассматривать как потенциальную проблему при запуске нашего кода в других средах. Я специально хотел, чтобы вы увидели эту ошибку, чтобы помочь вам узнать о проблеме, и еще раз подчеркнуть важность использования контейнеров для развертывания нашего кода, чтобы избежать подобных проблем. На данный момент вы можете просто вручную установить все необходимые пакеты с помощью команды pip install. Мы вернемся сюда позже, чтобы сделать это автоматически.

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

Predicted result for observation [[3.2 1.1 1.5 2.1]] is: [1]


Как вы можете здесь увидеть, для тестирования модели мы используем захардкоженные неизвестные данные. Эти числа представляют собой длину чашелистика, его ширину, длину лепестка и его ширину соответственно. Однако, поскольку мы хотим предоставлять нашу модель как сервис, она должна быть представлена как функция, принимающая запросы, содержащие эти четыре параметра, и возвращающая результат прогноза. Затем эту функцию можно использовать для сервер API (бекенда) или развернуть в бессерверной среде выполнения, такой как Google Cloud Functions. В этом руководстве мы попытаемся вместе создать сервер API и поместить его в контейнер Docker.


Как работает API?


Давайте поговорим о том, как сегодня устроены веб-приложения. В большинстве веб-приложений есть два основных компонента, которые покрывают почти все функции, необходимые приложению: фронтенд и бекенд. Фронтенд ориентирован на обслуживание интерфейса (веб-страницы) для пользователя, а фронтенд сервер часто хранит HTML, CSS, JS и другие статические файлы, такие как изображения и звуки. С другой стороны, бекенд сервер будет обрабатывать всю бизнес-логику, которая отвечает на любые запросы, отправленные из фронтенда.


Иллюстрация структуры веб-приложений.

Вот что произойдет, когда вы откроете Medium в своем браузере.

  1. Ваш браузер отправляет HTTP-запрос на адрес medium.com. Потребуется ряд операций на DNS-сервере, маршрутизаторах, межсетевых экранах и т. д., но для простоты этой статьи мы их проигнорируем.
  2. Фронтенд сервер отправляет обратно * .html, * .css, * .js и все другие файлы, необходимые для рендеринга веб-страницы в вашем браузере.
  3. Теперь в браузере вы должны увидеть страницу Medium и начать с ней взаимодействовать. Допустим, вы только что нажали кнопку clap (похлопать) на статье.
  4. Скрипты (javascript) в вашем браузере отправят HTTP-запрос на бекенд сервер с id истории. URL-адрес запроса сообщит бекенду, какое действие следует выполнить. В этом примере он скажет бекенду обновить количество хлопков в истории с id XXXXXXX.
  5. Бекенд программа (которая, может быть написана на любом языке) получит текущее количество хлопков в базе данных и увеличит его на единицу.
  6. Затем бекенд программа отправляет актуальное количество хлопков обратно в базу данных.
  7. Бекенд отправляет новое количество хлопков в браузер, чтобы его можно было отразить в интерфейсе.


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

Теперь я хочу, чтобы вы сосредоточили внимание на синих стрелках на рисунке выше. Это HTTP-запросы (отправленные из браузера) и HTTP-ответы (полученные браузером или отправленные в браузер). Компоненты, обрабатывающие запросы от браузера и возвращающие ответы на бекенд сервере, называются API.

Ниже приведено определение API:

Из Вебопедии

Интерфейс прикладного программирования (API application program interface) это набор процедур, протоколов и инструментов для создания программных приложений. По сути, API определяет, как должны взаимодействовать программные компоненты.


Создаем наш собственный API!


Существует множество фреймворков, которые помогают нам создавать API с помощью Python, включая Flask, Django, Pyramid, Falcon и Tornado. Преимущества и недостатки, а также сравнение этих структур, перечислены здесь. В этом уроке я буду использовать Flask, но техника и рабочий процесс остаются такими же, как и для других, и в качестве альтернативы вы вполне можете использовать на этом этапе свой любимый фреймворк.

Последнюю версию Flask можно установить через pip с помощью этой команды:

pip install Flask


Все, что вам нужно сделать сейчас это превратить код из предыдущего шага в функцию и зарегистрировать для нее эндпоинт API после инициализации приложения Flask. По умолчанию приложение Flask запускается на локалхосте (127.0.0.1) и будет прослушивать запросы на порту 5000.

import pickle# Импортируем все пакеты, которые необходимы для вашей моделиimport numpy as npimport sysfrom sklearn.neighbors import KNeighborsClassifier# Импортируем Flask для создания APIfrom flask import Flask, request# Загружаем обученную модель из текущего каталогаwith open('./model.pkl', 'rb') as model_pkl:   knn = pickle.load(model_pkl)# Инициализируем приложение Flaskapp = Flask(__name__)# Создайте конечную точку API@app.route('/predict')def predict_iris():   # Считываем все необходимые параметры запроса   sl = request.args.get('sl')   sw = request.args.get('sw')   pl = request.args.get('pl')   pw = request.args.get('pw')# Используем метод модели predict для# получения прогноза для неизвестных данных   unseen = np.array([[sl, sw, pl, pw]])   result = knn.predict(unseen)  # возвращаем результат    return 'Predicted result for observation ' + str(unseen) + ' is: ' + str(result)if __name__ == '__main__':   app.run()


Представление вашей модели как API

На терминале вы должны увидеть следующее:

* Serving Flask app "main" (lazy loading)* Environment: production  WARNING: This is a development server. Do not use it in a production deployment.  Use a production WSGI server instead.* Debug mode: off* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)


Откройте браузер и введите в адресной строке следующий запрос:

http://localhost:5000/predict?sl=3.2&sw=1.1&pl=1.5&pw=2.1

Если в вашем браузере появляется что-то подобное, поздравляем! Теперь вы предоставляете свою модель машинного обучения в качестве службы с эндпоинтом API.

Predicted result for observation [['3.2' '1.1' '1.5' '2.1']] is: [1]

Тестирование API с помощью Postman


Недавно мы использовали наш браузер для быстрого тестирования API, но это не очень эффективный способ. Например, мы могли бы не использовать метод GET, а вместо этого использовать метод POST с токеном аутентификации в заголовке, а сделать так, чтобы браузер отправлял такой запрос, непросто. При разработке программного обеспечения Postman широко используется для тестирования API-интерфейсов и совершенно бесплатен для базового использования.


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


Отправка GET запроса с помощью Postman

  1. Убедитесь, что вы выбрали GET запрос, поскольку мы настраиваем API только для получения GET запроса. Это может не сработать, если вы случайно выберете POST запрос.
  2. Вставьте сюда URL вашего запроса.
  3. В этой таблице необходимо обновить параметры запроса. Не стесняйтесь поиграть с этими параметрами и посмотреть, что вы получите в результате.
  4. Нажмите кнопку Отправить, чтобы отправить запрос на наш сервер API.
  5. Здесь будет отображаться ответ нашего сервера.
  6. Вы также можете проверить дополнительную информацию об этом HTTP-ответе. Это может быть очень полезно для отладки.


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

Читать первую часть.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru