MeteorJS, Nginx, mongodb, iptables… продакшен

Здравствуйте, меня зовут Александр Зеленин, и я веб-разработчик сисадмин.


К сожалению, вся информация о полноценной развёртке приложения на MeteorJS довольно разрозненна, и приходится вручную решать миллион задач. В этой статье я хочу разобрать самую базовую (но уже достаточную для безопасной работы в продакшене) настройку сервера и последующий процесс ручного деплоя.


Разворачивать будем на Ubuntu 16, но в целом схема на 99% совпадает и для Debian 8.


На самом деле я и близко не сисадмин, так что буду очень рад предложенным правкам, но в целом схема достаточно рабочая.


В качестве файловой системы при установке выбираем XFS — монга с ней хорошо дружит.


Готовим SSH

Если доступ у нас рутовый, то первое, что надо сделать — создать пользователя.


adduser zav # Используйте свой %username% вместо zav
apt-get install sudo 
usermod -aG sudo zav # Добавляем нашего пользователя в группу sudo, 
                     # чтобы иметь возможность далее всё делать из-под него
vi /etc/ssh/sshd_config # Если вы совсем не можете в vi — используйте тут nano вместо vi. 
                        # Если он не установлен — apt-get install nano

Нам надо изменить порт на случайный, по вашему выбору — это сразу защитит от части атак на ssh сервера.


Port 355 # Обычно изначально стоит Port 22
PermitRootLogin no # Запрещаем вход под рутом

Перезагружаем SSH


/etc/init.d/ssh restart

Теперь переподключаемся по SSH на новый порт (355), на нового, свежесозданного пользователя.


Подготавливаем второй диск и tmpfs (чтобы разместить монгу в памяти)

Я использую 2 раздельных физических диска чтобы минимизировать шанс отказа.
Вы можете размещать всё на 1 на свой страх и риск, схема от этого не изменится.
В этом случае можете точно так же использовать папку /secondary, но не монтировать в неё диск.


sudo cfdisk /dev/sdb # имя второго диска (sdb) может отличаться.
                     # Выбираем создать новый, 100% места, запись
sudo mkfs.xfs /dev/sdb1 # создаем ФС на втором диске
sudo mkdir /secondary
sudo vi /etc/fstab # делаем авто-монтирование при запуске системы

Добавляем в конец (если второго диска нет — первую строку опускаем)
tmpfs — файловая система будет расположена в оперативной памяти. Т.е. запись в раздел /data/inmemory будет делать запись в оперативную память, а не на диск. Надо понимать, что при перезагрузке оно очищается. size задает размер данной области. Ваша задача — что бы её хватило на размещение монги со всеми индексами. В моём случае оперативной памяти 128Gb, соответственно под tmpfs выделено 32 гигабайта.


/dev/sdb1       /secondary      xfs     defaults        0       2
tmpfs           /data/inmemory  tmpfs   size=25%        0       0

Ставим монгу, настраиваем реплику, подготавливаем реалтайм бекап

Актуальный способ установки можно посмотреть на официальном сайте.
В данном примере идёт установка версии 3.4 на Ubuntu 16.


Устанавливаем:


sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
echo "deb [ arch=amd64,arm64 ] http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
sudo apt-get update
sudo apt-get install mongodb-org

Создаем папки, конфиги и ставим права:


sudo mkdir /data
sudo mkdir /data/db
sudo mkdir /data/inmemory
sudo mkdir /secondary/data
sudo mkdir /secondary/data/db
sudo vi /data/mongod-memory.conf

Подход, в котором primary в памяти может быть опасен, т.к. в случае внезапного выключения питания данные, которые не успели перейти в реплики — теряются. В моём случае потеря 1–2 секунд не страшна, т.к. всё приложение пишется с учетом возможности восстановления из любой позиции. Финансовые данные пишутся с параметром, подтверждающим, что данные уже есть на реплике (т.е. на диске).
Если ваше приложение к такому не готово — вы можете отказаться от memory раздела и делать всё классически. В целом для этого достаточно будет убрать монтирование tmpfs и немного изменить конфиг, сделав его схожим с mongod-sec-d1.conf


