Деплой .NET приложений для самых маленьких. Часть 0

Давным-давно, когда Linux был ещё на ядре 2.6, а PHP5 был глотком свежего воздуха, я впервые заинтересовался миром веб-технологий. Читал учебники, статьи, зависал на форумах, но все равно мало мог понять как код, который я вижу на экране, превращается в волшебные сайты с кнопками, формами и анимациями. Узнал про LAMP и его аналоги для Windows, узнал, что, оказывается, есть хостинги, где такие сайты размещаются. Как только появился внешний интернет без трафика, я поспешил перенести свои локальные поделия во внешний мир, попутно узнав про замечательный протокол FTP. Просто мир волшебных открытий был для меня, особенно когда узнал, что не нужно писать свой форум с нуля, а можно использовать что-то из phpBB, vBulletin и других уже готовых движков.

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

И когда я переключился в мир .NET и начал его изучение, перечисленные ранее умения сыграли со мной злую шутку — я долго не мог понять, как мне найти хостинг для .NET приложений. Почему все известные мне хостинги с лёгкостью предоставляли возможность развернуть PHP приложения, причём даже предлагая какие-то предустановленные версии CMS, но днём с огнём не сыщешь хостинг под .NET. Мое непонимание принципа развертывания приложений усугубляли статьи, которые предлагали их размещать в подходящих сервисах типа Heroku, Digital Ocean или Azure — ведь это так просто и дешево… Потом конечно пришло понимание, что такой папки не существует, и приложения вообще не «хостятся» (на то они и приложения), но давайте будем честны — синьоры не рождаются со знанием и пониманием таких вещей, понимание очевидных вещей не всем сразу приходит.

Поэтому, как логичное продолжение прошлой статьи, где мы запускали локально наши приложения, мы перенесём их на внешний сервер и развернём базу данных, используя ssh и git, рассмотрим конкретные примеры через Github, заодно на практике найдём ответы на следующие вопросы, которые, скорее всего, озадачивают новичков, изучающих программирование в .NET. Цикл статей призван раскрыть следующие вопросы:

  • Как перенести код на внешний сервер и запустить его?

  • Как подключить SSL сертификат?

  • Как развернуть свою базу данных?

  • Как безопасно хранить чувствительные данные и использовать их на сервере?

  • Как автоматизировать разворачивание приложений?

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

Итак, начнем с самого начала и самого простого — создадим пустое приложение, используя актуальный .NET 8.0.

dotnet new razor -n SimpleApp
cd ./SimpleApp
dotnet run

После этого запустится браузер по пути, указанному в ./Properties/launchSettings.json. В моем случае это http://localhost:5144. При переходе мы увидим стандартное приветствие.

e25dcdbb8cf3b89a866635dff1ce1e91.png

Допустим, у нас есть доменное имя, и мы хотим теперь сделать приложение доступным по нашему адресу и доступным в режиме 24/7, как это сделать? Для начала нужно арендовать VPS, для нашего простого и пустого приложения подойдет абсолютно любой. В качестве устанавливаемой операционной системы выберите что-нибудь из Debian семейства, например Ubuntu 22.04. После оплаты тарифа вам предоставят доступ к панели управления, где будет указан IP адрес, по которому можно обращаться с выделенной машиной, а также привязать к этому адресу ваш домен.

Я тут укажу очевидное, но поверьте, для начинающих это не всегда очевидно и понятно. По сути, развертывание .NET приложений в какой-то внешней среде, будь то облако, или VPS ничем не отличается от обычного запуска приложения на вашей машине. Как вы запускаете приложение через dotnet run локально (или через IDE, или через docker) — тоже самое надо повторить, но на удаленной машине, предоставляемой провайдером. Вам не нужно какое-то специальное окружение, вам просто нужен компьютер с предустановленной операционной системой, где можно собрать и запустить приложение.
И всё развертывание .NET приложений и деплой сводится к простому переносу кода с вашей машины (или из системы контроля версий) на какую-то внешнюю виртуальную машину. Всё просто.

Тогда возможно у Вас появляется логичный вопрос: «А к чему тогда сложности с Heroku, Azure и другими специализированными сервисами для хостинга .NET приложений? Зачем их тогда использовать, если можно просто «скопировать» код на VPS и запустить?». И, если ответить кратко — то в нашем случае такие сервисы не нужны, как и не нужны для большого количества продакшн решений. Помимо того, что они стоят дороже обычной аренды VPS (если речь про длительное размещение), их основное преимущество — автоматизация развертывания ваших приложений после изменения кода, которое можно настроить путем использования различных инструментов, таких как Jenkins в более сложных задачах, или средствами Github / Gitlab в более простых случаях. Опять же, если интересно подробнее — гуглим CI/CD и все, что с ним связано. Это отдельная обширная тема и рассматривать сейчас ее не будем.

