Первичная настройка облачной виртуальной машины
Первичная настройка облачной виртуальной машины#
Для кого и для чего#
Пожалуй, каждый разработчик в какой-то момент решается запустить что-то своё: для тестов или в качестве пет-проекта, а может быть, и полноценное приложение. Все с чего-то начинают. И вот в этот момент, когда приложение готово к запуску, приоткрывается дверь в мир облачных технологий. А разработчик осваивает новый для себя навык и становится немного DevOps-инженером. Если серьёзно, то полноценное управление приложением в проде — дело непростое, и не просто так DevOps'ы существуют как отдельный вид специалистов. Нужно многое знать, понимать и уметь применять на практике. Но становиться таким специалистом каждому, конечно, не нужно. А вот знать некоторые основы и уметь минимально управлять облачным «железом» и софтом лишним не будет ни для кого: ни фронтендерам, ни бэкендерам. Что делать в первую очередь и как со всем этим подружиться, мы и рассмотрим в данном материале.
Мы начинаем с SSH#
Ещё до того, как арендовать виртуальную машину (далее просто ВМ), стоит создать SSH-ключи. Они понадобятся как раз на этапе создания ВМ. Чтобы познать всю глубину глубин SSH, нужно сильно отойти в сторону от темы статьи, поэтому ограничимся базовыми понятиями. Итак, SSH — это протокол защищённого подключения к серверу, а ключи — способ в нём аутентифицироваться. Ключ — это пара файлов: публичный (его мы отдаём серверу) и закрытый (его храним у себя и никому не показываем). Такой вход гораздо безопаснее, чем пара логин-пароль, следовательно, всегда, когда есть возможность выбора, используем именно ключи. Ещё сильнее повысить защиту можно, установив на ключ парольную фразу (Passphrase, пароль). Это делается при создании ключа, об этом ниже. Тогда даже утечка самого файла ключа не даст постороннему доступ к серверу, по крайней мере пока он не подберёт пароль, так что делаем его надёжным.
Если у вас ещё нет SSH-ключа, то самое время создать первый.
Создаём SSH-ключи#
Рассказывать буду на примере Windows. На Маках и Линуксах всё, в общем-то, то же самое.
Для дальнейшей работы рекомендую использовать PowerShell, причём не встроенную Windows PowerShell, а PowerShell 7 (актуальная версия на сегодня). Отличие в том, что Windows PowerShell встроена в ОС и написана на .NET Framework, а PowerShell 7 — это уже современный .NET, кроссплатформенность и скорость. Кроме того, некоторые команды ниже (например, с &&) работают только в PowerShell 7 и новее.
Запускаем PowerShell и вводим:
ssh-keygen -t ed25519
Включается интерактивный режим. Сначала предлагается указать, куда сохранить ключ (можно просто нажать Enter и согласиться с вариантом по умолчанию), затем ввести парольную фразу (далее просто пароль) и повторить её. Так мы создадим пару ключей id_ed25519 и id_ed25519.pub в папке .ssh (по умолчанию она располагается в C:\Users\{username}\.ssh).
Поскольку ключей может быть несколько под разные задачи и сервисы, я предпочитаю сразу наводить в них порядок и использовать отдельные папки под разные ключи.
Делаем так:
# создаём папку .ssh, если её ещё нет, и переходим в неё
mkdir -Force $env:USERPROFILE\.ssh
cd $env:USERPROFILE\.ssh
# создаём папку для нового ключа и переходим в неё (&& работает в PowerShell 7+)
mkdir example && cd example
# создаём ключ
ssh-keygen -t ed25519 -f example_key
# проверяем
ls # выводит список файлов в папке
Получили пару ключей:

Если не срабатывает команда ssh-keygen#
В Windows 10 (начиная с версии 1809) и Windows 11 клиент OpenSSH обычно уже установлен. Если же команда ssh-keygen не срабатывает, значит, надо доустановить компонент. Тут есть два пути:
- Через графический интерфейс:
Параметры>Приложения>Дополнительные компоненты
- Через командную строку: запустите PowerShell от имени администратора и выполните
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
После добавления компонента перезапустите окно PowerShell.
Соединение с сервером#
Процесс выбора облачного провайдера и конфигурации ВМ я здесь описывать не буду. Сойдёмся на том, что стандартным выбором будет ВМ на Linux Ubuntu. Другие дистрибутивы работают примерно так же, но со своими нюансами. Главное — при аренде ВМ выбрать доступ по SSH-ключу и добавить свой созданный публичный ключ (файл с расширением .pub). У некоторых провайдеров (например, в Yandex Cloud) есть альтернатива — OS Login, когда доступ управляется через учётные записи самого облака. Это тоже безопасный вариант, но со своей спецификой, поэтому в рамках статьи используем классический вход по ключу.
Первое подключение#
Продолжаем работать в PowerShell. Выполняем:
ssh -i "<путь-к-ключу>" <username>@<server_ip>
На что тут обратить внимание:
-i— указание на то, что далее мы предоставим путь к конкретному закрытому ключу. Если ключ лежит в папке.sshи имеет стандартное имя (id_ed25519,id_rsaи т. п.), то-iи путь можно не указывать. Но в нашем примере ключ лежит в отдельной папке и называется по-своему, так что указывать путь нужно обязательно;<username>— имя пользователя, которое указали при оформлении ВМ;<server_ip>— как следует из названия, публичный IP-адрес ВМ.
При первом подключении появится сообщение о подключении к неизвестному хосту, и нужно будет подтвердить его, набрав yes (именно слово целиком, y не подойдёт). Это выполняется один раз, при следующих подключениях повторно соглашаться будет не нужно. Затем, если при создании ключей вы вводили пароль (а я настаиваю, что его необходимо установить), появится запрос на его ввод.
Упростим себе жизнь: файл config#
Каждый раз набирать путь к ключу, имя пользователя и IP — утомительно. Поэтому создадим файл config в папке .ssh:
notepad $env:USERPROFILE\.ssh\config
И добавим в него:
Host myvm
HostName <server_ip>
User <username>
IdentityFile ~/.ssh/example/example_key
myvm — это любое удобное вам имя (алиас) для сервера. Сохраняем файл (важно, чтобы у него не было расширения .txt) и теперь подключаемся просто:
ssh myvm
Первым делом#
Обновляем систему#
Вот мы вошли на ВМ, и что нужно сделать в первую очередь? Правильно — обновиться! Выполняем:
sudo apt-get update && sudo apt-get upgrade -y
Что у нас тут:
sudo— если совсем на пальцах, аналог запуска от имени администратора в Windows;apt-get update— обновляет списки пакетов (информацию о том, какие версии доступны), но сами пакеты не устанавливает;&&— как в PowerShell (или, правильнее, наоборот), выполняет вторую часть команды, только если первая завершилась успешно;apt-get upgrade -y— устанавливает доступные обновления, а флаг-yавтоматически отвечает «да» на все вопросы в процессе.
Включаем обновления безопасности#
Теперь сделаем так, чтобы обновления безопасности ставились сами:
sudo apt-get install -y unattended-upgrades
unattended-upgrades — инструмент, который автоматически скачивает и устанавливает обновления безопасности (а иногда и другие обновления) без участия пользователя. Это важно для поддержания системы в актуальном с точки зрения безопасности состоянии. В Ubuntu Server он, как правило, уже установлен — тогда команда просто сообщит, что пакет на месте, ничего страшного.
Далее:
sudo dpkg-reconfigure -plow unattended-upgrades
Эта команда запускает уже установленный пакет unattended-upgrades в режиме перенастройки, чтобы включить автоматические обновления.
После выполнения команды на экране появится интерактивное окно (обычно в текстовом режиме) с вопросом, хотите ли вы включить автоматические обновления. Выбираем Yes («Да»).
После этого нам нужно будет настроить время автоматической перезагрузки. Но чуть позже. А пока вернёмся к настройкам.
Смена оболочки по умолчанию#
Не всегда, но бывает, что по умолчанию для пользователя в Linux стоит оболочка sh, а она не умеет запоминать предыдущие команды. Кому-то и так нормально, но мне такой подход совсем не нравится. Да и просто перемещаться внутри строки терминала стрелками не получится — печатаются символы. Значит, надо поменять оболочку по умолчанию, например на bash. Делается это одной командой:
sudo chsh -s /bin/bash "$USER"
Обратите внимание на "$USER" в конце: без него команда, запущенная через sudo, поменяет оболочку не вам, а пользователю root.
Перезапускаем соединение с сервером (в терминале: exit, затем подключаемся снова) и пользуемся bash.
Текстовый редактор#
Буквально пару слов о текстовом редакторе. Вообще, можно пользоваться разными, в т. ч. установить Vim и потом долго искать из него выход )) Но есть встроенный nano, и его возможностей нам хватит. Открывается он просто:
nano <filename>
# для системных файлов и конфигураций потребуются права суперпользователя
sudo nano <filename>
Если такого файла нет, редактор всё равно откроется с пустым файлом, а сам файл создастся при сохранении. Ещё такой момент. Если указать не просто имя файла, а ещё и путь до него, то неважно, в каком каталоге вы находитесь, — откроется файл из того места, которое вы указали. Так, на всякий случай уточню.
Внутри всё просто и понятно: стрелками на клавиатуре перемещаемся по строкам и символам внутри строки, правой кнопкой мыши (ПКМ) вставляем текст из буфера. Тут есть нюанс: вставку делает не сам nano, а ваш терминал. В Windows Terminal работает и Ctrl + v, но в других терминалах может понадобиться Ctrl + Shift + v. А внутри самого nano сочетание Ctrl + v означает «пролистать страницу вниз», так что если текст не вставился, а экран уехал, — вы знаете почему.
Главное, что нужно запомнить, — две команды:
Ctrl + o — сохранить изменения (затем Enter, чтобы подтвердить имя файла)
Ctrl + x — выйти из редактора (если есть несохранённые изменения, nano спросит, сохранить ли их)
Отключим вход по паролю изнутри#
Да, при создании ВМ мы выбрали вход по ключу, но стоит продублировать ограничение изнутри. Для этого открываем (скорее всего, создаём новый) файл:
sudo nano /etc/ssh/sshd_config.d/10-hardening.conf
Вводим в него:
PasswordAuthentication no
PermitRootLogin no
KbdInteractiveAuthentication no
Сохраняем изменения и выходим.
PasswordAuthentication no— отключает вход по паролю. Это основная и самая важная директива, которая заставляет сервер принимать только ключи.PermitRootLogin no— полностью запрещает прямой вход пользователяrootпо SSH.KbdInteractiveAuthentication no— закрывает лазейку, при которой сервер через PAM-аутентификацию и режимkeyboard-interactiveмог бы по-прежнему принимать пароли, даже еслиPasswordAuthenticationвыключен.
Почему файл называется именно 10-hardening.conf? Файлы из папки sshd_config.d читаются по алфавиту, а для каждой настройки действует первое встреченное значение. В облачных образах часто лежит файл 50-cloud-init.conf, в котором вход по паролю может быть включён. Префикс 10- гарантирует, что наш файл прочитается раньше и его настройки победят.
Перед перезапуском проверим, что в конфигурации нет ошибок:
# проверка синтаксиса: если всё в порядке, команда ничего не выведет
sudo sshd -t
# проверяем итоговые значения, с которыми будет работать сервер
sudo sshd -T | grep -iE "passwordauthentication|permitrootlogin|kbdinteractive"
Если везде no — перезапускаем сервис SSH:
sudo systemctl restart ssh
ВАЖНО! Не закрывайте текущую сессию после перезапуска! Уже открытое соединение не оборвётся, а вот новое может не установиться, если что-то пошло не так. Поэтому откройте второе окно
PowerShellи проверьте, что подключение проходит. Если не проходит — исправляйте настройки в первом окне. В противном случае доступ к ВМ можно будет получить только через серийную консоль облачного провайдера.
Firewall#
Теперь настроим правила подключения к машине. Для этого используем ufw. В Ubuntu он обычно уже установлен, но в минимальных образах его может не быть — тогда ставим:
sudo apt-get install -y ufw
Настраиваем:
# запрещаем все входящие соединения
sudo ufw default deny incoming
# разрешаем все исходящие соединения
sudo ufw default allow outgoing
# разрешаем подключение по SSH. 22 порт стандартный для SSH
sudo ufw allow 22/tcp
# разрешаем подключение по http
sudo ufw allow 80/tcp
# разрешаем подключение по https для tcp
sudo ufw allow 443/tcp
# разрешаем подключение по https для udp (HTTP/3)
sudo ufw allow 443/udp
# включаем firewall
sudo ufw enable
# смотрим статус
sudo ufw status verbose
Тут всё интуитивно. 22, 80 и 443 порты — стандарт. Главное — разрешить 22 порт до ufw enable, иначе firewall отрежет вас от сервера. Да, можно изменить порт для SSH или вообще включить port knocking, но если вы не знаете, что это, то, скорее всего, на данном этапе оно и не нужно. А кто знает — зачем вы тут? )))
Про tcp и udp. Это два разных протокола передачи данных, и один и тот же номер порта может использоваться каждым из них. 443/udp нужен для HTTP/3 (протокол QUIC) — его используют современные браузеры, если веб-сервер это поддерживает (например, Caddy делает это из коробки). Если ваш сервер HTTP/3 не умеет, то это правило можно не добавлять. Особо заморачиваться на предмет различий нет смысла, но если коротко, то вот сравнительная таблица:
| TCP | UDP | |
|---|---|---|
| Соединение | Устанавливается | Нет |
| Доставка | Гарантирована | Не гарантирована |
| Порядок пакетов | Гарантирован | Не гарантирован |
| Скорость | Медленнее | Быстрее |
| Накладные расходы | Высокие | Низкие |
| Где используется | Веб, SSH, email, файлы | DNS, стриминг, игры, VoIP |
Ещё нужно будет настроить у вашего облачного провайдера политику безопасности (группу безопасности). Там тоже надо будет открыть необходимые порты: те же
22,80,443/tcpи443/udp. И это не формальность: ниже, в разделе проDocker, станет понятно, почему этот уровень защиты особенно важен.
На этом самая базовая настройка безопасности подходит к концу. Дальше настроим Docker.
Docker#
Docker сейчас, как мне представляется, первая технология, которая приходит на ум, когда речь идёт о контейнеризации приложений. Да, есть Podman и другие, но с Docker'ом по популярности они не сравнятся. Поэтому будем ставить его.
# этой командой скачивается скрипт установки и выполняется установка
curl -fsSL https://get.docker.com | sudo sh
# а этой командой мы добавляем текущего пользователя в группу docker, чтобы он мог запускать команды Docker без sudo
sudo usermod -aG docker "$USER"
# после второй команды нужно перезайти на сервер (exit и подключиться снова)
# можно и выполнить newgrp docker, но это подействует только в текущей сессии
# проверяем, что мы в группе
groups
Пара замечаний. Скрипт get.docker.com — самый быстрый способ установки, и для наших целей его достаточно, но сами разработчики Docker для продакшена рекомендуют установку через официальный apt-репозиторий (инструкция есть в документации Docker). И второе: членство в группе docker фактически равносильно правам root, так что добавляйте в неё только тех, кому доверяете как себе.
Docker и firewall: важная ловушка#
А теперь то, на чём регулярно попадаются новички и не только, а потом БД оказываются доступны извне. Docker сам управляет сетевыми правилами системы (iptables) и обходит ufw. Это значит, что если вы в docker-compose.yml опубликуете порт, например базы данных:
ports:
- "5432:5432"
то этот порт будет доступен из интернета, несмотря на то, что ufw его запрещает. Что с этим делать:
- наружу публикуем только порты веб-сервера (
80и443), а внутренние сервисы (базы данных, кэш и т. п.) оставляем вообще безports— контейнеры внутри одной сети compose и так видят друг друга по имени сервиса; - если доступ к порту всё же нужен с самой ВМ, привязываем его к
localhost:ports: - "127.0.0.1:5432:5432" - и не забываем про группу безопасности у облачного провайдера: она работает на уровне сети облака, и
Dockerеё обойти не может.
Где будем разворачивать приложение#
Вариантов немного, но они есть:
- В папке пользователя (
/home/username/appname). - В папке
/opt/appname.
Создадим папку приложения, чтобы переместить туда, например, файлы .env и docker-compose.yml:
mkdir -p ~/appname
mkdir— создать директорию.-p— создать все промежуточные папки, если их нет, и не ругаться, если папка уже существует. Это удобно и безопасно.~/appname— путь в вашей домашней директории (~=/home/username).
Для папки /opt нужны права суперпользователя:
sudo mkdir -p /opt/appname
# упростим себе жизнь и поменяем владельца папки на вас, чтобы вы могли писать в неё файлы без sudo
sudo chown -R "$USER":"$USER" /opt/appname
Без смены владельца каждая правка .env или docker-compose.yml потребует sudo, а это неудобно и опасно.
Передача файлов#
Последнее, что базово остаётся сделать, — передать файлы на ВМ с локального компьютера. Задача несложная. Можно использовать приложения с графическим интерфейсом, например FileZilla. А можно прямо из PowerShell.
Разберём второй вариант, всё-таки практикуемся в работе с терминалом.
# переходим в папку с файлами
cd C:\files
# выполняем команду
scp -i "<путь-к-ключу>" docker-compose.yml .env <пользователь>@<ip>:~/appname/
# или, если настроили файл config, то короче:
scp docker-compose.yml .env myvm:~/appname/
scp— команда копирования файлов;-i "<путь-к-ключу>"— ключ для подключения к серверу. Тут такое же правило, как сssh: если ключ со стандартным именем лежит в папке.sshили настроен вconfig, то можно не указывать;docker-compose.yml .env— через пробел имена файлов для передачи из текущей директории;<пользователь>@<ip>— это мы уже проходили, указываем имя пользователя на ВМ и адрес;:~/appname/— папка на ВМ, куда будут скопированы файлы. Она должна уже существовать (мы создали её выше). Если вы выбрали вариант с/opt, то указываем:/opt/appname/.
Пара слов про права#
Папку с проектом и чувствительные файлы можно дополнительно защитить, даже просто на всякий случай, ограничив их видимость для других пользователей. Например:
# это даст полный доступ только владельцу (вам) и закроет папку от других пользователей системы
chmod 700 ~/appname
# ещё можно отдельно ограничить права на конкретный файл: чтение и запись только для владельца
chmod 600 ~/appname/.env
Техдолг#
В самом начале мы включили автоматические обновления безопасности, но не настроили перезагрузку. Некоторые обновления (например, ядра) вступают в силу только после неё. Здесь парой строк закроем пробел.
# открываем файл настроек в редакторе
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Находим, раскомментируем (убираем // в начале) и устанавливаем значения:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:30";
Вот как это выглядит в окне терминала:

Можно увидеть, что некоторые другие настройки закомментированы. Это значит, что для них действует значение по умолчанию, — трогать их без необходимости не нужно.
Важный нюанс: время 04:30 отсчитывается по часовому поясу сервера, а на облачных ВМ это обычно UTC. То есть перезагрузка случится в 07:30 по Москве — как раз когда пользователи просыпаются. Проверить часовой пояс и при необходимости поменять его можно так:
# смотрим текущий часовой пояс
timedatectl
# устанавливаем нужный
sudo timedatectl set-timezone Europe/Moscow
Главное, чтобы при таких настройках был включён автоматический запуск приложения, а то система тихо перезагрузится, и приложение не поднимется. Для проектов в docker-compose это делается одной строкой у каждого сервиса в docker-compose.yml:
services:
app:
restart: unless-stopped
Сам Docker при установке через скрипт уже настроен на автозапуск вместе с системой, но проверить не помешает:
systemctl is-enabled docker # должно вывести enabled
И чтобы убедиться, что всё работает:
sudo reboot
Это всё актуально уже после развёртывания своего приложения, заранее не проверить. Но когда дойдёте, будете знать, что делать и что проверять.
Итог#
Что ж, пожалуй, мы рассмотрели все самые базовые моменты для начала работы с ВМ. Узнали, как создать SSH-ключи и подключиться к серверу, обновили систему и включили автообновления, сменили оболочку, закрыли вход по паролю, включили firewall, установили Docker и разобрались, как он уживается с firewall, создали папку для приложения и передали в неё файлы. Дальнейшие настройки будут касаться непосредственно того, как вы разворачиваете свой проект и что используете в качестве веб-сервера. Например, можно использовать nginx, Apache, Caddy. Можно разворачивать их на уровне ВМ, а можно внутри отдельного контейнера. В любом случае это отдельная история, и в рамках текущей статьи рассматривать этот вопрос мы не будем.
Успешных всем деплоев и не падающих серверов!