Заметки в категории «Разное»

Автоматическое развёртывание проекта при обновлении кода в репозитории

Я уже не представляю себе процесс разработки без системы контроля версий. Но помимо явного полезного действия, такие системы при определённых событиях могу вызывать различные скрипты (например, запрос по URL или отправка уведомлений на электронную почту). Этим можно воспользоваться для автоматического обновления кода проекта на сервере при наличии изменений в определённой ветке.

Коммит в репозиторий, сборка, копирование артефакта

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

Для хостинга приватных Git-репозиториев я использую Bitbucket. Там есть настройка «Webhooks». Webhook — это URL, на который Bitbucket делает POST-запрос. Нам нужно написать скрипт, который бы вызывался бы на каждый запрос, загружал изменения из Git-запозитория и выполнял другие полезные действия.

Все команды и настройки были произведены на чистом сервере Ubuntu 17.04

Подготовка окружения

В файл /etc/apt/sources.list.d/nginx.list добавляем адрес репозитория с официальными пакетами nginx:

deb http://nginx.org/packages/ubuntu/ zesty nginx
deb-src http://nginx.org/packages/ubuntu/ zesty nginx

Теперь загрузим ключ, которым подписаны пакеты и установим nginx и fcgiwrap:

curl -L https://nginx.ru/keys/nginx_signing.key | apt-key add -
apt-get update
apt-get install nginx fcgiwrap

Добавим конфигурацию сервера, который будет принимать запросы от Git-репозитория. Создадим файл /etc/nginx/conf.d/deploy.conf со следующим содержимым:

server {
  listen 80;
  listen [::]:80;

  server_name deploy.my-cool-project.com;

  location /push-hook {
    gzip off;
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
    include /etc/nginx/fastcgi_params;
    fastcgi_param DOCUMENT_ROOT /var/www/deploy;
    fastcgi_param SCRIPT_FILENAME /var/www/deploy/deploy.sh;
  }
}

Каждый раз, когда nginx получить запрос по адресу http://deploy.my-cool-project.com/push-hook, на сервере будет выполнен скрипт /var/www/deploy/deploy.sh. Давайте создадим его.

mkdir -p /var/www/deploy
nano deploy.sh

Содержимое файла deploy.sh:

#!/bin/sh

echo "Content-type:text/plain\n"
echo "OK!"

Обязательно установим флаг «eXecute» у этого скрипта:

chmod 755 /var/www/deploy/deploy.sh

Теперь можно проверить как он работает:

curl http://ci.my-cool-project.com/push-hook

Клонирование репозитория

Перейдём к настройке git и первоначальному клонированию проекта на сервере.

apt-get install git

Генерируем ключ для доступа:

ssh-keygen -t rsa -f /var/www/deploy/access-key -N ""

Добавляем этот ключ в панели управления репозиторием. Ключ для доступа имеет права «только чтение» и специально создаётся для того, чтобы система CI/CD могла получать изменения.

Сохраняем публичный ключ удаленного сервера:

ssh-keyscan -t rsa bitbucket.org 2>&1 >> /etc/ssh/ssh_known_hosts

Команда ssh делает это автоматически при первом подключении к неизвестному хосту во время процедуры обмена ключами. Но сохраняет она этот публичный ключ в файл для пользователя, выполнившего команду, и только после положительного ответа на запрос. Чтобы команда git pull нормально выполнялась в фоновом процессе под пользователем www-data, публичный ключ нужно добавить вручную в общий файл.

Установим владельца папки /var/www/deploy и её содержимого:

chown -R www-data:www-data /var/www/deploy

Теперь можно клонировать репозиторий:

cd /var/www/deploy
GIT_SSH_COMMAND='ssh -i /var/www/deploy/access-key -o UserKnownHostsFile=/dev/null'
git clone git@bitbucket.org:mistakster/test-project.git ./repo
chown -R www-data:www-data ./repo

Так как мы используем специальный ключ для доступа к репозитории, то мы явно должны сообщить команде git о нём. Однако она не предоставляет соответствующей опции. Данная проблема решается через переменную окружения GIT_SSH_COMMAND. В ней можно указать любые настройки, которые нам нужны.

Подтягивание изменений

Когда мы запушим в репозиторий какие-то изменения, Git-сервер вызовет webhook и передаст информацию о них в виде JSON-объекта. В самом простом варианте, деплой можно запускать при любых изменениях. Но корректнее было бы отслеживать только изменения в нужной ветке, например, master.

Для парсинга JSON в bash-скрипте я буду использовать команду jq.

apt-get install jq

Обновим файл deploy.sh:

#!/bin/sh

echo "Content-type:text/plain\n"

if
  [ -n "$HTTP_X_HOOK_UUID" ] &&
  [ -n "$HTTP_X_REQUEST_UUID" ] &&
  [ "$HTTP_X_EVENT_KEY" = "repo:push" ] &&
  [ "$HTTP_CONTENT_TYPE" = "application/json" ] &&
  [ "$REQUEST_METHOD" = "POST" ]
