[Из песочницы] Раздача фронтенда через CDN

В мире современных веб-технологий все стремительно развивается и меняется. Пару лет назад совершенно нормальным было по запросу клиента рендерить DOM структуру на сервере (например, при помощи PHP) и отдавать уже полностью сформированную страницу. Сейчас все чаще появляются сайты c полным отделением фронтенда (Angular, React, Vue.js…) от бэкенда (некие API эндпоинты), где на фронтенде почти весь контент формируется посредством скриптов, а сервер отдает только данные по запросу. Тут можно было бы упомянуть SSR (Server Side Rendering), но не об этом данное произведение.

В любые времена перед разработчиками и владельцами сайтов стояла непростая задача: доставить контент как можно быстрее, как можно большему количеству клиентов. Одно из самых правильных решений — использовать CDN (Content Delivery Network) для раздачи статичных файлов. В случае с динамическим рендером страниц на сервере мы должны были ограничиваться небольшим списком объектов, которые можно было разместить в CDN: таблицы стилей, файлы скриптов, изображения. Однако, фронтенд, написанный на Angular (React, Vue.js…), статичен целиком, включая индексную страницу. Вот тут и возникает мысль:, а почему бы не организовать раздачу через CDN всего фронтенда?

В данной статье пойдет речь о настройке комплексного решения для разработки, контроля версий, автоматической сборки и доставки статического сайта с использованием Gitlab CI, Amazon S3 и Amazon CloudFront. Также речь пойдет о настройке сопутствующих вещей: git, безопасное соединение по протоколу HTTPS, доменная почта, DNS хостинг, бэкенд сервер…

Если вас заинтересовала эта тема, добро пожаловать под кат. Осторожно! Много скриншотов.

Что мы получим в результате выполнения пошагового руководства:


  • Стартовый сайт на Angular.
  • Версионный контроль (git).
  • Автоматическая сборка и публикация фронтенда в корзине Amazon S3.
  • Раздача фронтенда через CDN (Amazon CloudFront).
  • Бесплатный SSL сертификат от Amazon с автопродлением (для работы сайта через HTTPS).
  • Бесплатная доменная почта от Яндекса и удобный интерфейс управления записями DNS (и не только).
  • Bash скрипт автоматической настройки бэкенд сервера.
  • Бэкенд сервер, работающий на ОС Debain 9 (nginx + PHP7.1-FPM).
  • Бесплатный SSL сертификат от Let’s Encrypt с автопродлением (для работы корневой зоны домена и бэкенд сервисов через HTTPS).


Что для этого нам понадобится:


  • Доменное имя с доступом к панели управления регистратора.
  • Аккаунт на Gitlab.
  • Аккаунт на AWS (Amazon Web Services).
  • Аккаунт на Яндексе.
  • VDS (Virtual Dedicated Server) с установленной ОС Debain 9.


Несколько слов о таком выборе инструментов


Я потратил немало времени на поиски подходящих сервисов по ряду критериев. Во первых, это цена — хотелось бы, чтобы сервис был или бесплатным, или не очень дорогим. Во вторых — надежность. Также хорошо, если сервис берет большинство обязанностей на себя, избавляя от необходимости что-то устанавливать и настраивать. Например, зачем настраивать собственный git сервер, использовать какой-то сторонний сервис CI (Continuous Integration), если Gitlab предоставляет все эти сервисы «из коробки», а также неограниченное количество приватных репозиториев, организаций и коворкеров? Зачем настраивать доменную почту на собственном сервере, если можно это предоставить Яндексу? По поводу CDN — я просто не нашел ничего дешевле Amazon CloudFront. Файловое хранилище Amazon S3 также не дорогое (а зачем хранить много файлов на VDS, если это дорого?).

Итак, поехали!


1. DNS хостинг и почта Яндекс


Для начала привяжем ваш домен к Яндекс почте. Делаем этот шаг в первую очередь для настройки доменной почты. Нам необходимо будет подтвердить владение доменом для получения SSL сертификата Amazon, а для этого мы должны будем получить письмо на адрес webmaster@yourdomain.com.

1.1. Регистрируемся / логинимся в Яндексе: https://yandex.ru.

1.2. Переходим в доменную почту Яндекса: https://pdd.yandex.ru и добавляем свой домен (пункт меню «Подключить домен»).

tavd_cxjmjg2rlx3r6ps499m6im.png

Тут хочу сразу отметить, что у Яндекса появился «ПДД 2.0» или «Яндекс.Коннект». Однако, никак не могу определиться — то ли интерфейс Яндекс.Коннект не доработан излишне простой и в нем не хватает нужного функционала, то ли он излишне сложный и нужного функционала просто не найти. Так или иначе, мне не удалось добавить несколько доменов сразу в Яндекс.Коннект, с возможностью редактировать DNS записи каждого их них отдельно. Поэтому лично для меня оказалось проще сначала добавить домен в ПДД (Почту Для Домена), а затем перенести его в Яндекс.Коннект, что мы и сделаем.

1.3. Подтверждаем владение доменом.

syeqlu9gl5taycd7hwht51cxhfa.png

Самый простой для нас способ, это делегировать домен на Яндекс. Для этого в панели управления регистратора устанавливаем для вашего домена следующие NS-серверы:

dns1.yandex.net
dns2.yandex.net

Внимание! Этот шаг должен быть выполнен строго после подключения домена к почте для домена (пункт 1.2).

1.4. Ждем до 72 часов (как повезет).
Проверка владения доменом должна пройти автоматически. После этого на странице ПДД https://pdd.yandex.ru в списке «Моих доменов» вы должны увидеть сообщение зеленого цвета о том, что «домен подключен и делегирован на Яндекс».

che_57hwluadcnuxeowkhwdghxs.png

Теперь мы можем подключить почту для домена. Настраивать MX-записи для домена не нужно, т.к. мы делегировали домен на NS-серверы Яндекса.
На данном этапе мы уже получили доменную почту с возможностью добавлять до 1000 почтовых ящиков и с правильными почтовыми DNS записями.

1.5. Создаем первый почтовый ящик johndoe@yourdomain.com.

1.6. В верхнем меню переходим по ссылке «Мигрировать в Коннект».
Осуществляем перенос домена в Яндекс.Коннект, указываем название организации. После этого нам становится доступна страница https://connect.yandex.ru/portal/home.

fea8q-ewp3gg0uukh4rdosg9yna.png

1.7. Переходим в «Админку». Здесь нам доступны различные настройки.
Самое интересное для нас — пункт меню «Управление DNS». Вернемся к нему позже. Сейчас нам необходимо добавить алиас почтового ящика с названием webmaster@yourdomain.com. Этот ящик нам понадобится для получения письма с запросом подтверждения владения доменом от Amazon.

1.8. Переходим в пункт меню «Оргструктура» и выбираем единственного (пока) пользователя, который был автоматически создан при добавлении первого почтового ящика (пункт 1.5).

xnkbaz7af0nfmeu4dzqemn0egjc.png

1.9. Нажимаем троеточие в правом верхнем углу карточки пользователя, в выпадающем меню выбираем пункт «Управление алиасами».

В появившемся окне нажимаем кнопку «Добавить новый».

c5yojhjw3maa9iqvpqejo_usdvu.png

Вводим «webmaster» и нажимаем кнопку «Добавить».

Теперь у нас появился алиас почтового ящика webmaster@yourdomain.com и мы готовы принимать письма на доменную почту.

2. Аккаунт Amazon Web Services


Для выполнения дальнейших действий данного руководства вам потребуется корневая учетная запись (root user) AWS: https://aws.amazon.com/ru.

Если у вас еще нет учетной записи, то вам сказочно повезло и вы можете воспользоваться уровнем бесплатного пользования AWS. Для этого необходимо пройти процедуру регистрации: https://aws.amazon.com/ru/free. В процессе регистрации у вас запросят данные банковской карты, откуда снимут (и не вернут!) 1$ для проверки платежеспособности. Также вам нужно будет указать свой номер телефона, на который позвонит робот из Америки.

В целом, процесс регистрации довольно прост и на мой взгляд не требует подробного описания.

3. Amazon Security Credentials


Для автоматического обмена данными с сервисами AWS мы будем использовать консольную утилиту aws cli (AWS Command Line Interface). Утилита авторизуется с помощью пары Access key ID и Secret access key. Создадим их.

3.1. Заходим в консоль AWS.

3.2. В верхнем меню справа нажимаем на свой логин. В выпадающем меню выбираем пункт «My Security Credentials».

f5uc6idzbwpkufu3ibkyu_emi28.png

3.3. Здесь может появиться окошко с предупреждением.

mmzpiuwv8rbu2v0jpahkmmu0wes.png

Можно его игнорировать и нажать кнопку «Continue to Security Credentials».

3.4. В меню слева выбираем пункт «Users». Нажимаем кнопку «Add user».

3.5. В поле «User name» пишем имя пользователя. Например, «cli-manager».
В пункте «Access type» ставим галочку «Programmatic access».

6q7ixbet1gxjlsplbmjuvrufuii.png

Нажимаем кнопку «Next: Permissions».

3.6. В следующем пункте выбираем «Attach existing policies directly». Выбираем галочку «AdministratorAccess».

u3lj5hodfenlby5cstuhyfqjehu.png

Нажимаем кнопку «Next: Review».

3.7. В следующем пункте нажимаем кнопку «Create user».

3.8. В последнем пункте мы увидим только что созданного пользователя и его данные.

sprtn_pox79g3gcsvw2pko9lth4.png

Внимание! Сразу запишите Access key ID и Secret access key (его можно увидеть, нажав «show») и/или скачайте файл .csv с данными пользователя (нажав кнопку «Download .csv»). Секретный ключ доступа вы больше нигде и никогда не увидите.

4. Сертификат SSL Amazon


Теперь нам нужно получить сертификат SSL для нашего домена.

4.1. В консоли AWS в верхнем меню выбираем «Services».

4.2. В поиске по сервисам набираем «Certificate manager».

4.3. Нажимаем кнопку «Request a certificate».

4.4. В поле «Domain name» пишем »*.yourdomain.com».

ebww3wjj0w9cpbbc-iraunzqigw.png

