Предисловие


Эта статья является результатом недельного поиска весьма разрозненной информации о том, как же настроить деплой web-сервиса на Go. Не на Heroku, не на Docker, не на Digital Ocean, а просто на старомодный VDS с CentOS 7x64. Почему-то в сети нет этой информации, а большинство туториалов начинаются с того, как настроить билд, и заканчиваются запуском тестов.

Сразу предупрежу, что впервые настраивал CI/CD процесс, так что это статья от новичка новичку.

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

Исходные данные


  • VDS сервер
  • ОС: CentOS 7x64
  • Проект на go со следующей структурой:

src/
    public/
         index.html
    main.go

Настройка сервера: Создаем сервис


Сначала создадим сервис для нашего приложения. В CentOS 7 это делается довольно просто. Нужно написать вот такой скрипт в файле под названием serviceName.service:

[Unit]
# Описание сервиса
Description=Service Description
After=network.target

[Service]
Type=simple
# Имя пользователя, от имени которого будет запускаться сервис
User=username
# Путь до сервиса
ExecStart=/username/service/binaryFile
Restart=on-abort


[Install]
WantedBy=multi-user.target

Сам скрипт необходимо положить в папку etc/systemd/system/

Настройка SSH


На сервере запускаем команду:

ssh-keygen -f /etc/ssh/hmp.key

На просьбу
Enter passphrase (empty for no passphrase)
не вводим пароль, просто нажимаем на Enter.

В папке /etc/ssh/ сгенерируется два файла:

  1. hmp.key — приватный ключ
  2. hmp.key.pub — публичный ключ

Нам нужен приватный ключ. Просматриваем его содержимое с помощью команды:

cat /etc/ssh/hmp.key

Оно будет выглядеть примерно так:

-----BEGIN RSA PRIVATE KEY-----
{Здесь сам по себе ключ}
-----END RSA PRIVATE KEY-----

Все полностью копируем себе в буфер обмена

ВНИМАНИЕ! — не только сам ключ, но и
-----BEGIN RSA PRIVATE KEY----- и -----END RSA PRIVATE KEY-----

Настройка Gitlab


Сначала заполним данные, важные для деполя, (имя пользователя, пароль и.т.п.).
Даже если Ваш репозиторий публичный, они останутся закрытыми.

В Gitlab в репозитории заходим в Settings --> CI/CD --> Variables. Создаем там следующие переменные:

  • SSH_PRIVATE_KEY — сюда вставляем значение, скопированное в предыдущем пункте
  • USER_PASS — пароль пользователя, из под которого будет запускаться приложение
  • USER — имя пользователя, из под которого будет запускаться приложение
  • HOST — адрес вашего VDS
  • TARGET_DIR_ON_HOST — целевая папка, в которой будет находиться ваш сервис в моем примере это /username/service/
  • SERVICE_NAME — имя сервиса
  • GROUP_NAME — имя вашего пользователя на Gitlab
  • REPOSITORY_NAME — название вашего репозитория

Добавляем в репозиторий файл .gitlab-ci.yml со следующим содержимым:

image: golang:latest

before_script:
# Гитлаб скачивает себе утилиту sshpass 
  - apt-get update -qq && apt-get install -y -qq sshpass
# Скачиваем зависимости, необходимые для сборки проекта. Можно использовать govendor, но его нужно отдельно настраивать
  - go get github.com/gorilla/mux
  - go get github.com/gorilla/websocket
# Настраиваем SSH
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  - eval $(ssh-agent -s)
  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
  - mkdir -p ~/.ssh
  - chmod 700 ~/.ssh
  - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
# Создаем папку для репозитория  
  - mkdir -p /go/src/gitlab.com/$GROUP_NAME
# Клонируем в неё репозиторий
  - git clone git@gitlab.com:$GROUP_NAME/$REPOSITORY_NAME.git /go/src/gitlab.com/$GROUP_NAME/$REPOSITORY_NAME
# Создаем папку под готовый билд
  - mkdir -p $CI_PROJECT_DIR/build/
# Копирем в папку с билдом все ассеты (картинки, HTML-файлы и.т.п) из репозитория.
# У меня они лежат в папке src/public   
  - cp -r $CI_PROJECT_DIR/src/public $CI_PROJECT_DIR/build
  