then
  MASTER_COMMITS=`jq '[.push.changes[].new | select(.name == "master" and .type == "branch")] | length'`

  if [ "$MASTER_COMMITS" -gt 0 ]
  then
    cd ./repo

    GIT_SSH_COMMAND='ssh -i /var/www/deploy/access-key -o UserKnownHostsFile=/dev/null'
    git fetch --all 2>&1
    git reset --hard origin/master
    ./build.sh

    echo "Build complete"
  else
    echo "Build skipped"
  fi
else
  echo "Wrong request"
fi

Теперь мы ожидаем только POST-запрос с соответствующим заголовками и данными. Если в среди всех коммитов встретились изменения в ветке master, то запускается загрузка этих изменений из репозитория и вызов скрпита build.sh в корне проекта.

Фактические действия, необходимые для сборки, например, загрузка пакетов из npm-репозитория, запуск webpack, копирование артефактов и т.п., удобнее вынести именно в скрипт build.sh. Это вполне логично, так как для разных проектов требуются разные действия. К тому же он будет обновляться вместе с проектом и загружаться автоматически.

Теперь можно настроить webhook в Bitbucket.

Настройка webhook в Bitbucket

Заключение

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

Оставте свой комментарий

Код состояния HTTP для неавторизованного пользователя

Среди официального списка кодов состояний HTTP есть такой код:

401 Unauthorized

The request requires user authentication.

И разработчики ошибочно начинают его применять в ответах на REST-запросы асинхронные запросы, чтобы сообщить браузеру о том, что текущий пользователь их приложения не авторизован. Это в корне неверно.

В описании кода 401 ясно указано, что вместе с таким ответом сервер должен передать заголовок WWW-Authenticate с перечнем условий. А браузер может повторить запрос, включив в него заголовок Authorization с требуемыми для аутентификации данными.

Чаще всего в реальной жизни этот код можно встретить, если доступ к ресурсу защищён паролем. Например, в Apache это можно сделать добавив в конфигурацию следующие строки:

AuthName "Secure Area"
AuthType Basic
AuthUserFile "/usr/local/apache/passwd/passwords"
Require valid-user

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

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

А как лучше?

По моему мнению, разумнее отвечать неавторизованному пользователю кодом 403 Forbiden

403 Forbidden

The server understood the request, but is refusing to fulfill it.

А в теле ответа передать JSON с описанием проблемы.

Комментарии к заметке: 4

Знакомство с AWS Lambda

Amazon Web Services Lambda — это веб-сервис, запускающий ваш код на Node.js, Python или Java в ответ на определенные события и отвечающий за автоматическое выделение необходимых вычислительных ресурсов. Функции Lambda не хранят состояние, поэтому AWS Lambda может быстро запустить столько копий функции, сколько нужно для обработки входящих событий.

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

Создание пользователя

После регистрации в AWS нужно создать пользователя и назначить ему права доступа к сервисам.

Все инструкции вы найдёте в документации AWS Identity and Access Management «IAM Users». Сохраните файл с ключами — они потребуются при настройке CLI.

Только что созданный пользователь не имеет никаких прав. Чтобы он получил доступ к созданию и запуску функций Lambda, нужно назначить ему соответствующую политику. В разделе «Working with Policies» описано как добраться до списка политик. Нам нужно добавить для пользователя политику «AWSLambdaFullAccess». Позже аналогично можно будет добавить или удалить другие политики.

AWS Command Line Interface

Почти все функции AWS доступны через веб-интерфейс, но на практике удобнее использовать консольные команды или скрипты.

Инструкции по установке утилит вы найдёте на странице «AWS Command Line Interface». После того как вы проделаете все описанные там действия, запустите команду:

aws configure

и укажите ключи пользователя, которые вы получили при его создании. Я так же выбрал регион по-умолчанию «eu-central-1». В разных регионах доступны разные наборы сервисов.

Создание функции AWS Lambda

Для начала, напишем простой скрипт, который будет возвращать строку "Hello, world!". Так же в логах мы увидим с какими параметрами вызывалась функция.

'use strict';

exports.handler = (event, context, callback) => {
  console.log(JSON.stringify(event, null, 2));
  callback(null, 'Hello, world!');
};

Сохраним этот код в файл index.js.

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

Перед тем как загрузить функцию в AWS, нужно создать роль для неё. Если у вас уже существует подходящая роль, то этот шаг можно пропустить.

aws iam create-role \
  --role-name MyFirstLambda-Execution \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }]
  }' \
  --output text \
  --query 'Role.Arn'

В ответ вы получите строку вида:

arn:aws:iam::000000000000:role/MyFirstLambda-Execution

Так как наша функция будет вести логгирование, то нужно добавить ей соответствующую политику. Иначе лог не сохранится в CloudWatch.

aws iam attach-role-policy \
  --role-name MyFirstLambda-Execution \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Вот теперь можно загрузить нашу функцию в Lambda.