Обратите внимание на звездочку и точку перед названием домена. Таким образом мы получим wildcard certificate для домена и всех его поддоменов. Нажимаем кнопку «Review and request».

4.5. В следующем пункте нажимаем кнопку «Confirm and request».

4.6. В следующем пункте нажимаем кнопку «Continue».

zg-dvi2mqqiw_ceiyfpefg71xkq.png

Здесь мы увидим только что запрошенный сертификат и его статус: «Pending validation».

4.7. Теперь заходим в почту Яндекса: mail.yandex.ru.
Логинимся в ваш доменный почтовый ящик, который вы создали в пункте 1.5. настоящего руководства (johndoe@yourdomain.com). Вам должно было прийти письмо с запросом подтверждения владения доменом от Amazon.

8bew-j2jzsmu6vcvszs4bvkr2_4.png

4.8. Переходим по ссылке в письме.
Откроется новая вкладка браузера со страницей подтверждения владения доменом.

o5xcbhe0lf7cjow-h-rmttpigxm.png

Нажимаем кнопку «I Approve».
Сертификат должен пройти успешное подтверждение.

Вернувшись в Certificate manager мы увидим наш сертификат и его статус: «Issued».

5. Корзина Amazon S3


Создадим корзину Amazon S3, в которой будут храниться статичные файлы нашего фронтенда.

5.1. В консоли AWS в верхнем меню выбираем «Services».

5.2. В поиске по сервисам набираем «S3».

5.3. Нажимаем кнопку «Create bucket».

sie7gr8ijbhckoq5gxq2ofny2cu.png

В поле «Bucket» name указываем имя хоста (вместе с www). Оно должно полностью совпадать с названием вашего домена. Например: www.yourdomain.com.

В поле «Region» выбираем «US East (N. Virginia)». Во первых, это позволит избежать возможных проблем с перенаправлением на неправильные объекты (статья документации). Во вторых, это самый популярный регион и здесь самые недорогие расценки. В третьих, нам все равно, в каком регионе расположена корзина, т.к. раздавать контент будет CDN, а напрямую к файлам S3 клиенты обращаться не будут.

Нажимаем «Next» несколько раз, оставляем все поля как есть.

В списке ваших корзин появится новая корзина с названием www.yourdomain.com

5.4. Редактируем свойства корзины.
Кликаем на пустое место рядом с названием вашей корзины. В правой части окна появится всплывающее окно.

p4qnkazkutqv4ghyigrg8gzyrwq.png

5.5. Выбираем пункт «Properties».

rdwczcb44uvtsai1gowgdsykixq.png

5.6. Нажимаем на «Static website hosting».

zdqpkg3j4e95vd1k9ls1yguni4u.png

Сразу скопируйте «Endpoint» URL (указан в верхней части окна). Он понадобится для дальнейших настроек.

Выбираем первый пункт «Use this bucket to host a website».

В поле «Index document» пишем «index.html».

Нажимаем кнопку «Save».

5.7. Выбираем вкладку «Permissions».

Здесь нам нужно добавить www.yourdomain.com.s3-website-us-east-1.amazonaws.com и https://*.yourdomain.com в список разрешенных хостов для кроссдоменных запросов.

1godb4lstru3l1h4kqeqp7-chme.png

Нажимаем кнопку «CORS configuration».
Не будем разбирать формат XML файла. Для детального изучения данного вопроса, можно ознакомиться с документацией. Нам достаточно просто скопировать в текстовое поле вот это:




    http://www.yourdomain.com.s3-website-us-east-1.amazonaws.com
    GET
    HEAD
    Content-*
    Host
    Origin


    https://*.yourdomain.com
    GET
    HEAD
    Content-*
    Host
    Origin



Замените значения тегов на ваши. Первый адрес — тот самый Static website endpoint, который вы скопировали в пункте 5.6 настоящего руководства.
Нажимаем кнопку «Save».

6. Раздача Amazon CloudFront


Теперь нам нужно организовать раздачу файлов через CDN из только что созданной корзины.

6.1. В консоли AWS в верхнем меню выбираем «Services».

6.2. В поиске по сервисам набираем «CloudFront».

6.3. Нажимаем кнопку «Create Distribution».

m_tgt6u69nc9powfp-vy9ytwxbc.png

Выбираем метод доставки «Web», нажимаем соответствующую кнопку «Get Started».
Откроется очень большая форма (скрин делать не буду).
Пройдемся по нужным нам полям. Остальные поля не трогаем, оставляем как есть. В любом случае у нас есть возможность в дальнейшем менять все эти настройки.

Origin Settings

Origin Domain Name: здесь внимательно! Не нужно выбирать домен «www.yourdomain.com.s3.anazonaws.com» из подсказки к этому полю! Вставляем сюда Static website endpoint, который вы скопировали в пункте 5.6 настоящего руководства, без «http://» в начале.

Default Cache Behavior Settings

Viewer Protocol Policy: выбираем пункт «Redirect HTTP to HTTPS».
Allowed HTTP Methods: выбираем пункт «GET, HEAD, OPTIONS».
Cached HTTP Methods: ставим галочку напротив «OPTIONS».
Cache Based on Selected Request Headers: выбираем пункт «Whitelist». В появившемся пункте »Whitelist Headers» выбираем «Origin», нажимаем кнопку «Add».
Object Caching: Выбираем пункт «Customize».
Minimum TTL: пишем значение »300».
Compress Objects Automatically: выбираем «Yes».

