Основы DevOps для фронтендера

Как вечером в попу меня укусила пчела и сказала, пора заняться DevOps, хватит только программировать. К тому же, я недавно написал проект «Сервис по учёту финансов "MyMoney"» https://github.com/orion55/mymoney Это простой проект на ReactJS у которого даже не собственного бека, база данных храниться в облаке у Google и называется Firebase.

Иными словами, моё желание было следующее отправил commit на github система подумала, собрала мой проект из исходников, упаковала его в контейнер и опубликовала на хостинге.

То, что сейчас расскажу, это мой собственный путанный опыт, который не претендует на полную достоверность и объективность.

Пошёл я искать информацию о DevOps и ужаснулся. Информации по DevOps не то, что мало, она есть, но разбита на уйму несвязанных кусков, из этой ухи аквариум не восстановить. И 99% информации полная хрень, которая не работает.

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

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

На любой вопрос, у них всегда есть список универсальных ответов, которые ничего не дают:

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

А курсов чисто по практикам DevOps в котором всё и сразу, я нашёл всего два.

  1. [OTUS] DevOps практики и инструменты
  2. [Слёрм] DevOps - история одной компании

У OTUS есть великолепный бесплатный урок «Один день из жизни DevOps», который простыми словами и на примерах, объясняет основные практики DevOps и рекламирует полный курс.

А вот сам курс получился мягко говоря не очень… В одной и той же теме постоянно меняются тренера, часть тренеров просто зачитывает слайды, при чём на редкость нудно. Вся практика на уровне приложения «Привет мир». Мне понравились только методички для домашней работы, вот их делал хороший специалист.

Так, что не стоит путать туризм и иммиграцию.

А вот курс «[Слёрм] DevOps - история одной компании» очень сильно понравился, коротко 20-30 минут необходимой теории и практика с примерами из жизни.

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

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

Основные принципы, которые мне запомнились:

  1. Инфрастуктура как код. Т.е. всё, создание виртуальных машин, их настройка, уничтожение решается через написание соответствующих скриптов. Никаких ручных настроек. Что в принципе логично, когда у тебя кластер из 5000 тыс. виртуалок. Зайти на каждую и что-то прописать сильно трудоёмко.
  2. Все виртуалки, контейнеры по факту одноразовые. Их никто не холит и не лелеет, а они создаются, работают. И если что-то не работает, или меняется сильно конфигурация, виртуалку уничтожают и создают новую. Вся постоянная информация храниться в отдельных томах на диске или в каком-то внешнем хранилище, но никак не в самой виртуалке.

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

Контейнеризация Docker

  1. Скопировать исходный код приложения в контейнер Docker.
  2. Скачать необходимые библиотеки, собрать приложение.
  3. Опубликовать приложение

Хостинг

  1. Создать виртуалку на хостинге DigitalOcean
  2. Установить туда приложение Docker, в котором будет крутиться контейнер.

CI\CD

  1. Создать конвейер (Pipeline), который при поступлении нового commit на github будет собирать свежие контейнеры и опубликовывать их на хостинге DigitalOcean.

Контейнеризация Docker

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

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

В освоении docker очень помогло видео «Docker для фронтендера / Алексей Авдеев (Neuron.Digital)»

Мой первый Dockerfile был такой https://github.com/orion55/mymoney/blob/master/deploy/old/Dockerfile1

«RUN npm -g install serve» устанавливаем веб-сервер, написанный на NodeJS serve, копируем список библиотек, исходники и разные иконки через команду «COPY» и «RUN npm ci --silent && npm run build» скачиваем сами библиотеки из интернета и собираем результирующий bundle (финальный вариант приложения).

И в конце запускаем веб-сервер «CMD ["serve", "-s", "build", "-l", "3000"]»

Serve – это веб-сервер, который написанный на NodeJS, одна команда для установки и ещё одна для запуска.

