Один из сотни способов публикации нескольких production проектов на одном сервере

image
Когда сайтов становится чуть больше чем один, а ресурсов одного сервера хватает с лихвой, встает вопрос как не переплачивать и упаковать все в одну виртуальную машину самого привлекательного сервиса, с учетом того, что когда-нибудь наши приложения разрастутся в масштабную распределенную сеть — мы обязаны заложить зернышко 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

index.js
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")
})

package.json
{
  "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

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

web-one.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;
    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;
    }
}

web-two.oyeooo.com.conf
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, простите, на деле система все больше и больше хочет разрастись и превратиться в в gui для управления докер, которы сейчас есть. Простите.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 17

    0
    Я бы сказал что это не очень хорошее решение.
    Есть три более правильных подхода
    1) Minikube со всеми вытекающими
    2) Поставить на хостовую машину nginx и использовать его как proxy, сертификаты для SSL можно хранить в нём же. Достаточно поднять пул контейнеров для каждого приложения на разных портах и проксировать запросы на них. (Чтобы доступа извне не было достаточно написать 127.0.0.1:%port%:80)
    3) Если и использовать nginx в контейнере, тогда использовать docker external network и разделить приложения по подсетям докера (очень геморойный путь, проще использовать пункт 2)
      0
      Там была поправочка один из ста способов:)
      Kubernetes — это следующий шаг, немного не пойму зачем использовать разные порты и nginx ставить непосредственно на хосте? Ну а по сетям разделять — это уже тоже дополнительный шаг изоляции.
        0
        Потому что nginx работает как reverse proxy, и отправлять его на ip адреса внутренней сети докера грешно. Во-первых могут быть разные подсетки. Во вторых после ребута или ребилда докер контейнер может сменить IP адрес docker сети и вы получите нерабочий хост. Придётся лезть в nginx править конфиг.
        Поэтому и линкуют контейнеры через имена, потому что IP может быть любым и в теории может сменится, так как там свой аналог dhcp
          0
          Если назначить ip адреса контейнерам, то даже при рестарте системы они не изменятся. Спасибо, добавил в статью.
      0
      По поводу прокси-пас в конфигах я искал более-менее полное и «без танце с бубном» решение такое чтобы поддердивало например и сокеты, и переписывало правильные 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;
      
        0
        Спасибо! Поправил.
        0
        Use docker-compose Luke
          0
          Ждём CRM для управления логикой.
            0
            Приятно. В воскресенье вечером)
              0
              Прошу прощения. Еще пару дней и залью на git
                0
                Почти готово. Сегодня-завтра (До вечера среды) и выкладываю.
                  0

                  Ничего не вышло с crm, простите, на деле система все больше и больше хочет разрастись и превратиться в в gui для управления докер, которых сейчас уже есть. Простите.

                  0
                  С бекэндами nginx там все понятно, вы лучше напишите про распределенную базу данных.
                  Нужно обеспечить 99,99% доступность системы. При выходе из строя одного сервера Nginx переключит на другой бекэнд, а база?
                  Хотелось бы, что бы каждый из бекэндов был полностью независимый, чтобы при возвращении онлайн он собирал с других серверов изменения в свою базу и продолжал работать, распределяя нагрузку в системе. Как это хоть примерно реализовать?
                    0
                    Ну в статье я заложил зернышко highload, зернышко — контейнеры которые мы сначала можем вынести на отдельный сервер а потом и масштабировать.
                    Если я вас правильно понял, Вы говорите про распределенные системы
                      0
                      severalnines.com/blog/how-deploy-postgresql-high-availability

                      Базы можно расположить в docker, но есть нюанс, если грохнуть все контейнеры, или грохнуть до окончания синхронизации, то все данные упадут вместе с контейнером.
                      Так что как вариант — проброс данных в хостовую систему. Для кворума лучше использовать 3 сервера с базами. Чтобы если грохнулась одна, оставалось ещё две и если грохнется ещё одна база до восстановления первой, приложение жило.
                      +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'


                      config.toml
                      Заголовок спойлера
                      debug = false
                      checkNewVersion = false
                      logLevel = "ERROR"
                      defaultEntryPoints = ["https","http"]

                      [entryPoints]
                      [entryPoints.http]
                      address = ":80"
                      [entryPoints.http.redirect]
                      entryPoint = "https"
                      [entryPoints.https]
                      address = ":443"
                      [entryPoints.https.tls]

                      [retry]

                      [docker]
                      endpoint = "unix:///var/run/docker.sock"
                      domain = "docker.localhost"
                      watch = true
                      exposedbydefault = false

                      [acme]
                      email = "administrator@example.com"
                      storage = "acme.json"
                      entryPoint = "https"
                      OnHostRule = true
                      [acme.httpChallenge]
                      entryPoint = "http"




                      И часть docker-compose.yaml из контейнера, где это используется:
                      Заголовок спойлера
                      networks:
                      traefik:
                      external: true
                      services:
                      postfix:
                      image: marooou/postfix-roundcube
                      environment:
                      ADMIN_USERNAME: root
                      ADMIN_PASSWD: pass
                      DOMAIN_NAME: example.com
                      labels:
                      traefik.backend: example-demo-mailhog
                      traefik.docker.network: traefik
                      traefik.enable: "true"
                      traefik.frontend.rule: Host:example.com;PathPrefixStrip:/postfix/
                      traefik.port: '80'
                      ports:
                      - 10110:110/tcp #POP3 not SSL
                      - 10143:143/tcp #IMAP not SSL
                      - 10025:25/tcp #SMTP not SSL
                      - 10465:465/tcp #SMTPS
                      - 10993:993/tcp #IMAPS
                      - 10995:995/tcp #POP3S
                      networks:
                      default: null
                      traefik: null
                      volumes:
                      - /a/data/example.com/mail/mysql:/var/lib/mysql
                      - /a/data/example.com/mail/vmail:/var/vmail
                      - /a/data/example.com/mail/log:/var/log
                      restart: unless-stopped
                      version: '3.0'



                      Это вариант конфигурации, при которой создаются сертификаты от Let's encrypt.
                      Делается всё просто. Стартуете контейнер с traefik, а в остальных добавляете сеть и лайблы и всё.
                        0

                        Спасибо за инфу!
                        Полезный момент, особенно с Let's encrypt

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

                      Самое читаемое