Distribution Settings

Alternate Domain Names (CNAMEs): в текстовом поле пишем «www.yourdomain.com» и «static.yourdomain.com» — по одному на каждой строчке. По адресу static.yourdomain.com у нас будет доступно все то же самое, что и через www.yourdomain.com. Мы будем использовать его для получения статичных файлов, чтобы уменьшить количество запросов к основному домену.
SSL Certificate: выбираем пункт «Custom SSL Certificate». Ниже в выпадающем списке выбираем ранее полученный SSL сертификат »*.yourdomain.com».
Default Root Object: вводим «index.html» (без слеша в начале).

Нажимаем кнопку «Create Distribution».
Раздача создана. Она появится в списке со статусом «In Progress». В течении некоторого времени (обычно до 10 минут) она приобретет статус «Enabled».
Сразу скопируем ID и Domain Name нашей раздачи, они потребуются нам для дальнейших настроек.

6.4. Настроим страницы ошибок.

Нажимаем на ID раздачи в списке. Переходим во вкладку «Error Pages».

e5vg_iehxrvqh-wecuelmauljaq.png

Нажимаем кнопку «Create Custom Error Response».
В поле «HTTP Error Code» выбираем »403: Forbidden».
«Customize Error Response» — выбираем «Yes».
В поле «Response Page Path» вводим »/index.html».
В поле «HTTP Response Code» выбираем »200: OK».

Повторяем эти же действия для ошибки 404.

Таким образом, не найденные или запрещенные адреса будут перенаправляться на index.html и обрабатываться Angular Router.

7. Бэкенд сервер


Настало время настроить бэкенд сервер.

Даже если у вас полностью статичный сайт, не требующий обращений к API на сервере, VDS не помешает. Дело в том, что корневая запись домена должна быть типа A (и/или AAAA, если у вас есть IPv6) и, соответственно, ссылаться на IP адрес. Мое мнение — самый простой и дешевый способ получить постоянный IP адрес в Интернете, это арендовать VDS. В придачу к этому мы получаем возможность разместить на этом IP адресе различные сервисы: API, базы данных, службу обмена сообщениями в реальном времени, и вообще что угодно. Некоторые DNS хостинги предоставляют возможность вместо записи типа A использовать ALIAS, где можно прописать не IP адрес, а доменное имя. Например, можно использовать Amazon Route 53, настроить корневую запись домена как ссылку на еще одну корзину S3, которая будет осуществлять переадресацию на вашу раздачу CloudFront.

В любом случае, это выбор каждого. Я же склоняюсь к аренде VDS, тем более, что цены на них сейчас довольно доступные. Например, Айхор Хостинг предоставляет VDS (1 СPU / 512 MB RAM / 10 GB HDD) всего за 1080 рублей в год. Это самый дешевый тариф, для начала нам он вполне подойдет. Хотя, я рекомендую приобрести VDS с диском SSD.

Далее я буду описывать процесс настройки VDS (или полноценного сервера) с root доступом и операционной системой Debian 9 на борту.

В своей работе я использую несколько однотипно настроенных VDS и не использую никаких панелей управления, типа ISPManager. Поэтому я автоматизировал процесс настройки сервера, написав несложный bash скрипт. Давайте поступим так же и создадим несколько файлов. Будьте внимательны! Переводы строк в файлах должны быть в стиле Unix (LF), а не Windows (CRLF).

nginx.conf:

user www-data;
worker_processes 1;

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

events {                                                                                               
    worker_connections 1024;
    use epoll;
}                                                                                                      

http {                                                                                                 
    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;

    sendfile on;
    tcp_nopush on;

    client_header_timeout 30;
    client_body_timeout 30;
    reset_timedout_connection on;
    keepalive_timeout  30;

    client_max_body_size 32m;
    client_body_buffer_size 128k;

    server_tokens off;

    gzip on;
    gzip_vary on;
    gzip_disable "msie6";
    gzip_proxied any;
    gzip_min_length 1024;
    gzip_comp_level 5;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/ld+json
        application/manifest+json
        application/octet-stream
        application/rss+xml
        application/vnd.geo+json
        application/vnd.ms-fontobject
        application/x-font-ttf
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/bmp
        image/svg+xml
        image/x-icon
        text/cache-manifest
        text/css
        text/plain
        text/vcard
        text/vnd.rim.location.xloc
        text/vtt
        text/x-component
        text/x-cross-domain-policy;
    expires max;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}


Это основной конфигурационный файл nginx. Главное, что мы тут делаем — это включаем компрессию gzip и подключаем конфиги, которые будут находиться в каталоге /etc/nginx/sites-enabled.

ssl.conf:

ssi on;
ssl on;
ssl_certificate "/etc/letsencrypt/live/{{DOMAIN}}/fullchain.pem";
ssl_certificate_key "/etc/letsencrypt/live/{{DOMAIN}}/privkey.pem";
ssl_trusted_certificate "/etc/letsencrypt/live/{{DOMAIN}}/chain.pem";
ssl_ciphers AES256+EECDH:AES256+EDH;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2;
ssl_ecdh_curve secp384r1;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_session_timeout 24h;
ssl_session_cache shared:SSL:24m;
ssl_buffer_size 1400;