mongod-memory.conf — primary, in memory
processManagement:
  fork: true 
  pidFilePath: "/data/inmemory/mongod.pid"

storage:
  dbPath: "/data/inmemory"
  journal:
    enabled: false # Отключаем журналирование
                   # т.к. в памяти в случае падения оно нас всё равно не спасёт
  indexBuildRetry: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 8 # Ограничиваем размер кеша. 
                     # Я, честно говоря, не уверен, но его можно убрать полностью,
                     # т.к. это то, что монга для оптимизации поместит 
                     # в оперативную память, а данный инстанс уже весь в ней

systemLog:
  destination: "file"
  path: "/var/log/mongodb/mongodb.log" # Для всех экземпляров указываем один инстанс.
                                       # Теоретически тут можно получить просадку, 
                                       # если монга в памяти начнёт писать
                                       # на диск, но при нормальном функционировании 
                                       # эти события редки (ошибки, медленный запросы)
  logAppend: true
  quiet: false
  verbosity: 0
  logRotate: "reopen"
  timeStampFormat: "iso8601-local"

net:
  bindIp: 127.0.0.1 # Делаем монгу доступной только для локального интерфейса
                    # в случае, если настраиваем реалтайм бекап — добавляем внешний интерфейс
  port: 27000 # указываем, на каких портах будем размещать, 
              # они должны быть разные для разных инстансов в пределах сервера
  http:
    enabled: false # отключаем доступ по http и прочим интерфейсам
    JSONPEnabled: false
    RESTInterfaceEnabled: false
  ssl: # ssl нам тоже не нужен в данном случае
    mode: disabled

security: # Мы будем настраивать корректный доступ по правам, 
          #чтобы клиенты видели только то, что им надо
  authorization: "enabled"
  keyFile: "/data/mongod-keyfile" # Это ключ для общения реплик между собой
  javascriptEnabled: false # Запрещаем исполнение JS в БД.

replication:
  oplogSizeMB: 4096 # Указываем максимальный размер oplog'а. 
                    # В случае падения реплик их восстановление будет быстрым
                    # только если они отстали не более чем на размер oplog'a. 
                    # Корректный размер можно определить
                    # в зависимости от приложения — как часто идёт изменение БД?
  replSetName: "consulwar"
  enableMajorityReadConcern: false # указываем, что мы НЕ дожидаемся подтверждение 
                                   # от реплики на запись для возврата значений поиска.

operationProfiling:
  slowOpThresholdMs: 30 # указываем, сколько максимум может выполнятся запрос, 
                        # после чего будет считаться медленным
                        # и записан в лог для последующей ручной обработки
  mode: "slowOp"

sudo vi /data/mongod-sec-d1.conf

mongod-sec-d1.conf — secondary, disk 1

По большей части конфиг повторяется, разница всего в паре мест.
Но оставляю для удобства


processManagement:
  fork: true
  pidFilePath: "/data/db/mongod.pid"

storage:
  dbPath: "/data/db"
  journal:
    enabled: true # Обращаем внимание, что тут журнал уже включен, 
                  # диск у нас считается надёжным источником
  indexBuildRetry: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 8 # Фактически, даже если по какой-то причине Primary упадёт, 
                     # secondary будет использовать память, но только для чтения

systemLog:
  destination: "file"
  path: "/var/log/mongodb/mongodb.log"
  logAppend: true
  quiet: false
  verbosity: 0
  logRotate: "reopen"
  timeStampFormat: "iso8601-local"

net:
  bindIp: 127.0.0.1
  port: 27001
  http:
    enabled: false
    JSONPEnabled: false
    RESTInterfaceEnabled: false
  ssl:
    mode: disabled

security:
  authorization: "enabled"
  keyFile: "/data/mongod-keyfile"
  javascriptEnabled: false