Для сборки используется хитрая команда «npm ci --silent» Что это за дичь такая? Обычно же библиотеки устанавливаются через команду «npm i», которая анализирует файл package.json и все зависимости одних библиотек от других пишет в локфайл (package-lock.json). И на основании этого локфайла качает необходимые библиотеки. Npm ci игнорирует package.json пакета и устанавливает модули руководствуясь только локфайлом (package-lock.json). Это сильно ускоряет сборку.

Всё хорошо, всё работает, но неправильно. А проблем две:

  1. Получившийся docker образ «весит» 550 Мегабайт.
  2. Веб-сервер serve, отдавал получившиеся статические файлы, но делал он это сильно медленно, даже на глаз без хитрых замеров производительности.
Основы DevOps для фронтендера

Решить эти проблемы помогла статья «Dockerizing a React App» https://mherman.org/blog/dockerizing-a-react-app/ Необходимо разбить процесс на 2 этапа:

  1. В первом контейнере, копируем библиотеки и собираем проект
  2. Получившийся bundle копируем в другой контейнер с веб-сервером nginx
  3. А первый контейнер убиваем

Это называется multistage builds.

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

Далее nginx. Лично я с nginx работал слабо. Но почитал статью NGINX https://sheensay.ru/nginx и понял это быстрый веб-сервер, который или отдаёт статические файлы или переадресовывает непонятные запросы другому серверу, который может на них ответить.

Конфиг nginx, который был в официальном контейнере поражал простой, а вот то, что я прочитал в интернете наоборот сильной сложностью. Истина где-то посередине. Тот конфиг, который первый раз написал сам, не заработал. Но ничего, я нашёл генератор конфигов для nginx https://www.digitalocean.com/community/tools/nginx Берём прописываем откуда куда, используем нужные шаблоны и всё.  Из него я взял включение алгоритма сжатия и ряд опций по безопасности. Вот результирующий конфиг https://github.com/orion55/mymoney/blob/master/deploy/nginx.conf

Один Dockerfile, 2 контейнера и уже можно в production? А, нет! Надо проверить всё ли грамотно я написал, для этого есть специальный линтер Haskell Dockerfile Linter https://github.com/hadolint/hadolint который проверяет правильность созданного Dockerfile. Вот после его проверки действительно всё. Вот что получилось в результате https://github.com/orion55/mymoney/blob/master/Dockerfile

И размер контейнера в 31 Мегабайт и быстрая отдача от веб-сервера nginx со сжатием.

Треть пути пройдена. Следующий этап хостинг.

Хостинг

Я выбрал хостинг DigitalOcean, он не такой монстрообразый как Amazon Web Services (AWS) или Google Cloud Platform, но с другой стороны его программисты сделали очень много для автоматизации. И этого хостинг поддерживает очень много приложений DevOps.

Итак, как создать виртуальную машину на этом хостинге или по-другому Droplet (дроплет)? Правильные девоперы для этого используют Terraform https://www.terraform.io/, более начинающие Docker Machine https://github.com/docker/machine Т.е. в файле описывается конфигурация создаваемой сети, поднимаемые виртуальные машины, к какому порту виртуального коммутатора всё подключено и для управления ими используется Ansible https://www.ansible.com/

А для оркестации (координации взаимодействия нескольких контейнеров) используется Kubernetes https://kubernetes.io/ в самом крайнем случае Docker Swarm

Я плохой девопсер, я ВРУЧНУЮ создал виртуальную машину и ВРУЧНУЮ установил туда Docker. Вот по этой инструкции «Как установить и использовать Docker в Ubuntu 18.04»  https://www.digitalocean.com/community/tutorials/docker-ubuntu-18-04-1-ru

Я сознал, что ради одной виртуалки использование Terraform + Ansible + Kubernetes как-то многовато.

CI\CD

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

Цель мой автоматизации просто deploy, тестировать я не стал (ну не написал я тесты 🙂 нечего тестировать).