Шаблон конфигурации nginx для SSL. Тут мы подключаем сертификат, который нам выдаст Let’s Encrypt и устанавливаем необходимые параметры SSL, которые должны дать нам рейтинг A+ в Qualys SSL Server Test. Обратите внимание на подстроки {{DOMAIN}} — так и должно быть. Скрипт настройки сам заменит их на ваш домен.

site.conf:

server {
    server_name {{DOMAIN}};
    listen 80;
    listen 443 ssl http2;
    error_log off;
    access_log off;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000;";
    add_header Cache-Control public;

    include /etc/nginx/ssl.conf;

    location /.well-known/acme-challenge/ {
        alias /var/www/.well-known/acme-challenge/;
    }
    location / {
        return 301 https://www.$host:443$request_uri;
    }
}


Шаблон конфигурации nginx для корневой зоны домена. Здесь мы подключаем конфигурацию SSL, делаем доступным извне каталог, в который Let’s Encrypt будет складывать файлы для валидации доменов. Все запросы к yourdomain.com или yourdomain.com перенаправляем на www.yourdomain.com.

api.conf:

server {
    server_name {{DOMAIN}};
    listen 80;
    error_log off;
    access_log off;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    location /.well-known/acme-challenge/ {
        alias /var/www/.well-known/acme-challenge/;
    }
    location / {
        return 301 https://$host:443$request_uri;
    }
}