replication:
  oplogSizeMB: 4096
  replSetName: "consulwar"
  enableMajorityReadConcern: false

operationProfiling:
  slowOpThresholdMs: 30
  mode: "slowOp"

sudo vi /data/mongod-sec-d2.conf

mongod-sec-d2.conf — secondary, disk 2

Разница, по сути, только в пути до БД и в зависимости от используемого порта.


processManagement:
  fork: true
  pidFilePath: "/secondary/data/db/mongod.pid"

storage:
  dbPath: "/secondary/data/db"
  journal:
    enabled: true
  indexBuildRetry: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 8

systemLog:
  destination: "file"
  path: "/var/log/mongodb/mongodb.log"
  logAppend: true
  quiet: false
  verbosity: 0
  logRotate: "reopen"
  timeStampFormat: "iso8601-local"

net:
  bindIp: 127.0.0.1
  port: 27002
  http:
    enabled: false
    JSONPEnabled: false
    RESTInterfaceEnabled: false
  ssl:
    mode: disabled

security:
  authorization: "enabled"
  keyFile: "/data/mongod-keyfile"
  javascriptEnabled: false

replication:
  oplogSizeMB: 4096
  replSetName: "consulwar"
  enableMajorityReadConcern: false

operationProfiling:
  slowOpThresholdMs: 30
  mode: "slowOp"

Добавляем ключ для корректной работы реплики, устанавливаем права на папки


sudo openssl rand -base64 741 > ~/mongod-keyfile
sudo mv mongod-keyfile /data/mongod-keyfile
sudo chmod 600 /data/mongod-keyfile
sudo chown mongodb:mongodb -R /data
sudo chown mongodb:mongodb -R /secondary/data

Создаем скрипты автозапуска


sudo apt-get install numactl
sudo mv /lib/systemd/system/mongod.service /lib/systemd/system/mongod@.service
sudo vi /lib/systemd/system/mongod@.service

mongod@.service

@ в названии сервиса означает что мы можем запускать его с параметрами.
Данный скрипт устанавливает все необходимые параметры ОС для работы с монгой — удобно.


[Unit]
Description= Mongo Database on %i
After=network.target

[Service]
Type=forking
ExecStartPre=/bin/sh -c '/bin/echo never > /sys/kernel/mm/transparent_hugepage/enabled'
ExecStartPre=/bin/sh -c '/bin/echo never > /sys/kernel/mm/transparent_hugepage/defrag'
User=mongodb
Group=mongodb
PermissionsStartOnly=true
ExecStart=/usr/bin/numactl --interleave=all /usr/bin/mongod --config /data/mongod-%i.conf
LimitFSIZE=infinity
LimitCPU=infinity
LimitAS=infinity
LimitNOFILE=64000
LimitNPROC=64000
TasksMax=infinity
TasksAccounting=false

[Install]
WantedBy=multi-user.target

Говорим БД запускаться при старте системы, и так же запускаем экземпляры прямо сейчас.
Наш параметр устанавливается после @, например, memory укажет использовать при запуске /data/mongod-memory.conf


sudo systemctl enable mongod@memory
sudo systemctl enable mongod@sec-d1
sudo systemctl enable mongod@sec-d2
sudo service start mongod@memory
sudo service start mongod@sec-d1
sudo service start mongod@sec-d2

Подключаемся к монге, инициализируем реплику, устанавливаем пользователей


mongo localhost:27000/admin

Выполняем в консоли монги