stages:
    - build
    - deploy

compile:
    stage: build
    script:
# Переходим в папку с Go файлами
    - cd /go/src/gitlab.com/$GROUP_NAME/$REPOSITORY_NAME/src
# Вызываем в ней сборку. Указываем, что готовый бинарник будет положен в папку build рядом с ассетами и будет называться main
    - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/build/main
    artifacts:
      paths:
        - $CI_PROJECT_DIR/build/main

deploy:
    stage: deploy
    script:
# Переходим в папку с билдом () там лежит бинарник и папка public
    - cd $CI_PROJECT_DIR/build
# Используем утилиту sshpass для удаленного выполнения команд на VDS     
    - sshpass -V
    - export SSHPASS=$USER_PASS 
# Останавливаем сервис
    - sshpass -e ssh -o stricthostkeychecking=no $USER@$HOST systemctl stop $SERVICE_NAME
# Копируем новые файлы
    - sshpass -e scp -o stricthostkeychecking=no -r . $USER@$HOST:$TARGET_DIR_ON_HOST
# Перезапускаем сервис
    - sshpass -e ssh -o stricthostkeychecking=no $USER@$HOST systemctl restart $SERVICE_NAME

После донастройки, этот скрипт пушим в репозиторий и у нас оказывается готовая сборка и деплой. На этом все!

Надеюсь статья была полезной. По любым вопросам и замечаниям с радостью отвечу в комментариях!