Адекватной информации по CI\CD, которая выходит за рамки «Привет мир» и cвязанной с созданием Docker-контейнеров как-то мало. Вот 2 хорошие статьи:

  1. «Как мы делаем CI/CD» https://medium.com/breadhead-stories/ci-cd-workflow-65a93a72eef6
  2. «Создание CI/CD-цепочки и автоматизация работы с Docker» https://habr.com/ru/company/ruvds/blog/488668/

Русские тренеры по DevOps для CI\CD системы очень любят Gitlab https://gitlab.com/ иногда вспоминают Jenkins https://www.jenkins.io/ и всё.

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

Есть замечательное видео «CI/CD a NodeJS API with GitLab Runner and Docker-Compose» https://www.youtube.com/watch?v=Qlj6NiOy5jM

Где автор за 11 минут, показывает построение всей цепочки CI\CD: и Docker, и Gitlab, и DigitalOcean. У меня на подготовку к этой статье ушло 2 недели. До автора видео мне ещё далековато…

Изначально я синхронизировал аккаунт с исходным кодом на Github со своим аккаунтом Gitlab. Но Gitlab сам не работает, ему для работы на моём хостинге нужна специальная программа Gitlab Runner. Устанавливается и настраивается она вот так https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#building-docker-images-with-gitlab-cicd

Весь сценарий работы CI\CD храниться всего в одном файле .gitlab-ci.yml Вот подробная инструкция по его созданию https://docs.gitlab.com/ee/ci/yaml/

Есть 2 подхода к формированию образа docker в автоматическом режиме:

  1. Образ создаётся локально на сервере, никуда дальше не уходит, там и работает.
  2. Образ создаётся, отправляется в хранилище на Docker hub https://hub.docker.com/ или в хранилище артефактов и оттуда уже копируется на рабочий сервер.

Насчёт артефактов, помните я говорил о прекрасных эльфах в начале статьи. Так вот, они очень любят артефакты. Но к DevOps это не имеет никакого отношения. 🙂

Артефакты в Gitlab имеют немного иное значение, артефакт – это законченный образ приложения. https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html

Я решил напрямую на хостинге создавать давать контейнер и никому его не отдавать ни эльфам, ни docker hub.

Чтобы собрать и запустить мой контейнер нужны всего 4 команды. Но их просто так запустить нельзя. Придут злые хакеры и всё украдут. Поэтому создание докер контейнера запускает в другом докер-контейнере докер-в-докере docker:dind. Загадка, завернутая в тайну и помещенная внутрь головоломки. Хакеры не пройдут.

Итак, команды.

«docker build --tag mymoney:latest .» Создать контейнер

«docker stop $(docker ps -a -q) || true» Остановить работающий контейнер, хитрый хвост у этой команды «|| true», если контейнер не был запущен, то и остановить его нельзя. Но цепочка команд прервётся, если хоть одна команда завершить не нулём. Поэтому эта команда всегда возвращает true.

«docker run --restart unless-stopped -p 80:80 -d mymoney:latest» Запустить контейнер по 80 порту, но его нужно будет запустить заново, например, после перезагрузки виртуалки. F если вручную контейнер был остановлен, его заново запускать не нужно.

«docker system prune -a –f» удалить все неиспользуемые образы, которые система скачала\создала в процессе работы и без подтверждения.

Вот файл .gitlab-ci.yml что получился в результате https://github.com/orion55/mymoney/blob/master/.gitlab-ci.yml

И вот он конечный результат http://mymoney.infoblog72.ru/ всех этих DevOps-упражнений.

Login: demo@demo.com
Pass: demodemo

Возникает резонный вопрос: «А нафига козе баян? Разве нельзя было просто полученный код разместить на любом бесплатном хостинге?» Можно, например, github даёт бесплатный хостинг для статических файлов. Вот ссылка https://orion55.github.io/mymoney/ на мой проект.

Просто большинство видео и обучающих курсов страдают излишней простотой. Давайте соберём сервер, который выводит «Привет мир» и его опубликуем. А вот как только проект чуть сложнее, чем «Привет мир», всё рушиться, надо вникать, разбираться со всеми используемыми технологиями. Как учебный проект по DevOps, эта задача превосходна.

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