Когда сайтов становится чуть больше чем один, а ресурсов одного сервера хватает с лихвой, встает вопрос как не переплачивать и упаковать все в одну виртуальную машину самого привлекательного сервиса, с учетом того, что когда-нибудь наши приложения разрастутся в масштабную распределенную сеть — мы обязаны заложить зернышко highload.
Описанное ниже скорее будет полезно тем, кто только начинает идти уверенными шагами в своем ремесле.
Из превьюшки видно, всю текущую архитектуру, она довольна проста, в основе лежит docker, который включает в себя
- Nginx контейнер который смотрит во вне и проксирует все запросы
- Много-много наших приложений, соответственно заключенных в контейнеры
- Управление всем процессом
Настроим окружение
sudo apt update
sudo apt install docker.io
mkdir /home/$USER/app
Давайте развернем наши приложения
Добавлю небольшую помарочку, для тех кто еще мало знаком с docker, все контейнеры приложений будут запущены без доступа ко внешней среде, это очень удобно — сильно увеличивает безопасность, доступ к ним будет осуществляться только путем проксирования трафика через nginx. Вы также можете запустить для своих нужд отдельные хосты, например с MariaDB или Mongo и получить к ним доступ по локальному IP.
Первое будет на nodejs с подключенным ssl
mkdir /home/$USER/app/web-one.oyeooo.com
mkdir /home/$USER/app/web-one.oyeooo.com/ssl
nano /home/$USER/app/web-one.oyeooo.com/index.js
nano /home/$USER/app/web-one.oyeooo.com/package.json
const fs = require("fs"),
https = require("https"),
express = require("express"),
app = express(),
port = 443
let options = {
key: fs.readFileSync("ssl/web-one.oyeooo.com.key"),
cert: fs.readFileSync("ssl/web-one.oyeooo.com.crt")
}
https.createServer(options, app).listen(port, function(){
console.log("Express server listening on port " + port);
})
app.get("/", function (req, res) {
res.writeHead(200)
res.end("Oyeooo")
})
{
"name": "oyeooo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
}
}
Получаем SLL и кладем сертификаты в папку /home/app/web-one.oyeooo.com/ssl
Второе будет простое apache static приложение
mkdir /home/$USER/app/web-two.oyeooo.com
nano /home/$USER/app/web-two.oyeooo.com/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to me!</title>
</head>
<body>
<h1>Oyeooo!</h1>
<p><em>Thank you for using habr.</em></p>
</body>
</html>
Создаем сеть
docker network create --subnet=172.18.0.0/24 oyeooo
Теперь запускаем контейнеры
sudo docker run --net oyeooo --ip 172.18.0.2 --name web-one -v /home/$USER/app/web-one.oyeooo.com:/home/app -it node bash
cd /home/app
npm i
npm start
ctrl + q + p
sudo docker run --net oyeooo --ip 172.18.0.3 --name web-two -d -v /home/$USER/app/web-two.oyeooo.com:/usr/local/apache2/htdocs httpd
ctrl + q + p
Переходим к nginx-proxy
mkdir /home/$USER/app/nginx
mkdir /home/$USER/app/nginx/conf
mkdir /home/$USER/app/nginx/ssl
mkdir /home/$USER/app/nginx/logs
mkdir /home/$USER/app/nginx/logs/web-one.oyeooo.com
mkdir /home/$USER/app/nginx/logs/web-two.oyeooo.com
nano /home/$USER/app/nginx/conf/web-one.oyeooo.com.conf
nano /home/$USER/app/nginx/conf/web-two.oyeooo.com.conf
server {
listen 80;
server_name web-one.oyeooo.com;
access_log /var/log/nginx/web-one.oyeooo.com/http-access.log;
error_log /var/log/nginx/web-one.oyeooo.com/http-error.log;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name web-one.oyeooo.com.conf;
access_log /var/log/nginx/web-one.oyeooo.com/https-access.log;
error_log /var/log/nginx/web-one.oyeooo.com/https-error.log;
ssl_certificate /etc/nginx/ssl/web-one.oyeooo.com/web-one.oyeooo.com.crt;
ssl_certificate_key /etc/nginx/ssl/web-one.oyeooo.com/web-one.oyeooo.com.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
location / {
proxy_pass https://172.18.0.2/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
client_max_body_size 512M;
}
}
server {
listen 80;
server_name web-two.oyeooo.com;
access_log /var/log/nginx/web-two.oyeooo.com/http-access.log;
error_log /var/log/nginx/web-two.oyeooo.com/http-error.log;
location / {
proxy_pass http://172.18.0.3/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
client_max_body_size 512M;
}
}
Разворачиваем nginx:
sudo docker run --net oyeooo --ip 172.18.0.4 --name nginx -it -v /home/$USER/app/nginx/logs:/var/log/nginx -v /home/$USER/app/nginx/ssl:/etc/nginx/ssl -v /home/$USER/app/nginx/conf:/etc/nginx/conf.d -p 80:80 -p 443:443 nginx bash
#Проверим присутствуют ли наши conf и ssl
ls /etc/nginx/conf.d
ls /etc/nginx/ssl
#Запускаем nginx
service nginx start
Окружение развернуто. Спасибо!
Регулировать наши маршруты можно файлами conf — создаем или удаляем и перезагружаем:
sudo docker exec -it nginx nginx -s reload
Если статья выйдет из песочницы — напишу crm для управления логикой.
Комментарии (14)
apapacy
18.06.2019 18:37По поводу прокси-пас в конфигах я искал более-менее полное и «без танце с бубном» решение такое чтобы поддердивало например и сокеты, и переписывало правильные ip-адреса. Пока что склоняюсь к такому решению:
proxy_pass http://уууу:хххх; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-NginX-Proxy true;
IRT
19.06.2019 11:23С бекэндами nginx там все понятно, вы лучше напишите про распределенную базу данных.
Нужно обеспечить 99,99% доступность системы. При выходе из строя одного сервера Nginx переключит на другой бекэнд, а база?
Хотелось бы, что бы каждый из бекэндов был полностью независимый, чтобы при возвращении онлайн он собирал с других серверов изменения в свою базу и продолжал работать, распределяя нагрузку в системе. Как это хоть примерно реализовать?oyeooo Автор
19.06.2019 11:38Ну в статье я заложил зернышко highload, зернышко — контейнеры которые мы сначала можем вынести на отдельный сервер а потом и масштабировать.
Если я вас правильно понял, Вы говорите про распределенные системы
devian3000
19.06.2019 12:19severalnines.com/blog/how-deploy-postgresql-high-availability
Базы можно расположить в docker, но есть нюанс, если грохнуть все контейнеры, или грохнуть до окончания синхронизации, то все данные упадут вместе с контейнером.
Так что как вариант — проброс данных в хостовую систему. Для кворума лучше использовать 3 сервера с базами. Чтобы если грохнулась одна, оставалось ещё две и если грохнется ещё одна база до восстановления первой, приложение жило.
de1m
19.06.2019 19:49+1Мы до того, как на kubernetes пересели, использовали traefik. Ещё на нескольких докер серверах крутиться. Он может всё, что вы написали, только автоматически и ещё Let's encrypt сертификаты.
Выглядит это как-то так.
docker-compose.yaml для traefik
Заголовок спойлераnetworks:
traefik:
external: true
services:
traefik:
image: traefik:alpine
networks:
traefik: null
ports:
- 80:80/tcp
- 443:443/tcp
restart: unless-stopped
volumes:
- ./config/traefik.toml:/traefik.toml:rw
- /a/data/traefik/acme.json:/acme.json:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
version: '3.0'
devian3000
Я бы сказал что это не очень хорошее решение.
Есть три более правильных подхода
1) Minikube со всеми вытекающими
2) Поставить на хостовую машину nginx и использовать его как proxy, сертификаты для SSL можно хранить в нём же. Достаточно поднять пул контейнеров для каждого приложения на разных портах и проксировать запросы на них. (Чтобы доступа извне не было достаточно написать 127.0.0.1:%port%:80)
3) Если и использовать nginx в контейнере, тогда использовать docker external network и разделить приложения по подсетям докера (очень геморойный путь, проще использовать пункт 2)
oyeooo Автор
Там была поправочка один из ста способов:)
Kubernetes — это следующий шаг, немного не пойму зачем использовать разные порты и nginx ставить непосредственно на хосте? Ну а по сетям разделять — это уже тоже дополнительный шаг изоляции.
devian3000
Потому что nginx работает как reverse proxy, и отправлять его на ip адреса внутренней сети докера грешно. Во-первых могут быть разные подсетки. Во вторых после ребута или ребилда докер контейнер может сменить IP адрес docker сети и вы получите нерабочий хост. Придётся лезть в nginx править конфиг.
Поэтому и линкуют контейнеры через имена, потому что IP может быть любым и в теории может сменится, так как там свой аналог dhcp
oyeooo Автор
Если назначить ip адреса контейнерам, то даже при рестарте системы они не изменятся. Спасибо, добавил в статью.