server {
    server_name {{DOMAIN}};
    listen 443 ssl http2;

    access_log /var/log/nginx/{{DOMAIN}}.access.log;
    error_log /var/log/nginx/{{DOMAIN}}.error.log;

    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000;";
    add_header Cache-Control public;
    add_header 'Access-Control-Allow-Origin' "$http_origin";
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS, POST, PUT, DELETE, PATCH';
    add_header 'Access-Control-Allow-Headers' 'Accept,Accept-Encoding,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Type,Origin,If-Modified-Since,User-Agent,X-Requested-With';
    add_header 'Access-Control-Expose-Headers' 'X-Powered-By';

    set $root_path /var/www/{{DOMAIN}};
    root $root_path;
    disable_symlinks if_not_owner from=$root_path;
    charset utf-8;
    index index.php;
    autoindex off;

    include /etc/nginx/ssl.conf;

    if ($request_method ~* ^(OPTIONS|HEAD)$) {
        return 204;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}


Шаблон конфигурации nginx для поддомена api.yourdomain.com. Здесь мы также подключаем конфигурацию SSL, делаем доступным извне каталог, в который Let’s Encrypt будет складывать файлы для валидации доменов. Все запросы к api.yourdomain.com перенаправляем на api.yourdomain.com. Подключаем PHP7.1-FPM.

setup.sh:

#!/usr/bin/env bash
time_start=`date +%s`

########################
# EDIT THESE VARIABLES:
########################
DOMAIN=yourdomain.com
API_DOMAIN="api.$DOMAIN"
SUPERUSER="inpassor"
SUPERUSER_PASSWORD="12341234"
USER="johndoe"
USER_EMAIL="johndoe@yourdomain.com"
USER_PASSWORD="12341234"
########################

apt update
echo y | apt install dialog apt-utils
echo y | apt install sed wget gnupg nano htop curl zip unzip apt-transport-https lsb-release ca-certificates debian-archive-keyring certbot

# add users
adduser --quiet --disabled-password --gecos "" $SUPERUSER
echo "$SUPERUSER:$SUPERUSER_PASSWORD" | chpasswd
adduser --quiet --disabled-password --gecos "" $USER
echo "$USER:$USER_PASSWORD" | chpasswd
sed -i "s/$SUPERUSER:x:1000:1000/$SUPERUSER:x:0:0/" /etc/passwd

# add external repositories and GPG keys
echo "deb http://nginx.org/packages/debian/ stretch nginx" > /etc/apt/sources.list.d/nginx.list
wget --quiet -O - https://nginx.org/packages/keys/nginx_signing.key | apt-key add -
echo "deb https://packages.sury.org/php/ stretch main" > /etc/apt/sources.list.d/php.list
wget --quiet -O - https://packages.sury.org/php/apt.gpg | apt-key add -

apt update
echo y | apt upgrade
echo y | apt install nginx php7.1-cli php7.1-fpm php7.1-mbstring php7.1-curl php7.1-xml

# nginx setup
mkdir /var/www
mkdir "/var/www/$API_DOMAIN"
chown -R $USER:www-data /var/www

rm /etc/nginx/conf.d/default.conf
cp nginx.conf /etc/nginx/nginx.conf
echo "" > /etc/nginx/ssl.conf
mkdir /etc/nginx/sites-available
mkdir /etc/nginx/sites-enabled

sed "s@{{DOMAIN}}@$DOMAIN@g" site.conf > /etc/nginx/sites-available/$DOMAIN.conf
ln -s /etc/nginx/sites-available/$DOMAIN.conf /etc/nginx/sites-enabled
sed "s@{{DOMAIN}}@$API_DOMAIN@g" api.conf > /etc/nginx/sites-available/$API_DOMAIN.conf
ln -s /etc/nginx/sites-available/$API_DOMAIN.conf /etc/nginx/sites-enabled

# ssl setup
service nginx restart
certbot register --agree-tos --email $USER_EMAIL
certbot certonly --webroot -w /var/www -d $DOMAIN -d $API_DOMAIN --rsa-key-size 4096
openssl dhparam -out /etc/nginx/dhparam.pem 4096
sed "s@{{DOMAIN}}@$DOMAIN@g" ssl.conf > /etc/nginx/ssl.conf
service nginx restart
chown -R nginx:$USER /var/log/nginx
chmod 664 /var/log/nginx/*

time_end=`date +%s`
echo Execution time: $((time_end-time_start)) sec.


Bash скрипт для автоматической настройки сервера. В начале файла объявляется несколько переменных:

DOMAIN=yourdomain.com # ваш домен (без www в начале)
API_DOMAIN=«api.$DOMAIN» # поддомен, на котором будет бэкенд API (здесь это api.yourdomain.com)
SUPERUSER=«inpassor» # имя суперпользователя, обладающего правами root
SUPERUSER_PASSWORD=»12341234» # пароль суперпользователя
USER=«johndoe» # имя пользователя
USER_EMAIL=«johndoe@yourdomain.com» # email пользователя — используется для регистрации в Let’s Encrypt
USER_PASSWORD=»12341234» # пароль пользователя

Разумеется, необходимо их переназначить.

Далее последовательно выполняются следующие действия:

  • Устанавливаются необходимые пакеты.
  • Добавляются внешние репозитории и GPG ключи для доступа к более свежим пакетам nginx и PHP.
  • Устанавливаются пакеты nginx, php7.1-cli, php7.1-fpm, php7.1-mbstring, php7.1-curl и php7.1-xml.
  • Настраивается nginx.
  • Запускается процесс получения SSL сертификата Let’s Encrypt. Обратите внимание, что здесь же генерируется файл dhparam.pem. Этот процесс весьма долгий, поэтому можно сохранить сгенерированный файл и в дальнейшем копировать его.


Все, скрипт настройки сервера у нас готов.

Теперь мы можем зайти на наш VDS через SSH (по его IP адресу) под пользователем root, скопировать туда файлы *.conf и setup.sh, сделать setup.sh исполняемым (chmod 700 setup.sh) и запустить его.

После процесса настройки VDS готов к работе.

У нас установлен nginx, который слушает запросы к адресам: yourdomain.com, yourdomain.com, api.yourdomain.com, api.yourdomain.com. Запросы к http перенаправляются на https, запросы к yourdomain.com перенаправляются на www.yourdomain.com. По адресу api.yourdomain.com у нас работает API. Сейчас там пусто и чтобы он отвечал что-то на наши запросы, необходимо создать в каталоге /var/www/api.yourdomain.com/ файл index.php.

Но пока это все не доступно, по той причине, что мы не прописали необходимые настройки DNS.

8. Настройки DNS


После того, как мы настроили раздачу CloudFront и бэкенд сервер, мы готовы произвести окончательную настройку DNS записей.

8.1. Возвращаемся в Яндекс.Коннект: https://connect.yandex.ru/portal/home.

8.2. Переходим в «Админку».

8.3. Выбираем пункт меню «Управление DNS».

Нам нужно добавить четыре новые DNS записи.

  • Запись типа A: в поле «Хост» указываем »@», в поле «Значение записи» — IP адрес нашего VDS, в поле «TTL» — »3600».
  • Запись A: «Хост» — «api», «Значение записи» — IP адрес VDS, «TTL» — »3600».
  • Запись CNAME: «Хост» — «www», «Значение записи» — Domain Name раздачи CloudFront (см. пункт 6.3 настоящего руководства), «TTL» — »3600».
  • Запись CNAME: «Хост» — «static», «Значение записи» — Domain Name раздачи CloudFront, «TTL» — »3600».


Только что мы направили запросы к yourdomain.com (без www) и api.yourdomain.com к нашей VDS, а запросы к www.yourdomain.com и static.yourdomain.com — к раздаче CloudFront.

9. Gitlab репозиторий


Далее нам нужно создать приватный git репозиторий для хранения и версифицирования исходных файлов нашего проекта на Angular.

9.1. Регистрирумся / логинимся на Gitlab: https://gitlab.com.

9.2. Нажимаем кнопку «New project».

op60_lacfqvh-qpg7gsks8tvqcc.png

9.3. Далее выбираем путь к проекту и его название.

tst-gsqbwi3imhnkdbqmsefur08.png

Устанавливаем «Visibility Level» в значение «Private» и нажимаем кнопку «Create project».

9.4. Добавляем ваш публичный SSH ключ в настройках.

Если у вас нет ключа, необходимо его создать. Как создать пару приватный ключ — публичный ключ в системе Windows можно почитать, например, тут.

В верхнем меню нажимаем на свой аватар, в выпадающем меню выбираем пункт «Settings».

canvaybush07g48nqbpa_y9hjyi.png

В меню слева выбираем пункт «SSH Keys».
В поле «Key» вставляем содержимое файла публичного ключа. В системе Windows обычно он распололагается по пути: C:\Users\YourUsername\.ssh\id_rsa.pub.
В поле «Title» пишем название ключа (что угодно).

9.5. Клонируем репозиторий на локальный компьютер.

Для этого у нас на компьютере должен быть установлен git / git for Windows.

В командной строке заходим в локальную папку с вашими проектами, например, в C:\Projects.
Запускаем команду:

git clone git@gitlab.com:YourLogin/my-awesome-project.git MyAwesomeProject


Адрес репозитория «git@gitlab.com: YourLogin/my-awesome-project.git» можно посмотреть на главной странице проекта на сайте Gitlab. Здесь «MyAwesomeProject» — название локальной папки с проектом, которая будет создана автоматически.

Мы только что склонировали пустой репозиторий на локальный компьютер и у нас появилась папка проекта, в которой можно начинать создавать ваш проект.

10. Gitlab CI


Настраиваем Gitlab CI для автоматической сборки проекта, синхронизации собранных файлов с корзиной Amazon S3 и обновлении раздачи CloudFront.

10.1. Заходим в наш проект на сайте https://gitlab.com.

10.2. В меню слева выбираем «CI / CD» — «Environments».

27codys5-r76nppw11yuba7larg.png

Нажимаем кнопку «New environment».

10.3. В поле «Name» вводим «prod».

В поле «External URL» вводим ваш домен (вместе с https:// и www вначале). Например: www.yourdomain.com. Нажимаем кнопку «Save».

10.4. В меню слева выбираем «Settings» — «CI / CD».

9f4xoxunzk4i_p9x6qymqwl4v1s.png

10.5. Напротив пункта «Secret variables» нажимаем кнопку «Expand».

В поле «Key» вводим «AWS_ACCESS_KEY_ID».
В поле «Value» вводим ваш Access key ID, полученный в пункте 3.8 настоящего руководства.
В поле «Environment scope» оставляем звездочку.
Нажимаем кнопку «Add new variable».

Добавляем еще одну переменную «AWS_SECRET_ACCESS_KEY».
Повторяем действия, в поле «Value» вводим ваш Secret access key, полученный в пункте 3.8 настоящего руководства.

И еще одна переменная — «AWS_DISTRIBUTION_ID».
Повторяем действия, в поле «Value» вводим ваш Distribution ID, полученный в пункте 6.3 настоящего руководства.
На этот раз в поле «Environment scope» вводим «prod».

Должно получиться как на скриншоте:

7hv_pzsw6l8rlzto0u7oayxsusg.png

Настройки среды окружения и переменных CI мы выполнили, остальное пропишем далее непосредственно в нашем проекте в файле .gitlab-ci.yml.

11. Стартовый сайт на Angular


Настало время создать наш проект на Angular.

11.1. В командной строке заходим в каталог нашего проекта — туда, куда мы склонировали пустой репозиторий (см. пункт 9.5 настоящего руководства).
В нашем примере это C:\Projects\MyAwesomeProject.
Выполняем следующие команды:

npm i -g @angular/cli
ng new yourdomain.com --style=scss --skip-git=true --directory=.


yourdomain.com заменяем на название вашего проекта (не обязательно должно совпадать с названием домена).

У нас глобально установился Angular cli и в каталоге проекта появились файлы, с которыми можно начинать работать.

11.2. Создадим файл .gitignore:

/.idea
/dist
/out-tsc
/node_modules
/e2e/*.js
/e2e/*.map
npm-debug.log
package-lock.json

11.3. Отредактируем файл .angular-cli.json.

В секции «apps» находим ключ «assets» и меняем его значение на:

[
    {
        "glob": "**/*",
        "input": "./assets/",
        "output": "./"
    }
]


