пятница, 9 августа 2019 г.

Azure DevOps Pipeline с удаленным выполнением команд по ssh


Недавно я столкнулся с интересной задачей, решение для которой толком нигде не описано. Заключается она в том, что есть у нас Linux VM, на которой надо последовательно выполнить набор команд. Скрипты по некоторым причинам в моем случае использовать не получилось, поэтому пришлось возпользоваться задачами (task) "Bash", которые позволяют запускать Bash скрипты локально или на удаленных машинах. Вроде же всё просто - есть у нас видение, как делать, есть таска. Ну ничего же сложного и необычного, верно? ;) Конечно, нет. Было бы так, то я бы не стал тратить время на написание статьи. Итак, поехали!


Публикация артефакта


Хорошим тоном для любого пайплайна является создание артефакта. Да, можно напрямую каждый раз тягать свежачок из Git, но это не наш метод. По крайней мере - не в данной задаче.

Тут казалось всё очень просто. Так как нам небыло необходимости делать сборку перед релизом, то требовалось просто скачать исходный код из Git, затем опубликовать артефакт таской:


По-сути это пайплайн, состоящий из одной задачи. Тут же не может быть никаких особенностей! Или... может? ;)

Вот я тоже думал, что тут всё предельно просто. Забрали сорцы из Git, опубликовали и забыли. Собственно, так и было, пока я не начал пробовать запускать сборку на сервере в процессе релиза. Там получал очень странную ошибку:

/usr/bin/env: ‘sh\r’: No such file or directory

WTF? Разбираясь, откуда растут ноги у проблемы, я клонировал репозиторий сразу на VM, где должна была проходить сборка и увидел, что размер файлов различается. Если честно, я немного офигел, зато на этом этапе стало всё предельно понятно. Воспользовавшись "cat -v" я увидел, что билд агент поменял кодировку файлов на DOS, что было для меня, мягко говоря, неожиданно:

tema@linby:/tmp$ cat -v JavaAPI/README.md                                                                                                             
### This is my JavaAPI^M

Оказалось проблема в том, что я для публикации артефакта использовал дефолтный агент "Hosted VS2017". Непонятно почему, но так делать было нельзя. После замены его на "Hosted Ubuntu 1604" сразу стало всё собираться хорошо.

Релиз проекта


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

Копирование файлов на удаленную VM

Так получилось, что артефакт у меня относительно большой - около полутора гигабайт. Для его закачки я попробовал использовать стандартную таску "Copy files over SSH":


Но тут оказалось, что есть особенности:

1. Требуется создавать "SSH service connection". Мягко говоря, не самый удобный вариант при условии, что виртуальные машины постоянно новые.
2. Время закачки файлов на VM было чертовски выскоким - минут 20! При условии что конечная машина была в Azure.

В итоге от данного решения пришлось сразу отказаться, я решил выполнить эту задачу с помощью таски "Bash":

######Create folder######
echo "Create folder $(Target_Folder_To_Upload)"
sshpass -p $(VM_Password) ssh -o StrictHostKeyChecking=no $(VM_User)@$(VM_IP) "rm -rf $(Target_Folder_To_Upload_JavaAPI); mkdir $(Target_Folder_To_Upload)"
######Upload files#######
sshpass -p $(VM_Password) scp -o StrictHostKeyChecking=no -r $(Upload_JavaAPI_Path)/* $(VM_User)@$(VM_IP):$(Target_Folder_To_Upload_JavaAPI)

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

sshpass -p $(VM_Password) - позволяет нам указывать пароль для подключения по ssh. Без него ssh клиент будет запрашивать пароль, поэтому в скрипте подключение работать не будет. Но если у вас есть SSH Key, то ситуация, конечно, будет несколько другая. В моем конкретном случае есть только логин и пароль.

StrictHostKeyChecking=no - автоматически добавляет ключ хоста в ~/.ssh/known_hosts

-r $(Upload_JavaAPI_Path)/* - что надо закачать на удаленный сервер

$(VM_User)@$(VM_IP):$(Target_Folder_To_Upload_JavaAPI) - путь, куда закачиваем.

Эта таска в моем случае отрабатыавет примерно в 20! раз быстрее, чем стандартная, даже не знаю, по какой причине. Ну и плюс в том, что нет необходимости для каждой VM создавать "SSH service connection".


Последованельный запуск команд на удаленной ВМ

Для данной задачи у нас опять же есть дефолтная таска:

Всем она хороша (возможно, я так и не попробовал с ней работать), кроме одного - требуется для каждой VM, с которой работаем, создавать "SSH service connection". Поэтому пришлось снова использовать таску "Bash":


Пример одного из шагов:

sshpass -p $(VM_Password) ssh -o StrictHostKeyChecking=no $(VM_User)@$(VM_IP) "sudo apt-get update; sudo apt-get install default-jre default-jdk gradle  -y"

В данном случае просто устанавливаем пакеты с зависимостями на VM, если их там еще нет.

Раз-раз и в продакшен


Итак, первые проблемы были решены, Azure Pipeline настроен. Состоял он из некоторого количества тасок, которые последовательно выполняли команды удаленно по ssh на VM.  Казалось бы, что может пойти не так. Но проблема появилась, причем плавающая, возникающая каждый раз на новом шаге. Выглядела она как внезапный разрыв ssh соединения со стороны сервера:

ssh_exchange_identification: Connection closed by remote host

При этом в логах sshd на VM я ничего странного не нашел. В интернете предлагали решения, которые вообще не коррелировали с моей проблемой, но натолкнули на мысль, что может быть ошибка из-за того, что агент пытается очень быстро установить новое соединение, после завершения предыдущего. Честно - глубже копать не стал (каюсь) и основываясь на этой гипотезе я просто добавил "sleep 3" в начало каждой таски, которая устанавливала соединение ssh. WIN! проблема решена, костыль в моем случае не проблема вообще:

sleep3
sshpass -p $(VM_Password) ssh -o StrictHostKeyChecking=no $(VM_User)@$(VM_IP) "sudo apt-get update; sudo apt-get install default-jre default-jdk gradle  -y"



Итак, краткий курс по теме "Костыли и велосипеды" завершен. Комментарии, как всегда, приветствуются.



Комментариев нет:

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