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

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

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

Но что, если бы я мог сам размещать свои проекты?

Что, если я сделаю это… своим новым сайд-проектом?

Самостоятельное размещение моих собственных приложений принесло бы некоторые приятные преимущества, верно?

  • Бесплатный хостинг
  • Нет ограничений
  • Полный контроль над оборудованием

Но, конечно, не все персики:

  • Безопасность
  • Масштабируемость
  • Вредоносные атаки
  • Затраты на оборудование, энергию и пропускную способность

Так зачем это делать?

Конечно же, для науки!

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

Мое обычное решение — докеризировать мой сервис и запустить его в моем RasperryPi.

Это очень хорошо работает в самых разных ситуациях, но постоянное развертывание моих сервисов вручную очень быстро надоедает.

  • Не было бы лучше, если бы мой код автоматически развертывался на моем RaspberryPi без моего вмешательства, как в любой респектабельной системе CI/CD? Конечно, было бы! Итак, давайте попробуем построить один.

Установка

• Репозиторий GitHub

• Локальный клон репозитория

• Локальный экземпляр Docker

• Экземпляр Docker, всегда доступный и доступный через SSH (как уже упоминалось, мой находится на моем RasperryPi)

• Простое приложение Node для тестирования процесса.

ожидаемый результат

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

Требования

  • Базовые знания JavaScript и NodeJS для приложения, используемого в качестве примера.
  • Базовые знания Git и GitHub, необходимые для фиксации кода на GitHub.
  • Базовые знания Bash для понимания команд, необходимых для автоматизации.
  • Базовые знания YAML, чтобы понять, как читать конфигурацию пайплайна GitHub.
  • Базовые знания докера и системы образов/контейнеров.

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

Полный пример окончательного проекта представлен на



Шаг 1: Демонстрационное приложение

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

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

  • Создать проект:
npm init
  • Давайте установим «express», чтобы мы могли создать с его помощью пример сервера.
npm i express
  • Давайте настроим базовый экспресс-сервер
  • Создать стартовый скрипт
"start": "node index.js"
  • И, наконец, давайте запустим скрипт
npm run start

После этого мы должны подключиться к http://localhost:3000 и увидеть:

Шаг 2. Докеризируйте приложение

Давайте теперь создадим базовый образ Docker, который запускает приложение.

Обратите внимание, что в моем случае я использую arm32v7 версию node:lts-alpine, созданную для работы на процессоре руки RaspberryPI. Если используется другая архитектура, следует использовать другой базовый образ. Список всех доступных таргетов доступен по адресу: https://hub.docker.com/_/node?tab=tags.



Давайте теперь создадим образ:

docker build . -t test-server

И запустите его:

docker run --name test-server -d -p 3000:3000 test-server

Если все пойдет так, как ожидалось, теперь у нас будет локальный док-контейнер с нашим тестовым приложением, доступным для навигации по адресу http://localhost:3000 с помощью нашего браузера.

Хороший!

Давайте теперь сделаем его более интересным.

Шаг 3. Развертывание на удаленном Docker

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

Я не буду вдаваться в подробности об этом, так как это подробно объяснено здесь:



Короче говоря, это позволяет нам получить доступ к удаленной среде SSH без запроса аутентификации и позволит нам автоматизировать этот процесс в нашей системе ci/cd.

Теперь, когда у нас есть доступ к удаленной системе, и мы также проверили, что контейнер Docker работает в нашей локальной среде, давайте опубликуем его в нашем удаленном экземпляре Docker. Для этого мы используем переменную среды «DOCKER_HOST», которая сообщает докеру, что каждая команда будет перенаправлена ​​на удаленный экземпляр:

DOCKER_HOST=ssh://[email protected] docker run --name test-server -d -p 3000:3000 test-server

Если все пойдет так, как ожидалось, мы сможем увидеть наше демонстрационное приложение при доступе к http://remote.address:3000.

Развернуть команды

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

Давайте создадим новую команду deploy в нашем файле package.json. Этот файл позаботится обо всех потребностях для публикации проекта в нашем удаленном экземпляре докера. Он должен уметь:

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

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

Давайте проанализируем каждую из этих команд:

deploy:выполняет все остальные команды только в правильной последовательности.

deploy:docker-build:выполняется процесс сборки и присваивается имя образу (в данном случае test-server)

deploy:start-container: запускает образ нашего образа тестового сервера, называет его (снова используя test-server, но может быть любым) и настраивает его так, чтобы порт 3000 внутреннего контейнера открывался для внешний порт 3000. Подробнее о публикации портов в докере можно прочитать здесь.

