Azure: VMSS за Load Balancer, renew SSL от Let’s Encrypt, SMB file share и NGINX-proxy

By | 07/19/2017
 

Продолжаем танцевать с проектом на Azure. Описание самого проекта можно найти в посте Azure: CDN, NodeJS, Docker Swarm и Blue-Green деплой.

Сейчас имеется одна виртуальная машина, на которой работает NGINX, выполняющий роль прокси для всех доменов и всех сервисов проекта.

Изначально – этот прокси планировался для двух WordPress сайтов проекта, и никоим образом не для того, в каком виде он используется нынче – в настоящий момент проксируется порядка 90 доменов на ~30 приложений.

Т.к. это по сути наш single point of failure для всего аккаунта и всех сервисов в нём – то держать его в виде единственной виртуальной машины далеко не самая лучшая идея.

Для исправления ситуации – было решено добавить новую группу ресурсов, которая бы включала в себя Load Balancer и две виртуальные машины с NGINX за ним, объединённые в VMSS.

UPD Деплой описан в посте Travis: деплой конфигов NGINX в Azure File share.

Но тут возникла другая проблема – на этом прокси имеется множество Let’s Encrypt сертификатов (экономили время во время сетапа перед релизом):

root@jm-gw:~# find /etc/letsencrypt/ -name "*.pem" | wc -l
170

И на каждой машине – необходимо делать renew имеющихся ключей.

Решение было найдено следующее: на одной из машин (“primary” или “мастер”) по крону запускается скрипт letsencrypt-auto (вообще достаточно интересный пример bash-скрипта), который обращается к серверам Let’s Encrypt для обновления информации о сертификатах. Ключи,  файлы настроек NGINX будут храниться на отдельной Samba-шаре, по аналогии с Azure: CLI – пример создания SMB file share, Load Balancer и Linux VM с NGINX.

Верификация выполняется через webroot, который так же будет расшарен через файл-шару на обе машины, а потому неважно – на какую из них придёт запрос верификации от Let’s Encrypt.

Сам запрос выглядит примерно так:

http://jm-gw-proxy-dev.domain.ms/.well-known/acme-challenge/_kA_3GVvSOvWWEBHPN1DztcE1D1ZZaYefeWMIrDIWUY

Далее создадим:

  1. группу ресурсов
  2. установим Azure CLI v2
  3. VMSS с Load Balancer-ом
  4. File share
  5. установим на обе машины NGINX
  6. установим на _мастер_ Let’s Ecnrypt клиент
  7. получим новый сертификат, протестируем работу основной и запасной машин

Создание VMSS, Load Balancer и SMB file share

Создаём группу ресурсов:

azure group create -n jm-gw-proxy-dev -l westeurope

Установка Azure CLI

Azure CLI v1 весьма ограничена в плане создания VMSS, потому – устанавливаем v2:

sudo curl -L https://aka.ms/InstallAzureCli | sudo bash
...
===> In what directory would you like to place the install? (leave blank to use '/root/lib/azure-cli'): /opt/azurecliv2
-- Creating directory '/opt/azurecliv2'.
-- We will install at '/opt/azurecliv2'.
===> In what directory would you like to place the 'az' executable? (leave blank to use '/root/bin'): /opt/azurecliv2/bin
...
===> Modify profile to update your $PATH and enable shell/tab completion now? (Y/n): n
-- If you change your mind, add 'source /opt/azurecliv2/az.completion' to your rc file and restart your shell to enable tab completion.
-- You can run the CLI with '/opt/azurecliv2/bin/az'.
-- Installation successful.
-- Run the CLI with /opt/azurecliv2/bin/az --help

Проверяем:

/opt/azurecliv2/bin/az -v
azure-cli (2.0.10)

Логинимся:

/opt/azurecliv2/bin/az login

Создание VMSS

Документация (внезапно – достаточно толковая и удобная) по az vmss createтут>>>.