Комментарии (12)


  1. andreymal
    23.08.2018 21:01

    Эм, я правильно понимаю, что отключены чуть ли не все средства безопасности SSH?

    Ещё я не понял, зачем применяются одновременно SSH_PRIVATE_KEY и USER_PASS


    1. Indermove Автор
      24.08.2018 11:44

      По первому вопросу, если честно не отвечу, поскольку не знаю, какие есть и как их включить, если подскажете, то я попробую решить этот вопрос и обновить статью.

      По второму вопросу. Там смотрите есть два сервера — первый это тот который находится в Gitlab и SSH_PRIVATE_KEY — это его ключ, он нужен чтобы мы могли проводить над ним разные манипуляции (создавать папки, клонировать репозиторий и т.п.). И второй — это пароль от сервера куда мы деплоим.


      1. andreymal
        24.08.2018 11:54

        какие есть и как их включить

        stricthostkeychecking=no это явное отключение, например


        разные манипуляции (создавать папки, клонировать репозиторий и т.п.)

        А разве гитлаб не клонирует репозиторий для CI самостоятельно? Когда я гонял тесты на GitLab CI, я обходился без этого


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

        А вот тут как раз и нужно использовать ключи, а не пароль. И вообще пароли в SSH в идеале никогда не нужно использовать


        1. Indermove Автор
          24.08.2018 12:15

          А разве гитлаб не клонирует репозиторий для CI самостоятельно? Когда я гонял тесты на GitLab CI, я обходился без этого

          У меня так не получилось, если честно( Если запускать билд и тесты, то да, без этого можно обойтись, но если нужно деплоить, то я не знаю как без этого и нигде не написано, можно ли обойтись без этого.

          stricthostkeychecking=no это явное отключение, например

          К сожалению с включением не работает, собирал это место скрипта из разрозненных ответов, подобно вот этому: stackoverflow.com/a/47536235/10239772
          А также из ответа мне в toster от одного из товарищей toster.ru/q/555101

          Вопрос, а чем опасно отключение этой настройки в данном случае? Почему ключи SSH безопаснее паролей?


          1. andreymal
            24.08.2018 18:34

            подобно вот этому

            Ну по этой ссылке делается git push с тегом, там все эти манипуляции действительно необходимы


            К сожалению с включением не работает

            Потому что, наверно, ssh ругался, что он не знает публичного ключа гитбала, а вы вместо добавления ключа в доверенные просто отключили проверку ключей и открыли дорогу MITM-атакам :) (хотя, конечно, в инфрастуктуре гитлаба MITM-атака, наверно, очень маловероятна, но всё равно подобные отключения мне как-то не очень нравятся)


            Почему ключи SSH безопаснее паролей?

            Я не знаю всех тонкостей аутентификации SSH, и на этот вопрос какие-нибудь спецы могут ответить лучше чем я (если они тут есть)


        1. rusbaron
          24.08.2018 18:17

          а можете мне скинуть пример gitlab-ci.yml файла для прогонки тестов на Go? Где не видел, везде делали go get проекта, а не пользовались уже тем, что CI склонировал


          1. andreymal
            24.08.2018 18:22

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


            В этом моём gitlab-ci.yml pip install -e . — установка проекта из текущего каталога, где текущим каталогом является как раз склонированный репозиторий


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


  1. hazratgs
    23.08.2018 21:28

    В последнее время только у меня проблемы с CI/CD GitLab? постоянно ошибка:
    ERROR: Job failed (system failure): error during connect: Get https://***.**.**.**:2372/v1.18/containers/******/json: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "root") (executor_docker.go:965:2s)


    1. Indermove Автор
      24.08.2018 11:44

      У меня такого нет, конкретно сейчас все работает нормально


  1. alexstup
    24.08.2018 00:48

    Стоп стоп стоп
    Создание приватного ключа на сервере, кто же так делает?


    1. Indermove Автор
      24.08.2018 11:45

      Хм, это интересно! В этом я пока новичок, а где это рекомендуется делать? И почему на сервере небезопасно?


  1. ashumkin
    25.08.2018 13:59

    Cуть CI/CD для абстрактного проекта такова:
    1. вы пушите код в репозиторий
    2. сервер сборок (CI-сервер) (либо по триггеру от шага 1, либо проверяет сам наличие изменений и если они есть) клонирует репозиторий (точнее даёт команду нужному «агенту сборки» ) и запускает некие этапы сборки (обычно это `build — test — deploy`), которые указаные в конфигурации
    3. на этапах могут появляться артефакты сборки, которые могут быть доступны в веб-интерфейсе и передаются между этапами (например, после build появляется некий (раз у вас CentOS) RPM-пакет, который на этапе deploy, устанавливается на целевые системы.

    Касательно, GitLab:
    1. Конфигурация сборки — это .gitlab-ci.yml файл. Он ОБЫЧНО должен быть в репозитории того приложения, которое деплоится, т.к. только он может быть нестандартным. В общем, каждый репозиторий сам должен «знать» как себя деплоить.
    2. При запуске pipeline-а проект (если брать настройки по умолчанию: см. Git strategy) КЛОНИРУЕТСЯ на runner-е. Runner — это «агент сборки», на котором, собственно и происходит этап (выбирается из списка доступных по tag-у). Так что ваши действия по клонированию репозитория в before_script — лишние, если этот .gitlab-ci.yml лежит в том же репозитории, что и проект. GiLab Runner сам всё делает.
    3. ставить приложение, залезая на сервер, добавляя там файлик сервиса и копируюя в каталоги свои файлы — это кустарщина. Если хотите «по-взрослому», то на этапе build у вас после компиляции Go-бинарника, должен собраться RPM-пакет, который устанавливается уже на любой сервер (а не только на тот, который вы предварительно залезете и ручками его настроите). Этот RPM и будет содержать все необходимые файлы (включая файл ServiceName.service и все другие). Кроме того, деплой приложения (если на другой сервер) сведётся к
    rsync/scp app.rpm <remote-server>:/tmp && ssh <remote-server> yum/dnf install /tmp/app.rpm. такой же простой будет откат, если вдруг новая версия сервиса оказалась непригодной.
    4. На каком runner-е у вас запускается pipeline? не на том же VDS-е случаем? тогда свистопляски с SSH — не нужны.
    5. Преимущество SSH-ключей перед паролями в том, что это: безопасно (гуглите «асимметричное шифрование»), и легко автоматизируется, т.к. не требует `sshpass`, а уже «встроено» в ssh: ssh -i /another/private.key
    6. к слову у GitLab-а есть deploy keys, так что не надо раскрывать генерировать новые ключи или раскрывать свой пароль для доступа к репозиторию
    7. пока писал, увидел, что сборка происходит в docker-контейнере (из-за чего и клонируется в него репозиторий?), тогда подключите volumes и всё написаноне мной выше — остаётся в силе )))))

    З.Ы. Know your tools…