Копирование с использованием SSH

Итак, возвращаясь к нашему основному вопросу, как же скопировать код на арендованный VPS? Есть два способа, как обычно один простой, второй правильный. Начнем с простого, так как для каких-то простых одноразовых запусков его тоже вполне может быть достаточно. Это обычное копирование через ssh вашего проекта на арендованный VPS.

scp -r ~/SimpleApp root@100.92.66.104:/root/

где SimpleApp — папка с Вашим проектом, root — учетное имя, для доступа через ssh к VPS, 100.92.66.104 — адрес VPS, :/root/ — папка на VPS, куда следует скопировать код (указывайте удобный вам).

Тут стоит уточнить, что использовать root — плохая практика, но по умолчанию это единственная запись, которая существует в системе. После создания VPS стоит создать другую учетную запись, разрешить ей обращаться к sudo, отключить возможность удаленно подключаться через root, а также сменить порт с 22 на какой-либо отличный. Это сделать несложно, как именно — можно узнать в гугле или GPT. Если вдруг что-то пойдет не так, то у Вас всегда есть возможность через панель управления виртуализации подключиться через VNC.

После ввода пароля от root, папка с проектом будет скопирована и доступна по пути /root/SimpleApp. Подключаемся к VPS через ssh и проверяем.

ssh root@100.92.66.104
# ls /root/ | grep 'SimpleApp'
SimpleApp

Осталось совсем ничего — перейти в папку и запустить приложение. Для этого достаточно установить необходимую среду запуска для .NET приложений. Как это сделать подробно можно узнать здесь.

sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-8.0

Среда установлена, можем перейти в папку и запустить приложение.

cd SimpleApp && dotnet run

Вывод

Building...
warn: Mi-crosoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {bc671e4e-e40d-4f31-a79e-4badce1e734f} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5144
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /root/SimpleApp

Запуск произошел успешно, а значит наше приложение доступно теперь по адресу локально, проверяем через lynx.

Lynx

lynx localhost:5144
c54c4606c84e60532a0ea5fa9a7f0194.png

К сожалению, по умолчанию извне наше приложение не будет доступно.

netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.1:5144          0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:5555            0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp6       0      0 ::1:5144                :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
tcp6       0      0 :::5555                 :::*                    LISTEN     
udp        0      0 127.0.0.53:53           0.0.0.0:*                          

Чтобы наше приложение было доступно извне по доменному имени или IP адресу, нужно использовать обратный прокси. Для этого отлично подойдет Nginx. Настроить его несложно, а если использовать ChatGPT — то вообще просто. Его нужно поставить, настроить по своему вкусу (в примере переадресация входящих внешних запросов на 80 порт на наш локальный 5144).

Установка и настройка Nginx

Если NGINX еще не установлен на вашем сервере, вы можете установить его, используя пакетный менеджер apt на Ubuntu:

apt update
apt install nginx

Создайте новый файл конфигурации для вашего веб-приложения в директории /etc/nginx/sites-available/ и создайте символическую ссылку на него в /etc/nginx/sites-enabled/, чтобы активировать конфигурацию. В нашем конкретном примере, это веб-приложение, работающее на порту 5144:

nano /etc/nginx/sites-available/mydotnetapp

Добавляем в него следующую конфигурацию