Важный нюанс: т.к. в Azure VMSS и Load Balancer интегрированы до сих пор криво – вы никоим образом не можете использовать уже созданный LB с новым VMSSAzure Resource manager просто не пробросит порты на бекенды и не даст сделать это вручную через Azure Portal. Поэтому с помощью CLI – LB создаётся одновременно с VMSS, в той же команде.

Полностью команда будет выглядеть так:

az vmss create --resource-group jm-gw-proxy-dev \
--image UbuntuLTS \
--name jm-gw-proxy-dev-vmss \
--admin-username jmadmin \
--admin-password p@ssw0rd \
--authentication-type password \
--backend-pool-name jm-gw-proxy-dev-beip \
--instance-count 2 \
--load-balancer jm-gw-proxy-dev-lb \
--lb-nat-pool-name jm-gw-proxy-dev-lb-nat \
--public-ip-address jm-gw-proxy-dev-lb-ip \
--public-ip-address-allocation Static \
--location westeurope \
--subnet jm-gw-proxy-dev-gws-subnet \
--vnet-name jm-gw-proxy-dev-vnet \
--disable-overprovision

Выполняем:

/opt/azurecliv2/bin/az vmss create --resource-group jm-gw-proxy-dev \
> --image UbuntuLTS \
> --name jm-gw-proxy-dev-vmss \
> --admin-username jmadmin \
> --admin-password p@ssw0rd \
> --authentication-type password \
> --backend-pool-name jm-gw-proxy-dev-beip \
> --instance-count 2 \
> --load-balancer jm-gw-proxy-dev-lb \
> --lb-nat-pool-name jm-gw-proxy-dev-lb-nat \
> --public-ip-address jm-gw-proxy-dev-lb-ip \
> --public-ip-address-allocation Static \
> --location westeurope \
> --subnet jm-gw-proxy-dev-gws-subnet \
> --vnet-name jm-gw-proxy-dev-vnet \
> --disable-overprovision
{
"vmss": {
"overprovision": false,
"provisioningState": "Succeeded",
"singlePlacementGroup": true,
"uniqueId": "a9202c3a-4d00-4fff-ad3f-c001c573e66e",
"upgradePolicy": {
"mode": "Manual"
},
"virtualMachineProfile": {
"networkProfile": {
"networkInterfaceConfigurations": [
{
"name": "jmakqec01Nic",
"properties": {
"dnsSettings": {
"dnsServers": []
},
"enableAcceleratedNetworking": false,
"ipConfigurations": [
{
"name": "jmakqec01IPConfig",
"properties": {
"loadBalancerBackendAddressPools": [
{
"id": "/subscriptions/0a4f2b9c-****-****-****-40b17ef8c3ab/resourceGroups/jm-gw-proxy-dev/providers/Microsoft.Network/loadBalancers/jm-gw-proxy-dev-lb/backendAddressPools/jm-gw-proxy-dev-beip",
"resourceGroup": "jm-gw-proxy-dev"
}
],
"loadBalancerInboundNatPools": [
{
"id": "/subscriptions/0a4f2b9c-****-****-****-40b17ef8c3ab/resourceGroups/jm-gw-proxy-dev/providers/Microsoft.Network/loadBalancers/jm-gw-proxy-dev-lb/inboundNatPools/jm-gw-proxy-dev-lb-nat",
"resourceGroup": "jm-gw-proxy-dev"
}
],
"privateIPAddressVersion": "IPv4",
"subnet": {
"id": "/subscriptions/0a4f2b9c-****-****-****-40b17ef8c3ab/resourceGroups/jm-gw-proxy-dev/providers/Microsoft.Network/virtualNetworks/jm-gw-proxy-dev-vnet/subnets/jm-gw-proxy-dev-gws-subnet",
"resourceGroup": "jm-gw-proxy-dev"
}
}
}
],
"primary": true
}
}
]
},
"osProfile": {
"adminUsername": "jmadmin",
"computerNamePrefix": "jmakqec01",
"linuxConfiguration": {
"disablePasswordAuthentication": false
},
"secrets": []
},
"storageProfile": {
"imageReference": {
"offer": "UbuntuServer",
"publisher": "Canonical",
"sku": "16.04-LTS",
"version": "latest"
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"managedDisk": {
"storageAccountType": "Standard_LRS"
}
}
}
}
}
}

