Nginx caching reverse proxy

Материал из poiuty wiki
Перейти к: навигация, поиск

В 2016 году значительно выросло кол-во просмотров аниме на сайте anilibria.tv
Sanasol предложил кешировать видео с помощью cloudflare (бесплатный тариф).
Статистика cloudflare за последний месяц (август 2017).
Халява закончилась в октябре. Cloudflare отключил cache.

Total Bandwidth 205.71 TB
Cached Bandwidth 170.13 TB
Uncached Bandwidth 35.58 TB

Есть свободные мощности и желание помочь проекту anilibria.tv?
Запустите на своем сервере cache proxy. Минимальные требования.

RAM: 16GB
Disk space: 2TB
Network: 250Mbit/s

1. Напишите IP адрес и конфиг вашего сервера в telegram @poiuty или на почту poiuty@lepus.su
2. Сделаю поддомен.
3. Настройте cache proxy, сгенерируйте secret cookie (proxy_cache_bypass) и отправьте мне.
4. Проверяю, если все ок - добавляю ваш сервер.

Настройка

Поднимем несколько nginx caching reverse proxy. Сделаем два поддомена.

test  A 144.217.255.69 # OVH CANADA      [PROXY]
test2 A 176.9.48.58    # HETZNER GERMANY [PROXY]
x     A 5.9.82.141     # HETZNER GERMANY [MAIN]

Устанавливаем пакеты.

apt-get update && apt-get upgrade -y
apt-get install nginx munin munin-node certbot spawn-fcgi libcgi-fast-perl htop bwm-ng strace lsof libfile-readbackwards-perl

Устанавливаем munin плагин nginx-cache-hit-rate

# wget -O /usr/share/munin/plugins/nginx-cache-hit-rate https://raw.githubusercontent.com/munin-monitoring/contrib/master/plugins/nginx/nginx-cache-hit-rate
# chmod 755 /usr/share/munin/plugins/nginx-cache-hit-rate
# ln -s /usr/share/munin/plugins/nginx-cache-hit-rate /etc/munin/plugins/

# nano /etc/munin/plugin-conf.d/munin-node
...
[nginx-cache-hit-rate]
user www-data

# /etc/init.d/munin-node restart

Генерируем dhparam. Получаем letsencrypt сертификат.

mkdir /etc/nginx/ssl/
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
chown www-data:www-data /etc/nginx/ssl/dhparam.pem
chmod 400 /etc/nginx/ssl/dhparam.pem
certbot certonly --webroot -w /var/www/html -d test.anilibria.tv -m admin@anilibria.tv --agree-tos

Настраиваем автопродление сертификата, добавляем renew-hook, перезагружаем cron

# nano /etc/cron.d/certbot
...
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew --renew-hook "/etc/init.d/nginx restart"

# /etc/init.d/cron restart

Редактируем /etc/nginx/nginx.conf. Изменяем кол-во процессов.

worker_processes auto;

Увеличиваем open file limit, отключаем accept_mutex.

worker_rlimit_nofile 65535;

events {
	worker_connections 32768;
	accept_mutex off;
	use epoll;
}

Включаем sendfile, tcp_nopush, tcp_nodelay

http {
	...
	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	...
}

Добавляем настройки ssl.

http {
	...
	ssl_dhparam /etc/nginx/ssl/dhparam.pem;
	ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
	ssl_prefer_server_ciphers on;
	ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA512:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:ECDH+AESGCM:ECDH+AES256:DH+AESGCM:DH+AES256:RSA+AESGCM:!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;
	ssl_session_cache shared:TLS:128m;
	...
}

Настройки proxy cache, log_format для munin плагина nginx-cache-hit-rate

http {
	...
	proxy_cache_path /var/www/cache levels=1:2 keys_zone=STATIC:50m inactive=90d max_size=1000g;
	
	log_format cache '$remote_addr - $host [$time_local] "$request" $status '
                 '$body_bytes_sent "$http_referer" '
                 'rt=$request_time ut="$upstream_response_time" '
                 'cs=$upstream_cache_status';
	...
}

Редактируем /etc/nginx/sites-available/default.

server { # munin
	listen 85;
	keepalive_timeout 30;
	root /var/cache/munin/www/;
	location /munin/static/ {
		alias /etc/munin/static/;
	}
	location ^~ /munin-cgi/munin-cgi-graph/ {
		access_log off;
		fastcgi_split_path_info ^(/munin-cgi/munin-cgi-graph)(.*);
		fastcgi_param PATH_INFO $fastcgi_path_info;
		fastcgi_pass unix:/var/run/munin/fcgi-graph.sock;
		include fastcgi_params;
	}
}

