Контейнеризация и автоматизация веб-сайтов, или Docker + Nginx + LetsEncrypt

Docker + Nginx + LetsEncrypt

Цели которые мы реализуем в рамках статьи:

  • использовать только OpenSource решения с неограниченной бесплатной лицензией для коммерческого использования
  • использовать Docker-контейнеры как механизм технической и организационной архитектуры
  • использовать Nginx как маршрутизатор и балансировщик HTTP-сервисов
  • использовать LetsEncrypt для обеспечения бесплатного и автоматизированного получения и продления SSL сертификатов
  • настроить Nginx на автоматическую динамическую настройку маршрутизации доменов при изменении состава активных Docker-контейнеров

Требования:

  • в качестве ОС хост-машин используется Ubuntu 18.04 LTS на платформе x86_64 или amd64 (для других ОС могут потребоваться изменения в действиях раздела "Установка Docker", смотрите официальное руководство по установке)

Приступим!

Установка Docker

Для начала подготовим хост-машину к установке, для этого добавим ключ и репозиторий Docker:

sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

Установим сам Docker Community Edition:

sudo apt-get update
sudo apt-get install docker-ce

Установка из репозитория позволяет не думать об обновлениях Docker, так как обновления производятся стандартными системными средствами.

После установки требуется произвести ещё пару настроек:

sudo groupadd docker
sudo usermod -aG docker $USER

Установка Docker-Compose

Для установки docker-compose требуется ещё меньше действий:

sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Обновление docker-compose производится аналогичными командами с заменой номера версии в URL.

Создание отдельной внутренней сети

Для работы системы потребуется несколько служебных контейнеров, которые будут взаимосвязаны между собой и которые будут обмениваться настройками с подключаемыми HTTP-сервисами через отдельную виртуальную сеть, назовём эту сеть "nginx".
Для создания сети достаточно исполнить:

docker network create nginx

Создание структуры каталогов

Создадим каталог проекта:

mkdir nginx-stack
cd nginx-stack

Внутри каталога проекта создадим каталоги для SSL-сертификатов, настроек, лог-файлов и публичных файлов необходимых для подтвеждения сертификатов LetsEncrypt:

mkdir certs cfg log www

По необходимости создадим файлы для системы контроля версий, на примере Git:

git init
printf "*\n\!.gitignore" > certs/.gitignore
printf "" > cfg/.gitignore
printf "*\n\!.gitignore" > log/.gitignore
printf "" > www/.gitignore

Создание файла настроек для Nginx

Создадим файл nginx.conf в каталоге проекта и заполним следующими настройками:

user nginx;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 8192;
error_log /var/log/nginx/error.log crit;

events {
  worker_connections 1024;
  use epoll;
  multi_accept on;
}

