Автоматический деплой из Тревиса

Между делом я поддерживаю на плаву сервер сообщества Веб-Стандарты. Среди прочего там у нас расположен сайт проекта Web Standards Days. Сайт простой, голая статика, но собирался и деплоился вручную. Нет, мы пробовали генераторы статики, и даже писали свой — но не взлетело. И вот что-то подустал Вадим руками ещё и деплоить, решили попробовать автоматизировать деплой через Тревис (в dev.opera.com так сделано, например). Оказалось, что в этом нет ничего сложного.

Деплой у нас происходит крайне примитивно: файлы проекта прогоняются через пачку gulp-тасков и получается набор из html, css и картинок. Всё это через ещё один gulp-таск (на самом деле это просто обёртка над rsync) отправляется на сервер. Ревизий, роллбека и вот этого всего, что есть в капистрано у нас нет, да и не очень нужно — всё же в гите лежит. А вот чтобы положить всё на сервер, нужно туда залогиниться. Логин только по ssh-ключу и встаёт проблема — как положить приватный ключ в тревис?

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

Шифрование ключа

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

mkdir .travis
ssh-keygen -f .travis/deploy_key

Ключ -f создаёт файлы с нужным нам именем, без дополнительного вопроса со стороны генератора, каталог .travis просто для порядка в репозитории, и его нужно создать до вызова ssh-keygen.

Теперь самое вкусное — шифруем ключ. Для работы нужно установить CLI для Тревиса, если он ещё не установлен. Вот ссылка на руководство по установке.

travis encrypt-file .travis/deploy_key .travis/deploy_key.enc --add
  • Первый аргумент — путь к исходному файлу, второй — путь к зашифрованному файла. Если не указать, куда класть шифрованный файл, то тревис положит его в текущий каталог.
  • Ключ --add сразу добавляет нужную команду для расшифровки в .travis.yml.

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

Файл .travis/deploy_key.enc нужно добавить в репозиторий, а вот исходный deploy_key — ни в коем случае. Лучше его вообще удалить. Публичный ключ нужно добавить в ~/.ssh/authorized_keys на сервере, в тот аккаунт, от имени которого будет выполняться деплой. В нашем случае это было так:

ssh-copy-id -i .travis/deploy_key.pub travis@web-standards.ru

Утилита ssh-copy-id для мака берётся из homebrew, на линуксах она есть сразу.

Скрипт деплоя

Как было указано выше, вызов утилиты travis с ключом --add добавил в .travis.yml команду для расшифровки ключа на билд-машине, но на мой взгляд внутри Yaml всё это выглядит очень страшно. Кроме того, нам нужно не только расшифровать ключ, но и выполнить пару дополнительных команд, поэтому создадим ещё один служебный файл в нашем каталоге — .travis/set-up-ssh. Файлик нужно сразу сделать исполняемым:

chmod a+x .travis/set-up-ssh

Чтобы получить возможность подключиться по ssh с билд-машины, нужно чтобы наш ключ был опознан как ssh-ключ, для этого нужно поменять права доступа к файлу ключа и переименовать его. Если нужны дополнительные параметры ssh-соединения — укажем их в файле ~/.ssh/config. В итоге у нас получился вот такой файл настройки ssh для деплоя.

#!/usr/bin/env bash
openssl aes-256-cbc -K $encrypted_123456789dfs_key -iv $encrypted_123456789dfs_iv -in ./.travis/deploy_key.enc -out ${TRAVIS_BUILD_DIR}/.travis/deploy_key -d
chmod 600 ${TRAVIS_BUILD_DIR}/.travis/deploy_key
mv ${TRAVIS_BUILD_DIR}/.travis/deploy_key ~/.ssh/id_rsa
cat ${TRAVIS_BUILD_DIR}/.travis/ssh_config >> ~/.ssh/config
  1. shebang, куда без него;
  2. расшифровка ключа. Для того, чтобы не было проблем с путями к файлам, используем переменную $TRAVIS_BUILD_DIR, подробнее про доступные в тревисе переменные на можно прочитать в документации;
  3. Меняем права у файла, иначе ssh откажется его читать;
  4. Кладём ключ под стандарным именем, можно указать путь в ~/.ssh/config, но как-то лень;
  5. Дописываем нужные нам параметры в ~/.ssh/config, например, имя пользователя, под которым будем логиниться на сервер.