rs.initiate({
  _id: "consulwar", // название реплики
  version: 1,
  protocolVersion: 1,
  writeConcernMajorityJournalDefault: false, // Говорим чтобы реплика не дожидалась 
                                             // подтверждения о записи от других
  configsvr: false, // Указываем, что это не конфигурационный сервер для кластера 
                    // (кластер — тема отдельной статьи)
  members: [
    {
      _id: 0, // id начинаем от нуля и далее инкрементим
      host: 'localhost:27000', // по какому адресу доступна данная монга
      arbiterOnly: false, // может ли отдавать и хранить данные
      buildIndexes: true, // указываем, что строить индексы надо
      hidden: false, // не скрытая, т.е. приложение к ней может подключиться
      priority: 100, // приоритет при выборе Primary — чем больше, тем приоритетнее
      slaveDelay: 0, // задержка репликации. Нам задержка не нужна.
      votes: 1 // может ли голосовать при выборе Primary
    },
    {
      _id: 1,
      host: 'localhost:27001',
      arbiterOnly: false,
      buildIndexes: true,
      hidden: false,
      priority: 99, // Приоритет ниже
      slaveDelay: 0,
      votes: 1
    },
    {
      _id: 2,
      host: 'localhost:27002',
      arbiterOnly: false,
      buildIndexes: true,
      hidden: false,
      priority: 98,
      slaveDelay: 0,
      votes: 1
    }
  ],
  settings: {
    chainingAllowed : true, // Разрешаем репликам читать друг с друга, а не только с мастера
    electionTimeoutMillis : 5000, // Указываем время на переголосование, 
                                  // в случае падения одной из БД. ~7 секунд. 
                                  // Если все экземпляры у нас на 1 машине 
                                  // можем уменьшить до 500мс, скажем
    catchUpTimeoutMillis : 2000
  }
});

// Создаем первого пользователя, он должен иметь права root'а
db.createUser({user: 'zav', pwd: '7Am9859dcb82jJh', roles: ['root']}); 
// Выходим - ctrl+c, ctrl+c

Подключаемся под нашим пользователем


mongo localhost:27000/admin -u zav -p '7Am9859dcb82jJh' --authenticationDatabase admin

Добавляем пользователя для работы с приложением


use consulwar // вместо consulwar название основной БД вашего приложения
db.createUser({
    user: 'consulwar', 
    pwd: '37q4Re7m432dtDq', 
    roles: [{ // Права на чтение и запись в нашу БД. 
              // Включает ещё ряд прав, типа создание индексов и т.п.
        role: "readWrite", db: "consulwar" 
    }, { // Права на чтение oplog'а, для быстрой работы приложения метеора
        role: 'read', db: 'local'
    }]
});

Теперь довольно важный и сложный вопрос — реалтайм репликация для бекапа. Целиком он рассмотрен не будет, ввиду необходимости настройки ещё ряда оборудования, чего я бы хотел избежать в данной статье.


Настройка реалтайм-бекапа

Реплицировать мы будем на внешний сервер (иначе в чём смысл бекапа? :-)).
На внешнем сервере должна быть установлена монга схожим образом.
Примерный конфиг:


mongod-backup.conf
processManagement:
  fork: true
  pidFilePath: "/data/db/mongod.pid"

storage:
  dbPath: "/data/db"
  journal:
    enabled: true
  indexBuildRetry: false # Отключаем построение индексов
  wiredTiger:
    engineConfig:
      cacheSizeGB: 0 # Отключаем кеш

systemLog:
  destination: "file"
  path: "/var/log/mongodb/mongodb.log"
  logAppend: true
  quiet: false
  verbosity: 0
  logRotate: "reopen"
  timeStampFormat: "iso8601-local"

net:
  bindIp: 222.222.222.222 # Данный экземпляр должен быть доступен извне
  port: 27000
  http:
    enabled: false # но не по http, само собой
    JSONPEnabled: false
    RESTInterfaceEnabled: false
  ssl:
    mode: disabled

security:
  authorization: "enabled"
  keyFile: "/data/mongod-keyfile" # mongod-keyfile берем с основного сервера
  javascriptEnabled: false

replication:
  oplogSizeMB: 0
  replSetName: "consulwar"
  enableMajorityReadConcern: false

operationProfiling:
  mode: "off" # нам не нужно профилирование на бекапе