server { # caching reverse proxy
	listen 		 80;
	listen 		 443 ssl http2;
	server_name   test.anilibria.tv;
	ssl_certificate /etc/letsencrypt/live/test2.anilibria.tv/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/test2.anilibria.tv/privkey.pem;
	location ~*  \.(m3u8|ts)$ {                 
		access_log /var/log/nginx/cache-access.log cache;
		proxy_pass             http://x.anilibria.tv;
		proxy_cache            STATIC;
		proxy_cache_valid      404 302  1m;
		proxy_cache_valid      200      1y;
		proxy_ignore_headers Set-Cookie Expires Cache-Control;
		proxy_cache_use_stale  error timeout invalid_header updating http_500 http_502 http_503 http_504;

		# clean cache curl https://test.anilibria.tv/videos/ts/4576/0001/fff1.ts -s -I -H "965f76362acf00a460d86c8b40911ca2:true"
		proxy_cache_bypass $http_965f76362acf00a460d86c8b40911ca2;
	
		add_header X-Cache-Status $upstream_cache_status; # show cache status
		# dont block process
		# https://img.poiuty.com/img/08/e75c6adde3ee57e4bff48a3bc0b70908.png
		# https://img.poiuty.com/img/1d/adbe5915344c83528f0b1a6bbac2951d.png
		# https://nginx.org/ru/docs/http/ngx_http_core_module.html#aio
		# https://habrahabr.ru/post/260669/
		#aio	threads;
		#aio_write on;
		#sendfile_max_chunk 512k;
	}
	location /.well-known/ { # letsencrypt
		root /var/www/html;
	}
	location / {
		return 403;
	}
}

Перезагрузим nginx и munin.

/etc/init.d/nginx restart
/etc/init.d/munin-node restart

Проверим что cache работает.

# первый запрос => файл попадает в cache
# curl https://test.anilibria.tv/videos/ts/5223/0001/fff31.ts -s -I | grep x-cache-status
x-cache-status: MISS

# HIT => кеш работает.
# curl https://test.anilibria.tv/videos/ts/5223/0001/fff31.ts -s -I | grep x-cache-status
x-cache-status: HIT

За удаление файла из кеша - отвечает настройка proxy_cache_bypass в конфиге nginx.
Чтобы удалить - отправим запрос => 965f76362acf00a460d86c8b40911ca2 secret cookie.

curl https://test.anilibria.tv/videos/ts/4576/0001/fff1.ts -s -I -H "965f76362acf00a460d86c8b40911ca2:true"

Статистика

1. Cloudflare отключает cache.

9990b52919f5dca9b76ae9bd7d389a83.png


2. Munin nginx-cache-hit-rate.

3. Proxy node after 2 days.

4. Main node after 2 days.

Balancer

Как происходит балансировка трафика?
Для плеера - генерируем линк.

<?php
function anilibria_getHost(){
	$hosts = ['test' => '1', 'test2' => '1'];
	$host = [];
	foreach($hosts as $key => $val){
		$host = array_merge($host, array_fill(0, $val, $key));	
	}
	shuffle($host);
	return $host[mt_rand(0, count($host) - 1)].".anilibria.tv"; // if php7 use random_int
}

for($i=0;$i<10;$i++){
	echo anilibria_getHost()."<br/>";
}

Проверяем.

test.anilibria.tv
test2.anilibria.tv
test2.anilibria.tv
test2.anilibria.tv
test.anilibria.tv
test.anilibria.tv
test2.anilibria.tv
test.anilibria.tv
test.anilibria.tv
test2.anilibria.tv

Uppod autochange url

Что будет если мой хост перестанет отвечать?
Плеер переключится на другой, перемотает на то место где остановился просмотр.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sprintf/1.1.1/sprintf.min.js" type="text/javascript"></script>
<script src="/uppod/uppod.js" type="text/javascript"></script>
<script src="/uppod/video152-14701.js" type="text/javascript"></script>

<div class="player" id="videoplayer" style="width:853px;height:523px;"></div>
<script type="text/javascript">
	var releaseID = 4582;
	var uppodplayer = new Uppod({
		debud:"0",
		m:"video",
		uid:"videoplayer",
		pl:{"playlist":[{"comment":"test","file":"//test.anilibria.tv/videos/ts/4582/00011/playlist.m3u8"},]},
		st:"uppodvideo",
		swf:"/uppod/uppod_hls.swf",
		stflash:"/uppod/video152-1470.txt"
	});

	function url_domain(data) {
	  var    a      = document.createElement('a');
			 a.href = data;
	  return a.hostname;
	}

	function onError(e){
		var saveTime = uppodplayer.CurrentTime();
		var saveNumber = uppodplayer.PlNumber();
		playList = uppodplayer.Get('pl');
		bl = url_domain(playList[saveNumber-1]['file']);
		bl = bl.split('.');
		//console.log(bl[0])
		$.post("//x.anilibria.tv/index.php", { bl: bl[0] }, function( data ) {
			uppodplayer.Play('//'+data+'/videos/ts/'+releaseID+'/'+sprintf('%04d', saveNumber)+'/playlist.m3u8');
			//console.log('//'+data+'/videos/ts/'+releaseID+'/'+sprintf('%04d', saveNumber)+'/playlist.m3u8');
			setTimeout(function(){
				document.getElementById('videoplayer').addEventListener('player_error',onError,false);
				uppodplayer.Seek(saveTime);
			}, 300);	
		});
	}

	$(document).ready(function() {
		document.getElementById('videoplayer').addEventListener('player_error',onError,false);
	});	
</script>