Рассказываем тревису, как деплоить

Теперь, когда у нас есть ключ и скрипт настройки ssh, можно дописать в .travis.yml правила, по которым будет деплоиться. С недавнего времени в тревисе появилась отдельная стадия, которая так и называется deploy. Эта стадия выполянется только после успешного прохождения тестов.

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

before_deploy:
    - .travis/set-up-ssh
deploy:
  skip_cleanup: true
  provider: script
  script: npm run deploy
  on:
    branch: master

Также, как и для npm-скриптов, для этапов жизненного цикла процесса в тревисе есть pre- и post-хуки. Мне они прямо вот очень нравятся, сразу становится понятно, что тут важно, а у чего более утилитарная роль. У нас перед деплоем выполняется настройка ssh.

В секции деплоя мы говорим тревису не очищать каталог после прогона тестов (там как раз вся наша сборка, зачем ещё раз всё собирать?) и указываем, что нужно выполнить нашу кастомную команду (provider: script), команда для деплоя максимально простая — npm run deploy, так что в любой момент мы можем соскочить с галпа на что-то другое. Хоть обратно на make-файлы. Ну и естественно, авто-деплоимся мы только из ветки мастер.

Итого

Теперь, как только вы увидите опечатку на нашем сайте — вы можете прислать нам пулл-риквест, и в течение пары минут после его принятия фикс будет в продакшне. Обратная сторона простоты процесса — деплой будет происходить при любом пуше в master, даже если в коммите меняется что-то, не имеющее отношения к сайту — например, Readme.md или .gitignore, но тут нам помогает rsync, который отправить на сервер только то, что действительно должно быть на сервере.

Порт 8080 на MacOS

Уж не знаю почему (хотя есть догадки) каждый второй туториал по веб-приложениям пытается повеситься на порт 8080. Всё бы ничего, но по умолчанию этот порт слушает nginx, если он был установлен через Homebrew. Как результат — ошибки в консоли примерно такого вида:

Error: listen EADDRINUSE 127.0.0.1:8080

Мне это в конце концов надоело и я попросил nginx больше не прослушивать этот порт. Вообще, это стоит делать сразу после установки пакета, наверно. Для этого открываем файл usr/local/etc/nginx/nginx.conf с правами супер-пользователя и закоментируем секцию, начинающуюся с этих строк:

server {
    listen 8080;
    server_name localhost;

Проверяем конфигурацию и перезапускам nginx

sudo nginx -t
nginx -s reload

Разработчика туториалов же рекомендую писать чуть более сложный скрипт старта приложения и выбирать свободный порт из определённого диапазона.

Типичный зимний велосипедист

В пятницу, 12 февраля случился международный зимний день поездок на работу на велосипеде. Питер даже какое-то время был на третьем месте, но потом его обошла школота из Оулу.

По случаю праздника многие заведения города предлагали разные ништяки для велосипедистов (в основном, кофе). Всё это было сведено в публичную гугл-табличку, а вот карты точек не было. Ну я и воспользовался моментом — создал свою.

Естественно, работа была проделана не бескорыстно — на карте стоял счётчик аналитики. И теперь я представляют вам портрет среднестатистического петербуржца, который по зиме катает на велике.

Много ли таких? На сайте мероприятия зарегистрировалось 463 человека. Мою карту посмотрели 109 уников. Немного, для пятимилионного города. У победителя, Загреба, 842 человека на 790 тысяч жителей.

Посетителей в возрасте до 18-35 лет составили около 87%, при этом больше половины из них — старше 25 лет, так что ехали действительно на работу, а не в институт. Девушек, кстати, довольно много — больше четверти. При этом около 30% пришли со смартфона, боюсь, что им было не очень удобно, особенно на морозе.

Вот, кстати, моя фоточкапо пути на работу. В шлеме и очках.

A photo posted by Mikhail Baranov (@mkhlbrnv) on

Клонирование из GitHub в WebStorm

В WebStorm и все прочие IDE от JetBrains встроена интеграция с гитхабом. Например, можно клонировать репозиторий и создать проект не вводя лишних данных. Но, к сожалению, по умолчанию используется клонирование через https  – этот способ более универсален, но очень неудобен, если для работы с VCS используется не только gui, но и консоль. В этом случае терминал на каждый чих будет просить ввести пароль (в WS пароль от GH не спрашивается, если вы его сохранили в кейчейн или используете API Token).

Однако, это очень легко исправить: заходим в Preferences > Version Control > GitHub и отмечаем галочкой «Clone git repositories using ssh». Всё, теперь новые репозитории будут клонироваться правильно.

2015-12-24 09-34-54 Default Preferences

Вагрант и каталог, который не хотел удаляться

В одном из фриланс-проектов используется Vagrant, а я недавно поставил свежий El Capitan.

Клонирую я, значится, проект и делаю в нём $ vagrant up —provision. Что-то там крутится, пыхтит, выплёвывает в консоль кучу всякой информации, а потом — умирает. Ну, думаю, ладно. Обновлю-ка, для начала, всё. Сношу виртуальную машину, качаю последние стабильные VirtualBox и Vagrant, устанавливаю, запускаю…

А там сайт-то простой, на php. И всё ставится через composer install. И вот что-то этот Бетховен (см. на лого композера) снова падает с ошибкой, что он не может удалить какой-то там каталог в процессе установки. Ну, думаю, ладно. Нахожу этот каталог на хост-машине — он прекрасно удаляется. Но композер всё равно сходит с ума и не работает.

В общем, где-то час я занимался удалением каталогов на хост-машине и изнутри виртуалки, чистил ~/.composer, убивал и пересоздавал виртуалку — всё без толку. Если зайти через $ vagrant ssh на виртуалку и попробовать удалить каталоги там, то падает с ошибкой «cannot remove `/home/vagrant/project/vendor/packageName’: Is a directory». А если сделать «ls», то получаем следующую картину:

vagrant@vm:~$ ls -l project/vendor
ls: cannot access project/vendor/packageName: No such file or directory
total 0
?????????? ? ? ? ?            ? packageName

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

И тут, во время очередного перезапуска виртуалки, я увидел ворнинг в консоли:

=> default: Checking for guest additions in VM…
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 4.1.12
    default: VirtualBox Version: 4.3

А дай-ка, думаю, обновлю эти самые Guest Additions, ибо отстают они аж на два минорных релиза. Но тут надо заметить, что эта штуку нельзя поставить из виртуалбокса для для headless-вируалки. Поэтому воспользовался вот этим гистом, модифицировав его под себя.

$ vagrant ssh
vagrantup:~$ cd /opt
vagrantup:~$ sudo wget -c http://download.virtualbox.org/virtualbox/4.3.30/VBoxGuestAdditions_4.3.30.iso \
                       -O VBoxGuestAdditions_4.3.30.iso
vagrantup:~$ sudo mount VBoxGuestAdditions_4.3.30.iso -o loop /mnt
vagrantup:~$ cd /mnt
vagrantup:~$ sudo sh VBoxLinuxAdditions.run —nox11
vagrantup:~$ cd /opt
vagrantup:~$ sudo rm *.iso
vagrantup:~$ sudo /etc/init.d/vboxadd setup
vagrantup:~$ sudo apt-get install chkconfig #не уверен, что это нужно
vagrantup:~$ sudo chkconfig —add vboxadd
vagrantup:~$ sudo chkconfig vboxadd on
vagrantup:~$ exit

Тушу виртуалку, поднимаю, делаю composer install — всё работает. По хорошему, теперь бы надо ещё сделать кастомный package для этого всего, но как-то лень.