deploy:remove-container: удаляет ранее созданный контейнер. При первом запуске развертывания контейнера не будет, и команда завершится с кодом ошибки, что нарушит наш процесс. Чтобы избежать этого, мы добавляем «|| true» в конце, что позволяет обойти ошибку.

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

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

DOCKER_HOST=ssh://[email protected] npm run deploy

Давайте попробуем, и, если на нашей машине все работает, пришло время автоматизировать тот же процесс на GitHub!

Шаг 4. Действия на GitHub

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

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

Создайте новый файл в папке:

project_root/.github/workflows/any-name-you-want.yaml

Файл может иметь любое имя, но должен иметь расширение «.yml».

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

имя: имя нашего конвейера. Это будет показано в интерфейсе GitHub позже.

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

jobs: объединяет различные этапы конвейера развертывания. В этом случае я собираюсь использовать одно задание для простоты.

сборка: название, которое мы даем заданию.

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

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

uses: action/checkout@v2
Использует действие GitHub, уже подготовленное для определенной задачи. В этом случае это действие загрузит наш код в контейнер, где мы запускаем процесс сборки, чтобы мы могли над ним работать. Это действие общедоступно на GitHub по адресу https://github.com/actions/checkout/tree/v2.

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

run: запускаемая команда.

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

Шаг Настройка SSH-подключения

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

mkdir ~/.ssh

Создает каталог с именем .ssh внутри домашнего каталога текущего пользователя. Это место, где SSH проверяет все возможные конфигурации, и мы делаем их доступными, если их еще нет.

ssh-keyscan -p ${{ secrets.SSH_PORT }} -t rsa ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts

Команда ssh-keyscan собирает открытый ключ с SSH-сервера. Этот процесс необходим, чтобы агент SSH, существующий в контейнере действий GitHub, мог использовать открытый ключ нашей удаленной системы. Без этого шага агент SSH отказался бы общаться с. Две переменные ${{secrets.SSH_PORT}} и ${{secrets.SSH_HOST}} содержат, как следует из названия, порт и URL-адрес хоста нашего удаленного сервера. В следующей главе «Секреты» объясняется, как настраиваются эти секреты.

ssh-agent -a $SSH_AUTH_SOCK > /dev/null

Это было бы очень долго объяснять здесь, но давайте просто скажем, что это необходимо для того, чтобы следующий шаг мог работать правильно. Более подробную информацию можно найти здесь: http://blog.joncairns.com/2013/12/understanding-ssh-agent-and-ssh-add/

ssh-add — <<< “${{ secrets.SSH_KEY }}”

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

Секреты

Наш файл конфигурации содержит несколько ссылок на переменную secrets. Это функция GitHub, которая позволяет нам добавлять в наши пайплайны некоторые значения, которые необходимо держать в секрете из соображений безопасности. Подробную документацию о том, как это работает, можно найти по адресу: https://docs.github.com/en/actions/security-guides/encrypted-secrets.

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

SSH_HOST: должен содержать общедоступный интернет-IP-адрес нашего удаленного сервера. Это тот же IP-адрес, который мы используем для параметра хоста при использовании команды ssh. Делая это секретным, вы избегаете прямого доступа к IP-адресу нашего сервера в нашем репозитории. Это не будет проблемой безопасности как таковой, но может стать отправной точкой для тех, кто намерен начать исследовать наш сервер на наличие слабых мест.

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

SSH-PORT: должен содержать порт, на котором демон SSH нашего сервера прослушивает соединения. Обычно это 22, но в зависимости от конфигурации сервера оно может быть другим.

SSH-USER: должно содержать имя пользователя, которое мы используем для доступа к нашему SSH-серверу.

Шаг 5: Тестирование нашего пайплайна

Все настроено: наконец настал момент протестировать систему!

Давайте внесем небольшое изменение в наше приложение:

app.get(‘/’, (req, res) => {
 res.send(‘Hello World 2!’) // ← Let’s change our message
})

Теперь давайте зафиксируем и подтолкнем его.

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

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

Успех!

Заключение

Благодаря этому упражнению у нас теперь есть собственный очень простой, но бесплатный конвейер CI/CD, который развертывает наш контент на нашем собственном удаленном сервере. С этого момента мы можем создавать все более и более сложные сценарии с помощью GitHub Actions и докера, масштабируя систему все больше и больше с любым из наших проектов!

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