Проверяем. Находим Public IP:

azure network public-ip list --resource-group jm-gw-proxy-dev
info:    Executing command network public-ip list
+ Getting the public ip addresses
data:    Name                        Location    Resource group        Provisioning state  Allocation method  IP version  IP Address     Idle timeout, minutes  FQDN
data:    --------------------------  ----------  --------------------  ------------------  -----------------  ----------  -------------  ---------------------  ----
data:    jm-gw-proxy-dev-lb-ip  westeurope  jm-gw-proxy-dev  Succeeded           Static             IPv4        13.***.***.102  4

Подключаемся:

ssh -p 50000 jmadmin@13.***.***.102
...
jmadmin@jmakqec01000000:~$

Сеть работает.

Azure File share

Далее требуется создать storage, который будет использоваться в роли файловой шары на обеих машинах.

Создаём Storage account:

azure storage account create --resource-group jm-gw-proxy-dev \
> --location westeurope \
> --sku-name GRS \
> --kind storage \
> gwproxystorage

Проверяем:

azure storage account show -g jm-gw-proxy-dev gwproxystorage
info:    Executing command storage account show
+ Getting storage account
data:    Name: gwproxystorage
data:    Url: /subscriptions/0a4f2b9c-****-****-****-40b17ef8c3ab/resourceGroups/jm-gw-proxy-dev/providers/Microsoft.Storage/storageAccounts/gwproxystorage
data:    Type: Microsoft.Storage/storageAccounts
data:    SKU Name: Standard_GRS
data:    SKU Tier: Standard
data:    Kind: Storage
data:    Resource Group: jm-gw-proxy-dev
data:    Location: westeurope
data:    Provisioning State: Succeeded
data:    Primary Location: westeurope
data:    Primary Status: available
data:    Secondary Location: northeurope
data:    Creation Time: Wed, 19 Jul 2017 10:34:18 GMT
data:    Primary Endpoints: blob https://gwproxystorage.blob.core.windows.net/
data:    Primary Endpoints: queue https://gwproxystorage.queue.core.windows.net/
data:    Primary Endpoints: table https://gwproxystorage.table.core.windows.net/
data:    Primary Endpoints: file https://gwproxystorage.file.core.windows.net/

Создаём в этом аккаунте файл-шару:

azure storage share create gwproxydata \
--account-name gwproxystorage \
--account-key 8PS***MQA==

Далее нам потребуется создать три каталога – для конфигов NGINX, для сертификатов Let’s Encrypt и для acme-верификации.

Создаём каталог для файлов NGINX с именем datanginx-conf.d:

azure storage directory create --share gwproxydata  \
> --path datanginx-conf.d \
> --account-name gwproxystorage \
> --account-key 8PS***MQA==
info:    Executing command storage directory create
+ Creating storage file directory 'datanginx-conf.d' in share gwproxydata
info:    Directory datanginx-conf.d has been created successfully
data:    {
data:        name: 'datanginx-conf.d',
data:        etag: '"0x8D4CE92CA34139D"',
data:        lastModified: 'Wed, 19 Jul 2017 10:42:01 GMT',
data:        requestId: '8f3f45b7-001a-003e-447b-00e140000000'
data:    }
info:    storage directory create command OK

Аналогично – для Let’s Encrypt с именем dataletsencrypt:

azure storage directory create --share gwproxydata  \
> --path dataletsencrypt \
> --account-name gwproxystorage \
> --account-key 8PS***MQA==

И datahtml для webroot:

azure storage directory create --share gwproxydata  \
> --path datahtml \
> --account-name gwproxystorage \
> --account-key 8PS***MQA==

Подключаемся на “мастер” хост, устанавливаем cifs-utils: (позже, при создании ARM провижена всего этого – все необходимые действия будут выполняться через custom-data):

root@jmakqec01000000:~# apt update && apt install cifs-utils

Создаём каталог, в который смонтируем общий ресурс:

root@jmakqec01000000:~# mkdir /data

Монтируем шару:

root@jmakqec01000000:~# mount -t cifs //gwproxystorage.file.core.windows.net/gwproxydata /data -o vers=3.0,username=gwproxystorage,password=8PS***MQA==,dir_mode=0755,file_mode=0644

Проверяем:

root@jmakqec01000000:~# ls -l /data/
total 0
drwxr-xr-x 2 root root 0 Jul 19 10:45 datahtml
drwxr-xr-x 2 root root 0 Jul 19 10:43 dataletsencrypt
drwxr-xr-x 2 root root 0 Jul 19 10:42 datanginx-conf.d

Повторяем на “слейве”:

root@jmakqec01000001:~# mkdir /data
root@jmakqec01000001:~# mount -t cifs //gwproxystorage.file.core.windows.net/gwproxydata /data -o vers=3.0,username=gwproxystorage,password=8PS***MQA==,dir_mode=0755,file_mode=0644
root@jmakqec01000001:~# ls -l /data/
total 0
drwxr-xr-x 2 root root 0 Jul 19 10:45 datahtml
drwxr-xr-x 2 root root 0 Jul 19 10:43 dataletsencrypt
drwxr-xr-x 2 root root 0 Jul 19 10:42 datanginx-conf.

Обновляем /etc/fstab на обеих машинах:

jmadmin@jmakqec01000000:~$ tail -n 1 /etc/fstab
//gwproxystorage.file.core.windows.net/gwproxydata /data cifs vers=3.0,username=gwproxystorage,password=8PS***MQA==,dir_mode=0755,file_mode=0644,serverino

Проверяем монтирование без ребута:

jmadmin@jmakqec01000000:~$ sudo umount /data
jmadmin@jmakqec01000000:~$ sudo mount -a
jmadmin@jmakqec01000000:~$ ls -l /data/
total 0
drwxr-xr-x 2 root root 0 Jul 19 10:45 datahtml
drwxr-xr-x 2 root root 0 Jul 19 10:43 dataletsencrypt
drwxr-xr-x 2 root root 0 Jul 19 10:42 datanginx-conf.d

Готово.

По теме:

https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mount-azure-file-storage-on-linux-using-smb

https://docs.microsoft.com/en-us/azure/storage/storage-blob-storage-tiers

https://medium.com/@wimcoekaerts/creating-an-azure-file-share-and-mounting-it-in-your-linux-instance-2d011fbacdbc

https://docs.microsoft.com/en-us/azure/storage/storage-how-to-use-files-linux

NGINX

Следующим шагом – устанавливаем NGINX, и подключаем к нему каталог /data/datanginx-conf.d/:

root@jmakqec01000000:~# apt install nginx -y

В файл /etc/nginx/nginx.conf в блок http {} добавляем:

