Первый сервис для self‑hosting: руководство новичка
Какой сервис выбрать первым для self‑hosting: сравнение популярных вариантов, факторы выбора и практические примеры для начинающих в homelab.
Подготовка инфраструктуры
Выбор облачного провайдера, создание сервера, базовая настройка, создание пользователя и настройка безопасности.
# Обновление системы
apt update && apt upgrade -y
# Установка необходимых компонентов
apt install -y nginx git curl htop unzip
# Настройка файрвола
ufw allow ssh
ufw allow http
ufw allow https
ufw enableУстановка Node.js
Установка Node.js с помощью Node Version Manager (nvm) для управления версиями.
# Установка nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Загрузка nvm
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \."$NVM_DIR/nvm.sh"
# Установка последней LTS версии
nvm install --ltsУстановка базы данных MongoDB
Установка и настройка MongoDB в качестве базы данных для приложения.
# Импорт ключей репозитория
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add -
# Добавление репозитория
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list
# Установка и запуск
apt update
apt install -y mongodb-org
systemctl start mongod
systemctl enable mongodНастройка приложения
Создание директории, клонирование репозитория и установка зависимостей.
# Создание директории и клонирование репозитория
sudo -u deployer mkdir -p /var/www/blog
cd /var/www/blog
sudo -u deployer git clone https://github.com/username/blog-repo .
# Установка зависимостей
sudo -u deployer npm install --production
# Создание конфигурации
sudo -u deployer nano .envНастройка PM2
Настройка PM2 для управления процессами Node.js в продакшене.
module.exports = {
apps: [{
name: 'blog',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: '/var/log/blog/error.log',
out_file: '/var/log/blog/out.log',
log_file: '/var/log/blog/combined.log',
time: true
}]
};Настройка Nginx
Настройка Nginx как reverse proxy для Node.js приложения с оптимизацией производительности.
server {
listen 80;
server_name your_domain.com www.your_domain.com;
root /var/www/blog/public;
index index.html;
# Оптимизация производительности
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_proxied any;
gzip_comp_level 6;
# Кэширование статики
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# API-запросы
location /api {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Остальные запросы
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}Настройка CI/CD с GitHub Actions
Автоматизация развертывания с помощью GitHub Actions.
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build app
run: npm run build
- name: Deploy to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
source: "dist/"
target: "/var/www/blog"
- name: Restart application
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/blog
pm2 restart blogНастройка SSL-сертификата
Установка и настройка SSL-сертификата с помощью Certbot.
# Установка Certbot
apt install -y certbot python3-certbot-nginx
# Получение сертификата
certbot --nginx -d your_domain.com -d www.your_domain.comРезервное копирование
Создание и настройка скрипта автоматического резервного копирования.
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/blog"
APP_DIR="/var/www/blog"
MONGO_USER="mongo_user"
MONGO_PASS="mongo_password"
# Создание директорий
mkdir -p $BACKUP_DIR
# Создание бэкапа кода приложения
tar -czf $BACKUP_DIR/app_$DATE.tar.gz -C $APP_DIR .
# Создание бэкапа базы данных
mongodump --username $MONGO_USER --password $MONGO_PASS --db blog --out $BACKUP_DIR/mongo_$DATE
# Компрессия бэкапа БД
tar -czf $BACKUP_DIR/mongo_$DATE.tar.gz -C $BACKUP_DIR mongo_$DATE
rm -rf $BACKUP_DIR/mongo_$DATE
# Удаление бэкапов старше 7 дней
find $BACKUP_DIR -type f -mtime +7 -deleteОптимизация производительности
Настройка Redis для кеширования и оптимизация изображений с помощью Sharp.
const redis = require('redis');
const client = redis.createClient();
// Middleware для кеширования
app.get('/api/posts', async (req, res) => {
const cacheKey = 'all_posts';
// Попытка получить из кеша
client.get(cacheKey, async (err, reply) => {
if (reply) {
return res.json(JSON.parse(reply));
}
// Если в кеше нет, получить из БД
try {
const posts = await Post.find();
// Сохранение в кеш на 1 час
client.setex(cacheKey, 3600, JSON.stringify(posts));
res.json(posts);
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
});
});Как развернуть первый Node.js приложение: практическое руководство
Введение
Размещение первого собственного сервиса в интернете — важный этап в карьере любого IT-специалиста. Это позволяет получить практический опыт работы с инфраструктурой, научиться решать реальные проблемы и почувствовать гордость за созданный продукт. В этой статье мы подробно рассмотрим процесс развертывания веб-приложения на Node.js с акцентом на безопасность, оптимизацию и надежность.
Мой выбор: простейший блог на Node.js
В качестве первого сервиса был выбран простой блог на базе Node.js и Express. Вот почему этот выбор оправдан:
- Знакомая технология: Разработка на JavaScript требует минимального порога входа
- Простота развертывания: Легче монолитных приложений на Java или .NET
- Масштабируемость: Возможность вертикального и горизонтального масштабирования
- Активное сообщество: Гарантированно найдется решение для большинства проблем
- Минимальные требования к ресурсам: Достаточно даже виртуального machines с 1GB RAM
Подготовка инфраструктуры
Выбор облачного провайдера
Для новичков оптимальны провайдеры с простым интерфейсом:
- DigitalOcean: Простой интерфейс, понятная ценовая политика
- Vultr: Аналогичный функционал, конкурентные цены
- Hetzner: Высокая производительность за низкую стоимость
Критерии выбора: Стоимость, простота управления, наличие SSD-накопителей.
Создание сервера
Оптимальная конфигурация для первого проекта:
- 1 vCPU
- 1 GB RAM
- 25 GB SSD
- 1 TB трафика
- ОС: Ubuntu 20.04 LTS
# Подключение по SSH после создания сервера
ssh root@your_server_ip
Базовая настройка сервера
# Обновление системы
apt update && apt upgrade -y
# Установка необходимых компонентов
apt install -y nginx git curl htop unzip
# Настройка файрвола
ufw allow ssh
ufw allow http
ufw allow https
ufw enable
Установка пользователя
# Создание пользователя с sudo-правами
adduser deployer
usermod -aG sudo deployer
# Настройка SSH для пользователя
mkdir /home/deployer/.ssh
cp /root/.ssh/authorized_keys /home/deployer/.ssh/
chown -R deployer:deployer /home/deployer/.ssh
chmod 700 /home/deployer/.ssh
chmod 600 /home/deployer/.ssh/authorized_keys
Настройка безопасности
# Отключение входа под root
passwd -l root
# Настройка SSH
nano /etc/ssh/sshd_config
Ключевые параметры конфигурации SSH:
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
X11Forwarding no
# Перезапуск SSH
systemctl restart sshd
Настройка окружения
Установка Node.js
# Установка nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Загрузка nvm
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Установка последней LTS версии
nvm install --lts
Установка базы данных
Для проекта выбран MongoDB из-за простоты интеграции с Node.js:
# Импорт ключей репозитория
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add -
# Добавление репозитория
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list
# Установка и запуск
apt update
apt install -y mongodb-org
systemctl start mongod
systemctl enable mongod
Настройка приложения
# Создание директории и клонирование репозитория
sudo -u deployer mkdir -p /var/www/blog
cd /var/www/blog
sudo -u deployer git clone https://github.com/username/blog-repo .
# Установка зависимостей
sudo -u deployer npm install --production
# Создание конфигурации
sudo -u deployer nano .env
Пример содержимого .env:
NODE_ENV=production
PORT=3000
MONGO_URI=mongodb://localhost:27017/blog
JWT_SECRET=your_secure_secret_here
Развертывание приложения
Настройка PM2
# Установка PM2
sudo -u deployer npm install pm2 -g
# Создание конфигурации ecosystem
sudo -u deployer nano ecosystem.config.js
Пример ecosystem.config.js:
module.exports = {
apps: [{
name: 'blog',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: '/var/log/blog/error.log',
out_file: '/var/log/blog/out.log',
log_file: '/var/log/blog/combined.log',
time: true
}]
};
# Запуск приложения
sudo -u deployer pm2 start ecosystem.config.js
# Настройка автозапуска
pm2 startup
pm2 save
Настройка Nginx
nano /etc/nginx/sites-available/blog
Пример конфигурации Nginx:
server {
listen 80;
server_name your_domain.com www.your_domain.com;
root /var/www/blog/public;
index index.html;
# Оптимизация производительности
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_proxied any;
gzip_comp_level 6;
# Кэширование статики
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# API-запросы
location /api {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# Таймауты
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Заголовки безопасности
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Остальные запросы
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# Таймауты
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
# Активация конфигурации
ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/
nginx -t
systemctl restart nginx
Автоматизация развертывания
Настройка CI/CD с GitHub Actions
Создать файл .github/workflows/deploy.yml:
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build app
run: npm run build
- name: Deploy to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
source: "dist/"
target: "/var/www/blog"
- name: Restart application
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/blog
pm2 restart blog
Ansible Playbook для автоматизации настройки
Пример site.yml:
---
- name: Configure production server
hosts: blog_server
become: yes
tasks:
- name: Update apt cache
apt: update_cache=yes cache_valid_time=3600
- name: Install required packages
apt:
name: [nginx, git, curl, htop, unzip]
state: present
- name: Install Node.js
shell: |
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install --lts
- name: Install MongoDB
block:
- name: Add MongoDB repo
ansible.builtin.apt_repository:
repo: deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse
- name: Install MongoDB
apt:
name: mongodb-org
state: present
- name: Configure firewall
ufw:
rule: allow
name: OpenSSH
state: present
- name: Enable firewall
ufw:
state: enabled
policy: deny
- name: Create application user
user:
name: deployer
groups: sudo
shell: /bin/bash
create_home: yes
- name: Setup SSH keys
ansible.posix.authorized_key:
user: deployer
state: present
key: "{{ lookup('file', 'deploy_key.pub') }}"
Обработка ошибок
Логирование и мониторинг
Настройка Winston для логирования:
// logger.js
const winston = require('winston');
const { format, transports } = winston;
const { combine, timestamp, printf, colorize } = format;
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} [${level}]: ${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
logFormat
),
transports: [
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new transports.Console({
format: combine(
colorize(),
format.simple()
)
}));
}
module.exports = logger;
Интеграция с Sentry:
# Установка Sentry SDK
npm install @sentry/node
# Настройка Sentry
const Sentry = require("@sentry/node");
Sentry.init({
dsn: "https://your-dsn@sentry.io/1234567",
tracesSampleRate: 1.0,
});
Стратегии восстановления
Настройка PM2 для автоматического восстановления:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'blog',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: '/var/log/blog/error.log',
out_file: '/var/log/blog/out.log',
log_file: '/var/log/blog/combined.log',
time: true,
max_memory_restart: '1G',
min_uptime: '10s',
max_restarts: 5,
listen_timeout: 60000,
kill_timeout: 5000
}]
};
Настройка безопасности
SSL-сертификат
# Установка Certbot
apt install -y certbot python3-certbot-nginx
# Получение сертификата
certbot --nginx -d your_domain.com -d www.your_domain.com
Дополнительные меры защиты
# Установка fail2ban
apt install -y fail2ban
# Создание конфигурации
nano /etc/fail2ban/jail.local
Пример конфигурации fail2ban:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
findtime = 60
maxretry = 10
# Перезапуск fail2ban
systemctl restart fail2ban
Защита от SQL-инъекций
Пример использования санитизации ввода:
const express = require('express');
const { body, validationResult } = require('express-validator');
const app = express();
app.use(express.json());
app.post('/api/posts',
[
body('title').trim().isLength({ min: 5 }).escape(),
body('content').trim().isLength({ min: 10 }).escape()
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Обработка данных
const { title, content } = req.body;
// ...
}
);
Резервное копирование
Скрипт резервного копирования
nano /usr/local/bin/backup.sh
Содержимое скрипта:
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/blog"
APP_DIR="/var/www/blog"
MONGO_USER="mongo_user"
MONGO_PASS="mongo_password"
# Создание директорий
mkdir -p $BACKUP_DIR
# Создание бэкапа кода приложения
tar -czf $BACKUP_DIR/app_$DATE.tar.gz -C $APP_DIR .
# Создание бэкапа базы данных
mongodump --username $MONGO_USER --password $MONGO_PASS --db blog --out $BACKUP_DIR/mongo_$DATE
# Компрессия бэкапа БД
tar -czf $BACKUP_DIR/mongo_$DATE.tar.gz -C $BACKUP_DIR mongo_$DATE
rm -rf $BACKUP_DIR/mongo_$DATE
# Удаление бэкапов старше 7 дней
find $BACKUP_DIR -type f -mtime +7 -delete
# Уведомление о статусе
if [ $? -eq 0 ]; then
echo "Бэкап успешно создан: $DATE" | mail -s "Бэкап блога" admin@example.com
else
echo "Ошибка при создании бэкапа: $DATE" | mail -s "Ошибка бэкапа блога" admin@example.com
fi
Настройка cron:
# Добавление ежедневного задания
crontab -e
Содержимое crontab:
0 2 * * * /usr/local/bin/backup.sh
Тестирование и мониторинг
Проверка работы
# Проверка статуса приложения
pm2 status
# Просмотр логов
pm2 logs blog
# Проверка через curl
curl -I https://your_domain.com
# Проверка производительности
ab -n 1000 -c 10 https://your_domain.com/
Настройка мониторинга
Интеграция с Prometheus:
// monitoring.js
const promClient = require('prom-client');
// Создание метрик
const httpRequestDurationMicroseconds = new promClient.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'status_code'],
buckets: [50, 100, 200, 300, 400, 500, 1000, 2000]
});
const httpRequestCounter = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
// Middleware для сбора метрик
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
httpRequestDurationMicroseconds
.observe(
{ method: req.method, route: req.route?.path || 'unknown', status_code: res.statusCode },
duration
);
httpRequestCounter
.inc({ method: req.method, route: req.route?.path || 'unknown', status_code: res.statusCode });
});
next();
});
// Эндпоинт для метрик
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
Uptime-мониторинг
Интеграция с UptimeRobot:
- Создать аккаунт на uptimebot.com
- Добавить мониторинг:
- URL: https://your_domain.com
- Тип: HTTP
- Интервал проверки: 5 минут
- Таймаут: 30 секунд
- Оповещения: Email, Slack, Telegram
Оптимизация производительности
Кеширование Redis
# Установка Redis
apt install -y redis-server
# Настройка Redis
nano /etc/redis/redis.conf
Ключевые параметры:
maxmemory 256mb
maxmemory-policy allkeys-lru
# Перезапуск Redis
systemctl restart redis
Интеграция в Node.js:
const redis = require('redis');
const client = redis.createClient();
// Middleware для кеширования
app.get('/api/posts', async (req, res) => {
const cacheKey = 'all_posts';
// Попытка получить из кеша
client.get(cacheKey, async (err, reply) => {
if (reply) {
return res.json(JSON.parse(reply));
}
// Если в кеше нет, получить из БД
try {
const posts = await Post.find();
// Сохранение в кеш на 1 час
client.setex(cacheKey, 3600, JSON.stringify(posts));
res.json(posts);
} catch (err) {
res.status(500).json({ message: 'Server error' });
}
});
});
Оптимизация изображений
Автоматизация оптимизации с Sharp:
const sharp = require('sharp');
app.post('/upload', upload.single('image'), async (req, res) => {
try {
const optimized = await sharp(req.file.buffer)
.resize(800, 600, { fit: 'inside' })
.jpeg({ quality: 80 })
.toBuffer();
// Сохранение оптимизированного изображения
fs.writeFileSync(`uploads/${req.file.originalname}`, optimized);
res.json({ message: 'Image optimized and saved' });
} catch (err) {
res.status(500).json({ message: 'Optimization failed' });
}
});
Заключение
Развертывание первого Node.js приложения — это комплексный процесс, требующий внимания к деталям. Ключевые выводы:
- Автоматизация критически важна - используйте инструменты вроде Ansible и Docker для воспроизводимых развертываний
- Безопасность начинается с базовых настроек - SSH, файрволы и SSL-сертификаты
- Мониторинг и логирование - основы надежной работы в продакшене
- Резервное копирование - защита от потери данных
- Производительность требует постоянного внимания - оптимизация запросов, кеширование, CDN
Этот опыт создает фундамент для более сложных проектов и дальнейшего развития в области DevOps. Успешное развертывание первого сервиса открывает путь к созданию масштабируемых, надежных и безопасных приложений.