server {
    listen 80;
    server_name asyncnoway.ru; # Замените на ваш домен или IP-адрес
location / {
    proxy_pass http://localhost:5144; # Перенаправление на ваше .NET приложение
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

}

Добавляем ссылку

ln -s /etc/nginx/sites-available/mydotnetapp /etc/nginx/sites-enabled/

Проверяем на наличие ошибок

nginx -t

И если все ок, то перезагружаем сервис

systemctl restart nginx

Теперь NGINX будет принимать входящие соединения на порт 80 и перенаправлять их на ваше .NET веб-приложение, работающее на порту 5144. Можем подключиться по доменному имени или IP (смотря, что вы добавили в /etc/nginx/sites-available/mydotnetapp)

0cef80b275a5829705538f4a4bf8cbb4.png

Лучшее — враг хорошего.
Конечно, такой способ сложно назвать хорошим, и я заочно согласен со всеми возмущенными таким подходом. Однако, если вам надо быстро что-то развернуть и проверить, вы не используете систему контроля версий, то это вполне себе действенный способ. В идеале, лучше, конечно, использовать не dotnet run, так как приложение запускается в Development окружении, а все-таки делать релиз и запускать его в Production окружении. Нужно ли конкретно это Вам — Вам же и решать, как это сделать можете легко найти в интернете.

Копирование с использованием GIT

Все же стоит понимать, что указанный выше способ имеет несколько весомых недостатков. Во-первых, скорость копирования через ssh довольно медленная, а значит с увеличением проекта копирование будет занимать все больше времени. Во-вторых, если проект живой и вы постоянно что-то в нем меняете, то при каждом изменении придется копировать все файлы. Либо копировать только измененные файлы, но надо как-то фиксировать их изменения. Собственно, именно для этого и предназначен git. При использовании системы контроля версий на VPS будут обновляться только измененные файлы, подтягивая обновления через git pull из сервиса хранения кода. Использовать Github или Gitlab — вопрос исключительно вкуса и в контексте нашей задачи между ними лишь одна существенная разница: при попытке клонировать или обновить приватный репозиторий для гитлаба достаточно вести логин-пароль от учетной записи, а для гитхаба такая возможность недоступна из-за соображений безопасности, поэтому придется изгаляться с созданием ключей и их добавлением. Если вы используете публичный репозиторий, то разницы никакой нет. Поэтому в дальнейшем я буду использовать приватный репозиторий Github, но все действия полностью аналогичны и для Gitlab, за исключением добавлением ключа для аутентификации. Приступим.

Самым простым способом будет отправка проекта, используя какую-либо IDE — VSCode, Rider или Visual Studio. В таких системах системах можно раз сохранить токен учетной записи и избегать проблем с доступом при работе с Github, а все взаимодействие будет строиться через нажатие кнопочек, но мы разберем все это через консоль без привязки к какой-либо среде разработки.
Нужно создать приватный репозиторий в Github с именем нашего приложения — для простоты просто на сайте Github. Затем создадим локальный репозиторий, сделаем первый коммит и отправим приложение в удаленный репозиторий.

dotnet new gitignore
Шаблон "файл gitignore dotnet" успешно создан.

git init
Initialized empty Git repository in /Users/vasjen/SimpleApp/.git/
git add . && git commit -m "Init"
git remote add origin https://github.com/vasjen/SimpleApp.git
git push -u origin master
Enumerating objects: 98, done.
Counting objects: 100% (98/98), done.
Delta compression using up to 4 threads
Compressing objects: 100% (94/94), done.
Writing objects: 100% (98/98), 914.56 KiB | 2.34 MiB/s, done.
Total 98 (delta 33), reused 0 (delta 0)
remote: Resolving deltas: 100% (33/33), done.
To https://github.com/vasjen/SimpleApp.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

Загрузка произошла успешно, в нашем удаленном репозитории появился наш проект с нашим коммитом. Теперь для того, чтобы перенести приложение нужно всего лишь клонировать репозиторий через git clone, а после изменения кода их обновить через git pull. Но, как и говорил ранее, поскольку наш репозиторий приватный, он доступен только вам самим по умолчанию, а значит на VPS надо явно указать вашу учетную запись для аутентификации. Переключаемся в консоль VPS

git clone https://github.com/vasjen/SimpleApp
Cloning into 'SimpleApp'...
Username for 'https://github.com': vasjen
Password for 'https://vasjen@github.com': 
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/vasjen/SimpleApp/'

Как видим по логам, просто через ввод связки логин-пароль это сделать невозможно, в отличии от Gitlab. Поэтому, нужно создать SSH ключ и указать его в Github

ssh-keygen -t rsa -b 4096 -C "vasjen@github.com" #замените на имя вашей учетной записи
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub

Выведенный ключ полностью копируем и вставляем в свой профиль Github, в секцию ключей. Указываем удобное имя, ключ помечаем как авторизационный. Пробуем клонировать еще раз через ssh

git clone git@github.com:vasjen/SimpleApp.git
Cloning into 'SimpleApp'...
The authenticity of host 'github.com (140.82.121.3)' can't be es-tablished.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com' (ED25519) to the list of known hosts.
remote: Enumerating objects: 98, done.
remote: Counting objects: 100% (98/98), done.
remote: Compressing objects: 100% (61/61), done.
remote: Total 98 (delta 33), reused 98 (delta 33), pack-reused 0
Receiving objects: 100% (98/98), 914.56 KiB | 1.92 MiB/s, done.
Resolving deltas: 100% (33/33), done.

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

git pull
Already up to date. # на текущий момент проект в актуальном состоянии

Далее, повторяем шаги из начала — запускаем приложение удобным вам способом, будь то простой запуск через dotnet run или релиз приложения.

В следующей серии мы поступим более грамотно, повторим шаги из прошлой статьи, создав dockerfile, и используем его для запуска приложения. Добавим SSL ключи и настроим работу через HTTPS. Если есть вопросы и пожелания, какие темы стоит рассмотреть — оставляйте их в комментариях. Можете подписаться на мой телеграм, чтобы быть в курсе планов выхода следующих статей.

© Habrahabr.ru