Статьи

Настройка непрерывной интеграции и непрерывного развертывания с Jenkins

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

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

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

Мы будем использовать DigitalOcean для быстрого и простого создания облачных виртуальных частных серверов (VPS) для размещения нашего приложения и Jenkins.

Примечание. В этом руководстве предполагается, что у вас есть базовые знания по работе в командной строке и что на вашем компьютере установлены и Git, и Node.js.

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

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

Поскольку мы будем хранить наш проект на GitHub, давайте начнем с него. Войдите (или создайте) свою учетную запись GitHub и создайте новый репозиторий. Назовите его «hello-jenkins» и дайте ему следующее описание:

1
My super sample app to test out Jenkins.

Для простоты давайте сохраним репо Public . Продолжите и проверьте Инициализировать этот репозиторий с опцией README и выберите опцию Node из выпадающего списка Add .gitignore .

Нажмите кнопку Создать репозиторий , и наше репо будет готово.

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

1
2
git clone git@github.com:<you>/hello-jenkins.git
cd hello-jenkins

Вот какова будет окончательная структура нашего приложения:

1
2
3
4
5
6
7
8
9
├── .gitignore
├── app.js
├── package.json
├── README.md
├── script
│ ├── deploy
│ └── test
└── test
    └── test.js

Давайте займемся этим один за другим. Первым шагом к созданию любого приложения Node.js является создание файла package.json . Вот наши:

01
02
03
04
05
06
07
08
09
10
11
12
13
{
  «name»: «hello-jenkins»,
  «description»: «hello jenkins test app»,
  «version»: «0.0.1»,
  «private»: true,
  «dependencies»: {
    «express»: «3.12.0»
  },
  «devDependencies»: {
    «mocha»: «1.20.1»,
    «supertest»: «0.13.0»
  }
}

В dependencies мы добавили express , который мы будем использовать для создания нашего приложения Node.js. В devDependencies мы добавили supertest и supertest , которые помогут нам написать наши тесты.

Теперь, когда наш package.json определен, установите зависимости нашего приложения, выполнив:

1
npm install

Пришло время написать код нашего приложения. Создайте файл с именем app.js и добавьте в него следующее:

01
02
03
04
05
06
07
08
09
10
11
var express = require(‘express’);
 
var app = express();
 
app.get(‘/’, function (req, res) {
  res.send(‘hello world’);
});
 
app.listen(process.env.PORT || 5000);
 
module.exports = app;

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

  • Сначала мы импортируем express библиотеку, указанную в package.json .
  • Мы используем express для создания нового app.
  • Мы говорим нашему app отвечать на все запросы, попадающие в корневой каталог нашего сайта ( / ), с текстом «Привет, мир».
  • Далее мы сообщаем нашему app на каком порту прослушивать запросы ( process.env.PORT ссылается на переменную среды, называемую «PORT», и, если она не существует, вместо этого по умолчанию используется порт 5000).
  • Наконец, мы делаем наше app доступным для других модулей Node.js через module.exports (это пригодится позже, когда мы добавим тесты).

Это оно! Наше приложение готово — давайте запустим его:

1
node app.js

Откройте ваш любимый браузер и перейдите по http://localhost:5000 , и вы увидите привет мир, сидящий во всей своей славной простоте.

Это не самое захватывающее тестовое приложение, но оно работает! Выключите наше приложение Node.js с помощью Ctrl-C , и давайте двигаться дальше.

Пришло время написать тест для нашего приложения — в конце концов, если нам нечего тестировать, то Дженкинс не будет ничего делать!

Создайте папку с именем test и в ней создайте файл с именем test.js Добавьте следующий код в test/test.js :

1
2
3
4
5
6
7
8
var request = require(‘supertest’);
var app = require(‘../app.js’);
 
describe(‘GET /’, function() {
  it(‘respond with hello world’, function(done) {
    request(app).get(‘/’).expect(‘hello world’, done);
  });
});

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

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

1
./node_modules/.bin/mocha ./test/test.js

Когда закончите, вы должны увидеть зеленую точку вместе с информацией о том, что один тест пройден. Это значит, что наш тест прошел успешно! Но ввод этой команды снова и снова скоро вызовет судороги пальцев и подергивание глаз, поэтому давайте сделаем вспомогательный скрипт, который сделает это за нас (помните, что компьютерам не скучно!).

Создайте новый каталог с именем script и создайте в нем файл с именем test (обратите внимание, что расширение отсутствует). Добавьте следующее в script/test :