Таким образом все файлы и каталоги, которые будут находится в src/assets, попадут в корень нашей сборки.

В «styles» заменим «styles.scss» на «styles/styles.scss».

11.4. Создадим каталог src/styles и перенесем туда файл src/styles.scss.
Создадим файл src/styles/_variables.scss с таким содержимым:

$static-url: 'https://static.yourdomain.com';


В начале файла src/styles/styles.scss вставим строчку:

@import 'variables';


В дальнейшем скрипт сборки будет обновлять переменную $static-url в файле src/styles/_variables.scss.
Таким образом, у нас появится возможность прописывать в стилях пути к изображениям и шрифтам через эту переменную.

11.5. Добавим файл src/assets/favicon.ico. Как же мы без иконки то?

11.6. Создадим файл src/assets/robots.txt:

User-agent: *
Host: {{SERVER_URL}}
Sitemap: {{SERVER_URL}}/sitemap.xml

11.7. Создадим файл src/assets/sitemap.xml:



    
        {{SERVER_URL}}
    


11.8. Создадим файл .gitlab-ci.yml:

image: node:8.9

stages:
  - deploy

cache:
  paths:
    - node_modules/

before_script:
  - npm install --unsafe-perm --silent --global @angular/cli
  - npm install --unsafe-perm --silent
  - apt update
  - echo y | apt install python-dev unzip
  - curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"
  - unzip awscli-bundle.zip
  - ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws

