Я пытаюсь оптимизировать веб-приложение Unicorn base Ruby перед его переносом из EC2 в Kubernetes.
Проблемы: Основная проблема Unicorn заключается в том, что это веб-сервер, основанный на процессах. Может обслуживать фиксированное количество одновременных соединений. Это означает, что в случае, если все соединения заняты, приложение не будет отвечать на проверки работоспособности Kuebrnetes. Кроме того, он не сможет обслуживать наших пользователей.
Архитектура:
- Nginx Входной контроллер , который обслуживает как наш обратный прокси / балансировщик.
- Сервер, который работает (k8s: Pod с коляской):
- OpenResty (Nginx + Lua)
- Unicorn Ruby веб-приложение
Подходы: наивный подход, наш (1) Ingress выдаст запрос на один из серверов (Pods / Service), (2) OpenResty примет соединение и продолжит стучать (3) Единорог, пока не сможет удовлетворить запрос. Что касается запроса проверки работоспособности, (1) OpenResty немедленно ответит 200-ок, если в течение последних X секунд был ответ 200OK, в противном случае он отправит запрос Unicorn, как и любой другой запрос.
lua_shared_dict responses 1m;
server {
listen 80;
server_name _ ~^(.+)$;
try_files $uri @app;
location @app {
proxy_pass http://unicorn_server;
-- Update the TTL of a global variable "health_check" on any 200-299 response we had
log_by_lua_block {
if ngx.status >= ngx.HTTP_OK and ngx.status < ngx.HTTP_SPECIAL_RESPONSE then
local responses_dict = ngx.shared.responses
responses_dict:set("health_check", 1, 60)
end
}
}
location /health/check.json {
-- Return 200OK if "health_check" global variable exists, otherwise forward to Unicorn
content_by_lua_block {
local responses_dict = ngx.shared.responses
local health_check = responses_dict:get("health_check")
if health_check then
ngx.exit(ngx.HTTP_OK)
else
ngx.exec("@app")
end
}
}
}
upstream unicorn_server {
server 127.0.0.1:8082 max_fails=0 fail_timeout=0;
}
Другой подход состоит в том, чтобы наш (1) вход повторялся при каждой ошибке 503, до X секунд и Y повторов. (2) OpenResty будет принимать все соединения и будет передавать только Unicorn только N-1 (где N - максимальное количество соединений, которое может обработать Unicorn), OpenResty будет продолжать повторять попытку передать любой ожидающий запрос Unicorn в течение следующих X секунд, и верните 503, если это не удалось сделать. (3), который попытается повторить наш вход (и, возможно, ударит другой сервер / модуль).
lua_shared_dict try 10m;
server {
listen 80 default_server deferred;
server_name _ broker_server;
location / {
proxy_next_upstream http_503 non_idempotent;
proxy_next_upstream_timeout 1;
proxy_next_upstream_tries 120;
rewrite_by_lua_block {
ngx.req.set_header("X-Request-Id", ngx.var.request_id)
}
proxy_pass http://nginx_server;
}
}
upstream nginx_server {
server 0.0.0.1; # just an invalid address as a place holder
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local x_request_id = ngx.req.get_headers()["X-Request-Id"]
local try_dict = ngx.shared.try
-- Adding the Key + setting TTL to 10 sec, which is similar to proxy_next_upstream_timeout
try_dict:safe_add(x_request_id, 0, 10)
try_dict:incr(x_request_id, 1, 0)
balancer.set_more_tries(1)
local ok, err = balancer.set_current_peer("127.0.0.1", 8090)
if not ok then
ngx.log(ngx.ERR, "failed to set the current peer: ", err)
return ngx.exit(500)
end
}
keepalive 10; # connection pool
}
server {
listen 8090;
server_name _ ~^(.+)$;
location / {
content_by_lua_block {
local x_request_id = ngx.req.get_headers()["X-Request-Id"]
local try_dict = ngx.shared.try
-- local try = try_dict:get(ngx.var.x_request_id)
local try = try_dict:get(x_request_id)
-- Sleep between each retr
if try > 1 then
ngx.sleep( (try - 1 ) * 0.05)
end
ngx.exec("@unicorn_server")
}
log_by_lua_block {
local x_request_id = ngx.req.get_headers()["X-Request-Id"]
if x_request_id and ngx.status == ngx.HTTP_SERVICE_UNAVAILABLE then
ngx.shared.try:flush_expired(0)
else
ngx.shared.try:delete(x_request_id)
end
}
}
location @unicorn_server {
-- Assuming that we can serve only 5 request at a time
limit_conn perserver 5;
limit_conn_status 503;
proxy_pass http://127.0.0.1:8082;
}
}
Основная проблема этого подхода заключается в том, что я не уверен, что ngx.exec("@unicorn_server")
будет работать в аналогично proxy_pass
.
Есть ли другой вариант, который я должен рассмотреть? Помните, что я уже пытался перейти с Unicorn на Puma, но не смог этого сделать из-за устаревшего кода, который накапливался годами.