У меня есть экземпляр EC2 на AWS, на котором работает Amazon Linux 2.
На нем я установил Git, docker и docker-compose. Сделав это, я клонировал свой репозиторий и запустил docker-compose up
, чтобы запустить производственную среду. Я захожу в общедоступный DNS, и он работает.
Теперь я хочу включить HTTPS на сайте.
В моем проекте есть интерфейс, использующий React для работы на сервере Nginx-alpine. Бэкэнд — это сервер NodeJS.
Это мой файл nginx.conf:
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
}
location /api/ {
proxy_pass http://${PROJECT_NAME}_backend:${NODE_PORT}/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Вот мой файл docker-compose.yml:
version: "3.7"
services:
##############################
# Back-End Container
##############################
backend: # Node-Express backend that acts as an API.
container_name: ${PROJECT_NAME}_backend
init: true
build:
context: ./backend/
target: production
restart: always
environment:
- NODE_PATH=${EXPRESS_NODE_PATH}
- AWS_REGION=${AWS_REGION}
- NODE_ENV=production
- DOCKER_BUILDKIT=1
- PORT=${NODE_PORT}
networks:
- client
##############################
# Front-End Container
##############################
nginx:
container_name: ${PROJECT_NAME}_frontend
build:
context: ./frontend/
target: production
args:
- NODE_PATH=${REACT_NODE_PATH}
- SASS_PATH=${SASS_PATH}
restart: always
environment:
- PROJECT_NAME=${PROJECT_NAME}
- NODE_PORT=${NODE_PORT}
- DOCKER_BUILDKIT=1
command: /bin/ash -c "envsubst '$$PROJECT_NAME $$NODE_PORT' < /etc/nginx/conf.d/nginx.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"
expose:
- "80"
ports:
- "80:80"
depends_on:
- backend
networks:
- client
##############################
# General Config
##############################
networks:
client:
Я знаю, что для certbot есть образ Docker, но не знаю, как его использовать. Меня также беспокоит то, как я проксирую запросы на /api/
на сервер через http. Это тоже создаст мне проблемы?
Редактировать:
Попытка №1: Траефик
Я создал контейнер Traefik для маршрутизации всего трафика через HTTPS.
version: '2'
services:
traefik:
image: traefik
restart: always
ports:
- 80:80
- 443:443
networks:
- web
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/traefik/traefik.toml:/traefik.toml
- /opt/traefik/acme.json:/acme.json
container_name: traefik
networks:
web:
external: true
Для файла toml я добавил следующее:
debug = false
logLevel = "ERROR"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[retry]
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "ec2-00-000-000-00.eu-west-1.compute.amazonaws.com"
watch = true
exposedByDefault = false
[acme]
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
Я добавил это в свой производственный файл docker-compose:
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:ec2-00-000-000-00.eu-west-1.compute.amazonaws.com"
- "traefik.basic.port=80"
- "traefik.basic.protocol=https"
Я запустил docker-compose up
для контейнера Traefik, а затем запустил docker-compose up
для своего производственного образа. Я получил следующую ошибку:
unable to obtain acme certificate
Я читаю документы Traefik и, по-видимому, есть способ настроить файл toml специально для Amazon ECS: https://docs.traefik.io/configuration/backends/ecs/
Я на правильном пути?
Самый простой способ - настроить ALB и использовать его для HTTPS.
Запросы будут обслуживаться с использованием ALB с https
Это сработало и для меня, когда Application Load Balancer обслуживает кластер ECS, который (в настоящее время) имеет только один экземпляр EC2. Одна хитрость, на которую стоит обратить внимание / классный трюк: настройте Nginx на прослушивание только на порту 80, настройте прослушиватели балансировщика нагрузки (порты 80 и 443) так, чтобы они указывали на целевую группу, а затем на вашей целевой группе установите ТОЛЬКО цель для порта. 80 (так что НЕТ цели для 443). Это будет доставлять SSL-трафик в Nginx через порт 80, поэтому вам не нужно настраивать сертификат SSL в Nginx — он обрабатывается сертификатом AWS в балансировщике нагрузки.
Включение SSL выполняется с помощью руководства по Nginx и Let’s Encrypt с помощью Docker менее чем за 5 минут. Я столкнулся с некоторыми проблемами, следуя за ним, поэтому я попытаюсь прояснить некоторые вещи здесь.
Шаги включают добавление следующего к docker-compose.yml
:
##############################
# Certbot Container
##############################
certbot:
image: certbot/certbot:latest
volumes:
- ./frontend/data/certbot/conf:/etc/letsencrypt
- ./frontend/data/certbot/www:/var/www/certbot
Что касается раздела Контейнер Nginx файла docker-compose.yml
, его следует изменить, чтобы включить те же тома, что и в Контейнер Certbot, а также добавить порты и предоставить конфигурации:
service_name:
container_name: container_name
image: nginx:alpine
command: /bin/ash -c "exec nginx -g 'daemon off;'"
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
expose:
- "80"
- "443"
ports:
- "80:80"
- "443:443"
networks:
- default
Папка data
может быть сохранена в любом другом месте, но убедитесь, что знаете, где она находится, и убедитесь, что ссылаетесь на нее правильно при повторном использовании позже. В этом примере я просто сохраняю его в том же каталоге, что и файл docker-compose.yml
.
После установки вышеуказанных конфигураций необходимо выполнить пару шагов для инициализации выдачи сертификатов.
Во-первых, ваша конфигурация Nginx (default.conf
) должна быть изменена для соответствия запросу проверки домена:
server {
listen 80;
server_name example.com www.example.com;
server_tokens off;
location / {
return 301 https://$server_name$request_uri;
}
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
server {
listen 443 ssl;
server_name example.com www.example.com;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
После изменения файла конфигурации Nginx создается фиктивный сертификат, позволяющий выполнить проверку Давайте зашифруем. Существует сценарий, который делает все это автоматически, и его можно загрузить в корень проекта с помощью CURL, а затем изменить в соответствии со средой. Скрипт также необходимо сделать исполняемым с помощью команды chmod
:
curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh && chmod +x init-letsencrypt.sh
После загрузки сценария в него необходимо внести следующие изменения:
#!/bin/bash
if ! [ -x "$(command -v docker-compose)" ]; then
echo 'Error: docker-compose is not installed.' >&2
exit 1
fi
-domains=(example.org www.example.org)
+domains=(example.com www.example.com)
rsa_key_size=4096
-data_path = "./data/certbot"
+data_path = "./data/certbot"
-email = "" # Adding a valid address is strongly recommended
+email = "[email protected]" # Adding a valid address is strongly recommended
staging=0 # Set to 1 when testing setup to avoid hitting request limits
if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi
echo "### Creating dummy certificate for $domains ..."
path = "/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
-docker-compose run --rm --entrypoint "\
+docker-compose -f docker-compose.yml run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:1024 -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo
echo "### Starting nginx ..."
-docker-compose up --force-recreate -d nginx
+docker-compose -f docker-compose.yml up --force-recreate -d service_name
echo
echo "### Deleting dummy certificate for $domains ..."
-docker-compose run --rm --entrypoint "\
+docker-compose -f docker-compose.yml run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo
echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args = ""
for domain in "${domains[@]}"; do
domain_args = "$domain_args -d $domain"
done
# Select appropriate email arg
case "$email" in
"") email_arg = "--register-unsafely-without-email" ;;
*) email_arg = "--email $email" ;;
esac
# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg = "--staging"; fi
-docker-compose run --rm --entrypoint "\
+docker-compose -f docker-compose.yml run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo
echo "### Reloading nginx ..."
-docker-compose exec nginx nginx -s reload
+docker-compose exec service_name nginx -s reload
Я позаботился о том, чтобы всегда включать флаг -f
с командой docker-compose
на тот случай, если кто-то не знает, что изменить, если у него есть файл с пользовательским именем docker-compose.yml
. Я также установил имя службы как service_name
, чтобы различать имя службы и команду Nginx, в отличие от руководства.
Примечание: Если вы не уверены в том, что установка работает, обязательно установите для промежуточной стадии значение 1
, чтобы избежать превышения лимита запросов. Важно не забыть установить его обратно на 0
после завершения тестирования и повторить все шаги, начиная с внесения изменений в файл init-letsencrypt.sh
. После того, как тестирование завершено и для промежуточной стадии установлено значение 0
, важно остановить предыдущие запущенные контейнеры и удалить папку данных, чтобы обеспечить надлежащую начальную сертификацию:
$ docker-compose -f docker-compose.yml down && yes | docker system prune -a --volumes && sudo rm -rf ./data
Как только сертификаты будут готовы к инициализации, скрипт должен быть запущен с помощью sudo
; очень важно использовать sudo, так как при запуске без него будут возникать проблемы с разрешениями внутри контейнеров.
$ sudo ./init-letsencrypt.sh
После выдачи сертификата возникает вопрос об автоматическом продлении сертификата; нужно сделать две вещи:
service_name:
...
- command: /bin/ash -c "exec nginx -g 'daemon off;'"
+ command: /bin/ash -c "while :; do sleep 6h & wait $${!}; nginx -s reload; done & exec nginx -g 'daemon off;'"
...
certbot:
...
+ entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot; sleep 12h & wait $${!}; done;'"
Перед запуском docker-compose -f docker-compose.yml up
необходимо изменить право собственности на папку data
на ec2-user
; это сделано для того, чтобы избежать ошибок разрешений при запуске docker-compose -f docker-compose.yml up
или при запуске в режиме sudo:
sudo chown ec2-user:ec2-user -R /path/to/data/
Не забудьте добавить запись CAA в DNS-провайдера для Давайте зашифруем. Вы можете прочитать здесь для получения дополнительной информации о том, как это сделать.
Если у вас возникли проблемы с контейнером Nginx из-за подстановки переменных, а $server_name
и $request_uri
не отображаются должным образом, вы можете обратиться к Эта проблема.
Привет @yaharga! Во втором блоке кода у вас есть container_name: container_name, просто интересно, будет ли это имя внешнего контейнера или это container_name чего-то другого.
@ elco45 Это просто имя внешнего контейнера. Я использовал заполнитель для service_name и container_name, поскольку пытался не сбивать с толку.
обратите внимание, что этот подход не будет работать, если вы используете эластичный beanstalk, потому что каждый раз, когда новая версия приложения развертывается в Elastic Beanstalk, конфигурация обновляется, конфигурация Nginx/Apache перезаписывается.
Это хорошо сработало для меня. Я бы добавил, что вам нужно выбрать «Классический балансировщик нагрузки» (также помеченный как «предыдущее поколение») при создании балансировщика нагрузки в AWS, чтобы иметь возможность создать балансировщик нагрузки только с одним экземпляром EC2 за ним. Теперь AWS «рекомендует» вам выбрать «Балансировщик нагрузки приложений», для которого требуется как минимум 2 зоны доступности и, следовательно, 2 экземпляра EC2.