...
    include /data/datanginx-conf.d/*.conf;
...

Добавляем тестовый файл конфигурации /data/datanginx-conf.d/jm-gw-proxy-dev.domain.ms.conf:

server {
        server_name jm-gw-proxy-dev.domain.ms;

        access_log /var/log/nginx/jm-gw-proxy-dev.domain.ms-access.log;
        error_log /var/log/nginx/jm-gw-proxy-dev.domain.ms-error.log;

        root /usr/share/nginx/html;
}

Проверяем NGINX, перечитываем конфиги:

root@jmakqec01000000:~# nginx -t && service nginx reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Перезапускаем NIGNX на секондари.

Правила Load Balancer

Возвращаемся к Load Balancer-у, добавляем health probe для HTTP:

azure network lb probe create --resource-group jm-gw-proxy-dev \
> --lb-name jm-gw-proxy-dev-lb \
> --name http_health \
> --protocol tcp \
> --port 80

И HTTPS:

azure network lb probe create --resource-group jm-gw-proxy-dev \
> --lb-name jm-gw-proxy-dev-lb \
> --name https_health \
> --protocol tcp \
> --port 443

Создаём правило проброса трафика с внешнего IP, порт 80, на бекенд, порт 80:

azure network lb rule create --resource-group jm-gw-proxy-dev \
> --lb-name jm-gw-proxy-dev-lb \
> --name http \
> --protocol tcp \
> --frontend-port 80 \
> --backend-port 80 \
> --frontend-ip-name loadBalancerFrontEnd \
> --backend-address-pool-name jm-gw-proxy-dev-beip \
> --probe-name http_health

Тут уже можно проверить рабоспособность схемы:

curl 13.***.***.102
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Добавляем правило для HTTPS:

azure network lb rule create --resource-group jm-gw-proxy-dev \
> --lb-name jm-gw-proxy-dev-lb \
> --name https \
> --protocol tcp \
> --frontend-port 443 \
> --backend-port 443 \
> --frontend-ip-name loadBalancerFrontEnd \
> --backend-address-pool-name jm-gw-proxy-dev-beip \
> --probe-name https_health

По теме:

https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-get-started-ilb-arm-cli

https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-get-started-internet-arm-cli

SSL и Let’s Encrypt

Последний шаг – настроить SSL.

На мастере устанавливаем Let’s Encrypt клиент:

root@jmakqec01000000:~# git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

Возвращаемся к NGINX, добавляем порт 443, меняем root и добавляем location для верификации:

 

server {
        server_name jm-gw-proxy-dev.domain.ms;

        listen 80;
        listen 443 ssl;

        access_log /var/log/nginx/jm-gw-proxy-dev.domain.ms-access.log;
        error_log /var/log/nginx/jm-gw-proxy-dev.domain.ms-error.log;

        root /data/datahtml/;

        # Lets Encrypt Webroot
        location ~ /.well-known {
            allow all;
        }
}

Перезапускаем на мастере и слейве:

root@jmakqec01000000:~# nginx -t && service nginx reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

На мастере получаем сертификат (от обычного пользователя), выбрав webroot для авторизации:

jmadmin@jmakqec01000000:~$ /opt/letsencrypt/letsencrypt-auto certonly -d jm-gw-proxy-dev.domain.ms
...
How would you like to authenticate with the ACME CA?
-------------------------------------------------------------------------------
1: Spin up a temporary webserver (standalone)
2: Place files in webroot directory (webroot)
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
...
Select the webroot for jm-gw-proxy-dev.domain.ms:
-------------------------------------------------------------------------------
1: Enter a new webroot
-------------------------------------------------------------------------------
Press 1 [enter] to confirm the selection (press 'c' to cancel): 1
Input the webroot for jm-gw-proxy-dev.domain.ms: (Enter 'c' to cancel): /data/datahtml/
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/jm-gw-proxy-dev.domain.ms/fullchain.pem.
...

Let’s Encrypt и certbot размещают файлы сертификатов в каталоге /etc/letsencrypt/archive, а нам требуется хранить их в /data/dataletsencrypt/.

Как вариант – можно было бы использовать симлинк, но /data смонтирован через cifs, а потому:

root@jmakqec01000000:~# mkdir -p /etc/letsencrypt/archive/
root@jmakqec01000000:~# ln -s /etc/letsencrypt/archive/ /data/dataletsencrypt/
ln: failed to create symbolic link '/data/dataletsencrypt/archive': Operation not supported

Можно было бы связаться с саппортом Azure в поисках решения по использованию симлинков или погуглить дольше, но проще создавать ключи в каталоге по умолчанию (т.е. /etc/letsencrypt/archive), а затем – rsync-ать их в /data/dataletsencryp/.

Проверяем файлы:

jmadmin@jmakqec01000000:~$ sudo find /etc/letsencrypt/archive/ -name *.pem
/etc/letsencrypt/archive/jm-gw-proxy-dev.domain.ms/privkey1.pem
/etc/letsencrypt/archive/jm-gw-proxy-dev.domain.ms/cert1.pem
/etc/letsencrypt/archive/jm-gw-proxy-dev.domain.ms/chain1.pem
/etc/letsencrypt/archive/jm-gw-proxy-dev.domain.ms/fullchain1.pem

Выполняем rsync:

sudo rsync -avh /etc/letsencrypt/archive/jm-gw-proxy-dev.domain.ms /data/dataletsencrypt/
sending incremental file list
jm-gw-proxy-dev.domain.ms/
jm-gw-proxy-dev.domain.ms/cert1.pem
jm-gw-proxy-dev.domain.ms/chain1.pem
jm-gw-proxy-dev.domain.ms/fullchain1.pem
jm-gw-proxy-dev.domain.ms/privkey1.pem
sent 9.01K bytes  received 96 bytes  6.07K bytes/sec
total size is 8.66K  speedup is 0.95

Обновляем конфиг NGINX /data/datanginx-conf.d/jm-gw-proxy-dev.domain.ms.conf, добавляем поддержку SSL:

server {
        server_name jm-gw-proxy-dev.domain.ms;

        listen 80;
        listen 443 ssl;

        access_log /var/log/nginx/jm-gw-proxy-dev.domain.ms-access.log;
        error_log /var/log/nginx/jm-gw-proxy-dev.domain.ms-error.log;

        root /data/datahtml/;

        ssl on;
        ssl_certificate /data/dataletsencrypt/jm-gw-proxy-dev.domain.ms/fullchain1.pem;
        ssl_certificate_key /data/dataletsencrypt/jm-gw-proxy-dev.domain.ms/privkey1.pem;

        # Lets Encrypt Webroot
        location ~ /.well-known {
            allow all;
        }
}

Проверяем, перезапускаем на обоих инстансах:

root@jmakqec01000001:~# nginx -t && service nginx reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

И проверяем SSL:

curl -v https://jm-gw-proxy-dev.domain.ms
* About to connect() to jm-gw-proxy-dev.domain.ms port 443 (#0)
*   Trying 13.***.***.102... connected
* Connected to jm-gw-proxy-domain.domain.ms (13.***.***.102) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
* Server certificate:
*       subject: CN=jm-gw-proxy-dev.domain.ms
*       start date: Jul 19 10:42:00 2017 GMT
*       expire date: Oct 17 10:42:00 2017 GMT
*       common name: jm-gw-proxy-dev.domain.ms
*       issuer: CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US
> GET / HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: jm-gw-proxy-dev.domain.ms
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: nginx/1.10.3 (Ubuntu)
< Date: Wed, 19 Jul 2017 11:46:37 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
<
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.10.3 (Ubuntu)</center>
</body>
</html>
* Connection #0 to host jm-gw-proxy-dev.domain.ms left intact
* Closing connection #0

Всё работает. 403 – т.к. нет индексного файла, в данном случае роли не играет вообще, т.к. сервис будет только проксировать запросы через proxy_pass.

Для проверки – стопаем NGINX на мастере и проверяем ещё раз:

jmadmin@jmakqec01000000:~$ sudo service nginx stop

И:

curl -I https://jm-gw-proxy-dev.domain.ms
HTTP/1.1 403 Forbidden
Server: nginx/1.10.3 (Ubuntu)
Date: Wed, 19 Jul 2017 11:51:16 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive

Всё работает.

Let’s Encrypt SSL renew

Осталось добавить renew и rsync в cron.

На мастере проверяем renew:

jmadmin@jmakqec01000000:~$ /opt/letsencrypt/letsencrypt-auto renew
Requesting root privileges to run certbot...
/home/jmadmin/.local/share/letsencrypt/bin/letsencrypt renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log
-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/jm-gw-proxy-dev.domain.ms.conf
-------------------------------------------------------------------------------
Cert not yet due for renewal
The following certs are not due for renewal yet:
/etc/letsencrypt/live/jm-gw-proxy-dev.domain.ms/fullchain.pem (skipped)
No renewals were attempted.

Добавляем в крон:

jmdmin@jmakqec01000000:~$ sudo crontab -l
30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew >> /var/log/le-renew.log
30 2 * * 1 rsync -avh /etc/letsencrypt/archive/jm-gw-proxy-dev.domain.ms /data/dataletsencrypt/
35 2 * * 1 service nginx reload

Готово.