Перевели для вас статью Юлиуса Минмо о настройке непрерывного развертывания (Continuous Deployment) для своего проекта. Автоматизация позволяет сэкономить кучу времени и сил. Статья будет полезна, в первую очередь, начинающим программистам.
Непрерывное развертывание — отличная штука. Один раз коммитим проект и далее все происходит в автоматическом режиме, наблюдение за этим процессом просто гипнотизирует. В этой статье я покажу, как можно все настроить для домашнего проекта.
Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».Итак, для начала давайте посмотрим на схему, где объясняется разница между Continuous Delivery и Continuous Deployment.
Skillbox рекомендует: Онлайн-курс «Профессия Frontend-разработчик».
В случае с домашним проектом выбираем Continuous Deployment, поскольку никто, кроме вас, с ним (проектом) не работает и никто от него не зависит. Ну а поскольку в большинстве случаев хочется, чтобы изменения были немедленно развернуты, то выбор очевиден. Если же вам позже захочется изменить процесс, вы всегда сможете это сделать.
Вы изучите следующее:
- Как сделать Dockerfile.
- Как выгрузить проект на GitHub.
- Как автоматически построить образ docker на Docker Hub.
- Как автоматически загрузить и запустить образ с Watchtower.
Что требуется:
- Базовое понимание того, что представляют собой Docker и Dockerfile.
- Установленный Git.
- Учетная запись на <a href='https://hub.docker.com/">Docker Hub
Сервер (физический или виртуальный) с запущенным Docker.
Вот мои репозиторий GitHub и Docker Hub, с которыми я работаю.
Почему я использую Docker?
Он дает возможность использовать одно и то же окружение для разных процессов, что исключает появление гейзенбагов и проблемы «это работает только на моей машине». Контейнеры изолированы, что хорошо с точки зрения кибербезопасности. Есть и больше преимуществ, но, на мой взгляд, эти два являются главными.
Настройка Dockerfile
Сначала нам нужен Dockerfile для проекта. Этот файл всегда называется именно так и не имеет расширения. Он всегда находится в главной директории проекта.
Он начинается с оператора FROM, который сообщает Docker, с какого базового образа мы начинаем. Вы можете провести аналогию с живописью. Можем представить себе этот образ как готовый холст с нарисованным фоном и отсутствующим главным элементом композиции (вашей программой).
Далее копируем файлы проекта в контейнер при помощи команды COPY…
Она позволяет забрать файлы из начального расположения в текущее — конечно, внутри контейнера.
Далее необходимо установить зависимости, для этого я использую Python PIP. Главное, что нужно запомнить, — это запуск команд в контейнере с RUN.
From python:3.7
COPY..
RUN pip install -r requirements.txt
Все просто, правда? Теперь можно запускать программу в контейнере.
CMD [«python», "./my_script.py"]
Теперь все, вы закончили Dockerfile и можете вручную создать образ и контейнер. Сейчас просто пропустим этот момент.
Теперь давайте создадим репозиторий в GitHub, но помните, что строку “Initialize this repository with a README” не нужно трогать.
Теперь копируем URL.
Открываем cmd/shell корневой директории проекта. Теперь необходимо инициализировать репозиторий, добавить файлы, сконфигурировать remote-режим, закоммитить файлы и отправить проект на GitHub.
git init
git add *
git remote add origin https://github.com/<user>/<repository>.git
git commit -a -m "Make Dockerfile ready for CD"
git push -u origin master
Если все ОК, GitHub-репозиторий будет выглядеть вот так:
Мы на полпути к успеху!
Теперь нужно подключить GitHub к Docker Hub. Для этого нужно отправиться в настройки учетной записи.
Скролим вниз и подключаемся.
Теперь создаем репозиторий в Docker Hub.
Называем свой репо и кликаем по иконке GitHub или Bitbucket. Потом выбираем организацию (обычно это ваш ник) и название проекта. При желании настройки можно изменить.
Ну а теперь последний шаг — здесь нам необходим Watchtower на целевой машине. Это программа, которая позволяет автоматизировать процесс. Если появляется апдейт, то Watchtower убирает оригинальный контейнер и создает контейнер из нового образа с такими же настройками.
Хорошая новость в том, что можно установить Watchtower с Docker, для этого необходимо ввести в терминал такую команду:
docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock v2tec/watchtower
И теперь запускаем контейнер для своего проекта!
docker run -d --name <my-project> <username>/<my-project>
-d позволяет программе работать в фоне, так что она не закроется, если вы закроете терминал.
Завершая сказанное, если вы отправите коммит к репозиторию GitHub, Docker Hub автоматически создаст образ Docker. Затем с ним уже будет взаимодействовать Watchtower.
Что касается тестов, то вы сможете использовать Travis CI. Вы можете прочитать об этом здесь, но суть в том, что вы добавляете в свой репозиторий еще один файл, в котором есть инструкции для внешнего сервера для выполнения модульных тестов или любые другие инструкции.
Skillbox рекомендует:
- Онлайн-курс «Python-разработчик с нуля».
- Практический годовой курс «PHP-разработчик с нуля до PRO».
- Образовательный онлайн-курс «Профессия Java-разработчик».
Комментарии (8)
sha4
10.02.2019 03:21+1А auto pull по вебхуку? При обновлении репозитория на гитхабе, дёргается скрипт на целевой машине, и забираются изменения. Вроде проще же, или я не прочувствовал суть улучшения?
auto_pull.php<?php // USE APPLICATION/JSON CONTENT TYPE // https://***/auto_pull.php // REMOTE_REPOSITORY = 'https://github.com/***.git' define('SECRET', '***'); define('ROOT', "$_SERVER[DOCUMENT_ROOT]/***/"); define('BRANCH', 'refs/heads/master'); define('LOGFILE', "$_SERVER[DOCUMENT_ROOT]/***/auto_pull.log"); define('TIME', time()); // log the time to_log( "Date: " . date("Y-m-d H:i:s", TIME) . "UTC \n" . "IP: " . htmlspecialchars($_SERVER['REMOTE_ADDR']) . "\n" . "UA: " . htmlspecialchars($_SERVER['HTTP_USER_AGENT']) ); // Get the POST-data like this: {"key1":"value1","key2":"value2","key3":"value3"} $payload = file_get_contents("php://input"); // Payload must be non-empty if (empty($payload)) { stop("Payload: empty. Nothing to do."); // Exit } // Retrieve a signature if (isset($_SERVER["HTTP_X_HUB_SIGNATURE"])) { $parts = explode("=", htmlspecialchars($_SERVER["HTTP_X_HUB_SIGNATURE"]), 2); // разбить на максимум 2 части по символу "=" $algorithm = $parts[0]; $signature = $parts[1]; to_log("Signature: found. Algorithm: '$algorithm'. Value: '***'"); // Log and continue } else { stop("No signature found"); // Exit } // Check for a GitHub signature (hash_hmac - генерация хеш-кода на основе ключа, используя метод HMAC) if ($signature == hash_hmac($algorithm, $payload, SECRET)) { to_log("Signature: valid"); // Log and continue } else { stop("X-Hub-Signature does not match SECRET"); // Exit } // Is PING received? - log and exit if (isset($_SERVER['HTTP_X_GITHUB_EVENT']) && $_SERVER['HTTP_X_GITHUB_EVENT'] == 'ping') { to_log('Event: Ping. Nothing to do'); ok(); exit(); } // Is PUSH received if (isset($_SERVER['HTTP_X_GITHUB_EVENT']) && $_SERVER['HTTP_X_GITHUB_EVENT'] == 'push') { to_log('Event: push'); // Decode JSON data from Github if (isset($_SERVER['CONTENT_TYPE'])) { switch (htmlspecialchars($_SERVER['CONTENT_TYPE'])) { case 'application/json': to_log('Content type: JSON'); break; case 'application/x-www-form-urlencoded': to_log('Content type: x-www-form-urlencoded'); $payload = urldecode($payload); $payload = substr($payload, 8); // remove "payload= " break; default: stop('Unsupported content type: ' . htmlspecialchars($_SERVER['CONTENT_TYPE'])); } $payload = json_decode($payload); } else { stop('Content type mismatch'); } // Auto pull live if push was on master branch if ($payload->{'ref'} === BRANCH) { to_log("Branch: " . BRANCH); // Log and continue } else { stop("Warning: Pushed branch does not match " . BRANCH); // Exit } // Check for repository $rep_name = ROOT . htmlspecialchars($payload->{'repository'}->{'name'}); if (is_dir($rep_name) && file_exists("$rep_name/.git")) { to_log("Repository: " . htmlspecialchars($payload->{'repository'}->{'name'})); // Log and continue chdir($rep_name); // сменить директорию } else { stop("$rep_name is not a repository"); // Exit } // Pull // (2>&1 - перенаправить поток вывода ошибок STDERR &2 в стандартный вывод STDOUT &1 // чтобы в переменную $pull_result попадал также результат неудачного выполнения команды) try { to_log("AUTO PULL INITIATED"); $pull_result = '# git fetch --all: ' . shell_exec('git fetch --all 2>&1'); $pull_result .= '# git checkout --force "origin/master": ' . shell_exec('git checkout --force "origin/master" 2>&1'); to_log($pull_result."AUTO PULL COMPLETE"); } catch (Exception $e) { stop("Error during auto pull - $e"); } ok(); } // To Log progress function to_log($data = '') { $log = fopen(LOGFILE, "a"); if (!$log){ exit("Can not open log file to append data"); } fwrite($log, $data."\n"); fclose($log); } // To forbid access function stop($reason = '') { $log = fopen(LOGFILE, "a"); if (!$log){ exit("Can not open log file to append data"); } fwrite($log, "=== ERROR: $reason ===\n\n"); fclose($log); header("HTTP/1.0 403 Forbidden"); exit(); } // function to return OK function ok() { to_log(); ob_start(); header("HTTP/1.1 200 OK"); header("Connection: close"); header("Content-Length: " . ob_get_length()); ob_end_flush(); ob_flush(); flush(); } ?>
cynovg
10.02.2019 22:57Не понятно, каким образом это избавит от появления гайзенбагов?
gecube
11.02.2019 00:56Поясните более подробно, пожалуйста.
Дополнительно автору:
Этот файл всегда называется именно так и не имеет расширения
ну-ну. Можно переопределить имя Dockerfile, а так же положить его в другом каталоге. Но при этом при
docker build
придется передавать путь и имя файла (через-f
)
Vkuvaev
Режет глаз, простите, Continuous Deployment это непрерывное развертывание, а не интеграция.
gecube
Я соглашусь, что «Continuous Delivery и Continuous Deployment» в данной статье как-то кривовато объясняются. И вообще это вопрос дичайших споров, что есть что. Кратко общее мнение состоит в том, что CI — это постоянные сборки + автотесты (что и есть интеграция). Deployment — это именно развертывание постоянное на прод. А Delivery — это весь процесс поставки ценности ( т.е. CI + CD).
Дополнительно могу добавить, что continuous не подразумевает деления между авто-развертываниями с роллбеком и ручными. Какая разница, в конце-концов? Процесс он гораздо выше уровнем этих деталей.