deploy_prod:
  stage: deploy
  only:
    - master
  environment:
    name: prod
  artifacts:
    paths:
      - dist
  script:
    - DEPLOY_SERVER="${CI_ENVIRONMENT_URL/https:\/\/www./}"
    - STATIC_URL="https://static.$DEPLOY_SERVER"
    - sed -i "s@$static-url:.*;@$static-url:'$STATIC_URL';@g" src/styles/_variables.scss
    - ng build --prod --aot --build-optimizer --no-progress --extract-licenses=false
    - sed -i -e "s@\" href=\"@\" href=\"$STATIC_URL\/@g; s@href=\"styles@href=\"$STATIC_URL\/styles@g; s@src=\"@src=\"$STATIC_URL\/@g" dist/index.html
    - SED_PATTERN="s@{{STATIC_URL}}@$STATIC_URL@g; s@{{SERVER_URL}}@$CI_ENVIRONMENT_URL@g"
    - sed -i -e "$SED_PATTERN" dist/robots.txt
    - sed -i -e "$SED_PATTERN" dist/sitemap.xml
    - aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
    - aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
    - aws s3 rm s3://www.$DEPLOY_SERVER/ --recursive --exclude "*" --include "*.css" --include "*.js" --include "*.json" --include "*.html" --include "*.xml" --include "*.txt"
    - aws s3 sync dist/ s3://www.$DEPLOY_SERVER/ --no-progress --delete --size-only --acl public-read
    - aws cloudfront create-invalidation --distribution-id $AWS_DISTRIBUTION_ID --paths /*.html /*.xml /*.txt /*.json


Давайте посмотрим, что у нас тут.

Для работы скрипта используется Docker image «node:8.9».
Объявляется один этап сборки «deploy».
Каталог «node_modules» кешируется.
Перед выполнением скрипта сборки производится глобальная установка angular/cli, после этого устанавливаются все зависимости проекта.
Далее устанавливаются необходимые пакеты python-dev и unzip, скачивается утилита aws cli, распаковывается и устанавливается.
Объявляется одна задача с названием «deploy_prod», которая выполняется в окружении «prod», на этапе «deploy» и только в ветке «master».
В результате выполнения этой задачи будут созданы артефакты — все содержимое каталога «dist» (результат сборки проекта).

Процесс выполнения задачи:

  • Объявляется переменная $DEPLOY_SERVER, которая формируется на основе $CI_ENVIRONMENT_URL (значение поля «External URL», см. пункт 10.3 настоящего руководства), без «www.» в начале (yourdomain.com).
  • Объявляется переменная $STATIC_URL — static.$DEPLOY_SERVER (https://static.yourdomain.com).
  • Значение переменной »$static-url» в файле src/styles/_variables.scss заменяется значением $STATIC_URL (https://static.yourdomain.com).
  • Производится сборка проекта Angular (environment — production, AOT, используя build optimizer, не показывая прогресс на экране, не извлекая лицензии сторонних производителей).
  • В файле index.html все пути к файлам таблиц стилей, скриптов и изображений заменяются на абсолютные пути к $STATIC_URL.
  • В файлах robots.txt и sitemap.xml подстрока »{{SERVER_URL}}» заменяется на значение $CI_ENVIRONMENT_URL (https://www.yourdomain.com), подстрока »{{STATIC_URL}}» заменяется на значение $STATIC_URL (https://static.yourdomain.com).
  • Устанавливаются ID ключа доступа и секретный ключ для безопасного соединения с AWS.
  • Из корзины S3 рекурсивно удаляются все файлы *.css, *.js, *.json, *.html, *.xml и *.txt.
  • Каталог «dist» синхронизируется с корзиной S3 (без вывода прогресса на экран, удаляя из корзины отсутствующие в «dist» файлы, сравнивая только размеры файлов, публикуя файлы для чтения).
  • Для раздачи CloudFront создается invalidation (статья документации) с удалением файлов *.html, *.xml, *.txt и *.json из корневой папки.


11.9. Запускаем в каталоге проекта команды:

git add .
git commit -m "first commit"
git push


Всё! Если мы выполнили все пункты данного руководства правильно, осталось дождаться когда Gitlab выполнит задачу, и мы увидим наш стартовый сайт по адресу www.yourdomain.com.

mjqbrzhuq0lp-lwvvj4yaf4mpam.png

Поздравляю!

12. Заключение


Итак, мы только что развернули удобную рабочую среду для создания сайта на Angular, настроили автоматическую сборку проекта и его доставку через CDN. Также мы настроили бэкенд сервер, SSL сертификаты, DNS записи и у нас работает доменная почта.

О чем я не упомянул в статье и что еще можно сделать?

  • Если нам нужен бэкенд API, мы должны его создать (в нашем примере, на PHP). Скорее всего, понадобится усложнить bash скрипт настройки сервера, добавив туда, например, установку баз данных.
  • Несмотря на то, что мы создали файл robots.txt и явно указали в нем Host, все равно желательно защититься от нежелательного доступа клиентов напрямую к корзине S3 и раздаче CloudFront.
  • В Gitlab CI у нас настроено только одно окружение — «prod». В услов

    © Habrahabr.ru