zip - index.js | \
aws lambda create-function \
  --function-name MyFirstLambda \
  --runtime nodejs4.3 \
  --role arn:aws:iam::000000000000:role/MyFirstLambda-Execution \
  --handler index.handler \
  --zip-file fileb:///dev/stdin

Код функции будет архивироваться и загружаться в AWS без создания временных файлов. Так же код можно загружать через S3.

В параметре --handler указываем точку входа — название модуля (index) и метод, который экспортирует модуль.

В параметре --role указываем роль, которую мы создали для запуска функции.

Выполнение функции

Запустить функцию можно из терминала

aws lambda invoke \
  --function-name MyFirstLambda \
  /tmp/out.json && cat /tmp/out.json

Результат сохранится в файл /tmp/out.json, содержимое которого мы затем выведем в терминале.

aws lambda invoke \
  --function-name MyFirstLambda \
  --payload '{"foo":1,"bar":true}' \
  /tmp/out.json

Обновление кода и конфигурации функции

Обновляется код функции аналогично тому как она создавалась.

zip - index.js | \
aws lambda update-function-code \
  --function-name MyFirstLambda \
  --zip-file fileb:///dev/stdin

Если нужно обновить какие-то параметры конфигурации, то это делается командой update-function-configuration.

Заключение

С AWS Lambda можно легко получить масштабируемое окружение.

На практике функции вызываются по какому-либо событию. Например, запуск по расписанию, обновление таблицы в DynamoDB, появление файла в S3 или при поступлении HTTP-запроса в сервис API Gateway.

В свою очередь, код Lambda-функции может взаимодействовать с другими серверами AWS, внешними базами данных и веб-ресурсами.

Оставте свой комментарий

Конференции, на которых я был в 2015 году

Для меня сезон выступлений на конференциях начался только в мае.

В Москве в рамках фестиваля РИТ в этом году проходила большая конференция FrontendConf. Для неё я подготовил доклад «Пакуйте чемоданы. Грузите апельсины» (слайды). Модульная структура JS-приложения и инструменты для сборки уже стали признаком хорошего тона. Я сделал обзорный доклад, в котором рассказал о текущем состоянии технологий и инструментов для работы с JS-модулями.

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

Практически в то же время, когда я подавал заявку на FrontendConf, меня пригласили выступить на UWDC с докладом «От простого к сложному» (слайды). В этом году уклон всей секции был сделал на разработку под мобильные устройства.

Пикантность ситуации заключалась в том, что в четверг и пятницу я был в Москве на FrontendConf, а в субботу уже должен был быть в Челябинске на UWDC. В тщательно продуманный план закралась неопределённость в виде московского метро. Я опоздал на нужный мне рейс аэроэкспресса. Следующий прибывал в аэропорт уже впритык ко времени посадки. Я зарегистрировался через приложение, но у меня не было распечатанного посадочного талона.

Несмотря ни на что, я успел на свой рейс! Эта ситуация меня ещё раз научила тому, что не стоит опускать руки, если ты в силах ещё что-то изменить.

Летом я выступал на митапах ChellyJS в Челябинске и 4front в Минске с докладом «Переходи на HTTPS!». В своё время меня очень заинтересовала эта тема. Казалось, что настройка безопасного соединения подвластна только бородатым сисадминам. Однако, на практике это совсем не так.

Встреча 4front для меня стала особенной из-за того, что я первый раз приехал в Минск. А ещё этот день совпал с днём моего рождения. Огромное спасибо ребятам, с которыми мы душевно посидели в кафешке после докладов.

В сентябре в Екатеринбурге уже традиционно проходит большая конференция FrontTalks. Мне очень нравится формат и подготовка этого мероприятия. Я всегда с удовольствием приезжаю на неё с каким-нибудь докладом. В этом году одним из трендов в веб-разработке стал webpack. В отличии от обзорного доклада, который я делал на FrontendConf, тут я решил рассказать о том, как именно webpack позволяет упростить некоторые рутинные задачи. Мой доклад «Глубокое погружение в webpack» доступен в записи или слайдах. Ведущие так тепло представили меня, что я даже растрогался и немного позабыл о чём хотел рассказать.

Кроме этого, весной я был на DUMP. Организаторы FrontTalks делают там секцию, просвещённую фронтенд разработке. Как и любая многопоточная конференция, она хороша тем, что всегда можно найти тему, которую было бы интересно послушать. С другой стороны, очень часто такие интересные доклады пересекаются.

А в октябре я уже четвёртый год подряд езжу на Fronteers в Амстердам. В этот раз я вёл текстовую трансляцию мероприятия для сообщества «Веб-стандарты». Логи доступны на Гитхабе — первый день, второй день. (Примечание: кликайте по ссылкам на pic.twitter.com — там очень часто я прикреплял несколько картинок, но выгрузились только первые из них).

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

Комментарии к заметке: 2