1
2
3
#!/bin/sh
 
./node_modules/.bin/mocha ./test/test.js

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

1
chmod +x script/test

Давайте проверим это! Бегать:

1
./script/test

… и вы должны увидеть тот же проходной тест, что и раньше.

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

1
2
3
git add .
git commit -m ‘Add node app’
git push origin master

И это все — наше приложение готово и на GitHub!

У нас есть увлекательное и увлекательное приложение («Привет, мир» имеет в своем роде поэзию, не правда ли?), Но никто не может его увидеть! Давайте изменим это и запустим наше приложение на сервере.

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

Войдите в систему (или зарегистрируйтесь ) в DigitalOcean и нажмите кнопку « Создать каплю» . Для имени хоста, назовите его «привет-Дженкинс». Экземпляр самого низкого размера ( 512 МБ / 1/20 ГБ ) подойдет для наших потребностей и выберет ближайший к вам географический регион. Далее нам нужно выбрать изображение, используемое для создания капли. DigitalOcean предоставляет широкий выбор операционных систем на выбор, но что действительно приятно, так это то, что они также предоставляют изображения, специально предназначенные для определенных типов приложений.

Перейдите на вкладку « Приложения » и выберите параметр node-v0.10.29 в Ubuntu 14.04 — это создаст сервер, который будет прекрасно загружен для нашего приложения Node.js.

Теперь нажмите Create Droplet , и DigitalOcean начнет инициализацию нашего сервера.

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

1
ssh root@APP.SERVER.IP.ADDRESS

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

Прямо сейчас мы вошли в систему как root , который является всемогущим полубогом Linux-земли. Но тяжела голова, которая носит корону, и работать как root — вообще плохая идея. Итак, первое, что мы хотим сделать, это создать нового пользователя — назовем его «приложение»:

1
adduser app

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

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

1
usermod -a -G sudo app

Теперь закройте соединение с exit , а затем подключитесь как app :

1
ssh app@APP.SERVER.IP.ADDRESS

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

Давайте загрузим наше приложение на компьютер. Благодаря образам приложений DigitalOcean наша машина поставляется с предустановленными Node.js и npm, но нам все еще нужно установить Git:

1
sudo apt-get install git

Вам будет предложено ввести пароль (поскольку вы используете sudo ), и вам придется подтвердить установку с помощью Y. После установки Git мы можем использовать его для получения нашего приложения от GitHub.

Скопируйте URL-адрес клонирования HTTPS со страницы проекта GitHub, а затем клонируйте репозиторий в свою домашнюю папку на сервере:

1
2
cd
git clone https://github.com/<you>/hello-jenkins.git

Теперь наше приложение находится на нашем сервере, в папке с именем «hello-jenkins». Давайте перейдем к этому:

1
cd hello-jenkins

Первое, что нам нужно сделать, это установить зависимости приложения:

1
npm install —production

Как только это будет сделано, мы можем запустить наше приложение! Раскрути это:

1
node app.js

… и перейдите к IP-адресу вашего сервера в браузере.

Но подождите, это не работает! В чем дело?

Хорошо, давайте вспомним эту строку кода в нашем app.js :

1
app.listen(process.env.PORT || 5000);

