Домашний хостинг сайтов с динамическим IP

в 9:31, , рубрики: DNS, nat, Ubuntu, динамический ip, хостинг, хостинг дома, метки: ,

У меня (как и у многих web-разработчиков) имеется с десяток сайтов которые необходимо где-то размещать (хостить).

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

В то-же время в наличии имеется:

  • Домашний сервер на Ubuntu
  • Быстрый ethernet-интернет от МТС

Но не имеется ключевого — статического IP. Если бы он был, то все было-бы намного проще и данную статью я бы точно не писал. А выдавать статический IP мой МТС абсолютно не желает (если только я не подключусь как бизнес-клиент).

Разумеется есть всем известные Dynamic DNS сервисы вроде noip.com, но они успешно решают лишь задачу удаленного доступа к нашему серверу (по SSH или FTP), но для хостинга совершенно нам не подходят, поскольку в настройках домена на DNS-сервере нам нужно обязательно прописать A-запись с реальным IP-адресом (а не ссылку на наш виртуальный домен).

Что делать?

Я не буду останавливаться на том, как настроить linux сервер (и тем более как его выбрать), поскольку предполагаю, что он у вас уже есть. Также я не буду подробно расписывать настройки nginx и Apache, поскольку опять-таки предполагаю, что вы с этим справитесь самостоятельно.

Первое с чем у меня возникли проблемы — это как перенаправить посетителей с моих доменов (у меня есть 2 домена) на мой домашний сервер. То есть чтобы клиент который набрал domain.com попал ровно на мой домашний сервер с учетом того, что на нем каждый день меняется IP-адрес.

Для решения нам нужно настроить DNS-сервер, а именно следующие записи: SOA, NS, MX, A, CNAME. Важно чтобы мы имели возможность настройкой TTL (time to live), поскольку время жизни наших записей должно быть очень небольшим, буквально 60-120 секунд. В противном случае при смене IP-адреса сервера пользователи долго не смогут попасть на наш сервер (из-за кеширования).

Итак, нам нужен DNS сервер, варианты решения:

  1. Используем сервисы которые предоставляют нам DNS-хостинг
  2. Используем собственный DNS-сервер в связке с DDNS-доменом

Рассмотрим оба варианта.

Используем сервисы которые предоставляют нам DNS-хостинг

Для этого есть ряд бесплатных сервисов, из которых самым популярным является freedns.afraid.org. На таких сервисах можно добавить свой домен(ы) и получить возможность через API обновлять у них A-запись при помощи небольшого скрипта.

Выглядит вполне неплохо, но подвох в том, что эти сервисы оставляют за собой право довешивать к вашему домену поддомены третьего уровня. То есть вы зарегистрировали у них user.ru, а они спокойно довешают свои сайты вида hello.user.ru, shop.user.ru и так далее. Разумеется от этого можно отказаться, но… за деньги. Платить деньги за такие сервисы смысла я не вижу, поскольку за сравнимые деньги вы можете купить полноценный хостинг на каком-нибудь провайдере без всяких плясок вокруг DNS настроек.

Остальные сервисы рассматривать не будем, а сосредоточимся на втором варианте.

Используем собственный DNS-сервер в связке с DDNS-доменом

Для этого варианта у нас, во-первых, должен быть DDNS-домен (который обновляется при смене IP), например, domain.ddns.net, а во-вторых, придется установить и настроить BIND на нашем сервере.

Всего необходимо сделать ровно 5 шагов. Везде под словами «domain» или «domain.ru» подразумевается ваше имя домена (короткое или полное).

1. Настроить 2 или 3 DDNS-поддомена

Почему 2 или 3? Потому, что ряд регистрантов не разрешит вам использовать домен только с одним NS-сервером. Самое обидно, что не все про это скажут — ваш домен просто не будет работать, но вы не будете понимать почему.

Тут все просто — идем на noip.com, там регистрируем аккаунт и добавляем 3 бесплатных поддомена (больше 3 не даст).

2. Настраиваем собственный DNS-сервер

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

$ sudo apt-get install bind9

Создаем зоны (по одной зоне на каждый наш домен):

$ sudo nano /etc/bind/zones.my

с содержимым:

zone "domain.ru" {
	type master;
	file "/etc/bind/db.domain.ru";
};

и собственно файл с настройками зоны:

$ nano /etc/bind/db.domain.ru

и пишем внутри:

;
; BIND data file for local loopback interface
;
$TTL	60
@	IN	SOA	domain.ru. admin.domain.ru. (
			1477015437	; Serial
			10800		; Refresh
			3600		; Retry
			604800		; Expire
			1800 )		; Negative Cache TTL

@	IN	NS	domain.ddns.net.
@	IN	NS	domain.ddnsking.com.
@	IN	NS	domain.myftp.biz.

@	IN	MX	10 mx.yandex.net.

@	IN	A	1.2.3.4

mail	IN	CNAME	domain.mail.yandex.net.
*	IN	CNAME	domain.ru.

Примечание: обращаю внимание, что TTL устанавливаем равным 60 секунд. В файле /etc/bind/named.conf.local добавляем подключение нашей зоны:

include "/etc/bind/zones.my";

Все, рестартуем BIND:

$ sudo service bind9 restart

И глянем /var/log/syslog чтобы там не было сообщений об ошибках

3. Настроить наш домен(ы)

Идем в панель управления регистратора и там в настройках нашего домена в качестве NS-серверов указываем созданные DDNS-поддомены:

nameserver1 = domain.ddns.net
nameserver2 = domain.ddnsking.com
nameserver3 = domain.myftp.biz

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

4. Настроить периодическое обновление IP-адресов

