Объединенная транспортно-логистическая компания – Евразийский железнодорожный альянс» (ОТЛК ЕРА)
О компании:
«ОТЛК Ера» — предоставляет сервис в области организации транзитных перевозок контейнеров в составе регулярных контейнерных поездов в сообщении Китай – Европа по территориям Республики Казахстан, Российской Федерации и Республики Беларусь.
Руководитель:
Гром Алексей Николаевич
Уставной капитал:
Реквизиты:
ИНН: 9701104646
КПП: 770101001
ОКПО: 28117867
ОГРН: 1187746369590
Контакты:
Телефон: +7 (495) 995-95-91
Email: utlc@utlc.com
Сайт: www.utlc.com
Зарегистрирована: Россия, Москва
АО «ОТЛК» организовано в 2014 году для решения задачи евразийского железнодорожного транзита, для транспортировки грузовых контейнеров через территорию России, Казахстана и Белоруссии в составе регулярных контейнерных поездов из Китая в Европу. В 2018 году была проведена реорганизация компании, услуги контейнерных грузоперевозок были переданы вновь образованной компании АО «ОТЛК Ера». Грузы Из Китая доставляются в Европу железнодорожными составами с согласованным графиком движения. Такие же полносоставные контейнерные поезда доставляют грузы в обратном направлении. Маршруты движения проложены через терминалы Казахстана к терминалам в Беларуси и Калининграде.
В 2020 году между Китаем и Европой организованы мультимодальные контейнерные перевозки, которые помогают доставлять грузы, решая проблему отличия стандартов железнодорожной колеи в России и Европе.
Использование IoC контейнеров. За и Против
В свете выхода новой версии Enterprise Library, одной из важнейших частей которой является IoC-контейнер Unity, лично я ожидаю всплеск интереса к теме IoC(Inversion of Control) контейнеров, которые, по сути, являются реализацией достаточно известного паттерна Service Locator.
В этом топике я хотел бы подискутировать на тему «правильного» использования таких контейнеров, а также предостеречь новичков от применения его «везде и всюду». Ну и просто интересующимся возможностями «новых технологий» тоже должно быть любопытно.
Предыстория проблемы
Обычно к знакомству с IoC-контейнерами подталкивает осознание необходимости следования принципам проектирования классов (SOLID), а именно, последний принцип, который говорит о том, что каждый класс должен зависеть не от конкретных объектов, а от абстракций(интерфейсов), и совсем замечательно, когда все эти зависимости объявляются в конструкторе.
class TextManager <
public TextManager(ITextReader reader, ITextWriter writer) <
>
>
это хорошо, а код вида:
class TextManager <
public TextManager(TextFromFileReader reader) <
>
При этом, если приложение/библиотека у вас большая, ITextWriter используется во многих классах, а какой именно Writer будет использоваться определяется на входе в библиотеку (допустим, у нас есть Writer’ы в БД и файл с общим интерфейсом), то логично возникает желание как-то связать ITextWriter с DatabaseWriter’ом где-то в одном месте.
До SOLID-ов считалось вполне нормальным объявить внутри библиотеки в статическом классе переменную типа ITextWriter и хранить конкретный используемый на текущий момент Writer там. Но когда начинаешь задумываться о нормальной архитектуре… 🙂 минусы статичных классов становятся очевидными — совершенно непрозрачно от чего именно каждый класс зависит, что ему нужно для работы, а что нет, и страшно даже представить, что «потянется» вместе с этим классом, если вдруг необходимо будет его перенести в другой проект или хотя бы другую библиотеку.
IoC-контейнер
Что же предлагается нам для решения проблемы? Решение давно придумано в виде IoC-контейнеров: мы «связываем» интерфейсы с конкретными классами, как только получаем необходимую информацию:
var container = new UnityContainer();
container.RegisterInstance ( new DatabaseWriter());
и имеем удобный способ создания объектов:
И если на примере одной зависимости, преимущество контейнеров неочевидно, то если представить, что конструкторы требуют 2-3 интерфейса, и таких классов у нас хотя бы 5-10, то о применении контейнеров покажется многим настоящим спасением.
Когда контейнер — хорошо..
Собственно, Unity и создавался для активного применения в сложных составных приложениях, с множеством реализаций идентичных интерфейсов и нелинейной логикой взаимозависимостей между реализациями. Говоря проще, если у нас на всю библиотеку не один-единственный интерфейс IWriter с двумя реализациями DbWriter и TextWriter, а еще, к примеру, IReader и ITextProcessor, для каждого из которых тоже существует 3-4 реализации, и TextWriter работает только с CsvReader’ом и ExcelReader’ом, а какой именно из ридеров надо использовать, зависит от конкретного типа текущего TextProcessor’а, ну и от фазы луны заодно.
Очень сложно привести конкретные примеры кода, применение Unity в котором было бы, с моей точки зрения, обоснованно, и не загромоздить текст тонной ненужного кода. Но описанный «пример» создает некое подобие такой сложной и комплексной задачи 🙂
А когда — не очень
. Profit!
Подытоживая, мне кажется, что использование IoC-контейнеров идеально именно в ситуациях со сложными переплетениями взаимозависимостей между конкретными реализациями, и только на самых верхних уровнях библиотеки/приложения.
То есть сразу после точки входа в библиотеку следует регистрировать в Юнити реализации интерфейсов, и как можно раньше резолвить необходимые нам типы. Таким образом мы пользуемся всей мощью Юнити по упрощению процесса генерации сложнозависимых объектов, и в то же время следуем принципам хорошего дизайна и сводим к минимуму использование весьма абстрактного «контейнера» в нашем приложении, тем самым упрощая его тестирование.
Различия между Docker, containerd, CRI-O и runc
Появление Docker привело к взрывному росту популярности контейнеров, но с тех пор появились и другие инструменты. К сожалению, разобраться в них может быть совсем непросто. Но мы попробуем! И если вы считаете себя единственным, кто всего этого пока не понимает, не волнуйтесь. Это не так!
Что такое Docker?
Существует разница между компанией Docker, контейнерами Docker, образами Docker и инструментами Docker, к которым мы все привыкли. Важно понимать, что Docker — лишь один из инструментов для работы с контейнерами; существуют и другие инструменты, причем некоторые из них поддерживает сама компания Docker.
Как видите, вы не единственный, кто сбит с толку. Давайте разберёмся, чем отличаются Docker, containerd и CRI-O. Это особенно важно, если вы работаете с Kubernetes.
Обзор экосистемы контейнеров
Экосистема контейнеров состоит из множества технологий, специальной терминологии и компаний, конкурирующих друг с другом. К счастью, компании иногда заключают хрупкое перемирие, чтобы согласовать некоторые стандарты. Эти стандарты помогают добиться совместимости между различными инструментами и избежать зависимости от одной компании или проекта. Основные стандарты, о которых необходимо знать:
Container Runtime Interface (CRI) определяет API между Kubernetes и Container Runtime (средой выполнения контейнеров).
Open Container Initiative (OCI) определяет стандарт образов и контейнеров.
На этой иллюстрации показано, как Docker, Kubernetes, OCI, CRI, containerd и runc вписываются в эту экосистему:
Docker
Мы начнем с Docker, потому что это самый популярный инструмент для работы с контейнерами в настоящий момент. Для многих само название «Docker» является синонимом слова «контейнер».
Компания Docker создала очень удобный инструмент для работы с контейнерами. Docker можно установить на ноутбук (Docker Desktop) или сервер (Docker Engine). Он содержит набор инструментов, упрощающих труд разработчиков и DevOps—инженеров. С помощью Docker CLI можно создавать образы контейнеров, работать с репозиториями, создавать, запускать и управлять контейнерами.
Docker состоит из трех проектов:
containerd: Linux Daemon, который управляет контейнерами и запускает их. Он загружает образы из репозитория, управляет хранилищем и сетью, а также контролирует работу контейнеров.
runc: низкоуровневая среда выполнения контейнеров, которая создает и запускает контейнеры.
Dockershim: Docker в Kubernetes
Важно помнить, что Kubernetes поддерживает только Container Runtime, которые работают с Container Runtime Interface (CRI), но Docker не поддерживает этот стандарт напрямую, поэтому Kubernetes включает компонент под названием dockershim, который необходим для работы с Docker.
Компонент, который функционирует как мост между различными API, обеспечивая совместимость.
В дальнейшем Kubernetes откажется от поддержки dockershim и, соответственно, Docker и будет работать только с Container Runtime, поддерживающими Container Runtime Interface (CRI) — containerd или CRI-O.
Но это не означает, что Kubernetes не сможет запускать контейнеры из Docker—образов. И containerd, и CRI-O могут запускать образы в формате Docker (фактически в формате OCI), они просто делают это без использования команды docker и Docker Daemon.
Docker—образы
Container Runtime Interface (CRI)
CRI — это API, который Kubernetes использует для управления различными Container Runtime, создающими и управляющими контейнерами. CRI упрощает для Kubernetes использование различных Container Runtime. Вместо того, чтобы включать в Kubernetes поддержку каждой из них, используется стандарт CRI. При этом задача управления контейнерами полностью ложится на Container Runtime.
Поэтому, если вы можете использовать containerd для запуска контейнеров, вы также можете использовать CRI-O, потому что они поддерживают стандарт CRI. При этом, если вы конечный пользователь, то вам не важно, какая Container Runtime используется.
Помните, что Red Hat (проект OpenShift) использует CRI-O и отвечает за его поддержку (безопасность, исправления ошибок и т. д.). В то время как Docker поддерживает containerd.
Какая Container Runtime используется в Kubernetes
containerd
сontainerd — это Container Runtime, которая раньше была частью Docker. Реализует спецификацию CRI. Умеет скачивать образы из репозитория и управляет ими, а затем передает их Container Runtime нижнего уровня (о ней речь пойдет дальше), которая фактически создает и запускает процессы контейнера.
сontainerd был выделен из проекта Docker, чтобы сделать Docker модульным. Таким образом, Docker сам использует containerd. Когда вы устанавливаете Docker, он также устанавливает containerd. Кроме того, сontainerd использует собственный плагин для поддержки CRI в Kubernetes.
CRI-O — это еще одна Container Runtime, реализующая Container Runtime Interface (CRI). Она была специально создана с нуля при поддержке Red Hat, IBM, Intel и SUSE как Container Runtime для Kubernetes. Это альтернатива containerd, которая также позволяет загружать образы контейнеров из репозиториев, управлять ими и запускать Container Runtime нижнего уровня для запуска процессов контейнера.
Open Container Initiative (OCI)
OCI — это группа компаний, которые поддерживают спецификацию формата образа контейнера и метода запуска контейнеров. Идея OCI заключается в том, что вы можете выбирать между различными Container Runtime, которые соответствуют этой спецификации. При этом каждая из них может иметь разные реализации нижнего уровня. Например, у вас, может быть, одна OCI-совместимая Container Runtime для ваших хостов Linux и одна для ваших хостов Windows. В этом заключается преимущество стандартизации.
runc — это еще одна среда выполнения контейнера, совместимая с OCI, которая запускает процессы контейнеров. runc называют эталонной реализацией OCI.
Что такое эталонная реализация?
Эталонная реализация — это программное обеспечение, в котором реализованы все требования спецификации или стандарта. В случае с OCI runc предоставляет все функции, ожидаемые от OCI-совместимой среды выполнения.
Вот несколько альтернатив runc:
crun: среда выполнения контейнеров, написанная на C (в отличие от runc, которая написана на Go).
kata-runtime: из проекта Katacontainers, который реализует спецификацию OCI как отдельные небольшие виртуальные машины (аппаратная виртуализация).
Есть ли аналоги runc для Windows?
runc — это инструмент для запуска контейнеров в Linux. В Windows всё немного иначе. Эквивалент runc — это служба Microsoft Host Compute Service (HCS). Она содержит инструмент под названием runhcs, который является форком runc и также реализует спецификацию Open Container Initiative.
Подведем итоги
Docker — это лишь часть всей экосистемы контейнеров. Существуют открытые стандарты: CRI и OCI, и несколько Container Runtime с поддержкой CRI: containerd, runc, CRI-O и, конечно, сам Docker. Возможно, скоро мы увидим множество новых реализаций Container Runtime с поддержкой стандартов CRI и OCI.
IoC, DI, IoC-контейнер — Просто о простом
Думаю сейчас слова IoC, DI, IoC-контейнер, как минимум у многих на слуху. Одни этим активно пользуются, другие пытаются понять, что же это за модные веяния.
На данный момент, на эту тему уже довольно сказано, написано, в том числе и на хабре, но как раз из-за обилия информации сложно найти действительно полезный контент. Кроме того, данные понятия часто смешивают и/или путают. Проанализировав множества материалов я решил изложить вам свое видение предмета.
Теория
Для меня взаимосвязь между IoC и DI такая же как между Agile и Scrum, т.е.
Inversion of Control (инверсия управления) — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.
Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа (помимо этого есть еще Factory Method, Service Locator).
IoC-контейнер — это какая-то библиотека, фреймворк, программа если хотите, которая позволит вам упростить и автоматизировать написание кода с использованием данного подхода на столько, на сколько это возможно. Их довольно много, пользуйтесь тем, чем вам будет удобно, я продемонстрирую все на примере Ninject.
Практика
Согласно подходу инверсии управления если у нас есть клиент, который использует некий сервис, то он должен делать это не напрямую, а через посредника, своего рода аутсорсинг.
То как технически это будет сделано и определяет каждая из реализаций подхода IoC.
Мы будем использовать DI, на простом примере:
Скажем есть некий класс, который создает расписание, а другой класс его отображает (их как правило много, скажем один для десктоп-приложения, другой для веба и т.д.).
Если бы мы ничего не знали о IoC, DI мы бы написали что-то вроде этого:
Вроде бы все хорошо, код решает поставленную задачу, но что если мы захотим в последствии изменить реализацию менеджера расписаний и/или же иметь несколько таких менеджеров и динамически их заменять. Тогда в последствии нам придется менять и что-то в ScheduleViewer, а значит и снова его тестировать.
К счастью, разработчики, ленивые люди в хорошем смысле этого слова, и не любят делать одно и тоже дважды.
Мы, например, воспользуемся внедрением зависимостей (DI) для того, чтобы разорвать этот клубок стальных ниток — сделаем связь между этими классами более слабой, добавив прослойку в виде интерфейса IScheduleManager. И будем разрешать ее одним из способов техники DI, а именно Constructor Injection (помимо этого есть Setter Injection и Method Injection — если в двух словах, то везде используется интерфейс вместо конкретного класса, например в типе свойства или в типе аргумента метода):
И далее там где мы хотим воспользоваться нашим классом для отображения расписания мы пишем:
Вот уже почти идеально, но что если у нас много различных ScheduleViewer, разбросанных по проекту, которые использует всегда именно ScheduleManager (придется его руками каждый раз создавать) и/или мы хотим как-либо настроить поведение, так что бы в одной ситуации везде использовать ScheduleManager, а в другой скажем AnotherScheduleManager и т.д.
Решить эту проблему как раз и призваны IoC-контейнеры.
IoC-контейнеры
Они помогают уменьшить количество рутины, позволяя задать соответствие между интерфейсом и его конкретной реализацией, чтобы потом везде этим пользоваться.
Как я уже говорил выше, мы будем рассматривать это на примере Ninject —
1. Сначала мы создаем конфигурацию контейнера:
Теперь везде где требуется IScheduleManager будет подставляться ScheduleManager.
2. Создаем сам контейнер, указывая его конфигуратор:
Контейнер сам создаст экземпляр класса ScheduleManager, вызовет конструктор ScheduleViewer и подставит в него свежесозданный экземпляр ScheduleManager.
Создание CI/CD-цепочки и автоматизация работы с Docker
За прошедшие с тех пор два десятилетия всё изрядно изменилось. Сайты стали сложнее, их, перед выпуском в продакшн, надо собирать. Один единственный сервер стал множеством серверов, работающих за балансировщиками нагрузки, обычным делом стало использование систем контроля версий.
Для моего персонального проекта у меня была особая конфигурация. И я знала, что мне нужна возможность разворачивать сайт в продакшне, выполняя всего одно действие: запись кода в ветку master на GitHub. Я, кроме того, знала, что мне, для обеспечения работы моего маленького веб-приложения, не хочется заниматься управлением огромным кластером Kubernetes, или пользоваться технологией Docker Swarm, или поддерживать парк серверов с подами, агентами и всякими другими сложностями. Для того чтобы достичь цели по максимальному упрощению работы, мне понадобилось познакомиться с CI/CD.
Если у вас имеется маленький проект (в нашем случае речь идёт о Node.js-проекте) и вам хотелось бы узнать о том, как автоматизировать развёртывание этого проекта, сделав при этом так, чтобы то, что хранится в репозитории, в точности соответствовало бы тому, что работает в продакшне, то, полагаю, вас может заинтересовать эта статья.
Предварительные требования
Ожидается, что читатель этой статьи имеет базовые знания в области работы с командной строкой и написания Bash-скриптов. Кроме того, ему понадобятся учётные записи Travis CI и Docker Hub.
Не скажу, что эта статья может безоговорочно называться «учебным руководством». Это — скорее документ, в котором я рассказываю о том, что узнала, и описываю устраивающий меня процесс тестирования и развёртывания кода в продакшне, выполняемый за один автоматизированный проход.
Вот каким в итоге получился мой рабочий процесс.
Что такое CI/CD?
Аббревиатура CI/CD расшифровывается как «continuous integration/continuous deployment» — «непрерывная интеграция/непрерывное развёртывание».
▍Непрерывная интеграция
Непрерывная интеграция — это процесс, в ходе которого разработчики делают коммиты в главное хранилище исходного кода проекта (обычно в ветку master ). При этом качество кода обеспечивается путём проведения автоматизированного тестирования.
▍Непрерывное развёртывание
Непрерывное развёртывание — это частое автоматизированное развёртывание кода в продакшне. Вторая часть аббревиатуры CI/CD иногда раскрывается как «continuous delivery» («непрерывная доставка»). Это, в целом, то же самое, что и «непрерывное развёртывание», но «непрерывная доставка» подразумевает необходимость ручного подтверждения изменений перед запуском процесса развёртывания проекта.
Начало работы
Приложение, на котором я это всё осваивала, называется TakeNote. Это — веб-проект, над которым я работаю, предназначенный для того, чтобы делать заметки. Сначала я попыталась сделать JAMStack-проект, или только фронтенд-приложение без сервера, для того чтобы воспользоваться стандартными возможностями по хостингу и развёртыванию проектов, которые предлагает Netlify. По мере того, как росла сложность приложения, мне понадобилось создать и его серверную часть, а это означало, что мне надо было бы сформировать собственную стратегию по автоматизированной интеграции и автоматизированному развёртыванию проекта.
В моём случае приложение представляет собой Express-сервер, работающий в среде Node.js, обслуживающий одностраничное React-приложение и поддерживающий защищённый серверный API. Эта архитектура следует стратегии, которую можно найти в данном руководстве по фуллстек-аутентификации.
Я посоветовалась с другом, который является экспертом по автоматизации, и спросила его о том, что мне надо сделать для того, чтобы всё это работало так, как мне нужно. Он подкинул мне идею о том, как должен выглядеть автоматизированный рабочий процесс, изложенный в разделе «Цели» этой статьи. То, что я поставила перед собой подобные цели, означало, что мне нужно разобраться в том, как пользоваться Docker.
Docker
Docker — это инструмент, который, благодаря технологии контейнеризации, позволяет легко распространять приложения, а также выполнять их развёртывание и запуск в одном и том же окружении даже в том случае, если сама платформа Docker работает в различных средах. Для начала мне нужно было получить в своё распоряжение инструменты командной строки (CLI) Docker. Инструкцию по установке Docker нельзя назвать очень чёткой и понятной, но из неё можно узнать о том, что для того, чтобы сделать первый шаг установки, надо скачать Docker Desktop (для Mac или Windows).
Docker Hub — это примерно то же самое, что GitHub для git-репозиториев, или реестр npm для JavaScript-пакетов. Это — онлайн-репозиторий для образов Docker. Именно к нему подключается Docker Desktop.
Итак, для того чтобы приступить к работе с Docker, нужно сделать две вещи:
Далее, войдите в Docker Hub, введя, когда вас об этом спросят, свое имя пользователя и пароль:
Для того чтобы пользоваться Docker, вы должны понимать концепции образов и контейнеров.
▍Образы
Образ — это нечто вроде плана, содержащего инструкции по сборке контейнера. Это неизменяемый снимок файловой системы и настроек приложения. Разработчики могут с лёгкостью обмениваться образами.
Эта команда выведет таблицу со следующим заголовком:
Далее мы будем рассматривать некоторые примеры команд в таком же формате — сначала идёт команда с комментарием, а потом — пример того, что она может вывести.
▍Контейнеры
Контейнер — это исполняемый пакет, который содержит всё, что нужно для выполнения приложения. Приложение при таком подходе всегда будет работать одинаково, независимо от инфраструктуры: в изолированном окружении и в одной и той же среде. Речь идёт о том, что в разных окружениях запускаются экземпляры одного и того же образа.
Тег — это указание на конкретную версию образа.
▍Краткая справка по командам Docker
| Команда | Контекст | Действие |
|---|---|---|
| docker build | Образ | Сборка образа из Dockerfile |
| docker tag | Образ | Тегирование образа |
| docker images | Образ | Вывод списка образов |
| docker run | Контейнер | Запуск контейнера на основе образа |
| docker push | Образ | Отправка образа в реестр |
| docker pull | Образ | Загрузка образа из реестра |
| docker ps | Контейнер | Вывод списка контейнеров |
| docker system prune | Образ/Контейнер | Удаление неиспользуемых контейнеров и образов |
▍Файл Dockerfile
Надо отметить, что у меня нет приложения-примера для этого материала. Но тут, для экспериментов, подойдёт любое простое Node-приложение.
Но то, что в нём содержится, всего лишь описывает, особыми командами, нечто подобное настройке рабочего окружения. Вот некоторые из этих команд:
В зависимости от выбранного базового образа вам может понадобиться установить дополнительные зависимости. Дело в том, что некоторые базовые образы (вроде Node Alpine Linux) созданы с целью сделать их как можно более компактными. В результате в них могут отсутствовать некоторые программы, на которые вы рассчитываете.
▍Сборка, тегирование и запуск контейнера
▍Сборка
Сначала надо собрать образ, указав имя, и, что необзательно, тег (если тег задан не будет, система автоматически назначит образу тег latest ).
После выполнения этой команды можно наблюдать за тем, как Docker выполняет сборку образа.
Сборка может занять пару минут — тут всё зависит от того, сколько у вас имеется зависимостей. После завершения сборки можно выполнить команду docker images и взглянуть на описание своего нового образа.
▍Запуск
Если перейти теперь по адресу localhost:5000 — можно увидеть страницу работающего приложения, которая выглядит точно так же, как страница приложения, работающего в продакшн-окружении.
▍Назначение тега и публикация
Для того чтобы воспользоваться одним из созданных образов на продакшн-сервере, нужно, чтобы у нас была бы возможность загрузить этот образ с Docker Hub. Это значит, что сначала надо создать на Docker Hub репозиторий для проекта. После этого в нашем распоряжении окажется место, куда можно оправить образ. Образ надо переименовать так, чтобы его имя начиналось с нашего имени пользователя на Docker Hub. После этого должно идти название репозитория. В конце имени может располагаться любой тег. Ниже показан пример именования образов по этой схеме.
Теперь можно собрать образ с назначением ему нового имени и выполнить команду docker push для отправки его в репозиторий Docker Hub.
Если всё пройдёт как надо, образ будет доступен на Docker Hub и его легко можно будет загрузить на сервер или передать другим разработчикам.
Следующие шаги
К настоящему моменту мы убедились в том, что приложение, в виде контейнера Docker, работает локально. Мы загрузили контейнер на Docker Hub. Всё это значит, что мы уже очень неплохо продвинулись к цели. Теперь надо решить ещё два вопроса:
Надо отметить, что здесь можно воспользоваться и другой комбинацией сервисов. Например, вместо Travis CI можно воспользоваться CircleCI или Github Actions. А вместо DigitalOcean — AWS или Linode.
Мы решили работать с Travis CI, а в этом сервисе у меня уже кое-что настроено. Поэтому сейчас я кратко расскажу о том, как подготовить его к работе.
Travis CI
Travis CI — это инструмент для тестирования и развёртывания кода. Мне не хотелось бы входить в тонкости настройки Travis CI, так как каждый проект уникален, и это не принесёт особой пользы. Но я расскажу об основах, которые позволят вам начать работу в том случае, если вы решите пользоваться Travis CI. Что бы вы ни выбрали — Travis CI, CircleCI, Jenkins, или что-то другое, везде будут применяться похожие методы настройки.
Для того чтобы приступить к работе с Travis CI, перейдите на сайт проекта и создайте учётную запись. Затем интегрируйте Travis CI с вашим GitHub-аккаунтом. Вам, в ходе настройки системы, понадобится указать репозиторий, работу с которым вы хотите автоматизировать, и включить доступ к нему. (Я пользуюсь GitHub, но уверена, что Travis CI может интегрироваться и с BitBucket, и с GitLab, и с другими подобными сервисами).
Каждый раз, когда Travis CI принимается за работу, запускается сервер, выполняющий указанные в конфигурационном файле команды, включая развёртывание соответствующих веток репозитория.
▍Жизненный цикл задания
▍Тестирование
В конфигурационном файле я собираюсь настроить локальный сервер Travis CI. В качестве языка я выбрала Node 12 версии и указала системе на то, что нужно установить зависимости, необходимые для использования Docker.
Если вы хотите, чтобы в вашем репозитории выводились бы значки со сведениями о покрытии кода тестами, тут вы можете найти краткую инструкцию об использовании Jest, Travis CI и Coveralls для сбора и вывода этих сведений.
Здесь заканчиваются те действия, которые выполняются для всех ветвей репозитория и для pull-запросов.
▍Развёртывание
Скрипт развёртывания решает две задачи:
Итак, первая часть скрипта — это отправка образа на Docker Hub. Сделать это довольно просто. Использованная мной схема составления тегов подразумевает комбинирование git-хэша и git-тега, если он существует. Это позволяет обеспечить создание уникального тега и упрощает идентификацию сборки, на которой он основан. DOCKER_USERNAME и DOCKER_PASSWORD — это пользовательские переменные окружения, которые можно задать с помощью интерфейса Travis CI. Travis CI автоматически обработает секретные данные так, чтобы они не попали в чужие руки.
Настроить работу сервера было не особенно сложно. Так, я настроила дроплет, основанный на базовом образе. Надо отметить, что выбранная мной система требует выполнения однократной ручной установки Docker и однократного ручного запуска Docker. Я, для установки Docker, использовала Ubuntu 18.04, поэтому вы, если тоже используете Ubuntu, чтобы сделать то же самое, можете просто следовать этому простому руководству.
Я не говорю тут о конкретных командах для сервиса, так как этот аспект в разных случаях может сильно варьироваться. Я лишь приведу общий план действий, выполняемый после подключения по SSH к серверу, на котором будет развёрнут проект:
Некоторые вещи, на которые стоит обратить внимание
Возможно, когда вы подключитесь к серверу по SSH из Travis CI, вы увидите предупреждение, которое не позволит продолжить установку, так как система будет ждать реакции пользователя.
Я узнала о том, что строковой ключ можно закодировать в base64 для того, чтобы сохранить её в таком виде, в котором с ней можно будет удобно и надёжно работать. На стадии установки можно декодировать публичный ключ и записать его в файл known_hosts для того, чтобы избавиться от вышеописанной ошибки.
На практике эта команда может выглядеть так:
А вот как выглядит то, что она выдаёт — строка в кодировке base64:
Вот команда, о которой говорилось выше
Тот же подход можно использовать с приватным ключом при установлении соединения, так как вам, для доступа к серверу, вполне может понадобится приватный ключ. При работе с ключом вам лишь нужно обеспечить его безопасное хранение в переменной окружения Travis CI, и то, чтобы он нигде не выводился бы.
TLS/SSL и балансировка нагрузки
После того, как я сделала всё то, о чём шла речь выше, последней вставшей передо мной проблемой стало то, что у сервера не было SSL. Так как я пользуюсь Node.js-сервером, для того, чтобы заставить работать обратный прокси Nginx и Let’s Encrypt, нужно изрядно повозиться.
Итоги
После того, как я сделала всё то, о чём рассказала в этом материале, меня уже не пугала ни платформа Docker, ни концепции автоматизированных CI/CD-цепочек. Я смогла настроить цепочку непрерывной интеграции, в ходе выполнения которой производится тестирование кода до попадания его в продакшн и автоматическое развёртывание кода на сервере. Всё это для меня пока ещё относительно ново, и я уверена, что есть способы улучшить мой автоматизированный рабочий процесс и сделать его эффективнее. Поэтому если у вас есть идеи на этот счёт — дайте мне знать. Надеюсь, эта статья помогла вам в ваших делах. Мне хочется верить, что прочтя её, вы узнали столько же, сколько узнала я, пока разбиралась со всем тем, о чём в ней рассказала.
P.S. В нашем маркетплейсе имеется образ Docker, который устанавливается в один клик. Вы можете проверить работу контейнеров на VPS. Всем новым клиентам бесплатно предоставляются 3 дня для тестирования.
Уважаемые читатели! Пользуетесь ли вы технологиями CI/CD в своих проектах?






