Блок 19
Веб-сервер и базы данных
> Что вас ждёт в этом блоке: мы соберём воедино практически весь материал курса, настроив полноценный, работающий веб-стек — комбинацию веб-сервера, приложения и базы данных, — именно так, как устр…
Введение в блок
> Что вас ждёт в этом блоке: мы соберём воедино практически весь материал курса, настроив полноценный, работающий веб-стек — комбинацию веб-сервера, приложения и базы данных, — именно так, как устроено подавляющее большинство современных веб-сайтов и веб-приложений. Вы разберётесь, как устроен протокол HTTP, установите и настроите Nginx (самый популярный веб-сервер в мире), научитесь размещать на одном сервере несколько независимых сайтов через виртуальные хосты, познакомитесь с реляционными базами данных на примере PostgreSQL и MySQL, и в финале свяжете все компоненты в единую работающую систему.
>
> Что нужно знать перед началом: сеть и порты (Блок 12) — обязательное условие для понимания HTTP; безопасность, включая UFW (Блок 14); процессы и службы (Блоки 10-11); мониторинг и диагностика (Блок 18) пригодятся для отладки; желательно (но не обязательно) знакомство с Docker (Блок 17), поскольку многие практические примеры можно выполнять как напрямую на хосте, так и в контейнерах.
Урок 19.1. HTTP: клиент-сервер
Разобраться в протоколе HTTP на уровне, достаточном для осознанной работы с веб-серверами: понять модель запрос-ответ, структуру HTTP-запросов и ответов, методы HTTP, коды состояния, и роль заголовков.
Теория
Напоминание материала Блока 12. Мы уже неоднократно упоминали HTTP в контексте портов (80 и 443) и использовали curl для проверки веб-серверов ещё в Блоке 17. Теперь настало время разобраться в этом протоколе подробно, поскольку весь оставшийся материал этого блока строится именно на нём.
HTTP (HyperText Transfer Protocol) — протокол прикладного уровня (вспомните модель клиент-сервер и уровни сетевого взаимодействия из Блока 12), лежащий в основе Всемирной паутины. Как и SSH (Блок 13), HTTP работает по модели клиент-сервер: клиент (обычно браузер, но также может быть curl, мобильное приложение, или другая программа) отправляет запрос (request), сервер обрабатывает его и отправляет обратно ответ (response).
Ключевая характеристика HTTP — протокол без сохранения состояния (stateless). Каждый HTTP-запрос обрабатывается сервером независимо от всех предыдущих запросов — сервер по своей природе «не помнит» ничего о клиенте между отдельными запросами. Это фундаментальное архитектурное решение (не техническое ограничение, а осознанный дизайн) значительно упрощает масштабирование веб-серверов, но требует дополнительных механизмов (таких как cookies или токены аутентификации), когда приложению всё же нужно «помнить» состояние между запросами — например, что пользователь уже вошёл в систему.
Структура HTTP-запроса. Каждый запрос состоит из нескольких частей:
- Стартовая строка — содержит метод, путь запрашиваемого ресурса, и версию протокола:
GET /index.html HTTP/1.1 - Заголовки (headers) — пары ключ-значение, передающие дополнительную информацию о запросе:
Host: example.com,User-Agent: ...(какой браузер/клиент отправил запрос),Accept: text/html(какой формат ответа ожидается) и множество других. - Пустая строка, отделяющая заголовки от тела запроса.
- Тело запроса (body) — необязательная часть, присутствующая, например, при отправке данных формы или файла на сервер (обычно при использовании методов
POSTилиPUT, о которых чуть ниже) — при простом запросе страницы для чтения (GET) тело обычно отсутствует.
Основные методы HTTP:
| Метод | Назначение |
|---|---|
| GET | Получить ресурс (запросить страницу, файл, данные) — не должен изменять состояние сервера |
| POST | Отправить данные на сервер (например, заполненную форму), часто создавая новый ресурс |
| PUT | Полностью заменить существующий ресурс переданными данными |
| DELETE | Удалить указанный ресурс |
| HEAD | Аналогичен GET, но сервер возвращает только заголовки, без тела ответа (мы уже использовали флаг curl -I в Блоке 18, реализующий именно этот метод — быстрая проверка доступности без скачивания всего содержимого) |
| PATCH | Частично изменить существующий ресурс |
| OPTIONS | Запросить информацию о том, какие методы поддерживаются для данного ресурса |
Структура HTTP-ответа. Симметрично запросу, ответ также состоит из нескольких частей:
- Стартовая строка — версия протокола, код состояния, и текстовое пояснение:
HTTP/1.1 200 OK - Заголовки ответа — например,
Content-Type: text/html(какой тип содержимого возвращается),Content-Length: 1234(размер тела ответа в байтах),Server: nginx/1.25(какое программное обеспечение сервера обработало запрос). - Пустая строка.
- Тело ответа — собственно содержимое: HTML-код страницы, JSON-данные, изображение, и так далее.
Коды состояния HTTP (HTTP status codes) — систематизация по первой цифре:
| Диапазон | Категория | Примеры |
|---|---|---|
| 1xx | Информационные (редко встречаются напрямую) | 100 Continue |
| 2xx | Успех | 200 OK, 201 Created, 204 No Content |
| 3xx | Перенаправление | 301 Moved Permanently, 302 Found, 304 Not Modified |
| 4xx | Ошибка клиента | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found |
| 5xx | Ошибка сервера | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
Наиболее часто встречающиеся коды, которые важно знать наизусть:
- 200 OK** — запрос успешно обработан.
- 301/302** — ресурс перемещён (постоянно/временно) по другому адресу; клиент должен перейти по новому адресу, обычно указанному в заголовке
Location. - 404 Not Found** — запрошенный ресурс не найден на сервере — вероятно, самый узнаваемый код состояния HTTP, знакомый даже далёким от технологий людям.
- 403 Forbidden** — сервер понял запрос, но отказывается его выполнять (обычно проблема прав доступа, тесно связанная с материалом Блока 8, применённым уже на уровне веб-сервера, что мы разберём практически в следующем уроке).
- 500 Internal Server Error** — на сервере произошла непредвиденная ошибка при обработке запроса (часто указывает на проблему в самом приложении/коде, а не в веб-сервере как таковом).
- 502 Bad Gateway** — сервер, выступающий в роли посредника (о чём мы подробнее поговорим в уроке 19.6 про связку Nginx и приложения), получил недействительный ответ от вышестоящего сервера — очень частая, характерная ошибка именно при неправильно настроенной связке веб-сервера с приложением.
- 503 Service Unavailable** — сервер временно не может обработать запрос (например, перегружен или находится на обслуживании).
HTTP vs HTTPS. HTTPS — это тот же протокол HTTP, но обёрнутый в шифрованное TLS/SSL-соединение (концептуально похожий подход к добавлению шифрования поверх обычного протокола мы уже видели на примере SFTP как «SSH-обёртки» вокруг передачи файлов в Блоке 13). HTTPS работает на порту 443 (вместо стандартного порта 80 для обычного HTTP — мы упоминали оба порта ещё в Блоке 12) и обеспечивает шифрование трафика между клиентом и сервером, а также криптографическую проверку подлинности сервера через сертификаты — тема, которую мы кратко затронем в практических заданиях, хотя полноценная настройка HTTPS-сертификатов выходит за рамки этого вводного урока.
Практика
Шаг 1. Изучите структуру реального HTTP-запроса и ответа с помощью curl в подробном (verbose) режиме:
```bash
curl -v http://example.com 2>&1 | head -40
```
Что вы увидите: строки, начинающиеся с > — это то, что клиент (curl) отправил серверу (стартовая строка запроса и заголовки); строки, начинающиеся с < — это то, что сервер вернул в ответ (стартовая строка ответа, заголовки).
[Скриншот подробного вывода curl]
Шаг 2. Изучите только заголовки ответа, без загрузки полного тела страницы:
```bash
curl -I http://example.com
```
Обратите внимание на код состояния в первой строке (HTTP/1.1 200 OK или похожий) и заголовки, такие как Content-Type, Server.
[Скриншот терминала]
Шаг 3. Продемонстрируйте разные коды состояния HTTP на практике. Сначала — успешный ответ:
```bash
curl -o /dev/null -s -w "%{http_code}\n" http://example.com
```
(Флаги: -o /dev/null — отбросить тело ответа, нас интересует только код; -s — тихий режим без индикатора прогресса; -w "%{http_code}\n" — вывести именно код состояния HTTP после завершения запроса.)
Шаг 4. Запросите заведомо несуществующую страницу для получения кода 404:
```bash
curl -o /dev/null -s -w "%{http_code}\n" http://example.com/несуществующая-страница-xyz123
```
Шаг 5. Изучите редирект на практике (многие сайты автоматически перенаправляют с HTTP на HTTPS, что является хорошей демонстрацией кода 301):
```bash
curl -I http://github.com
```
Что вы увидите: скорее всего, код 301 (или похожий редирект-код) с заголовком Location, указывающим на HTTPS-версию того же адреса.
[Скриншот терминала с редиректом]
Шаг 6. Продемонстрируйте разные методы HTTP через curl:
```bash
curl -X GET -I http://example.com
curl -X HEAD -I http://example.com
```
(Флаг -X явно указывает используемый метод; для простого запроса страницы GET используется по умолчанию, если не указано иначе.)
Шаг 7. Изучите заголовки собственного запроса — добавьте пользовательский заголовок и отправьте его серверу (используем публичный тестовый сервис httpbin.org, который специально предназначен для отладки HTTP-запросов, возвращая обратно всё, что было в него отправлено):
```bash
curl -s http://httpbin.org/headers -H "X-Custom-Header: моё тестовое значение"
```
Что вы увидите: JSON-ответ, включающий все заголовки, отправленные вашим запросом, включая добавленный вами пользовательский заголовок — наглядная демонстрация того, что заголовки действительно передаются от клиента к серверу.
[Скриншот терминала с JSON-ответом]
Шаг 8. Изучите разницу между HTTP и HTTPS-запросами через явное указание протокола:
```bash
curl -v https://example.com 2>&1 | grep -E "SSL|TLS" | head -10
```
Что вы увидите: строки, связанные с процессом установки TLS-соединения (аналогичного по духу процессу установки зашифрованного SSH-соединения из Блока 13, хотя технические детали различаются) — подтверждение слов teории о том, что HTTPS добавляет шифрование поверх обычного HTTP.
Разбор команд
Флаги curl, применённые в этом уроке:
| Флаг | Назначение |
|---|---|
| -v | Подробный (verbose) вывод, включая все заголовки запроса и ответа |
| -I | Только заголовки ответа (использует метод HEAD) |
| -X метод | Явно указать HTTP-метод |
| -H "заголовок: значение" | Добавить собственный заголовок к запросу |
| -o файл | Сохранить тело ответа в файл (/dev/null для отбрасывания) |
| -s | Тихий режим, без индикатора прогресса |
| -w формат | Вывести дополнительную информацию по заданному формату после запроса (например, %{http_code}) |
Возможные ошибки
- Ошибка:** путаница между кодом 401 (Unauthorized) и 403 (Forbidden).
Почему возникает: оба кода семантически связаны с отказом в доступе, что вызывает интуитивную путаницу.
Как определить/различить: 401 означает, что сервер требует аутентификации (клиент не предоставил, или предоставил неверные учётные данные) — часто сопровождается заголовком, указывающим, как именно аутентифицироваться. 403 означает, что сервер понял, кто вы (или что аутентификация не требуется вовсе для данного ресурса), но у вас всё равно нет прав на выполнение данного конкретного действия — концептуально близко к разнице между «вход в систему не выполнен» и «вход выполнен, но прав недостаточно», которую мы подробно разбирали применительно к правам файлов в Блоке 8.
Практические задания
- С помощью
curl -Iпроверьте коды состояния для пяти разных, известных вам веб-сайтов, и запишите в конспект, какие коды вы получили — все ли они вернули 200, или встретились редиректы (3xx)? - Используя
httpbin.org(specifically эндпоинтhttpbin.org/status/КОД, специально возвращающий заданный код состояния для тестирования), продемонстрируйте себе на практике получение кодов 404, 500 и 503 черезcurl -I http://httpbin.org/status/404` (и аналогично для других кодов) — убедитесь, что видите ожидаемый код в ответе. - Изучите (через
curl -v) полный обмен заголовками при запросе кhttpbin.org/get— найдите в выводе заголовокUser-Agent, отправляемый по умолчанию самимcurl, и объясните в конспекте, для чего серверы обычно используют информацию из этого заголовка (подсказка: подумайте о том, как сайты определяют, с какого устройства/браузера пришёл посетитель).
Проверка знаний
Вопрос 1. Что означает утверждение «HTTP — протокол без сохранения состояния (stateless)», и какое практическое следствие это имеет для веб-приложений, которым нужно «помнить» о пользователе между запросами?
Ответ: Это означает, что каждый HTTP-запрос обрабатывается сервером полностью независимо от всех предыдущих запросов — сервер сам по себе не хранит никакой информации о том, что происходило в предыдущих взаимодействиях с этим же клиентом. Практическое следствие: веб-приложениям, которым необходимо «помнить» состояние между запросами (например, что пользователь уже вошёл в систему), приходится использовать дополнительные механизмы поверх самого HTTP — такие как cookies (небольшие данные, которые клиент сохраняет и повторно отправляет с каждым последующим запросом) или токены аутентификации, передаваемые в заголовках — само по себе тело протокола HTTP не предоставляет встроенного механизма «памяти» между отдельными запросами.
Вопрос 2. В чём разница между кодами состояния 404 и 500, и что каждый из них говорит о том, где именно произошла проблема — на стороне клиента или сервера?
Ответ: 404 (Not Found) относится к категории 4xx — ошибок клиента: сервер полностью корректно обработал запрос и определил, что запрошенный ресурс просто не существует по указанному адресу; это, по сути, ожидаемая, штатная реакция сервера на некорректный или устаревший запрос. 500 (Internal Server Error) относится к категории 5xx — ошибок сервера: что-то пошло не так непосредственно в процессе обработки запроса на стороне сервера (например, ошибка в коде приложения), независимо от того, насколько корректным был сам запрос со стороны клиента — это сигнализирует о проблеме, требующей внимания администратора или разработчика сервера, а не самого клиента.
Итоги урока
Вы изучили: модель клиент-сервер HTTP и его stateless-природу, структуру HTTP-запроса и ответа (стартовая строка, заголовки, тело), основные методы HTTP (GET/POST/PUT/DELETE/HEAD и другие), систематизацию кодов состояния по категориям (1xx-5xx) и наиболее важные конкретные коды, разницу между HTTP и HTTPS.
Вы умеете: изучать структуру реальных HTTP-запросов и ответов через curl -v, целенаправленно получать конкретные коды состояния для тестирования, добавлять собственные заголовки к запросам, интерпретировать наиболее распространённые коды ответов.
В следующем уроке потребуется: понимание протокола HTTP — теперь мы установим и настроим программное обеспечение, которое непосредственно обрабатывает эти запросы на стороне сервера — веб-сервер Nginx.
Урок 19.2. Установка и настройка Nginx
Установить веб-сервер Nginx, разобраться в его архитектуре и базовой структуре конфигурационных файлов, научиться размещать статические файлы и корректно перезагружать конфигурацию.
Теория
Что такое веб-сервер, и почему он нужен. Веб-сервер — это программное обеспечение, которое принимает HTTP-запросы (материал предыдущего урока), обрабатывает их — как правило, находя запрошенный файл на диске или передавая запрос дальше в приложение (материал урока 19.6) — и отправляет обратно соответствующий HTTP-ответ.
Nginx (произносится «энджин-экс») — один из самых популярных веб-серверов в мире (наряду с Apache HTTP Server), изначально созданный российским разработчиком Игорем Сысоевым в 2004 году специально для решения проблемы обработки большого числа одновременных подключений с минимальным потреблением ресурсов — задача, с которой на тот момент существовавшие альтернативы справлялись не так эффективно.
Архитектура Nginx — почему он так эффективен. В отличие от некоторых альтернативных веб-серверов, использующих отдельный процесс или поток операционной системы (вспомните материал о процессах из Блока 11) для каждого одновременного соединения, Nginx использует так называемую событийно-ориентированную (event-driven) архитектуру: относительно небольшое число рабочих процессов (worker processes) способно эффективно обрабатывать тысячи одновременных соединений, асинхронно переключаясь между ними по мере готовности данных, вместо того чтобы блокировать целый процесс/поток на ожидание каждого отдельного, потенциально медленного соединения. Это архитектурное решение — одна из ключевых причин широкой популярности Nginx для высоконагруженных сайтов.
Роли Nginx помимо простой раздачи файлов. Хотя мы начинаем с простейшего сценария использования Nginx (раздача статических файлов), важно понимать, что на практике Nginx крайне часто используется в нескольких дополнительных, не менее важных ролях:
- Обратный прокси (reverse proxy)** — принимает запросы от внешних клиентов и перенаправляет их внутреннему приложению (материал урока 19.6), скрывая детали внутренней инфраструктуры и добавляя дополнительный уровень контроля.
- Балансировщик нагрузки (load balancer)** — распределяет входящие запросы между несколькими экземплярами приложения для равномерного распределения нагрузки (тема, выходящая за рамки этого вводного блока, но важная для полноты картины возможностей Nginx).
- Терминатор SSL/TLS** — обрабатывает шифрованные HTTPS-соединения, разгружая от этой задачи внутреннее приложение.
Установка Nginx — стандартным для нас способом, через APT (материал Блока 9):
```bash
sudo apt install nginx
```
Структура конфигурационных файлов Nginx:
/etc/nginx/nginx.conf** — главный, «корневой» конфигурационный файл, определяющий глобальные настройки (количество worker-процессов, базовые параметры обработки соединений) и подключающий остальные конфигурационные файлы./etc/nginx/sites-available/** — директория, где принято хранить конфигурации отдельных сайтов (в терминологии Nginx — «серверных блоков», server blocks) — все доступные, но необязательно активные конфигурации./etc/nginx/sites-enabled/— директория с активными конфигурациями сайтов, обычно содержащая символические ссылки** (вспомните симлинки из Блока 5) на файлы изsites-available/. Такое разделение на «доступные» и «включённые» конфигурации — распространённый паттерн в Ubuntu (аналогичный подход мы, возможно, видели и для других сервисов на протяжении курса) — позволяет легко «включать» и «выключать» конфигурацию конкретного сайта простым созданием или удалением символической ссылки, не удаляя саму конфигурацию физически./var/www/html/** — директория по умолчанию для размещения статических файлов сайта (стандартная, «дефолтная» конфигурация, устанавливаемая вместе с Nginx, указывает именно на эту директорию).
Базовая структура серверного блока (server block) — фрагмент конфигурации Nginx, описывающий, как обрабатывать запросы для конкретного сайта/домена:
```nginx
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
```
Разбор ключевых директив:
listen** — на каком порту (и, опционально, IP-адресе) принимать соединения.server_name** — доменное имя (или несколько имён), для которого предназначен этот серверный блок (мы подробно разберём практическое значение этой директивы в следующем уроке про виртуальные хосты).root** — корневая директория на диске, откуда Nginx берёт запрашиваемые файлы.index** — какой файл(ы) отдавать по умолчанию, если запрошена директория, а не конкретный файл (например, при запросе/фактически будет отданindex.html, если он существует).location** — блок, определяющий, как обрабатывать запросы, соответствующие определённому пути (шаблону URL);location /соответствует любому запросу, не перехваченному более специфичным блокомlocation.
Команда проверки синтаксиса конфигурации — критически важная привычка, напрямую аналогичная тому, что мы делали для SSH-конфигурации в Блоке 14 (sshd -t):
```bash
sudo nginx -t
```
Применение изменений конфигурации:
```bash
sudo systemctl reload nginx # аккуратная перезагрузка — применяет новую конфигурацию без разрыва уже установленных соединений
sudo systemctl restart nginx # полный перезапуск службы (более "резкий" вариант, обычно достаточно reload)
```
reload обычно предпочтительнее restart для применения изменений конфигурации, поскольку не прерывает уже обрабатываемые запросы — Nginx корректно завершает старые процессы после того, как они закончат обслуживание текущих соединений, одновременно запуская новые процессы с уже обновлённой конфигурацией.
Практика
Шаг 1. Установите Nginx:
```bash
sudo apt update
sudo apt install nginx
```
Шаг 2. Проверьте, что служба запущена (материал Блока 10):
```bash
sudo systemctl status nginx
```
[Скриншот терминала]
Шаг 3. Убедитесь, что Nginx уже отвечает на запросы со стандартной конфигурацией по умолчанию:
```bash
curl http://localhost
```
Что вы увидите: HTML-код стандартной приветственной страницы Nginx («Welcome to nginx!»).
[Скриншот терминала]
Шаг 4. Убедитесь, что порт открыт в firewall, если вы настраивали UFW в Блоке 14 (если ещё не сделано, самое время):
```bash
sudo ufw status
sudo ufw allow 'Nginx HTTP'
```
(Обратите внимание: пакет Nginx автоматически регистрирует в UFW специальный, удобный профиль с именем 'Nginx HTTP', разрешающий стандартный порт 80, — вместо того чтобы вручную указывать номер порта, как мы делали в Блоке 14. Аналогично существует профиль 'Nginx HTTPS' для порта 443, и 'Nginx Full', разрешающий сразу оба порта.)
Шаг 5. Изучите структуру конфигурации:
```bash
ls -la /etc/nginx/sites-available/
ls -la /etc/nginx/sites-enabled/
```
Что вы увидите: в sites-available/ — файл default, в sites-enabled/ — символическая ссылка на этот же файл (обратите внимание на стрелку -> в выводе ls -la, обозначающую симлинк, материал Блока 5).
Шаг 6. Изучите содержимое конфигурации по умолчанию:
```bash
cat /etc/nginx/sites-available/default
```
Найдите знакомые нам из теории директивы: listen, root, index, блок location.
[Скриншот содержимого конфигурации]
Шаг 7. Создайте собственную, простую статическую страницу для практики. Сначала создайте новую директорию для нашего учебного сайта:
```bash
sudo mkdir -p /var/www/my-first-site
```
Шаг 8. Создайте HTML-файл:
```bash
sudo tee /var/www/my-first-site/index.html > /dev/null << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Мой первый сайт на Nginx</title></head>
<body>
<h1>Привет! Это моя собственная страница, настроенная в рамках курса Linux.</h1>
<p>Веб-сервер: Nginx</p>
</body>
</html>
EOF
```
(Использована конструкция sudo tee файл > /dev/null << 'EOF' ... EOF — способ записать многострочный текст в файл, требующий прав root, через sudo; напрямую sudo echo текст > файл не сработал бы корректно, поскольку перенаправление > в этом случае выполняется не от имени root, а tee, наоборот, сама программа получает права root через sudo и уже она выполняет запись в файл — важный нюанс, объясняющий, почему для записи в защищённые файлы через sudo часто используется именно tee, а не прямое перенаправление.)
Шаг 9. Установите правильного владельца для новых файлов (материал Блока 8 — Nginx обычно работает от специального системного пользователя, часто www-data, и должен иметь права на чтение этих файлов):
```bash
sudo chown -R www-data:www-data /var/www/my-first-site
```
Шаг 10. Создайте новый файл конфигурации серверного блока в sites-available/:
```bash
sudo nano /etc/nginx/sites-available/my-first-site
```
Содержимое:
```nginx
server {
listen 8081;
server_name localhost;
root /var/www/my-first-site;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
```
(Мы специально указали нестандартный порт 8081, чтобы избежать конфликта с уже работающей на порту 80 конфигурацией по умолчанию — детально мы разберём одновременную работу нескольких сайтов на одном сервере уже в следующем уроке.)
Сохраните: Ctrl+O, Enter, Ctrl+X.
Шаг 11. Активируйте новую конфигурацию, создав символическую ссылку:
```bash
sudo ln -s /etc/nginx/sites-available/my-first-site /etc/nginx/sites-enabled/
```
Шаг 12. ОБЯЗАТЕЛЬНЫЙ ШАГ — проверьте синтаксис перед применением:
```bash
sudo nginx -t
```
Что вы увидите: syntax is ok и test is successful — конфигурация корректна.
[Скриншот проверки синтаксиса]
Шаг 13. Примените изменения:
```bash
sudo systemctl reload nginx
```
Шаг 14. Проверьте, что новый сайт доступен:
```bash
```
Что вы увидите: HTML-код вашей собственной страницы.
[Скриншот терминала с ответом от вашего сайта]
Шаг 15. Откройте порт 8081 в firewall, если требуется для проверки из браузера с другой машины (для учебных целей через curl на localhost это не обязательно, но полезно знать):
```bash
sudo ufw allow 8081/tcp
```
Шаг 16. Смоделируйте ошибку в конфигурации, чтобы увидеть, как nginx -t защищает от применения сломанной конфигурации:
```bash
sudo nano /etc/nginx/sites-available/my-first-site
```
Намеренно удалите одну закрывающую фигурную скобку } в конце файла, сохраните, и проверьте:
```bash
sudo nginx -t
```
Что вы увидите: сообщение об ошибке синтаксиса с указанием номера строки — и, что важно, systemctl reload nginx в такой ситуации не применит сломанную конфигурацию, продолжая использовать последнюю успешно загруженную рабочую версию, если вы всё же попробуете выполнить reload:
```bash
sudo systemctl reload nginx
```
Восстановите файл (верните удалённую скобку), проверьте и перезагрузите снова:
```bash
sudo nginx -t
sudo systemctl reload nginx
```
[Скриншот с ошибкой синтаксиса и последующим исправлением]
Разбор команд
Команда nginx -t
- Назначение:** проверить синтаксическую корректность конфигурации Nginx без её применения.
- Синтаксис:**
sudo nginx -t
Директивы конфигурации Nginx — сводка:
| Директива | Назначение |
|---|---|
| listen | Порт (и опционально IP), на котором принимаются соединения |
| server_name | Доменное имя, для которого предназначен блок |
| root | Корневая директория статических файлов |
| index | Файл(ы) по умолчанию для директорий |
| location | Блок обработки запросов по определённому пути |
Команда tee
- Назначение:** одновременно вывести данные на экран (стандартный вывод) и записать их в файл; в комбинации с
sudoчасто используется для записи в файлы, требующие прав root, когда прямое перенаправлениеsudo command > файлне работает из-за того, что именно перенаправление выполняется от имени исходного, непривилегированного пользователя. - Синтаксис:**
команда | tee файл;> /dev/nullв конце часто добавляется, если вывод на экран не нужен, а важна только запись в файл.
Возможные ошибки
- Ошибка:**
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use).
Почему возникает: попытка запустить (или перезапустить) Nginx, когда порт 80 уже занят другим процессом — часто это может быть другой запущенный экземпляр Nginx, Apache, или, если вы проходили Блок 17, контейнер с проброшенным на этот же порт сервисом.
Как определить: sudo ss -tlnp | grep :80 (материал Блока 12) покажет, какой именно процесс занимает порт.
Как исправить: остановить конфликтующий процесс/службу/контейнер, либо изменить порт в конфигурации Nginx на свободный.
- Ошибка:** сайт возвращает 403 Forbidden при попытке доступа.
Почему возникает: чаще всего — неправильные права доступа к файлам сайта (материал Блока 8), пользователь, от имени которого работает Nginx (www-data), не имеет прав на чтение файла или на выполнение (x) для всех родительских директорий по пути к файлу.
Как определить: проверить логи ошибок Nginx (sudo tail -20 /var/log/nginx/error.log) — там обычно точно указывается, к какому файлу/директории не удалось получить доступ.
Как исправить: проверить и скорректировать владельца (chown) и права (chmod) для файлов и всех родительских директорий на пути к ним.
- Ошибка:** изменения в конфигурации, казалось бы, применены (
systemctl reloadвыполнен без ошибок), но сайт продолжает отображать старое содержимое.
Почему возникает: чаще всего — кэширование в браузере (не связано напрямую с Nginx), либо ошибка в том, какой именно файл конфигурации был отредактирован (возможно, отредактирован файл в sites-available/, но фактически используется другой файл, или изменения внесены не в тот блок location/server, который реально обрабатывает данный запрос).
Как исправить: для проверки кэша браузера — использовать curl напрямую (обходит кэш браузера); для проверки правильности файла — использовать sudo nginx -T (заглавная T — вывести полную, объединённую конфигурацию со всеми подключёнными файлами, что помогает удостовериться, какая именно конфигурация реально активна).
Практические задания
- Создайте второй учебный сайт (по аналогии с шагами 7-14 практики) на другом порту (например, 8082), с собственным HTML-содержимым, и убедитесь, что оба сайта (на портах 8081 и 8082) работают одновременно, независимо друг от друга.
- Изучите логи доступа Nginx (
/var/log/nginx/access.log) после нескольких выполненных вами черезcurlзапросов — найдите там записи об этих запросах и определите, какую информацию о каждом запросе Nginx логирует по умолчанию (IP-адрес источника, время, запрошенный путь, код ответа и подобное — сопоставьте со структурой HTTP-запроса/ответа, изученной в предыдущем уроке). - Изучите (через
man nginxили поиск в интернете) директивуerror_pageдля настройки собственной, кастомной страницы, отображаемой при ошибке 404, вместо стандартной страницы Nginx — попробуйте настроить её для одного из ваших учебных сайтов и убедитесь в результате черезcurlзапроса к несуществующему пути.
Проверка знаний
Вопрос 1. Почему sudo systemctl reload nginx обычно предпочтительнее, чем sudo systemctl restart nginx, для применения изменений в конфигурации?
Ответ: reload выполняет аккуратную, «мягкую» перезагрузку — Nginx запускает новые worker-процессы уже с обновлённой конфигурацией, при этом позволяя старым процессам корректно завершить обработку уже установленных соединений, прежде чем полностью остановиться. Это означает, что уже подключённые клиенты не почувствуют разрыва соединения. restart, напротив, полностью останавливает и заново запускает всю службу, что может кратковременно прервать обслуживание уже идущих запросов — менее плавный подход, обычно нужный лишь в специфичных ситуациях (например, при обновлении самого исполняемого файла Nginx, а не только его конфигурации).
Вопрос 2. Зачем нужна структура с двумя отдельными директориями sites-available/ и sites-enabled/, вместо того чтобы хранить все конфигурации сайтов в одной директории?
Ответ: Такое разделение позволяет удобно «включать» и «выключать» конфигурацию конкретного сайта простым созданием или удалением символической ссылки в sites-enabled/, не удаляя саму конфигурацию физически из sites-available/. Это удобно, например, для временного отключения сайта на обслуживание (удалить ссылку из sites-enabled/, конфигурация при этом сохраняется нетронутой в sites-available/ и может быть быстро восстановлена), или для хранения нескольких альтернативных вариантов конфигурации одного сайта, из которых в данный момент активен только один.
Итоги урока
Вы изучили: роль и архитектуру Nginx, структуру конфигурационных файлов (nginx.conf, sites-available/, sites-enabled/), базовый синтаксис серверного блока (listen, server_name, root, index, location), важность проверки синтаксиса через nginx -t и корректного применения изменений через reload.
Вы умеете: устанавливать Nginx, создавать и активировать конфигурацию нового сайта, безопасно проверять и применять изменения конфигурации, диагностировать типичные ошибки (занятый порт, неправильные права доступа).
В следующем уроке потребуется: понимание базовой конфигурации одного сайта — теперь мы научимся размещать несколько независимых сайтов на одном сервере через виртуальные хосты, используя доменные имена вместо портов для их различения.
Урок 19.3. Виртуальные хосты
Понять концепцию виртуального хостинга — размещения нескольких независимых сайтов на одном физическом (или виртуальном) сервере с одним IP-адресом, различаемых по доменному имени, и научиться настраивать такую конфигурацию в Nginx.
Теория
Проблема, решаемая виртуальными хостами. В предыдущем уроке мы различали два учебных сайта по разным портам (8081 и 8082) — рабочий, но не самый типичный для реального использования подход, поскольку конечные пользователи ожидают обращаться к сайтам по обычным адресам вида example.com (порт 80/443 по умолчанию), а не запоминать нестандартные номера портов. Возникает вопрос: как разместить множество разных, полностью независимых сайтов (site-one.com, site-two.com, blog.example.com) на одном сервере, у которого обычно есть лишь один (или ограниченное число) IP-адрес, и все они по умолчанию слушают стандартный порт 80/443?
Виртуальный хостинг (virtual hosting) — решение именно этой задачи: возможность обслуживать несколько независимых доменных имён с одного и того же сервера/IP-адреса, различая их не по порту, а по информации, содержащейся непосредственно в самом HTTP-запросе.
Заголовок Host — ключевой механизм, делающий виртуальный хостинг возможным. Вспомните из урока 19.1, что HTTP-запрос содержит заголовки, включая обязательный (начиная с HTTP/1.1) заголовок Host, указывающий, к какому именно доменному имени обращается клиент. Когда браузер запрашивает http://example.com/страницаexample.com, он сначала (используя DNS, материал Блока 12) узнаёт IP-адрес, соответствующий , устанавливает соединение именно с этим IP-адресом на стандартном порту 80, но внутри самого HTTP-запроса явно указывает: Host: example.com`. Именно эту информацию Nginx (или любой другой веб-сервер) использует, чтобы определить, какой из настроенных на сервере сайтов должен обработать данный конкретный запрос — даже если технически запрос физически пришёл на один и тот же IP-адрес и порт, что и запросы ко всем остальным сайтам на этом же сервере.
Директива server_name — практическая реализация виртуального хостинга в Nginx. Мы уже видели эту директиву в предыдущем уроке, но использовали её лишь с базовым значением localhost. Теперь раскроем её реальное предназначение: Nginx сопоставляет значение заголовка Host входящего запроса со значением server_name в каждом настроенном серверном блоке, находя наиболее подходящий блок для обработки данного конкретного запроса:
```nginx
server {
listen 80;
server_name site-one.example;
root /var/www/site-one;
...
}
server {
listen 80;
server_name site-two.example;
root /var/www/site-two;
...
}
```
При такой конфигурации запрос с заголовком Host: site-one.example будет обработан первым блоком (используя файлы из /var/www/site-one), тогда как запрос с Host: site-two.example — вторым блоком, несмотря на то что оба блока слушают один и тот же порт 80.
Локальное тестирование виртуальных хостов без реальных доменных имён — файл /etc/hosts. Мы уже упоминали этот файл в Блоке 12, обсуждая базовое разрешение имён. Для практического тестирования виртуального хостинга без необходимости покупать и настраивать реальные, публично доступные доменные имена, можно временно «подделать» соответствие имени и IP-адреса именно на своей локальной машине, добавив запись в /etc/hosts:
```
127.0.0.1 site-one.local
127.0.0.1 site-two.local
```
После такой записи любая программа на вашей локальной машине (включая curl и браузер), обращающаяся к site-one.local, будет направлена на 127.0.0.1 (localhost — материал Блока 12), и при этом корректно передаст Host: site-one.local в самом HTTP-запросе — что позволяет полноценно протестировать виртуальный хостинг локально, без реальной регистрации доменных имён.
Блок default_server — обработка запросов без совпадения по имени. Что происходит, если запрос приходит с заголовком Host, не совпадающим ни с одним из настроенных server_name? Можно явно указать, какой серверный блок должен обрабатывать такие «непонятные» запросы, через модификатор default_server у директивы listen:
```nginx
server {
listen 80 default_server;
server_name _;
return 444;
}
```
(Специальное значение server_name _; — соглашение, означающее «любое не совпавшее иначе имя»; код возврата 444 — специальный, нестандартный код самого Nginx, означающий немедленное закрытие соединения без какого-либо ответа — часто применяется именно для отсекания нежелательных, «случайных» запросов, не предназначенных ни для одного из реально настроенных сайтов, что является распространённой практикой базовой защиты от automated-сканеров, обращающихся к серверу напрямую по IP, минуя настоящие доменные имена.)
Поддержка нескольких server_name в одном блоке. Один серверный блок может обслуживать сразу несколько альтернативных имён (например, основной домен и его вариант с www):
```nginx
server_name example.com www.example.com;
```
Практика
Шаг 1. Добавьте тестовые записи в /etc/hosts для двух учебных виртуальных хостов:
```bash
sudo nano /etc/hosts
```
Добавьте в конец файла:
```
127.0.0.1 site-one.local
127.0.0.1 site-two.local
```
Сохраните: Ctrl+O, Enter, Ctrl+X.
[Скриншот отредактированного /etc/hosts]
Шаг 2. Проверьте, что оба имени теперь разрешаются в localhost (материал Блока 12 про ping/DNS):
```bash
ping -c 2 site-one.local
ping -c 2 site-two.local
```
Шаг 3. Создайте директории и содержимое для двух сайтов:
```bash
sudo mkdir -p /var/www/site-one /var/www/site-two
sudo tee /var/www/site-one/index.html > /dev/null << 'EOF'
<!DOCTYPE html>
<html><head><title>Site One</title></head>
<body><h1>Это Site One</h1></body></html>
EOF
sudo tee /var/www/site-two/index.html > /dev/null << 'EOF'
<!DOCTYPE html>
<html><head><title>Site Two</title></head>
<body><h1>Это Site Two</h1></body></html>
EOF
sudo chown -R www-data:www-data /var/www/site-one /var/www/site-two
```
Шаг 4. Создайте конфигурацию виртуального хоста для первого сайта:
```bash
sudo nano /etc/nginx/sites-available/site-one
```
Содержимое:
```nginx
server {
listen 80;
server_name site-one.local;
root /var/www/site-one;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
```
Шаг 5. Создайте конфигурацию для второго сайта:
```bash
sudo nano /etc/nginx/sites-available/site-two
```
Содержимое:
```nginx
server {
listen 80;
server_name site-two.local;
root /var/www/site-two;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
```
Шаг 6. Активируйте обе конфигурации:
```bash
sudo ln -s /etc/nginx/sites-available/site-one /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site-two /etc/nginx/sites-enabled/
```
Шаг 7. Отключите конфигурацию по умолчанию, чтобы избежать конфликта на порту 80 (стандартный default-сайт тоже слушает порт 80 без ограничения по server_name, что может конфликтовать с новыми виртуальными хостами при определённых сценариях — хорошая практика при добавлении реальных виртуальных хостов отключить служебный/тестовый сайт по умолчанию):
```bash
sudo rm /etc/nginx/sites-enabled/default
```
(Обратите внимание: мы удаляем именно символическую ссылку из sites-enabled/, а не сам файл конфигурации из sites-available/ — тот остаётся нетронутым, доступным для повторной активации в будущем при необходимости, ровно как мы обсуждали в теории предыдущего урока.)
Шаг 8. Проверьте синтаксис и примените изменения:
```bash
sudo nginx -t
sudo systemctl reload nginx
```
[Скриншот успешной проверки и применения]
Шаг 9. Проверьте, что оба сайта корректно различаются по имени, несмотря на общий IP-адрес и порт:
```bash
```
Что вы увидите: разное содержимое для каждого из двух запросов, несмотря на то что оба они технически направлены на один и тот же 127.0.0.1:80 — Nginx корректно различает их по заголовку Host.
[Скриншот терминала с разным содержимым для двух хостов]
Шаг 10. Продемонстрируйте наглядно, что именно заголовок Host определяет выбор сайта — обратитесь напрямую к IP-адресу, но явно подделав заголовок Host вручную:
```bash
curl -H "Host: site-one.local" http://127.0.0.1
curl -H "Host: site-two.local" http://127.0.0.1
```
Что вы увидите: тот же результат, что и на шаге 9, но на этот раз явно продемонстрировано, что именно заголовок Host (а не то, как вы «называете» адрес в командной строке) определяет, какой сайт обрабатывает запрос — прямое, наглядное доказательство механизма, объяснённого в теории.
[Скриншот терминала с явной подменой заголовка Host]
Шаг 11. Проверьте поведение при запросе с несуществующим (не настроенным) именем хоста:
```bash
curl -H "Host: unknown-site.local" http://127.0.0.1
```
Что вы увидите: скорее всего, содержимое одного из настроенных сайтов (обычно первого сайта, определённого в конфигурации, если явный default_server не настроен) — это стандартное поведение Nginx при отсутствии явного default_server.
Шаг 12. Настройте явный блок default_server для более предсказуемой и безопасной обработки нераспознанных запросов:
```bash
sudo nano /etc/nginx/sites-available/catch-all
```
Содержимое:
```nginx
server {
listen 80 default_server;
server_name _;
return 444;
}
```
```bash
sudo ln -s /etc/nginx/sites-available/catch-all /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
Шаг 13. Повторите проверку с неизвестным именем хоста:
```bash
curl -v -H "Host: unknown-site.local" http://127.0.0.1
```
Что вы увидите: соединение закрывается без ответа (код 444 не является стандартным HTTP-кодом с телом ответа — это сигнал самого Nginx немедленно оборвать соединение) — подтверждение того, что наш default_server теперь корректно перехватывает нераспознанные запросы.
[Скриншот с обрывом соединения для неизвестного хоста]
Разбор команд
Заголовок Host HTTP-запроса — ключевой механизм, позволяющий серверу определить, к какому именно виртуальному хосту (сайту) относится запрос, даже если запрос технически пришёл на общий, единый IP-адрес/порт.
Директива server_name — сопоставляется со значением заголовка Host для выбора подходящего серверного блока Nginx; специальное значение _ используется как «подстановочное», соответствующее любому не совпавшему иначе имени, в сочетании с default_server.
Модификатор default_server у директивы listen — явно назначает конкретный серверный блок для обработки запросов, не совпавших ни с одним другим server_name.
Файл /etc/hosts — используется для локального тестирования виртуальных хостов путём временного, локального переопределения соответствия доменного имени и IP-адреса, без необходимости в реальной, публичной DNS-настройке (материал Блока 12).
Возможные ошибки
- Ошибка:** конфликт
duplicate default server for 0.0.0.0:80.
Почему возникает: попытка настроить default_server в нескольких разных серверных блоках одновременно (или забытая, всё ещё активная конфигурация по умолчанию из sites-enabled/, которая тоже неявно претендует на роль сервера по умолчанию, если не была явно отключена, как в шаге 7 практики).
Как исправить: убедиться, что модификатор default_server используется только в одном месте на каждый уникальный порт/IP-комбинацию.
- Ошибка:** после настройки виртуальных хостов один из сайтов открывается корректно, а другой — нет, хотя конфигурация выглядит идентичной по структуре.
Почему возникает: частая причина — опечатка в server_name, не совпадающая с тем, что реально прописано в /etc/hosts или с тем, что реально указывается в запросе; либо забытая символическая ссылка в sites-enabled/ для второго сайта.
Как определить: sudo nginx -T (заглавная T, полная объединённая конфигурация) поможет увидеть, действительно ли конфигурация обоих сайтов присутствует и корректна; ls /etc/nginx/sites-enabled/ проверит, что обе ссылки на месте.
Практические задания
- Настройте третий виртуальный хост, обслуживающий сразу два альтернативных доменных имени в одном серверном блоке (например,
site-three.localиwww.site-three.local, используяserver_name site-three.local www.site-three.local;) — добавьте обе записи в/etc/hostsи убедитесь черезcurl, что оба имени успешно приводят к одному и тому же содержимому. - Изучите официальную документацию Nginx (через поиск в интернете) по алгоритму выбора наиболее подходящего серверного блока при частичном совпадении
server_name(например, использование wildcard-имён вида*.example.com) — опишите в конспекте, чем поддержка wildcard-имён могла бы быть практически полезна для реального, продакшен-сценария использования (например, для поддоменов клиентов в SaaS-приложении). - Проверьте поведение вашей конфигурации, отправив запрос с полностью отсутствующим заголовком
Host(технически невалидный для HTTP/1.1 запрос, но полезная демонстрация через опциюcurl --http1.0, использующую более старую версию протокола, где заголовокHostне был обязательным) — опишите в конспекте, к какому серверному блоку в итоге был направлен такой запрос, и как это соотносится с материалом теории этого урока оdefault_server.
Проверка знаний
Вопрос 1. Как Nginx определяет, какой именно из нескольких настроенных сайтов должен обработать входящий запрос, если все они принимают соединения на одном и том же IP-адресе и порту?
Ответ: Nginx использует значение заголовка Host, содержащегося в самом HTTP-запросе (материал урока 19.1), сопоставляя его со значением директивы server_name в каждом настроенном серверном блоке. Хотя технически все запросы физически приходят на один и тот же IP-адрес/порт, содержимое заголовка Host позволяет Nginx точно определить, для какого именно доменного имени предназначен данный конкретный запрос, и, соответственно, каким серверным блоком (и с какими файлами/настройками) его следует обработать.
Вопрос 2. Зачем нужен файл /etc/hosts при локальном тестировании виртуальных хостов, если у вас нет реального, зарегистрированного доменного имени?
Ответ: /etc/hosts позволяет вручную, локально «привязать» произвольное доменное имя к конкретному IP-адресу (обычно 127.0.0.1 для тестирования на своей же машине), минуя реальную систему DNS. Это позволяет браузеру или curl, обращаясь к тестовому имени вроде site-one.local, корректно направить запрос на localhost и при этом автоматически передать это же имя в заголовке Host самого HTTP-запроса — что позволяет полноценно протестировать логику виртуального хостинга Nginx локально, без необходимости покупать и настраивать реальное, публично зарегистрированное доменное имя.
Итоги урока
Вы изучили: концепцию и практическую необходимость виртуального хостинга, роль заголовка Host HTTP-запроса, директиву server_name и механизм её сопоставления, использование /etc/hosts для локального тестирования, модификатор default_server для обработки нераспознанных запросов.
Вы умеете: настраивать несколько независимых сайтов на одном сервере, различаемых по доменному имени, тестировать виртуальные хосты локально без реальной DNS-инфраструктуры, настраивать безопасную обработку нераспознанных запросов.
В следующем уроке потребуется: понимание веб-сервера как обработчика запросов к статическим файлам — теперь мы обратимся к совершенно новой теме этого блока, необходимой для динамических, интерактивных веб-приложений — базам данных.
Урок 19.4. Введение в базы данных
Понять, зачем нужны специализированные системы управления базами данных вместо простого хранения данных в файлах, разобраться в основных концепциях реляционной модели данных, и познакомиться с языком SQL на базовом, концептуальном уровне.
Теория
Проблема, решаемая базами данных. До сих пор весь курс касался работы с данными исключительно через обычные файлы (текстовые файлы, конфигурации, логи) и файловую систему (Блок 5). Это прекрасно подходит для многих задач, но становится крайне неудобным и ненадёжным, когда речь заходит о структурированных, взаимосвязанных данных, к которым нужен быстрый, гибкий доступ, возможно, одновременно от множества разных процессов/пользователей — типичная ситуация для практически любого реального веб-приложения (интернет-магазина, социальной сети, системы бронирования, и так далее).
Проблемы простого хранения структурированных данных в обычных файлах:
- Медленный поиск.** Найти конкретную запись среди миллионов строк простого текстового файла требует последовательного просмотра всего файла (даже если
grep, изученный в Блоке 6, делает это относительно быстро для умеренных объёмов — для по-настоящему больших данных этот подход перестаёт масштабироваться). - Сложность поддержания целостности данных** при одновременном доступе нескольких процессов — риск повреждения данных при одновременной записи, необходимость сложной, вручную реализуемой логики блокировок.
- Сложность выражения связей между данными. Например, у интернет-магазина есть покупатели, товары, и заказы — заказ связан и с конкретным покупателем, и с конкретными товарами. Выразить и, что важнее, эффективно использовать** такие связи в простых текстовых файлах крайне неудобно.
- Отсутствие гарантий согласованности** — например, гарантии, что при удалении покупателя автоматически (или с явным контролем) обрабатываются связанные с ним заказы, а не остаются «висящими», ссылающимися на несуществующего покупателя.
Система управления базами данных (СУБД, DBMS) — специализированное программное обеспечение, решающее все перечисленные проблемы: эффективное индексирование для быстрого поиска, надёжная обработка одновременного доступа, встроенные механизмы обеспечения целостности данных, и мощный, стандартизированный язык для выражения сложных запросов и связей.
Реляционная модель данных — доминирующий, наиболее распространённый подход. Хотя существуют разные типы баз данных (например, так называемые NoSQL-базы данных, оптимизированные для иных сценариев использования, которые мы не будем детально разбирать в этом вводном курсе), реляционные базы данных остаются наиболее распространённым, универсальным выбором для подавляющего большинства приложений. Ключевые концепции реляционной модели:
- Таблица (table)** — основная структура хранения данных, организованная как сетка из строк и столбцов, концептуально похожая на электронную таблицу (типа тех, что создают в Excel/Google Sheets), но с гораздо более строгими правилами и мощными возможностями.
- Столбец (column, или поле, field) — определяет конкретный атрибут данных (например, «имя», «email», «дата рождения») и его тип данных** (текст, число, дата и так далее — концептуально похоже на типизацию переменных, о недостатке которой в Bash мы говорили в Блоке 15, но в базах данных типизация строгая и обязательная).
- Строка (row, или запись, record)** — одна конкретная единица данных, содержащая значения для каждого столбца (например, одна конкретная запись о конкретном покупателе).
- Первичный ключ (primary key)** — специальный столбец (или комбинация столбцов), однозначно идентифицирующий каждую строку в таблице — концептуально похож на уникальный ID, о котором мы говорили применительно к процессам (PID, Блок 11) или Docker-объектам (Блок 17), но здесь это значение, которое явно определяется структурой самих данных.
- Внешний ключ (foreign key) — столбец в одной таблице, ссылающийся на первичный ключ другой** таблицы, что и реализует «связи» между разными таблицами данных (например, столбец
customer_idв таблице «Заказы», ссылающийся на первичный ключidв таблице «Покупатели» — именно так реляционная модель элегантно выражает связи, о трудности выражения которых в простых файлах мы говорили выше). - Схема (schema)** — формальное описание структуры базы данных: какие таблицы существуют, какие в них столбцы, какого типа, какие связи между таблицами через внешние ключи.
SQL (Structured Query Language) — стандартизированный язык взаимодействия с реляционными базами данных. SQL — это не язык программирования общего назначения (в отличие от Bash, изученного в Блоке 15), а специализированный, декларативный язык, предназначенный именно для определения структуры данных и формулирования запросов к ним. «Декларативный» означает, что вы описываете что хотите получить, а не пошагово как это вычислить (в отличие от императивного подхода bash-скриптов, где мы явно описывали последовательность шагов через циклы и условия) — СУБД сама определяет наиболее эффективный способ выполнить ваш запрос.
Основные категории команд SQL (концептуальное знакомство, без практического выполнения в этом уроке — детальную практику проведём в следующем уроке на конкретной СУБД):
- DDL (Data Definition Language)** — команды определения структуры:
CREATE TABLE(создать таблицу),ALTER TABLE(изменить структуру существующей таблицы),DROP TABLE(удалить таблицу). - DML (Data Manipulation Language)** — команды манипуляции данными:
INSERT(добавить новую строку/запись),UPDATE(изменить существующие строки),DELETE(удалить строки). - DQL (Data Query Language)**, часто рассматриваемый как часть DML — команда извлечения данных:
SELECT— вероятно, наиболее часто используемая команда SQL, применяемая для формулирования запросов «дай мне данные, соответствующие таким-то условиям».
Простейший концептуальный пример SQL-запроса (детальный синтаксис подробно разберём в практике следующего урока):
```sql
SELECT имя, email FROM покупатели WHERE город = 'Рига';
```
Этот запрос декларативно описывает: «дай мне столбцы имя и email из таблицы покупатели, но только для тех строк, где столбец город равен 'Рига'» — не указывая как именно база данных должна искать и фильтровать эти данные, лишь что требуется получить в результате.
PostgreSQL vs MySQL — две наиболее популярные бесплатные, открытые реляционные СУБД. Обе системы во многом схожи по своим фундаментальным принципам (обе полностью поддерживают реляционную модель и SQL), но имеют определённые различия в деталях реализации, производительности для разных сценариев использования, дополнительных функциональных возможностях, и историческом происхождении. В следующем уроке мы познакомимся с практической работой в обеих системах, чтобы вы могли осознанно ориентироваться в них при выборе для собственных будущих проектов.
Практика
В этом вводном уроке, посвящённом концепциям, мы не устанавливаем реальную СУБД (это будет темой следующего урока), а моделируем реляционную структуру данных с помощью уже знакомых нам инструментов — чтобы прочувствовать проблему на практике, прежде чем изучать её настоящее решение.
Шаг 1. Смоделируйте простую, «наивную» попытку хранения структурированных данных в текстовом файле (аналог таблицы «Покупатели»):
```bash
mkdir -p ~/db_intro_demo
cd ~/db_intro_demo
cat > customers.csv << 'EOF'
id,name,email,city
1,Анна Иванова,anna@example.com,Рига
2,Пётр Петров,petr@example.com,Вильнюс
3,Мария Сидорова,maria@example.com,Рига
EOF
cat customers.csv
```
(Формат CSV — Comma-Separated Values, значения, разделённые запятыми — простой, широко распространённый способ представления табличных данных в текстовом виде, естественным образом иллюстрирующий структуру «строк и столбцов», о которой шла речь в теории.)
[Скриншот терминала с CSV-файлом]
Шаг 2. Смоделируйте вторую «таблицу» — заказы, ссылающиеся на покупателей через customer_id (концептуальная демонстрация внешнего ключа):
```bash
cat > orders.csv << 'EOF'
id,customer_id,product,amount
101,1,Ноутбук,1200
102,1,Мышь,25
103,2,Клавиатура,80
104,3,Монитор,300
EOF
cat orders.csv
```
Шаг 3. Попробуйте найти все заказы конкретного покупателя, используя уже знакомые нам инструменты обработки текста (материал Блока 6) — примерно так выглядела бы «связь» между таблицами без настоящей реляционной СУБД:
```bash
grep "^1," customers.csv
grep ",1," orders.csv
```
Что вы увидите: вручную «связанные» данные — но обратите внимание, насколько хрупок и неудобен этот подход: команда grep ",1," могла бы случайно совпасть с чем-то ещё, если бы структура данных была сложнее (например, если бы значение "1" могло встретиться в другом столбце) — в реальной СУБД такие связи выражаются и обрабатываются гораздо надёжнее и эффективнее, через явно определённые внешние ключи и специализированный, оптимизированный механизм поиска.
[Скриншот терминала с ручным "соединением" данных]
Шаг 4. Попробуйте смоделировать операцию «обновления» записи вручную, ещё раз убедившись в неудобстве подхода без настоящей СУБД — представим, что покупатель №2 переехал в другой город:
```bash
sed -i 's/2,Пётр Петров,petr@example.com,Вильнюс/2,Пётр Петров,petr@example.com,Каунас/' customers.csv
cat customers.csv
```
Что вы увидите: изменение действительно применилось (используя знакомый нам из Блока 6 sed), но обратите внимание, насколько специфичной, подверженной ошибкам оказалась команда — она опирается на точное совпадение всей исходной строки целиком; в реальной СУБД для той же задачи потребовался бы всего один, значительно более надёжный и понятный SQL-запрос вида UPDATE customers SET city = 'Каунас' WHERE id = 2;.
Шаг 5. Задумайтесь над практическими вопросами, которые наглядно демонстрирует эта учебная демонстрация (запишите свои размышления в конспект, не выполняя дополнительных команд):
- Что произойдёт, если два разных процесса одновременно попытаются изменить один и тот же CSV-файл?
- Как быстро искать конкретную запись, если файл содержит не 3, а 3 миллиона строк?
- Что если нужно найти всех покупателей, у которых сумма всех их заказов превышает определённое значение — насколько сложной стала бы такая задача при работе с простыми CSV-файлами через bash?
Шаг 6. Очистите директорию — мы вернёмся к аналогичным по смыслу задачам уже с настоящей СУБД в следующем уроке:
```bash
cd ~
rm -rf ~/db_intro_demo
```
Разбор команд
В этом уроке мы не вводили новых команд — использовали уже знакомые grep, sed, cat (материал Блока 6) для наглядной демонстрации ограничений подхода без специализированной СУБД, применённых к структурированным, CSV-подобным данным.
Возможные ошибки
Специфичных технических ошибок в этом концептуальном уроке не возникает — основная «ошибка», которую полезно избегать в будущем, концептуальна:
- Заблуждение:** мысль о том, что для любых структурированных данных достаточно простых текстовых файлов и знакомых инструментов текстовой обработки (
grep/sed/awk), без необходимости в настоящей СУБД.
Почему возникает: для действительно небольших, простых, редко изменяющихся наборов данных (как в нашей учебной демонстрации из трёх строк) такой подход технически работает и может показаться достаточным.
Как избежать: осознавать границы применимости такого подхода — как только данные становятся достаточно большими, требуют одновременного доступа нескольких процессов, содержат сложные связи между разными наборами данных, или требуют гарантий целостности — правильным решением становится настоящая СУБД, о которой пойдёт речь в следующем уроке.
Практические задания
- Расширьте учебные CSV-файлы из практики, добавив третью «таблицу» — например, «продукты» с столбцами
id,name,price, и модифицируйте таблицу «заказы», чтобы она ссылалась на продукты черезproduct_idвместо хранения названия продукта напрямую текстом — опишите в конспекте, почему такой подход (хранение ссылки на ID, а не дублирование полного названия в каждой строке заказа) более правильный с точки зрения принципов, изложенных в теории. - Найдите в интернете сравнение реляционных и NoSQL баз данных (упомянутых кратко в теории, но не разбираемых подробно в этом вводном курсе) — опишите в конспекте, в чём заключается основная концептуальная разница подходов, и приведите пример сценария, где NoSQL-подход мог бы быть предпочтительнее строгой реляционной модели.
- Составьте в конспекте (текстом, в виде списка столбцов для каждой таблицы, без необходимости реального выполнения) реляционную схему для гипотетического простого блога: какие таблицы понадобились бы (например, «Посты», «Авторы», «Комментарии»), какие столбцы у каждой, и какие связи (внешние ключи) существовали бы между ними.
Проверка знаний
Вопрос 1. Что такое внешний ключ (foreign key), и какую фундаментальную проблему структурированных данных он решает по сравнению с попыткой хранить связанные данные в простых текстовых файлах?
Ответ: Внешний ключ — это столбец в одной таблице, ссылающийся на первичный ключ (уникальный идентификатор) строки в другой таблице, формально и надёжно выражая связь между этими двумя наборами данных (например, связь конкретного заказа с конкретным, точно идентифицированным покупателем). Это решает проблему, которую мы наблюдали в практике этого урока при попытке связать данные вручную через grep: без формально определённого, строго типизированного и индексированного внешнего ключа, связывание данных из разных «таблиц» становится хрупким (подверженным ошибкам совпадения текста), медленным (требующим полного перебора данных при каждом поиске связи) и сложным для поддержания корректности при изменениях данных.
Вопрос 2. Почему SQL называют «декларативным» языком, и чем это принципиально отличается от подхода, использованного в bash-скриптах (Блок 15)?
Ответ: В декларативном подходе SQL-запрос описывает что именно требуется получить в результате (например, «все покупатели из Риги»), не указывая пошаговый алгоритм того, как именно эти данные нужно искать и фильтровать — эту работу берёт на себя сама СУБД, которая самостоятельно выбирает наиболее эффективный способ выполнения запроса. Bash-скрипты, изученные в Блоке 15, напротив, представляют собой императивный подход — мы явно описываем последовательность конкретных шагов (циклы, условия, конкретные команды), которые должны быть выполнены именно в указанном нами порядке, для достижения желаемого результата.
Итоги урока
Вы изучили: проблемы хранения структурированных данных в простых текстовых файлах, назначение и роль СУБД, основные концепции реляционной модели (таблица, столбец, строка, первичный и внешний ключи, схема), базовое представление о языке SQL и его декларативной природе, категории SQL-команд (DDL/DML/DQL).
Вы умеете: объяснить, зачем нужны специализированные СУБД вместо простых файлов, распознать основные элементы реляционной модели данных, привести пример простого SQL-запроса и объяснить его смысл на концептуальном уровне.
В следующем уроке потребуется: понимание концепций реляционных баз данных — теперь мы установим настоящую СУБД (PostgreSQL или MySQL) и практически применим весь изученный материал через реальные SQL-команды.
Урок 19.5. PostgreSQL и MySQL
Установить и настроить одну из популярных реляционных СУБД, освоить базовые SQL-команды для создания таблиц и манипуляции данными, и понять ключевые практические различия между PostgreSQL и MySQL.
Теория
PostgreSQL — мощная, полнофункциональная объектно-реляционная СУБД с открытым исходным кодом, известная особым вниманием к строгому соответствию стандартам SQL, расширяемости, и продвинутым возможностям для сложных, требовательных к целостности данных приложений. Часто выбирается для приложений, где особенно важны сложные запросы, целостность данных, и продвинутые типы данных (включая, например, встроенную поддержку геопространственных данных через расширения, или JSON-документов).
MySQL — другая чрезвычайно популярная, также открытая реляционная СУБД, исторически особенно широко распространённая в веб-разработке (в частности, как часть классического стека «LAMP» — Linux, Apache, MySQL, PHP), известная относительной простотой использования и высокой скоростью для типичных, распространённых сценариев веб-приложений.
Практические различия — общие соображения (не абсолютные правила, а типичные тенденции). Для подавляющего большинства обычных, типовых приложений разница между PostgreSQL и MySQL на практике не является критичной — обе системы прекрасно справляются с большинством стандартных задач. Специфичные различия становятся значимыми в основном для сложных, крупномасштабных, или специализированных сценариев использования, детальное сравнение которых выходит за рамки этого вводного курса. В этом уроке мы установим PostgreSQL как основной пример для детальной практики (поскольку это несколько более «строгий» с точки зрения синтаксиса SQL выбор, что полезно для формирования правильных, переносимых привычек), но также кратко покажем аналогичные операции в MySQL, чтобы вы могли сориентироваться в обеих системах.
Установка PostgreSQL:
```bash
sudo apt install postgresql postgresql-contrib
```
Модель пользователей и аутентификации PostgreSQL — важная особенность. PostgreSQL по умолчанию устанавливает специального системного пользователя Linux с именем postgres (концепция системных пользователей, изученная в Блоке 8), и создаёт соответствующего суперпользователя внутри самой СУБД, тоже называемого postgres. Стандартный способ первоначального доступа — переключиться на этого системного пользователя, и уже от его имени взаимодействовать с СУБД:
```bash
sudo -i -u postgres
```
Интерактивная командная оболочка PostgreSQL — psql. Аналогично тому, как Bash является интерактивной оболочкой для взаимодействия с операционной системой, psql — это интерактивная оболочка непосредственно для взаимодействия с PostgreSQL, принимающая как специальные, начинающиеся с обратного слэша \ команды самого psql (для мета-операций вроде просмотра списка таблиц), так и обычные, стандартные SQL-команды (обязательно завершаемые точкой с запятой ; — важная синтаксическая деталь SQL, отличающая её от многих других изученных нами языков и оболочек):
```bash
psql
```
Базовые SQL-команды — практическое знакомство с материалом предыдущего урока.
Создание базы данных и таблицы:
```sql
CREATE DATABASE учебная_база;
\c учебная_база
CREATE TABLE покупатели (
id SERIAL PRIMARY KEY,
имя VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
город VARCHAR(50)
);
```
Разбор синтаксиса: SERIAL — специальный тип PostgreSQL для автоматически увеличивающегося целочисленного идентификатора (удобно для первичного ключа, изученного в предыдущем уроке); PRIMARY KEY — явно объявляет столбец первичным ключом; VARCHAR(100) — текстовый тип с ограничением максимальной длины в 100 символов; NOT NULL — ограничение, требующее обязательного наличия значения (запрещающее оставлять поле пустым); UNIQUE — ограничение, гарантирующее отсутствие повторяющихся значений в этом столбце среди всех строк таблицы.
Добавление данных:
```sql
INSERT INTO покупатели (имя, email, город) VALUES ('Анна Иванова', 'anna@example.com', 'Рига');
INSERT INTO покупатели (имя, email, город) VALUES ('Пётр Петров', 'petr@example.com', 'Вильнюс');
```
Извлечение данных:
```sql
SELECT * FROM покупатели;
SELECT имя, email FROM покупатели WHERE город = 'Рига';
```
(Символ * в SQL, аналогично уже знакомому нам из Блока 5 контексту подстановки имён файлов, здесь означает «все столбцы».)
Изменение данных:
```sql
UPDATE покупатели SET город = 'Каунас' WHERE имя = 'Пётр Петров';
```
Удаление данных:
```sql
DELETE FROM покупатели WHERE имя = 'Анна Иванова';
```
Важное предупреждение — WHERE в UPDATE/DELETE. Пропуск условия WHERE в командах UPDATE или DELETE приводит к применению операции ко всем строкам таблицы одновременно — крайне частая, потенциально катастрофическая ошибка начинающих (концептуально аналогичная предупреждению об осторожности с rm -rf из Блока 5, но, возможно, даже более коварная, поскольку синтаксически абсолютно валидна и не выдаёт никакого предупреждения перед выполнением). Хорошая практика — перед выполнением UPDATE/DELETE с потенциально широким условием сначала выполнить эквивалентный SELECT с тем же условием WHERE, чтобы явно увидеть, какие именно строки будут затронуты, прежде чем реально их изменять или удалять.
Основы MySQL — для сравнения. Установка:
```bash
sudo apt install mysql-server
```
Подключение к интерактивной оболочке MySQL (называемой просто mysql, аналогично psql для PostgreSQL):
```bash
sudo mysql
```
Синтаксис большинства базовых команд (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE) в MySQL практически идентичен PostgreSQL, поскольку оба следуют общему стандарту SQL — основные различия проявляются в более специфичных, продвинутых деталях (типах данных для специфичных случаев, синтаксисе некоторых расширенных функций), которые выходят за рамки этого вводного знакомства.
Практика
Шаг 1. Установите PostgreSQL:
```bash
sudo apt update
sudo apt install postgresql postgresql-contrib
```
Шаг 2. Проверьте статус службы (материал Блока 10):
```bash
sudo systemctl status postgresql
```
[Скриншот терминала]
Шаг 3. Переключитесь на системного пользователя postgres и войдите в интерактивную оболочку:
```bash
sudo -i -u postgres
psql
```
Что вы увидите: приглашение postgres=#, обозначающее, что вы находитесь внутри интерактивной SQL-оболочки.
[Скриншот приглашения psql]
Шаг 4. Создайте учебную базу данных и переключитесь на неё:
```sql
CREATE DATABASE учебный_магазин;
\c учебный_магазин
```
Шаг 5. Создайте таблицу «покупатели», применяя материал теории:
```sql
CREATE TABLE покупатели (
id SERIAL PRIMARY KEY,
имя VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
город VARCHAR(50)
);
```
Что вы увидите: CREATE TABLE — подтверждение успешного создания.
Шаг 6. Изучите список таблиц в текущей базе данных (специальная команда psql, начинающаяся с обратного слэша):
```sql
\dt
```
Шаг 7. Изучите структуру созданной таблицы:
```sql
\d покупатели
```
Что вы увидите: подробное описание столбцов, их типов, ограничений — своего рода аналог ls -l (Блок 5), но для структуры таблицы базы данных, а не файловой системы.
[Скриншот структуры таблицы]
Шаг 8. Добавьте несколько записей:
```sql
INSERT INTO покупатели (имя, email, город) VALUES ('Анна Иванова', 'anna@example.com', 'Рига');
INSERT INTO покупатели (имя, email, город) VALUES ('Пётр Петров', 'petr@example.com', 'Вильнюс');
INSERT INTO покупатели (имя, email, город) VALUES ('Мария Сидорова', 'maria@example.com', 'Рига');
```
Шаг 9. Извлеките все данные:
```sql
SELECT * FROM покупатели;
```
Что вы увидите: таблицу с тремя строками данных, включая автоматически сгенерированные значения id благодаря SERIAL.
[Скриншот результата SELECT]
Шаг 10. Извлеките данные с условием, применяя материал теории про WHERE:
```sql
SELECT имя, email FROM покупатели WHERE город = 'Рига';
```
Шаг 11. Продемонстрируйте безопасную практику проверки перед изменением — сначала SELECT, затем UPDATE:
```sql
SELECT * FROM покупатели WHERE имя = 'Пётр Петров';
UPDATE покупатели SET город = 'Каунас' WHERE имя = 'Пётр Петров';
SELECT * FROM покупатели WHERE имя = 'Пётр Петров';
```
Что вы увидите: подтверждение того, что город действительно изменился именно для нужной, единственной затронутой строки.
Шаг 12. Создайте вторую таблицу «заказы» со внешним ключом, применяя материал теории урока 19.4 практически:
```sql
CREATE TABLE заказы (
id SERIAL PRIMARY KEY,
покупатель_id INTEGER REFERENCES покупатели(id),
товар VARCHAR(100),
сумма NUMERIC(10, 2)
);
```
(REFERENCES покупатели(id) — это и есть синтаксис объявления внешнего ключа в SQL, формально связывающий столбец покупатель_id этой таблицы с первичным ключом таблицы покупатели; NUMERIC(10, 2) — тип данных для точных числовых значений, например денежных сумм, с указанием общего количества цифр и количества цифр после десятичной точки.)
Шаг 13. Добавьте данные в новую таблицу, используя реальные id из таблицы покупателей (полученные на шаге 9):
```sql
SELECT id, имя FROM покупатели;
```
(Изучите вывод, чтобы узнать точные значения id для использования в следующей команде — замените числа ниже на реально полученные в вашей системе значения.)
```sql
INSERT INTO заказы (покупатель_id, товар, сумма) VALUES (1, 'Ноутбук', 1200.00);
INSERT INTO заказы (покупатель_id, товар, сумма) VALUES (1, 'Мышь', 25.50);
INSERT INTO заказы (покупатель_id, товар, сумма) VALUES (3, 'Монитор', 300.00);
```
Шаг 14. Выполните запрос, объединяющий данные из обеих таблиц через JOIN (продвинутая, но чрезвычайно важная и распространённая конструкция SQL, о которой мы упоминаем здесь впервые — она напрямую реализует практическую пользу внешних ключей, о которой шла речь в предыдущем уроке):
```sql
SELECT покупатели.имя, заказы.товар, заказы.сумма
FROM заказы
JOIN покупатели ON заказы.покупатель_id = покупатели.id;
```
Что вы увидите: единую, объединённую таблицу, показывающую имя покупателя рядом с его заказами — данные, физически хранящиеся в двух разных таблицах, но логически связанные и представленные вместе благодаря внешнему ключу и операции JOIN.
[Скриншот результата JOIN-запроса]
Шаг 15. Продемонстрируйте намеренную ошибку, показывающую защиту целостности данных через внешний ключ — попробуйте удалить покупателя, у которого есть связанные заказы:
```sql
DELETE FROM покупатели WHERE имя = 'Анна Иванова';
```
Что вы увидите: ошибку, связанную с нарушением ограничения внешнего ключа (violates foreign key constraint) — PostgreSQL не позволяет удалить покупателя, на которого всё ещё ссылаются существующие заказы, защищая целостность данных именно так, как мы обсуждали в теории урока 19.4.
[Скриншот ошибки нарушения целостности]
Шаг 16. Выйдите из psql и от системного пользователя postgres:
```sql
\q
```
```bash
exit
```
Шаг 17. Кратко ознакомьтесь с MySQL для сравнения (опционально, если хотите увидеть аналогичный синтаксис на практике):
```bash
sudo apt install mysql-server
sudo mysql
```
```sql
CREATE DATABASE учебный_магазин_mysql;
USE учебный_магазин_mysql;
CREATE TABLE покупатели (
id INT AUTO_INCREMENT PRIMARY KEY,
имя VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE
);
INSERT INTO покупатели (имя, email) VALUES ('Тестовый Пользователь', 'test@example.com');
SELECT * FROM покупатели;
```
Обратите внимание на небольшие синтаксические отличия от PostgreSQL: AUTO_INCREMENT вместо SERIAL, USE база_данных; вместо \c база_данных — при в целом очень похожей общей структуре и логике команд.
```sql
exit
```
Разбор команд
Основные SQL-команды, разобранные в этом уроке:
| Команда | Назначение |
|---|---|
| CREATE DATABASE | Создать новую базу данных |
| CREATE TABLE | Создать новую таблицу с определением столбцов |
| INSERT INTO ... VALUES | Добавить новую строку данных |
| SELECT ... FROM ... WHERE | Извлечь данные с необязательным условием фильтрации |
| UPDATE ... SET ... WHERE | Изменить существующие строки, соответствующие условию |
| DELETE FROM ... WHERE | Удалить строки, соответствующие условию |
| JOIN ... ON | Объединить данные из нескольких связанных таблиц в одном запросе |
Специальные команды psql (начинающиеся с \):
| Команда | Назначение |
|---|---|
| \c база_данных | Переключиться на указанную базу данных |
| \dt | Список таблиц текущей базы данных |
| \d имя_таблицы | Структура (описание столбцов) конкретной таблицы |
| \q | Выйти из psql |
Возможные ошибки
- Ошибка:** выполнение
UPDATE/DELETEбез условияWHERE, случайно затрагивающее все строки таблицы.
Почему возникает: невнимательность, забытое условие WHERE (что синтаксически абсолютно допустимо в SQL — команда без WHERE просто применяется ко всем строкам, без какого-либо предупреждения).
Как определить: неожиданно большое количество затронутых строк в подтверждающем сообщении после выполнения команды (например, UPDATE 1000 вместо ожидаемого UPDATE 1).
Как исправить: если у вас настроено резервное копирование базы данных (тема, кратко затрагиваемая далее в этом блоке и подробнее в Блоке 20) — восстановить данные из резервной копии; в рамках транзакции (продвинутая тема, выходящая за подробные рамки этого урока, но полезно знать о её существовании) возможен явный откат через ROLLBACK, если транзакция ещё не была зафиксирована через COMMIT.
Как избежать: всегда сначала выполнять эквивалентный SELECT с тем же условием WHERE для проверки, какие именно строки будут затронуты, прежде чем выполнять реальный UPDATE/DELETE.
- Ошибка:**
ERROR: relation "покупатели" does not exist.
Почему возникает: попытка обратиться к таблице, которая либо не была создана, либо находится в другой базе данных, чем та, на которую вы переключены в данный момент (частая путаница — забыть выполнить \c имя_базы после подключения к psql, оставаясь при этом в базе данных по умолчанию).
Как исправить: проверить список таблиц (\dt) и подтвердить, что вы находитесь в правильной базе данных (приглашение psql обычно показывает имя текущей базы данных).
Практические задания
- Создайте третью таблицу «товары» (с столбцами
id,название,цена), модифицируйте таблицу «заказы», чтобы она ссылалась натовар_idвместо хранения названия товара текстом напрямую (по аналогии с заданием из предыдущего урока, но теперь реализовав это по-настоящему, через реальный SQL) — выполнитеJOIN-запрос, объединяющий все три таблицы одновременно (покупатели, заказы, товары). - Изучите (через
\h ИМЯ_КОМАНДЫвнутриpsql, встроенную справку по конкретной SQL-команде) синтаксис командыALTER TABLEдля добавления нового столбца в уже существующую таблицу «покупатели» — добавьте столбецдата_регистрацииподходящего типа для хранения даты. - Потренируйтесь в написании запроса с агрегацией — изучите (через поиск в интернете) функцию
SUM()и конструкциюGROUP BYв SQL, и напишите запрос, вычисляющий суммарную стоимость всех заказов для каждого покупателя (используя таблицы «покупатели» и «заказы», созданные в практике этого урока).
Проверка знаний
Вопрос 1. Зачем перед выполнением UPDATE или DELETE с условием WHERE, затрагивающим потенциально много строк, рекомендуется сначала выполнить эквивалентный SELECT с тем же условием?
Ответ: Выполнение SELECT с тем же условием WHERE позволяет заранее, безопасно увидеть, какие именно строки будут затронуты предстоящей операцией изменения или удаления, прежде чем реально вносить необратимые (или трудно обратимые, без специальной подготовки вроде резервных копий или незафиксированных транзакций) изменения. Поскольку синтаксически SQL не выдаёт предупреждения при отсутствии условия WHERE (команда просто применяется ко всем строкам таблицы), эта практика — единственный надёжный способ убедиться в правильности условия до совершения потенциально катастрофической ошибки, аналогично тому, как разумно сначала проверить результат сложного шаблона имён файлов через ls, прежде чем применять к тому же шаблону rm (материал Блока 5).
Вопрос 2. Что демонстрирует ошибка violates foreign key constraint при попытке удалить строку из таблицы «покупатели», на которую ссылаются существующие записи в таблице «заказы», и почему это полезное, а не просто «мешающее» поведение СУБД?
Ответ: Эта ошибка демонстрирует, что СУБД активно защищает целостность данных, не позволяя создать «осиротевшие» записи — заказы, которые формально ссылались бы (через внешний ключ) на покупателя, более не существующего в базе данных. Это полезное, желаемое поведение, а не просто техническое неудобство: без такой защиты база данных могла бы легко прийти в противоречивое, некорректное состояние, где данные технически «связаны», но эта связь фактически указывает в никуда — именно эту проблему целостности данных мы обсуждали в теории урока 19.4 как одну из ключевых причин использования настоящей реляционной СУБД вместо простых текстовых файлов.
Итоги урока
Вы изучили: установку и базовую модель аутентификации PostgreSQL, работу с интерактивной оболочкой psql, основные SQL-команды (CREATE DATABASE/TABLE, INSERT, SELECT, UPDATE, DELETE), синтаксис объявления внешнего ключа и практическую пользу JOIN-запросов, защиту целостности данных через ограничения внешних ключей, краткое сравнение с MySQL.
Вы умеете: создавать базы данных и таблицы, добавлять, извлекать, изменять и удалять данные через SQL, безопасно проверять условия перед потенциально широкими изменениями, объединять данные из нескольких связанных таблиц.
В следующем уроке потребуется: работающий веб-сервер (материал уроков 19.2-19.3) и работающая база данных (этот урок) — теперь мы свяжем их вместе через промежуточное приложение, завершив полный, реалистичный веб-стек.
Урок 19.6. Связка Nginx + приложение + БД
Понять архитектуру типичного трёхуровневого веб-приложения (веб-сервер, приложение, база данных), настроить Nginx в роли обратного прокси, перенаправляющего запросы к работающему приложению, и связать всё в единую, работающую систему.
Теория
Синтез всего блока — типичная архитектура современного веб-приложения. Мы изучили HTTP (урок 19.1), веб-сервер Nginx для раздачи статических файлов (уроки 19.2-19.3), и базы данных (уроки 19.4-19.5). Реальные, динамические веб-приложения (в отличие от простых статических сайтов, с которыми мы работали до сих пор) обычно состоят из трёх логических уровней:
- Веб-сервер (Nginx) — принимает входящие HTTP-запросы от пользователей.
- Приложение (application/backend) — содержит бизнес-логику: обрабатывает запросы, выполняет вычисления, взаимодействует с базой данных, формирует ответ (часто динамически генерируемый HTML, JSON для API, и подобное).
- База данных (PostgreSQL/MySQL) — хранит постоянные данные приложения.
Роль Nginx как обратного прокси (reverse proxy) — практическая реализация концепции, упомянутой в уроке 19.2. Вместо того чтобы напрямую раздавать статические файлы с диска (как мы делали в предыдущих уроках), Nginx в этой архитектуре перенаправляет входящие запросы работающему отдельно приложению, которое обычно слушает какой-то внутренний, часто нестандартный порт, недоступный напрямую извне. Nginx получает ответ от приложения и передаёт его обратно клиенту, как будто сам его сформировал.
Зачем нужен этот дополнительный уровень, а не позволить приложению напрямую обрабатывать все запросы? Несколько важных практических причин:
- Nginx значительно эффективнее самого приложения справляется с раздачей статических файлов (изображений, CSS, JavaScript) и обработкой большого числа одновременных соединений (материал урока 19.2 про событийно-ориентированную архитектуру), разгружая приложение для того, что оно действительно должно делать — обрабатывать бизнес-логику.
- Nginx может обрабатывать SSL/TLS-шифрование (терминацию HTTPS), избавляя внутреннее приложение от этой ответственности.
- Nginx предоставляет дополнительный уровень безопасности и контроля — можно настраивать firewall-подобные правила, ограничение частоты запросов, и многое другое непосредственно на уровне Nginx, прежде чем запрос вообще достигнет самого приложения.
- Возможность легко масштабировать приложение горизонтально (запуская несколько его экземпляров) с балансировкой нагрузки через Nginx — тема, выходящая за подробные рамки этого вводного курса, но логично вытекающая из описанной архитектуры.
Директива proxy_pass — сердце конфигурации обратного прокси в Nginx:
```nginx
server {
listen 80;
server_name myapp.local;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
Разбор директив:
proxy_pass** — указывает адрес (обычноlocalhostплюс порт, на котором слушает внутреннее приложение), куда Nginx должен перенаправлять запросы, соответствующие данному блокуlocation.proxy_set_header** — позволяет модифицировать или добавлять заголовки перед пересылкой запроса приложению. Это важно, поскольку по умолчанию приложение видело бы все запросы как приходящие непосредственно от Nginx (с IP-адресом самого сервера, обычно 127.0.0.1), а не от реального внешнего клиента — директивы вродеX-Real-IPявно передают эту важную информацию дальше, приложению.
Простейшее «приложение» для демонстрации — минимальный HTTP-сервер. Для практической демонстрации всей связки нам не нужно писать полноценное, сложное приложение (это тема, выходящая далеко за рамки курса по администрированию Linux) — достаточно простейшего скрипта, слушающего порт и отвечающего на запросы, что позволит наглядно продемонстрировать принцип работы всей архитектуры. Python (кратко упоминавшийся в Блоке 3 и в контексте Docker в Блоке 17) предоставляет для этого чрезвычайно удобный, встроенный, готовый к использованию инструмент.
Полная картина взаимодействия компонентов при обработке одного запроса:
- Клиент отправляет HTTP-запрос на
myapp.local(порт 80). - Nginx принимает запрос, определяет нужный серверный блок (материал урока 19.3), и, согласно
proxy_pass, перенаправляет его внутреннему приложению на, например,127.0.0.1:3000. - Приложение обрабатывает запрос — возможно, обращаясь к базе данных PostgreSQL/MySQL (материал урока 19.5) для получения или сохранения данных.
- Приложение формирует ответ и отправляет его обратно Nginx.
- Nginx пересылает этот ответ обратно исходному клиенту.
Для клиента весь этот многоуровневый процесс полностью прозрачен — он просто видит единый HTTP-ответ, не зная (и не нуждаясь знать) о внутренней архитектуре, задействованной для его формирования.
Практика
Шаг 1. Установите Python, если он ещё не установлен (обычно уже предустановлен в Ubuntu):
```bash
python3 --version
```
Шаг 2. Создайте простейшее демонстрационное «приложение» — минимальный веб-сервер на Python, отвечающий динамически сгенерированным содержимым (в отличие от статических файлов, которые мы раздавали напрямую через Nginx в предыдущих уроках):
```bash
mkdir -p ~/demo-app
cd ~/demo-app
nano app.py
```
Содержимое:
```python
from http.server import HTTPServer, BaseHTTPRequestHandler
import datetime
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
текущее_время = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ответ = f"""
<html>
<head><title>Демо-приложение</title></head>
<body>
<h1>Привет от Python-приложения!</h1>
<p>Это динамически сгенерированная страница.</p>
<p>Текущее время на сервере: {текущее_время}</p>
<p>Запрошенный путь: {self.path}</p>
</body>
</html>
"""
self.wfile.write(ответ.encode('utf-8'))
server = HTTPServer(('127.0.0.1', 3000), Handler)
print("Приложение запущено на порту 3000...")
server.serve_forever()
```
Сохраните: Ctrl+O, Enter, Ctrl+X.
(Обратите внимание на важную деталь: приложение слушает именно 127.0.0.1 — то есть только локальные соединения, а не 0.0.0.0, что означало бы доступность со всех сетевых интерфейсов, включая внешние — материал Блока 12. Это осознанное, важное архитектурное решение: приложение не должно быть напрямую доступно из внешней сети — только через Nginx, что мы сейчас и настроим.)
Шаг 3. Запустите приложение в фоновом режиме:
```bash
python3 app.py &
```
Шаг 4. Проверьте, что приложение действительно отвечает — но обратите внимание, что мы проверяем именно порт 3000 напрямую, что в реальной эксплуатации не должно было бы быть доступно извне:
```bash
```
Что вы увидите: динамически сгенерированный HTML с текущим временем сервера — при повторном запросе время изменится, наглядно демонстрируя, что это не статический файл, а действительно генерируемое приложением содержимое.
[Скриншот терминала с ответом от Python-приложения]
Шаг 5. Настройте Nginx как обратный прокси перед этим приложением. Добавьте запись в /etc/hosts для тестового домена:
```bash
echo "127.0.0.1 myapp.local" | sudo tee -a /etc/hosts
```
Шаг 6. Создайте конфигурацию Nginx:
```bash
sudo nano /etc/nginx/sites-available/myapp
```
Содержимое:
```nginx
server {
listen 80;
server_name myapp.local;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
Шаг 7. Активируйте конфигурацию:
```bash
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
[Скриншот успешной активации]
Шаг 8. Проверьте всю связку целиком — обратитесь к Nginx (порт 80, стандартный HTTP-порт), а не напрямую к приложению:
```bash
curl http://myapp.local
```
Что вы увидите: тот же самый динамический ответ от Python-приложения, но на этот раз запрос прошёл именно через Nginx (порт 80), который перенаправил его внутреннему приложению (порт 3000) — полная демонстрация архитектуры обратного прокси.
[Скриншот терминала с ответом через Nginx]
Шаг 9. Изучите логи доступа Nginx, чтобы увидеть, что запрос действительно был зарегистрирован именно Nginx как посредником:
```bash
sudo tail -5 /var/log/nginx/access.log
```
Шаг 10. Проверьте заголовки, которые приложение реально получает благодаря proxy_set_header — модифицируйте приложение, чтобы оно выводило полученные заголовки (остановите текущее приложение и создайте расширенную версию):
```bash
kill %1
```
(%1 — способ ссылаться на первую фоновую задачу текущей сессии, о котором мы кратко упоминали в контексте фоновых процессов, но не разбирали подробно в Блоке 11/15 — альтернативный вариант, использованный ранее в этом блоке, pkill -f app.py.)
```bash
nano app.py
```
Измените метод do_GET, добавив вывод заголовков:
```python
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
заголовки_html = "<br>".join(f"{k}: {v}" for k, v in self.headers.items())
ответ = f"""
<html><body>
<h1>Полученные приложением заголовки:</h1>
<p>{заголовки_html}</p>
</body></html>
"""
self.wfile.write(ответ.encode('utf-8'))
```
Сохраните и перезапустите приложение:
```bash
python3 app.py &
curl http://myapp.local
```
Что вы увидите: среди заголовков, полученных приложением, будут присутствовать Host: myapp.local, X-Real-Ip и X-Forwarded-For с реальным IP-адресом клиента — именно те заголовки, которые мы явно настроили передавать через proxy_set_header в конфигурации Nginx.
[Скриншот с заголовками, видимыми приложением]
Шаг 11. Продемонстрируйте важность безопасности архитектуры — убедитесь, что приложение, слушающее исключительно 127.0.0.1, недоступно напрямую, минуя Nginx, с точки зрения внешней сети (хотя в рамках учебной локальной среды это сложно продемонстрировать буквально, изучите конфигурацию через ss, материал Блока 12):
```bash
sudo ss -tlnp | grep :3000
```
Что вы увидите: приложение слушает именно на 127.0.0.1:3000, а не на 0.0.0.0:3000 — подтверждение того, что оно принципиально недоступно с других машин в сети, только с самого сервера (в том числе от Nginx, который тоже работает на этом же сервере) — важная практика безопасности для внутренних, «бэкенд»-приложений, не предназначенных для прямого внешнего доступа.
Шаг 12. Остановите демонстрационное приложение и очистите тестовые данные:
```bash
kill %1
```
Шаг 13. (Дополнительно, для полноты картины) Если вы хотите продемонстрировать полную трёхуровневую архитектуру, включив взаимодействие с базой данных из урока 19.5 — модифицируйте app.py, чтобы он подключался к PostgreSQL и выводил реальные данные из таблицы «покупатели», созданной в предыдущем уроке. Это потребует установки Python-библиотеки для работы с PostgreSQL:
```bash
sudo apt install python3-psycopg2
```
Это задание оставлено как расширенная практика (описана подробнее в практических заданиях ниже), поскольку требует более глубокого знакомства с программированием на Python, выходящего за рамки основного фокуса данного курса на администрировании Linux.
Разбор команд
Директивы Nginx для обратного прокси:
| Директива | Назначение |
|---|---|
| proxy_pass | Адрес (обычно localhost:порт) внутреннего приложения, куда перенаправляются запросы |
| proxy_set_header Host $host | Передать приложению оригинальный заголовок Host из запроса клиента |
| proxy_set_header X-Real-IP $remote_addr | Передать приложению реальный IP-адрес клиента |
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for | Передать полную цепочку IP-адресов, если запрос прошёл через несколько прокси |
Переменные Nginx, использованные выше ($host, $remote_addr, $proxy_add_x_forwarded_for) — специальные, автоматически заполняемые переменные, доступные внутри конфигурации Nginx, содержащие соответствующую информацию о текущем обрабатываемом запросе.
Возможные ошибки
- Ошибка:**
502 Bad Gatewayпри попытке обратиться к сайту через Nginx.
Почему возникает: Nginx не может получить корректный ответ от приложения, указанного в proxy_pass — чаще всего потому, что приложение вообще не запущено, слушает другой порт, или упало с ошибкой.
Как определить: проверить, что приложение действительно работает и слушает ожидаемый порт (sudo ss -tlnp | grep порт, материал Блока 12); проверить логи самого приложения на предмет ошибок; проверить логи ошибок Nginx (sudo tail -20 /var/log/nginx/error.log) — там обычно указывается точная причина, по которой не удалось связаться с upstream-приложением.
Как исправить: убедиться, что приложение запущено, слушает правильный адрес/порт, соответствующий указанному в proxy_pass, и в целом работоспособно.
- Ошибка:** приложение получает не тот IP-адрес клиента, который ожидается (видит IP самого Nginx вместо реального внешнего клиента).
Почему возникает: отсутствуют или неправильно настроены директивы proxy_set_header X-Real-IP/X-Forwarded-For — без них приложение по умолчанию видит только IP-адрес того, кто непосредственно установил с ним соединение, то есть самого Nginx (обычно 127.0.0.1), а не оригинального, внешнего клиента.
Как исправить: добавить соответствующие директивы proxy_set_header, как показано в практике этого урока, и убедиться, что само приложение действительно читает и использует именно эти заголовки для определения «настоящего» IP-адреса клиента, а не полагается на IP-адрес непосредственного TCP-соединения.
Практические задания
- Настройте второе демонстрационное Python-приложение на другом внутреннем порту (например, 3001), с собственным, отличающимся содержимым, и настройте для него отдельный виртуальный хост в Nginx (комбинируя материал этого урока с уроком 19.3) — убедитесь, что оба «приложения» доступны через свои отдельные доменные имена, каждое через собственный обратный прокси.
- Изучите директиву
proxy_passприменительно не ко всему сайту (location /), а только к определённому пути — например, настройте конфигурацию, при которой Nginx раздаёт статические файлы напрямую из/var/www/для путей вроде/static/, но перенаправляет все остальные запросы (location /) внутреннему приложению — это чрезвычайно распространённый, практичный паттерн в реальных production-конфигурациях, эффективно комбинирующий оба режима работы Nginx, изученные в этом блоке. - (Продвинутое, опциональное задание) Модифицируйте
app.py, чтобы он действительно подключался к базе данных PostgreSQL, созданной в уроке 19.5, и отображал реальные данные из таблицы «покупатели» в своём HTML-ответе — это потребует самостоятельного изучения базового синтаксиса библиотекиpsycopg2для Python (через поиск в интернете или официальную документацию), но полностью замкнёт цепочку «Nginx → Приложение → База данных», продемонстрированную теоретически в этом уроке, в виде реально работающей системы.
Проверка знаний
Вопрос 1. Зачем демонстрационное приложение в практике этого урока было настроено слушать именно 127.0.0.1:3000, а не 0.0.0.0:3000, и почему это важная практика безопасности?
Ответ: Слушая только 127.0.0.1 (loopback-адрес, материал Блока 12), приложение принимает соединения исключительно от процессов, работающих на этой же самой машине (включая Nginx) — оно принципиально недоступно напрямую из внешней сети, даже если бы кто-то знал его порт. Это важная практика безопасности: внутренние приложения, не предназначенные для прямого внешнего доступа (а предназначенные исключительно для обработки запросов, уже прошедших через Nginx с его дополнительным уровнем контроля и безопасности), не должны быть доступны напрямую, минуя этот контролирующий уровень — иначе злоумышленник мог бы обратиться прямо к внутреннему порту приложения, полностью обходя все меры защиты, настроенные на уровне Nginx.
Вопрос 2. Что означает ошибка 502 Bad Gateway, и на диагностику какого именно компонента архитектуры (из трёх, изученных в этом уроке) она обычно указывает в первую очередь?
Ответ: 502 Bad Gateway означает, что Nginx (выступающий в роли посредника, «шлюза») не смог получить корректный ответ от того компонента, которому он попытался перенаправить запрос через proxy_pass — то есть от внутреннего приложения. Эта ошибка указывает на необходимость диагностики именно уровня приложения: работает ли оно вообще, слушает ли ожидаемый порт, не завершилось ли с ошибкой при обработке конкретного запроса — сам Nginx в данном случае функционирует нормально (иначе клиент вообще не получил бы от него никакого HTTP-ответа), проблема именно в вышестоящем (upstream) сервисе, к которому Nginx пытается обратиться.
Итоги урока
Вы изучили: архитектуру типичного трёхуровневого веб-приложения (веб-сервер, приложение, база данных), роль Nginx как обратного прокси, синтаксис директивы proxy_pass и proxy_set_header, полный путь обработки запроса через все уровни архитектуры, практику ограничения приложения только локальным доступом для безопасности.
Вы умеете: настраивать Nginx для перенаправления запросов внутреннему приложению, передавать важные заголовки (реальный IP клиента, оригинальный Host) через прокси, диагностировать характерную ошибку 502 Bad Gateway, понимать полную картину взаимодействия компонентов современного веб-приложения.
В следующем блоке потребуется: весь материал этого блока и всего курса в целом — Блок 20 объединит всё изученное в комплексную задачу настройки и обслуживания реального сервера, включая полноценное развёртывание именно такой веб-инфраструктуры, которую мы собрали в этом уроке.
Мини-проект блока
Задача: объединить весь материал блока в единую, комплексную, реалистичную инфраструктуру, демонстрирующую уверенное владение всеми изученными компонентами.
Что нужно сделать:
- Настройте Nginx с как минимум тремя различными сайтами, используя виртуальные хосты (материал урока 19.3):
- Один статический сайт, напрямую раздающий HTML-файлы с диска (материал урока 19.2).
- Один сайт, работающий как обратный прокси к простому Python-приложению (материал урока 19.6).
- Один блок
default_server, корректно обрабатывающий нераспознанные запросы.
- Настройте PostgreSQL (материал урока 19.5) с базой данных, содержащей минимум две связанные таблицы (по аналогии с «покупатели» и «заказы» из практики), с несколькими записями данных.
- Свяжите Python-приложение с базой данных (расширяя опциональное задание урока 19.6) — приложение должно реально подключаться к PostgreSQL и отображать данные оттуда в своём HTTP-ответе, замыкая полную трёхуровневую архитектуру.
- Примените материал предыдущих блоков курса для обеспечения безопасности и надёжности всей настроенной инфраструктуры:
- Настройте UFW (материал Блока 14), разрешив только необходимые порты.
- Убедитесь, что внутреннее приложение и база данных не доступны напрямую извне, только через Nginx.
- Настройте (по аналогии с материалом Блока 15) простой bash-скрипт, проверяющий доступность всех трёх сайтов через
curlи корректность их кодов ответа, с логированием результата. - Настройте (материал Блока 18) периодическую проверку логов Nginx на предмет ошибок 5xx через
journalctl/анализ файлов логов.
- Задокументируйте всю архитектуру в файле
README.md(материал Блока 16, с возможностью версионирования всей конфигурации через Git) — включая схему взаимодействия компонентов, инструкции по развёртыванию с нуля, и список настроенных мер безопасности.
Критерий готовности: у вас есть работающая, задокументированная инфраструктура из нескольких сайтов, включающая полную трёхуровневую архитектуру (Nginx → Приложение → База данных) для как минимум одного из них, с применёнными мерами безопасности и базовым автоматизированным мониторингом доступности.
Контрольные вопросы
- Опишите структуру HTTP-запроса и HTTP-ответа, перечислив основные составляющие каждого из них.
- В чём разница между кодами состояния HTTP категорий 4xx и 5xx?
- Объясните архитектурное решение, делающее Nginx эффективным при обработке большого числа одновременных соединений.
- Как Nginx определяет, какой из нескольких настроенных виртуальных хостов должен обработать конкретный входящий запрос?
- Зачем нужен файл
/etc/hostsпри локальном тестировании виртуальных хостов? - Что такое первичный ключ и внешний ключ в реляционной модели данных, и как они связаны друг с другом?
- Почему рекомендуется выполнять
SELECTс тем же условиемWHEREперед выполнением потенциально широкогоUPDATEилиDELETE? - Опишите архитектуру трёхуровневого веб-приложения (веб-сервер, приложение, база данных) и роль каждого уровня.
- Что означает ошибка 502 Bad Gateway, и диагностику какого компонента архитектуры она предполагает?
- Почему внутреннее приложение в архитектуре с обратным прокси рекомендуется настраивать на прослушивание исключительно
127.0.0.1, а не всех сетевых интерфейсов?
Выводы по блоку
Этот блок объединил практически весь материал курса в единую, реалистичную, работающую систему — именно так, как устроена значительная часть современной интернет-инфраструктуры. Вы прошли путь от фундаментального понимания протокола HTTP, через практическую настройку веб-сервера Nginx (сначала для простой раздачи статических файлов, затем для обслуживания нескольких независимых сайтов через виртуальные хосты), к знакомству с реляционными базами данных и языком SQL, и в завершение — к сборке полной, многоуровневой архитектуры, связывающей все изученные компоненты в единое, работающее целое.
Важно осознавать: каждая из тем этого блока (HTTP, Nginx, SQL, архитектура веб-приложений) сама по себе является предметом отдельных, весьма объёмных специализированных курсов. Материал этого блока даёт вам прочный, практический фундамент — достаточный для того, чтобы уверенно разворачивать, настраивать и поддерживать типичную веб-инфраструктуру, диагностировать распространённые проблемы, и осознанно ориентироваться в этой предметной области для дальнейшего самостоятельного углубления, если ваш профессиональный путь потребует более глубокой специализации в любом из этих направлений.
Материал этого блока станет центральным для оставшейся части курса: в Блоке 20 (серверное администрирование) мы будем разворачивать именно такую инфраструктуру на реальном, боевом сервере с нуля, применяя весь накопленный курсом материал о безопасности, автоматизации и мониторинге; а в финальном практическом проекте Блока 21 развёртывание защищённого веб-приложения с базой данных, скорее всего, станет центральной, интегрирующей частью итоговой работы.
Список изученных тем
- HTTP: модель клиент-сервер, stateless-природа, структура запроса и ответа, методы (GET/POST/PUT/DELETE и другие), коды состояния по категориям (1xx-5xx), разница HTTP/HTTPS.
- Nginx: архитектура (событийно-ориентированная модель), роли (раздача файлов, обратный прокси, балансировка), структура конфигурации (
sites-available/sites-enabled), директивы серверного блока (listen,server_name,root,index,location), проверка синтаксиса и применение изменений (nginx -t,reload). - Виртуальные хосты: роль заголовка
Host, директиваserver_name, локальное тестирование через/etc/hosts,default_server. - Базы данных: проблемы хранения структурированных данных в файлах, реляционная модель (таблица, столбец, строка, первичный/внешний ключ, схема), SQL и его декларативная природа, категории команд (DDL/DML/DQL).
- PostgreSQL и MySQL: установка, модель аутентификации PostgreSQL, интерактивная оболочка
psql, основные SQL-команды (CREATE/INSERT/SELECT/UPDATE/DELETE/JOIN), защита целостности через внешние ключи. - Связка компонентов: архитектура трёхуровневого приложения, Nginx как обратный прокси, директивы
proxy_pass/proxy_set_header, диагностика 502 Bad Gateway, практика ограничения доступа к внутреннему приложению.
Рекомендации перед переходом
Убедитесь, что вы умеете:
- Настроить работающий сайт на Nginx с нуля, включая проверку синтаксиса и корректное применение конфигурации.
- Настроить несколько виртуальных хостов на одном сервере, различаемых по доменному имени.
- Создать базу данных, таблицы со связями через внешние ключи, и выполнить базовые операции CRUD (Create/Read/Update/Delete) через SQL.
- Настроить Nginx в роли обратного прокси перед работающим приложением, и диагностировать характерную ошибку 502 Bad Gateway.
В Блоке 20 мы применим весь материал курса целиком к комплексной, реалистичной задаче: развёртыванию и обслуживанию реального сервера с нуля — от выбора и аренды VPS до настройки регулярного резервного копирования, документирования инфраструктуры, и обеспечения бесперебойной работы именно такой веб-инфраструктуры, какую мы собрали в этом блоке.