В фаерволе сервера-бекапа разрешаем коннект на 27000 порт ТОЛЬКО с IP сервера-приложения/БД.
Тоже самое — на сервере приложения/БД в bindIp указываем смотреть ещё и во внешний интерфейс (ip внешний сервера), и в iptables разрешаем доступ на 27000–27002 порты ТОЛЬКО с ip севера-бекапа.


При инициализации/реинициализации реплики добавляем


{
    _id: 4,
    host: '222.222.222.222:27000', // собственно интерфейс, на который смотрит бекап
    arbiterOnly: false,
    buildIndexes: false, // не строим индексы вообще на бекапе 
    hidden: true, // скрытый! Используется ТОЛЬКО для хранения информации
    priority: 0, // Не участвует в выборах
    slaveDelay: 0, // Задержка бекапа нам не нужна, 
                   // но может использоваться, если нужно пару бекапов в реалтайме "час назад"
    votes: 0 // Не участвует в голосовании
}

Готово! Теперь данные будут литься в реалтайме ещё и во внешний бекап, что очень круто.
В случае полного краха приложения мы можем инициализировать реплику точно так же, и она восстановится из бекапа.
Поверьте, это намного быстрее, чем mongodump/mongorestore (по личным прикидкам в 25–100 раз).


Nodejs, npm, app user, meteor build

Устанавливаем ноду, ставим модуль n, ставим им версию ноды 4.8.1 (последняя, официально поддерживаемая метеором версия).
Устанавливаем pm2, т.к. именно им будем запускать процессы.


sudo apt-get install nodejs
sudo apt-get install npm
sudo npm install -g n
sudo n 4.8.1
sudo npm install pm2@latest -g

Добавляем пользователя, из-под которого будет всё запускаться и который будет отвечать за деплой


sudo adduser consulwar

Заходим за данного пользователя


su consulwar
mkdir app
mkdir app/current

На локальной машине заходим в директорию с нашим meteor проектом.
Создаем папку для билдов, собираем проект в эту папку.


mkdir ../build
meteor build ../build/ --architecture os.linux.x86_64

Полученный архив загружаем на наш сервер, например, по sftp. Заходим под нашим пользователем для приложения.
Загружаем в папку ~/app.
Заходим по ssh за нашего пользователя (consulwar у меня).


cd app
mkdir 20170429 # создаем папку по сегодняшней дате
tar -xvzf consulwar-master.tar.gz -C 20170429
ln -s 20170429/bundle ~/app/current # Создаем симлинк, чтобы быстро переключаться
(cd current/programs/server && npm install)
vi pm2.config.js # создаем конфиг для нашего pm2

pm2.config.js
var settings = { ... }; // Объект заменяем объектом из settings.json вашего приложения

var instances = 10; // Сколько экземпляров запускаем? Советую не более N-1
                    // Где N — количество ядер

var apps = [];

for (var i = 0; i < instances; i++) {
  apps.push({
    "script": "/home/consulwar/app/current/bundle/main.js", // укажите корректный путь
    "exec_mode": "fork", // т.к. рулить будем через Nginx, создаем форками
    "name": "consulwar", // имя приложения в списке процессов
    "env": {
      "ROOT_URL": "http://consulwar.ru/", // Адрес приложения
      "HTTP_FORWARDED_COUNT": 1, // Указываем количество прокси перед приложением
                                 // Чтобы корректно разрулить IP пользователя в приложении
      "PORT": 3000 + i, // Порты начинаются с 3000 (3000, 3001, 3002...)
      "MONGO_URL": "mongodb://consulwar:37q4Re7m432dtDq@localhost:27000,localhost:27001,localhost:27002/consulwar?replicaSet=consulwar&readPreference=primary&authSource=consulwar",
      "MONGO_OPLOG_URL": "mongodb://consulwar:37q4Re7m432dtDq@localhost:27000,clocalhost:27001,localhost:27002/local?replicaSet=consulwar&authSource=consulwar",
      "METEOR_SETTINGS": settings
    }
  });
}

module.exports = {
  apps : apps
}

И запускаем


