Как перенести базовое приложение Unicorn Rails в Kubernetes - PullRequest
0 голосов
/ 10 января 2020

Я пытаюсь оптимизировать веб-приложение Unicorn base Ruby перед его переносом из EC2 в Kubernetes.

Проблемы: Основная проблема Unicorn заключается в том, что это веб-сервер, основанный на процессах. Может обслуживать фиксированное количество одновременных соединений. Это означает, что в случае, если все соединения заняты, приложение не будет отвечать на проверки работоспособности Kuebrnetes. Кроме того, он не сможет обслуживать наших пользователей.

Архитектура:

  1. Nginx Входной контроллер , который обслуживает как наш обратный прокси / балансировщик.
  2. Сервер, который работает (k8s: Pod с коляской):
    1. OpenResty (Nginx + Lua)
    2. 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, но не смог этого сделать из-за устаревшего кода, который накапливался годами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...