Прямо сейчас у нас нет установленной переменной среды PORT , поэтому наше приложение по умолчанию использует порт 5000, и вам нужно добавить порт к IP-адресу в браузере ( http://YOUR.SERVER.IP.ADDRESS:5000 ).

Так как же заставить наше приложение работать так, как ожидается, без указания порта? Что ж, когда браузер отправляет HTTP-запрос, по умолчанию используется порт 80. Поэтому нам просто нужно установить переменную среды PORT 80 .

Мы установим наши переменные окружения в /etc/environment на сервере — этот файл загружается при входе в систему, и набор переменных будет доступен глобально для всех приложений. Откройте файл:

1
sudo nano /etc/environment

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

1
PORT=80

Затем нажмите Ctrl-X , Y и Enter, чтобы сохранить и выйти. Выход сервера ( exit ) и SSH снова (это загрузит новую переменную среды).

Еще одна маленькая рутина — запуск приложения на порту 80 требует привилегий root, но выполнение sudo node app.js не сохранит переменные среды, которые мы настроили. Чтобы обойти это, мы дадим node возможность работать на порту 80 без sudo :

1
sudo setcap cap_net_bind_service=+ep /usr/local/bin/node

Это должно сделать это. Теперь запустите:

1
node app.js

Перейдите по http://YOUR.SERVER.IP.ADDRESS , и вы увидите привет мир !

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

1
sudo npm install -g forever

Теперь вместо запуска нашего приложения с node app.js мы будем использовать:

1
forever start app.js

Обратите внимание, что вместо процесса, зависшего при выполнении, он немедленно завершается и возвращает вам контроль. Это потому, что сервер Node.js работает в фоновом режиме. Теперь нам не нужно беспокоиться о закрытии нашего сервера при выходе из системы. forever даже автоматически перезапустит наше приложение, если произойдет сбой!

Чтобы остановить наше приложение, мы можем запустить:

1
forever stopall

А пока давайте продолжим и перейдем к Дженкинсу.

Мы будем размещать наш сервер Jenkins на отдельной капле DigitalOcean. Давайте раскрутим это сейчас.

Создайте новую каплю с именем хоста «jenkins-box». Снова выберите 512MB / 1 / 20GB , с тем же расположением и тем же типом приложения ( node-v0.10.29 в Ubuntu 14.04 ), что и с предыдущей каплей.

Нажмите Create Droplet и после завершения используйте учетные данные, отправленные вам по электронной почте, для входа через SSH (вам нужно будет установить новый пароль, как и раньше).

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

1
2
adduser admin
usermod -a -G sudo admin

Выйдите из системы как root и войдите как вновь созданный admin .

Поскольку целью Jenkins является получение нашего проекта и запуск его тестов, на нашей машине должны быть установлены все зависимости проекта. Мы расширили этот экземпляр с помощью приложения DigitalOcean Node.js, поэтому Node.js и npm уже установлены. Но нам все еще нужно установить Git:

1
sudo apt-get install git

Далее идет Дженкинс. Установка Jenkins довольно проста — мы сделаем все возможное, чтобы apt-get выполнил всю тяжелую работу. Единственный улов в том, что нам нужно добавить новый репозиторий apt перед началом установки:

1
2
3
sudo wget -q -O — http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key |
sudo sh -c ‘echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list’
sudo apt-get update

Теперь мы можем установить Jenkins:

1
sudo apt-get install jenkins

После завершения Jenkins будет запущен и доступен через порт 8080. Перейдите в своем браузере по IP-адресу jenkins-box на порту 8080, и вы увидите целевую страницу Jenkins.

Нажмите ссылку « Управление Jenkins» , а затем ссылку « Управление плагинами» . Перейдите на вкладку « Доступные » и найдите плагин GitHub . Установите флажок « Установить» , а затем нажмите кнопку « Загрузить сейчас и установить после перезагрузки» .

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

После перезапуска Jenkins пришло время добавить наш проект. Нажмите кнопку « Новый элемент» . Используйте «hello-jenkins» для имени элемента, выберите « Создать проект программного обеспечения в свободном стиле» и нажмите кнопку с надписью « ОК» .

Как только проект будет настроен, вы окажетесь на странице настроек проекта. Добавьте URL-адрес GitHub нашего проекта в окно проекта GitHub :

1
https://github.com/<you>/hello-jenkins

Затем выберите опцию Git в разделе « Управление исходным кодом» . В появившихся полях добавьте URL-адрес нашего репозитория проекта GitHub в поле URL-адрес репозитория :

1
https://github.com/<you>/hello-jenkins.git

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

1
2
npm install
./script/test

Наша сборка состоит из двух этапов. Во-первых, он устанавливает зависимости нашего приложения. Затем он выполняет ./script/test для запуска наших тестов.

Нажмите « Сохранить ».

Чтобы завершить настройку интеграции, перейдите в репозиторий GitHub и нажмите « Настройки» . Нажмите вкладку Webhooks & Services , а затем раскрывающийся список Add service . Выберите сервис Jenkins (плагин GitHub) .

Добавьте следующее как URL-адрес крючка Дженкинса :

1
http://JENKINS.SERVER.IP.ADDRESS:8080/github-webhook/

Нажмите Добавить сервис . Наш проект готов к первому непрерывному интеграционному тестированию!

Давайте дадим что-то для тестирования. Откройте app.js локально и измените эту строку:

1
res.send(‘hello world’);

…к этому:

1
res.send(‘hello jenkins’);

Сохраните изменения и передайте их:

1
2
git add .
git commit -m ‘Switch to hello jenkins’

Теперь следите за Jenkins, пока вы вносите изменения в GitHub:

1
git push origin master

Через секунду или две вы увидите, что для нашего проекта hello-jenkins в Дженкинсе была начата новая работа — наша непрерывная интеграция работает!

Но … работа не удалась! Почему?

Хорошо, помните, что наш тест ожидает, что корневой вызов возвратит «hello world», но мы изменили его на «hello jenkins». Итак, давайте изменим ожидания нашего теста. Поменяйте местами эту строку:

1
request(app).get(‘/’).expect(‘hello world’, done);

… с этой строкой:

1
request(app).get(‘/’).expect(‘hello jenkins’, done);

Сохраните, подтвердите и снова нажмите:

1
2
3
git add .
git commit -m ‘Switch test to hello jenkins’
git push origin master

Смотрите Дженкинс — еще раз, вы увидите, что сборка запускается автоматически, и на этот раз она удалась!

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

Итак, мы автоматически тестируем наши изменения, но как насчет развертывания этих изменений? Нет проблем!

Если вы внимательно следите, вы, несомненно, заметили, что в нашем проекте пока что-то не хватает. В структуре проекта в начале урока есть файл script/deploy , но мы еще не создали ни одного такого файла. Что ж, теперь мы будем!

Но сначала давайте обсудим, как будет работать развертывание. Наш скрипт (запускаемый на этапе сборки Jenkin) войдет в систему на сервере приложений через SSH, перейдет в папку нашего приложения, обновит приложение и затем перезагрузит сервер. Перед написанием нашего сценария развертывания нам нужно разобраться, как наш сервер Jenkins будет использовать SSH на нашем сервере приложений.

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

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

jenkins-box в систему как admin на jenkins-box , выполните следующее:

1
sudo su

Укажите свой пароль admin , и он переключит вас на пользователя root . Затем выполните:

1
su jenkins

Теперь вы действуете как пользователь jenkins . Сгенерируйте ключ SSH:

1
ssh-keygen -t rsa

Сохраните файл в расположении по умолчанию ( /var/lib/jenkins/.ssh/id_rsa ) и убедитесь, что вы не используете парольную фразу (в противном случае доступ по SSH потребует пароль и не будет работать при автоматизации).

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

1
cat ~/.ssh/id_rsa.pub

… и скопировать вывод. Это должна быть длинная строка, начинающаяся с «ssh-rsa» и заканчивающаяся «jenkins @ jenkins-box».

Выйдите из jenkins-box и снова войдите на наш сервер приложений ( hello-jenkins ) как пользователь app . Нам нужно создать файл с именем .ssh папке пользователя нашего app .ssh :

1
2
mkdir ~/.ssh
nano ~/.ssh/authorized_keys

Вставьте открытый ключ, который вы скопировали, и затем нажмите Ctrl-X / Y / Enter, чтобы сохранить и выйти. Для корректной работы этого файла ему необходимо установить строгие разрешения:

1
2
chmod 700 ~/.ssh
chmod 600 ~/.ssh/*

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

1
ssh app@APP.SERVER.IP.ADDRESS

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

Создайте файл в папке script именем deploy (обратите внимание, что расширение отсутствует). Добавьте в script/deploy :

1
2
3
4
5
6
7
8
9
#!/bin/sh
 
ssh app@APP.SERVER.IP.ADDRESS <<EOF
  cd ~/hello-jenkins
  git pull
  npm install —production
  forever restartall
  exit
EOF

Давайте пройдемся по этому:

  • Сначала мы заходим на сервер приложений как пользователь app .
  • Затем мы переходим в папку нашего приложения и обновляем его до последней версии с GitHub.
  • После этого мы устанавливаем наши зависимости.
  • Наконец, как только код нашего приложения обновляется, мы перезагружаем наш сервер с forever restartall .

Сделайте наш новый исполняемый файл скрипта:

1
chmod +x script/deploy

Добавьте этот новый файл и передайте его:

1
2
git add .
git commit -m ‘Add deploy script’

Но давайте пока не будем давить. Сначала вернитесь к конфигурации нашего проекта в Jenkins и прокрутите вниз до команды build. Добавьте эту новую строку в конце:

1
./script/deploy

Сохранить проект Дженкинс.

Теперь перейдите к GitHub и посмотрите, как автоматически создается Jenkins. Как только сборка будет завершена (она должна завершиться успешно), перейдите в браузере по IP-адресу нашего сервера приложений. Presto! Наш восхитительный «привет мир» был заменен на волнующий «привет Дженкинс»!

Наше приложение постоянно разворачивается!

Уф. Это была настоящая поездка!

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