http {
  map $http_x_forwarded_proto $proxy_x_forwarded_proto {
    default $http_x_forwarded_proto;
    '' $scheme;
  }
  
  map $http_x_forwarded_port $proxy_x_forwarded_port {
    default $http_x_forwarded_port;
    '' $server_port;
  }
  
  map $http_upgrade $proxy_connection {
    default upgrade;
    '' close;
  }
    
  map $scheme $proxy_x_forwarded_ssl {
    default off;
    https on;
  }

  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 off;
  client_body_timeout 10;
  default_type application/octet-stream;
  keepalive_timeout 30;
  keepalive_requests 1000;
  reset_timedout_connection on;
  send_timeout 2;
  sendfile on;
  server_names_hash_bucket_size 128;
  server_tokens off;
  tcp_nopush on;
  tcp_nodelay on; 

  # Compression
  gzip on;
  gzip_min_length 10240;
  gzip_proxied expired no-cache no-store private auth;
  gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
  gzip_disable "msie6";

  # Caches information about open FDs, freqently accessed files
  open_file_cache max=8192 inactive=20s; 
  open_file_cache_valid 30s; 
  open_file_cache_min_uses 2;
  open_file_cache_errors on;

  proxy_http_version 1.1;
  proxy_buffering off;
  proxy_set_header Host $http_host;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection $proxy_connection;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
  proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
  proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
  proxy_set_header Proxy "";

  include /etc/nginx/mime.types;
  include /etc/nginx/cfg/*.conf;
}

При старте сервиса Nginx будет загружать файл настроек nginx.conf, а также mime.types и все файлы с расширением ".conf" из каталога cfg, в том числе automatic.conf, - автоматически генерируемый файл настроек.

В процессе работы каталог cfg может содержать:

  • automatic.conf - автоматически сгенерированные конфигуратором настройки для всех найденных доменов, контейнеры которых активны в сети "nginx"
  • default - автоматически сгенерированный letsencrypt-ом файл настроек для секции server, добавляемый каждому домену конфигуратором, в случае если нет файла специально под этот домен
  • default.location - опционально, файл который добавляется конфигуратором в секцию location каждому домену, в случае если нет файла специально под этот домен
  • опционально, файлы по имени домена, например, nixxlab.pro - содержимое файла добавляется конфигуратором в секцию server, заменяя собой default
  • опционально, файлы по имени домена с добавочным расширением .location, например, nixxlab.pro.location - содержимое файла добавляется конфигуратором в секцию location, заменяя собой default.location

Создание файла настроек для Nginx-Configurator

Создадим файл configuration.template в каталоге проекта и заполним следующими настройками:

{{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }}

{{ if (exists "/etc/nginx/certs/dhparam.pem") }}
  ssl_dhparam /etc/nginx/certs/dhparam.pem;
{{ end }}
  
{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
{{ $upstream := (first (groupByKeys $containers "Env.UPSTREAM")) }}
{{ $host := trim $host }}
{{ $is_regexp := hasPrefix "~" $host }}
{{ $upstream_name := when $is_regexp (sha1 $host) $host }}
{{ $certName := (first (groupByKeys $containers "Env.CERT_NAME")) }}
{{ $vhostCert := (closest (dir "/etc/nginx/certs") (printf "%s.crt" $host))}}
{{ $vhostCert := trimSuffix ".crt" $vhostCert }}
{{ $vhostCert := trimSuffix ".key" $vhostCert }}
{{ $cert := (coalesce $certName $vhostCert) }}

upstream {{ $upstream_name }} {
  server {{ $upstream }};
}

server {
  listen 80;
  listen [::]:80;
  server_name {{ $host }};

  {{ if (exists (printf "/etc/nginx/cfg/%s" $host)) }}
  include {{ printf "/etc/nginx/cfg/%s" $host }};
  {{ else if (exists "/etc/nginx/cfg/default") }}
  include /etc/nginx/cfg/default;
  {{ end }}

  location / {
    return 301 https://$host$request_uri;
  }
}

{{ if (exists (printf "/etc/nginx/certs/%s.crt" $host)) }}
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name {{ $host }};
  access_log /var/log/nginx/{{ $host }}.log main;

  add_header Strict-Transport-Security "max-age=31536000";
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:!DSS';
  ssl_prefer_server_ciphers on;
  ssl_session_timeout 5m;
  ssl_session_cache shared:SSL:50m;
  ssl_session_tickets off;

  ssl_certificate /etc/nginx/certs/{{ (printf "%s.crt" $cert) }};
  ssl_certificate_key /etc/nginx/certs/{{ (printf "%s.key" $cert) }};

  {{ if (exists (printf "/etc/nginx/certs/%s.dhparam.pem" $cert)) }}
  ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }};
  {{ end }}

  {{ if (exists (printf "/etc/nginx/certs/%s.chain.crt" $cert)) }}
  ssl_stapling on;
  ssl_stapling_verify on;
  ssl_trusted_certificate {{ printf "/etc/nginx/certs/%s.chain.crt" $cert }};
  {{ end }}

  {{ if (exists (printf "/etc/nginx/cfg/%s" $host)) }}
  include {{ printf "/etc/nginx/cfg/%s" $host }};
  {{ else if (exists "/etc/nginx/cfg/default") }}
  include /etc/nginx/cfg/default;
  {{ end }}

  location / {
    proxy_pass http://{{ trim $upstream_name }};

    {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
    auth_basic  "Restricted {{ $host }}";
    auth_basic_user_file    {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
    {{ end }}
    {{ if (exists (printf "/etc/nginx/cfg/%s.location" $host)) }}
    include {{ printf "/etc/nginx/cfg/%s.location" $host}};
    {{ else if (exists "/etc/nginx/cfg/default.location") }}
    include /etc/nginx/cfg/default.location;
    {{ end }}
  }
}
{{ end }}
{{ end }}

Создание правил для запуска стэка через Docker-Compose

Создадим файл docker-compose.yml в каталоге проекта и заполним следующими настройками:

version: '3'

networks:
  nginx:
    external: true
  project:
    driver: bridge

services:
  ######################################################################
  nginx:
    networks:
      - nginx
    ports:
      - "0.0.0.0:80:80"
      - "0.0.0.0:443:443"
    volumes:
      - ./log:/var/log/nginx:rw
      - ./certs:/etc/nginx/certs:ro
      - ./cfg:/etc/nginx/cfg:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./www:/usr/share/nginx/html:ro
    container_name: nginx
    image: nginx:1.13.1
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true"
    restart: always

  ######################################################################
  nginx-configurator:
    networks:
      - project
    volumes:
      - ./cfg:/etc/nginx/cfg:rw
      - ./configuration.template:/etc/nginx/configuration.template:ro
      - ./certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
    container_name: nginx-configurator
    image: jwilder/docker-gen:0.7.3
    command: -notify-sighup nginx -watch -wait 5s:30s /etc/nginx/configuration.template /etc/nginx/cfg/automatic.conf
    depends_on:
      - nginx
    restart: always

  ######################################################################
  letsencrypt:
    networks:
      - project
    volumes:
      - ./certs:/etc/nginx/certs:rw
      - ./cfg:/etc/nginx/vhost.d:rw
      - ./www:/usr/share/nginx/html:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      NGINX_PROXY_CONTAINER: nginx
      NGINX_DOCKER_GEN_CONTAINER: nginx-configurator
    container_name: letsencrypt
    image: jrcs/letsencrypt-nginx-proxy-companion
    depends_on:
      - nginx
      - nginx-configurator
    restart: always

Запуск, останов и прочие нюансы

Для запуска стэка контейнеров достаточно использовать из каталога проекта:

docker-compose up -d

Для остановки:

docker-compose down

Для просмотра логов:

docker-compose logs -f

Для правильной работы конфигуратора необходимо сначала запускать описанный стэк контейнеров и лишь затем контейнеры с сайтами. Остановка производится в обратном порядке - сначала отключаются сайты, а затем
стэк описанных контейнеров.

На этом всё! Три настроенных и запущенных контейнера позволяют подхватывать включаемые контейнеры с сайтами и получать для них SSL-сертификаты в автоматическом режиме.

dockerdocker-composenginxletsencryptitконтейнеризацияhttpавтоматизацияруководствоучебникадминистрированиесайтывебвеб-сервисыpsk
7
0 GOLOS
0
В избранное
nizovtsevnv
На Golos с 2017 M10
7
0

Зарегистрируйтесь, чтобы проголосовать за пост или написать комментарий

Авторы получают вознаграждение, когда пользователи голосуют за их посты. Голосующие читатели также получают вознаграждение за свои голоса.

Зарегистрироваться
Комментарии (0)
Сортировать по:
Сначала старые