Привет, Хабр! Меня зовут Артем Желтак, я teamlead, а также преподаватель курса “Разработчик Golang” в OTUS. В преддверии старта нового потока курса, хочу поделиться с вами своей авторской статьей.


Я верю, что Golang прекрасен, но в мире еще много php и других проектов работающих на VPS, VDS. Можно поставить туда докер, но это (по мнению автора) переусложнение задачи. Можно компилировать файлик и загружать по FTP — небезопасно и не по феншую, SFTP — безопаснее, но снова не феншуй. Тогда давайте автоматизируем этот процесс через CircleCI. Мы будем по шагам писать файл конфигурации для CI, в конце соберем результат и запустим deploy.




Требования к реализации


  1. Минимальные нововведения на сервере
  2. Deploy должен быть автоматизирован
  3. Входная точка для организации deploy — выставления тега для dev сборки и дополнительное ручное подтверждение для prod
  4. Сборка должна пройти автоматическое тестирование
  5. Механизм ручной откатки версии

Почему СircleCI?


На проекте с самого начала использовали приватный репозиторий bitbucket. (Сейчас приватные репозитории уже есть в гитхабе.) Не уходя из экосистемы Atlasian решили взять CircleCI (далее CI). Понравилось, что:

  • минимальная настройка
  • возможность debug по ssh
  • бесплатная версия, но с ограничениями
    • 2500 кредитов/в неделю (примерно 250 минут выполнения) #go собирается и деплоится быстро, нам хватит
    • однопоточное исполнение #у нас не так много pet project
    • только linux и windows #нам нужен linux

Часть первая, workflow


Создадим папку .circle и в ней создадим файл config.yml и опишем там ожидаемый workflow ( порядок исполнения задач)

    workflows:
      version: 2
      tagged-build:
        jobs:
          - test
          - dev_deploy:
              requires:
                - test
          - approve_master_deploy:
              type: approval
              requires:
                - test
                - dev_deploy
          - prod_deploy:
              requires:
                - dev_deploy
                - approve_master_deploy

Вот результат:

image

Мы описали паттерн, по которому каждый комит будет сначала проверяться тестами, далее выкатываться на dev, затем при ручном подтверждении отправляться на боевой сервер. Для наведения лоска добавим фильтр, что бы задача срабатывала только по тегу.

- dev_deploy:
    requires:
      - test
    filters:
      branches:
        ignore: /.*/
      tags:
        only: /.*/

Второй шаг, самый простой


Начнем с запуска тестов, тут будет минимум кода.

jobs:
  test:
    docker:
      - image: circleci/golang:1.12
    working_directory: ~/go-example/
    steps:
      - checkout
      # тут будут linter'ы и тд
      - run: go test -cover -v ./...

После того как наш код протестирован и прошел code style проверки, можно делать deploy на dev. Я предлагаю использовать для запуска go сервиса supervisor (ver 3.1.4 на момент написания статьи), логи будем собирать им же.

Добавим в папку .circleci файлик supervisor_ph.conf, в рамках CI PH_NAME будет меняться на имя проекта. И в такой же файл будем писать вывод логов.

[program:PH_NAME]
stopasgroup=true
user=deploy-user
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/PH_NAME.log
stderr_logfile=/var/log/supervisor/PH_NAME.log
redirect_stderr=true

Всё, что отличает наш проект от других:



Время Deploy


Для dev и prod изменяются только сервера и к названию приложения добавляется суффикс. Конфиг хранится в переменных окружения. (12 факторные приложения) Эту часть мы вынесем в environment, остальное продублируем.

prod_deploy:
  environment:
    TARGET_IP: 0.0.0.0
    TARGET_DIR: /var/www/deploy-user/go-example
    REMOTE_USER: deploy-user
    SERVICE_NAME: go_example_prod
  docker:
    - image: circleci/golang:1.12
  working_directory: ~/go-example/
  steps:
    - checkout
    - add_ssh_keys #копируем добавленные в ci ключи, о них позже
    - run: go build -ldflags "-X main.version=$CIRCLE_TAG" -o ./main ./src/main
    - run: ssh -o "StrictHostKeyChecking=no" $REMOTE_USER@$TARGET_IP "mkdir $TARGET_DIR/v$CIRCLE_TAG" #создаем папку по номеру тега, где будут лежать исполняемые и вспомогательные файлы
    - run: scp main $REMOTE_USER@$TARGET_IP:$TARGET_DIR/v$CIRCLE_TAG/ #загружаем исполняемый файл в директорию по тегу
    - run: sed "s/PH_NAME/$SERVICE_NAME/g" .circleci/supervisor_ph.conf > .circleci/$SERVICE_NAME.conf
    - run: echo command=$TARGET_DIR/v$CIRCLE_TAG/main >> .circleci/$SERVICE_NAME.conf
    - run: scp .circleci/$SERVICE_NAME.conf $REMOTE_USER@$TARGET_IP:$TARGET_DIR/v$CIRCLE_TAG/
    - run: ssh $REMOTE_USER@$TARGET_IP "ln -sf $TARGET_DIR/v$CIRCLE_TAG/$SERVICE_NAME.conf /etc/supervisord.d"
    - run: ssh $REMOTE_USER@$TARGET_IP "supervisorctl  -c /etc/supervisord.conf reread && supervisorctl -c /etc/supervisord.conf update"
    - run: curl "$TELEGRAM_SERVICE?msg=$SERVICE_NAME%20v$CIRCLE_TAG%20deployed&channel=go_deploy"

Для уведомлений мы используем собственного бота, который вызывается через curl. Команда `when: on_fail` срабатывает если что-то пошло не так, ее можно также использовать для отката изменений. Хотя у нас это телеграмм бот, но в целом можно обойтись без него и использовать стандартные нотификации: Slack,IRC. Плюс уведомления об ошибках уходят на email.

Переменную `$TELEGRAM_SERVICE` добавим через раздел BUILD SETTINGS>Environment Variables.

- run:
    command: curl "$TELEGRAM_SERVICE?msg=$SERVICE_NAME%20v$CIRCLE_TAG%20failed&channel=go_deploy"
    when: on_fail

Финишная прямая


Делаем push в github или в bitbucket. После идем в CircleCI в пункт Add project



Затем выбираем Start building. Финальным шагом будет добавления ssh ключа для авторизации на сервере под выбранным пользователем.



Всё можно делать deploy, ставим tag и начинаем радоваться жизни. Финальный вариант ./.circleci/config.yml — тут