pm2 startup pm2.js # Выведет команду, которую надо выполнить для автозапуска после перезагрузки
... # Выполняем команду
pm2 start pm2.js
pm2 status # Валидируем, что всё запустилось. В случае ошибок можем посмотреть pm2 logs

Готово, приложение развернуто и уже должно быть доступно по ip сервера/адресу с указанием порта, например http://consulwar.ru:3000


Nginx балансировка
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:nginx/stable
sudo apt-get install nginx
sudo vi /etc/nginx/nginx.conf

nginx.conf
user www-data; # из-под кого запускаем nginx
worker_processes  6; # Указываем количество воркеров
worker_rlimit_nofile 65535;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  4000; # Кол-во подключений на воркера
                              # В нашем случае мы можем обработать 6 * 4000 = 24 000 запросов 
                              # в момент времени
}

http {
    map $http_upgrade $connection_upgrade { # Для корретной установки сокет-подключения
        default upgrade;
        ''      close;
    }

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    server_tokens off;

    sendfile on; # Для отправки статики
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;

    gzip on; # Включаем gzip
    gzip_comp_level 6;
    gzip_vary on;
    gzip_proxied any;
    gzip_buffers 16 8k;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
    gzip_static on; # Разрешаем отдачу файлов с .gz на конце. Например, main.js.gz будет отдаваться при запросе main.js
    gzip_http_version 1.1;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)"

    proxy_connect_timeout      60;
    proxy_read_timeout         620;
    proxy_send_timeout         320;
    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

    upstream backends {
        #ip_hash; # указываем, если нам надо что бы пользователь всегда подключался к одному и тому же экземпляру приложения
        least_conn; # Я выбрал, чтобы подключение отдавалось наименее нагруженному процессу

        # Прописываем каждый из интерфейсов
        # Я запускал их 10, значит тут 10 записей
        server 127.0.0.1:3000 weight=5 max_fails=3 fail_timeout=60; 
        server 127.0.0.1:3001 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3002 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3003 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3004 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3005 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3006 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3007 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3008 weight=5 max_fails=3 fail_timeout=60;
        server 127.0.0.1:3009 weight=5 max_fails=3 fail_timeout=60;

        # Интерфейс, который будет отрабатывать в случае недоступности приложения
        server 127.0.0.1:3100 backup;
    }

    # Указываем пути до ssl сертификатов. Мы их создадим чуть позже
    ssl_certificate /etc/letsencrypt/live/consulwar.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/consulwar.ru/privkey.pem;

    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    ssl_stapling on;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers         'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!EXP:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SSLv:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

    server {
        server_name consulwar.ru;
        # указываем слушать 80 и 443 порты
        listen 80;
        listen 443 ssl http2;

        # любой запрос переадресовываем на одно из наших приложений 
        location / {
            proxy_pass http://backends;

            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header X-Forwarded-For $remote_addr;
        }

        # Статику раздаем с помошью nginx
        location ~* \.(jpg|jpeg|gif|ico|png)$ {
            root /home/consulwar/app/current/programs/web.browser/app;
        }

        # Основной css и js файл лежит отдельно, добавляем правило дополнительное
        location ~* "^/[a-z0-9]{40}\.(css|js)$" {
            root /home/consulwar/app/current/programs/web.browser;
        }

        location ~ "^/packages" {
            root /home/consulwar/app/current/programs/web.browser;
        }

        # В случае, если у вас никаких систем мониторинга не стоит, 
        # можно убрать следующую конструкцию.
        location /nginx_status {
            stub_status on;
            access_log off;
            allow 127.0.0.1;
            deny all;
        }

        # Для получения SSL сертификата
        location ~ "^/.well-known" {
            root /home/consulwar/app/current/programs/web.browser/app/.well-known;
        }
    }

    include /etc/nginx/conf.d/*.conf;
    client_max_body_size 128m;
}

Перезапускаем nginx


sudo service nginx restart

Получаем SSL от Let’s Enctypt.
Само собой, домен уже должен быть привязан к этому IP адресу.


sudo apt-get install letsencrypt
sudo letsencrypt certonly -a webroot --webroot-path=/home/consulwar/app/current/programs/web.browser/app -d consulwar.ru
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 # Генерируем дополнительный сертификат безопасности

Вжух! SSL Работает


iptables
sudo vi /etc/network/if-up.d/00-iptables

00-iptables
#!/bin/sh
iptables-restore < /etc/firewall.conf
ip6tables-restore < /etc/firewall6.conf

sudo chmod +x /etc/network/if-up.d/00-iptables
apt-get install xtables-addons-dkms
sudo vi /etc/firewall.conf

firewall.conf
*filter
:INPUT ACCEPT [193038:64086301]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [194475:60580083]
-A INPUT -i lo -j ACCEPT # Разрешаем локальное общение
-A INPUT -m state --state RELATED,ESTABLISHED -p all -j ACCEPT # Разрешаем существующие подключения

# Разрешаем SSH
-A INPUT -m state --state NEW -p tcp -m multiport --dport 355 -j ACCEPT

# Доступ к Nginx
-A INPUT -m state --state NEW -p tcp -m multiport --dport 80,443 -j ACCEPT

# Ловушка для кривых подключений, будет тратить ресурсы атакующего, а не сервера
-A INPUT -p tcp -m tcp -j TARPIT

# Дропаем всю фигню
-A INPUT -p udp -j DROP 

COMMIT

sudo vi /etc/firewall6.conf # оставляем пустым или заполняем тем, что вам надо
sudo iptables-restore < /etc/firewall.conf

itables настроены, и лишние порты закрыты.


DONE!


Если понравится подход/формат — выложу настройку почтовой системы, мониторинга инфраструктуры, CI через Bamboo и ещё ряд используемых у нас вещей.


Вполне вероятно, что-то я упустил (но в целом прямо вот так должно работать) — спрашивайте, дополню.
Надеюсь, у кого-то это теперь займет меньше времени, чем у меня :-)


PS: У нас открыта вакансия front-end разработчика.

Комментарии (8)

  • 29 апреля 2017 в 22:30

    0

    Полезно!
  • 29 апреля 2017 в 22:33 (комментарий был изменён)

    0

    /etc/init.d/ssh restart
    

    Всё же лучше использовать systemctl/service. Systemd же.
    -A INPUT -m state --state NEW -p tcp -m multiport --dport 355 -j ACCEPT
    

    не понятно, зачем нужен multiport, если открывается только один порт.
    • 29 апреля 2017 в 22:43

      0

      Дело в том, что лично у меня там было несколько портов :-)
      А так хорошее замечание, да, спасибо.
  • 29 апреля 2017 в 23:10

    0

    Спасибо за интересную статью — у вас классный стиль изложения. Хотя, можно легко уменьшить в несколько раз, если использовать docker.
    • 29 апреля 2017 в 23:21 (комментарий был изменён)

      +1

      Если ты хочешь решить проблему с помощью Docker, то теперь у тебя две проблемы :-)
      Вообще ни одна строка тут бы не сэкономилась при использовании докера.
      С этой логикой можно сказать — используй meteor up и он поставит всё за тебя. Но только разница в производительности будет катастрофической.
  • 29 апреля 2017 в 23:36

    +1

    интересные теги…
  • 30 апреля 2017 в 01:06

    +2

    Я ожидал что тема не популярная. Ну т.е. там ну 10 рейтинга…, но отрицательный.
    Пожалуйста, дайте обратный отклик — что не так?
    Мне видится что вышла отличная статья, для тех кто разрабатывает на метеоре и переходит к этапу развёртывания.
    Спасибо.
    • 30 апреля 2017 в 06:30

      0

      Лично мне полезно, я в избранное добавлю. Половину и так знал, другую половину как-то делал иначе (или вообще не делал) —, но пост как шпаргалка очень нужен.

© Habrahabr.ru