Мой роутер поддерживает обновление IP-адреса на одном домене, но мне нужно это делать сразу для 3-х доменов. Плюс нам надо обновлять IP-адрес в конфиге BIND'а, поэтому напишем скрипт который будет делать:

  1. Определять наш внешний IP-адрес
  2. Проверять изменился ли IP адрес, если не изменился, то ничего делать не надо
  3. Обновлять IP-адрес у всех DDNS-поддоменов через API сервиса noip.com
  4. Прописывать новый IP адрес в конфиг BIND'а
  5. Перезапускать BIND

Сам скрипт пусть будет на шелле:

#!/bin/sh

# This script works via noip.com service + local Bind server

# Settings
ZONES_CONFIG=zones.my
IP_FILE=./current_ip.txt
DDNS_USER=user
DDNS_PASS=password
DDNS_HOST=domain.ddns.net
DDNS_HOSTS=domain.ddns.net,domain.ddnsking.com,domain.myftp.biz

# Start
DATE=$(date +"%Y-%m-%d %H:%M:%S")

# detect an external IP
IP=$(dig +short $DDNS_HOST)
if [ $? -ne 0 ] || [ -z $IP ] || [ $IP = "0.0.0.0" ] ; then
	echo "$DATE Can't detect a remote IP. Aborting."
	exit 1
fi

# verify IP changing
PREV_IP="(unknown)"
if [ -e $IP_FILE ] ; then
	PREV_IP=$(cat $IP_FILE)
fi

if [ $IP = $PREV_IP ] ; then
	echo "$DATE IP '$IP' has not changed"
else
	echo "$DATE IP has been changed from '$PREV_IP' to '$IP'"

	echo "$DATE IP will be updated on DDNS server"
	/usr/bin/curl -u $DDNS_USER:$DDNS_PASS "https://dynupdate.no-ip.com/nic/update?hostname=$DDNS_HOSTS&myip=$IP"
fi
echo $IP > $IP_FILE

# check BIND config
cd /etc/bind
if [ ! -e $ZONES_CONFIG ] ; then
        echo "$DATE File $ZONES_CONFIG not found!"
        exit 1
fi

# read the list of active zones
ZONE_FILES=$(grep file $ZONES_CONFIG | grep -v ^# | perl -ne '/file "(.+)"/ && print "$1n"')
for ZONE_FILE in $ZONE_FILES; do
        echo "$DATE Process the zone config $ZONE_FILE"

	cat $ZONE_FILE | perl -ne "s/([t ]+IN[t ]+A[t ]+)[d.]*/${1}${IP}/; print ${_}" > $ZONE_FILE.tmp

	if [ $(diff -w $ZONE_FILE $ZONE_FILE.tmp | wc -l) -ne 0 ] ; then
		# update serial number
		STAMP=$(date +%s)
		cat $ZONE_FILE.tmp | perl -ne "s/d+(?=.+Serial)/$STAMP/; print ${_}" > $ZONE_FILE

		# reload BIND
		service bind9 reload

		echo "$DATE Config $ZONE_FILE is updated"
	else
		# nothing to do
		rm $ZONE_FILE.tmp
		echo "$DATE Config $ZONE_FILE is NOT changed"
	fi
done

Скрипт нужно запускать под рутом (чтобы ему хватило прав обновлять конфиги BIND'а и рестартовать его). Добавляем в crontab рута его запуск каждую минуту:

* * * * *	cd /home/root && ./update_bind_config.sh >> /var/log/update_bind_config.log

Пару слов про определение текущего IP-адреса. В скрипте выше это делается через резолвинг DDNS-поддомена domain.ddns.net. То есть сначала наш роутер его туда прописывает, а потом мы читаем. Это не очень хороший вариант, поскольку мы завязываемся на роутер и можем потерять несколько минут пока на DDNS-поддомене обновится IP-адрес на актуальный. Все это время наш сервер будет недоступен.

Поэтому у себя я использовал улучшенный вариант, который заодно не лазит в интернет:

IP=$(perl -le 'use LWP::UserAgent; my $content=LWP::UserAgent->new->get("http://router")->decoded_content(); $content =~ q(<span id="wan_ipaddr">([d.]+)</span>); print $1')

В данном варианте мы загружаем главную страницу роутера (через http), дальше регэкспом находим на ней текущий IP-адрес. Разумеется, этот вариант подходит далеко не всем, но на DD-WRT прошивках работает.

5. Настройка роутера

Про необходимость настроить обращение к DDNS-сервису я уже писал, но еще не забывайте про необходимость настроить форвардинг портов на вашем роутере:

  • HTTP — TCP, 80-ый порт
  • DNS — TCP+UDP, 53 порт

Вывод

Итак, что я получил в итоге:

  • Мои сайты живут на домашнем сервере, за который я никому не плачу;
  • Мои домены резолвятся через мой собственный DNS-сервер, время жизни записей 1 минута, то есть обновление происходит очень быстро;
  • В качестве NS-записей указаны не реальные IP-адреса (которые у меня часто меняются), а DDNS-поддомены;
  • Актуальность записей в DDNS-поддоменах и в конфиге моего DNS-сервера обеспечивается автоматически, без какого-либо вмешательства со моей стороны.

По моим замерам, когда МТС (мой провайдер) обновляет мне IP-адрес, то мои сайты начинают работать спустя где-то 2 минуты. Это вполне приемлемо для меня.

P.S. Если кому-то понравилась данная заметка, то я могу написать вторую часть, где расскажу как настроить работу с использованием DNS-хостинга Яндекса. Это позволит отказаться от собственного DNS-сервера, отказаться от DDNS-поддоменов, плюс чуть улучшит надежность работы (поскольку DNS-сервер никогда не будет менять свой IP). Именно такую схему я использую в настоящий момент.

Автор: spectreob

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js