Ruby Forum NGINX > Switching backends based on a cookie

Posted by saltyflorida (Guest)
on 29.01.2010 00:43
(Received via mailing list)
Is it possible to switch backend clusters of servers based on a cookie?

I would like to set a cookie named "env" and do something like this:

        if ($http_cookie ~* "env=testing(;|$)") {
            proxy_pass http://backend_testing;
        }
        if ($http_cookie ~* "env=staging(;|$)") {
            proxy_pass http://backend_staging;
        }
        if ($http_cookie ~* "env=production(;|$)") {
            proxy_pass http://backend_production;
        }

However the "proxy_pass" directive is not allowed inside an "if". Is 
there another way I can approach this?

Thanks,
Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,46979#msg-46979
Posted by Marcus Clyne (Guest)
on 29.01.2010 01:04
(Received via mailing list)
Hi,

saltyflorida wrote:
>         if ($http_cookie ~* "env=production(;|$)") {
>             proxy_pass http://backend_production;
>         }
>
> However the "proxy_pass" directive is not allowed inside an "if". Is there another way I can approach this?
>
>   
Take a look at the map module :

http://wiki.nginx.org/NginxHttpMapModule

One possibility would be :

http {

map  $cookie_env  $backend {

    testing      http://backend_testing;
    staging      http://backend_staging;
    production   http://backend_production;
}

server {
    ...
    proxy_pass   $backend;

}

}

Marcus.
Posted by merlin corey (Guest)
on 29.01.2010 01:25
(Received via mailing list)
On Thu, Jan 28, 2010 at 4:03 PM, Marcus Clyne <ngx.eugaia@gmail.com> 
wrote:
>>        }
>>
>
>
> }
>
> Marcus.
>
>
> _______________________________________________
> nginx mailing list
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx
>

Marcus' solution should be just fine, but I feel I must ask an
important question:

Doesn't it make more sense to have production, static, and dev as
separate server blocks entirely with their own hostnames?  This is, at
the least, traditional :).

-- Merlin
Posted by merlin corey (Guest)
on 29.01.2010 01:26
(Received via mailing list)
On Thu, Jan 28, 2010 at 4:24 PM, merlin corey <merlincorey@dc949.org> 
wrote:
> Marcus' solution should be just fine, but I feel I must ask an
> important question:
>
> Doesn't it make more sense to have production, static, and dev as
> separate server blocks entirely with their own hostnames?  This is, at
> the least, traditional :).
>
> -- Merlin
>

Of course static should read staging -.-
Posted by Marcus Clyne (Guest)
on 29.01.2010 01:49
(Received via mailing list)
Hi,

merlin corey wrote:
> Doesn't it make more sense to have production, static, and dev as
> separate server blocks entirely with their own hostnames?  This is, at
> the least, traditional :).
>   
Yes, I would agree with this (and it should perform a little better 
too).

Marcus.
Posted by saltyflorida (Guest)
on 29.01.2010 02:51
(Received via mailing list)
Eugaia Wrote:
-------------------------------------------------------
> {
> >         }
> One possibility would be :
> server {
> _______________________________________________
> nginx mailing list
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx

Marcus, thank you for your response. The map module is very useful.
I implemented your suggestion and am now able to switch backend servers
using the cookie.

Now I have another problem: the cache is storing pages generated by
the 3 different backend clusters. Is there a way I can bypass the cache
if the cookie is set to either "testing" or "staging"?

Here is my simplified config:
http {
    upstream backend_testing {
            ip_hash;
        server ...
    }
    upstream backend_staging {
            ip_hash;
        server ...
    }
    upstream backend_production {
            ip_hash;
        server ...
    }
    proxy_cache_path /mnt/nginx_cache levels=1:2
                     keys_zone=one:100m
                     inactive=7d max_size=10g;
    proxy_temp_path /var/www/nginx_temp;

    map $cookie_uslnn_env $mybackend {
        default      http://backend_production;
        testing      http://backend_testing;
        staging      http://backend_staging;
        production   http://backend_production;
    }

    server {
        location / {
            proxy_pass $mybackend;
            proxy_cache one;
            proxy_cache_key $my_cache_key;
            proxy_cache_valid  200 302 304 10m;
            proxy_cache_valid  301 1h;
            proxy_cache_valid  any 1m;
            proxy_cache_use_stale updating error timeout invalid_header 
http_500 http_502 http_503 http_504;
        }
        location /wp-admin {
            proxy_pass $mybackend;
            proxy_read_timeout 300;
        }
    }
}

Thanks,
Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,47024#msg-47024
Posted by saltyflorida (Guest)
on 29.01.2010 03:02
(Received via mailing list)
merlin corey Wrote:
-------------------------------------------------------
> > the least, traditional :).
> >
> > -- Merlin
> >
> 
> Of course static should read staging -.-
> 
> _______________________________________________
> nginx mailing list
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx

I realize this idea is unorthodox... We are using Wordpress MU on the
backend and it uses the domain name to generate pages. We want to serve
many domains with a single server cluster and we wanted to be able to
test using the production domain names.

Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,47030#msg-47030
Posted by Piotr Sikora (Guest)
on 29.01.2010 03:08
(Received via mailing list)
Eliot,
try using: proxy_cache_key $my_cache_key$cookie_uslnn_env;

Best regards,
Piotr Sikora < piotr.sikora@frickle.com >
Posted by Piotr Sikora (Guest)
on 29.01.2010 03:15
(Received via mailing list)
Sorry, I mis-read your question...
I don't think that you can conditionally disable cache.

Best regards,
Piotr Sikora < piotr.sikora@frickle.com >
Posted by saltyflorida (Guest)
on 29.01.2010 03:19
(Received via mailing list)
Piotr Sikora Wrote:
-------------------------------------------------------
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx

Hi Piotr,
Thank you, that helps. Ideally, we'd like to bypass the cache for faster 
testing.
But this will get us up and running. (Also, thank you for your help 
earlier-- I am still
looking into that problem.)

Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,47035#msg-47035
Posted by saltyflorida (Guest)
on 29.01.2010 03:20
(Received via mailing list)
merlin corey Wrote:
-------------------------------------------------------
> something like this:
> >>        }
> >>
> >
> >
> 
> -- Merlin
> 
> _______________________________________________
> nginx mailing list
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx

Marcus,
Thank you for the quick response. I will try the map module.
I forgot to mention that I am using caching with the HTTP Proxy module 
and that I only want to cache responses from the production servers. 
When I have the cookie set to "testing" or "staging", I'd like to bypass 
the cache and talk directly to the backend. Does this sound feasible?

Merlin,
I realize this setup is unorthodox. We are using Wordpress MU and it 
generates different pages based on the domain name. We are serving many 
domains with one server cluster and wanted to be able to test using the 
production domain names.

Thanks,
Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,47012#msg-47012
Posted by Marcus Clyne (Guest)
on 29.01.2010 03:50
(Received via mailing list)
saltyflorida wrote:
> I forgot to mention that I am using caching with the HTTP Proxy module and that I only want to cache responses from the production servers. When I have the cookie set to "testing" or "staging", I'd like to bypass the cache and talk directly to the backend. Does this sound feasible?
>   
Sure.  Do a rewrite using your $backend variable under the 'location /'
block to one of three other blocks, which have the different definitions
of your proxy_pass, proxy_cache_valid...

e.g.

map $cookie_[name]  $backend {

    default   production;
    test      test;
    ...
}

location   / {
    rewrite   ^(.*)$   /$backend/$1;
}

location   /production/ {
    proxy_pass            http://backend_production;
    proxy_cache_valid   ...
}

location   /test/ {
    proxy_pass
    # no proxy_cache_valid
    ...
}

Note, you'll need some way to catch the case of no cookie variable, so
it's unwise to put $cookie_[name] directly in the rewrite result (you'll
get an infinite loop on such results).

Marcus.
Posted by saltyflorida (Guest)
on 29.01.2010 08:05
(Received via mailing list)
Eugaia Wrote:
-------------------------------------------------------
> block to one of three other blocks, which have the
> }
> 
> get an infinite loop on such results).
> 
> Marcus.
> 
> _______________________________________________
> nginx mailing list
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx

Marcus,
Thank you for your help. I had wondered if I could use a rewrite, but I 
don't
understand how this works. I tried to implement your suggestion, but I 
am
being redirected to /testing/ or /production/. These show up as part of 
the
URL in the browser. Also, trying to visit pages other than the root 
return a
404 error. Here is my configuration. Can you point out what I'm doing 
wrong?

http {
    upstream backend_testing {
            ip_hash;
        server ...
    }
    upstream backend_staging {
            ip_hash;
        server ...
    }
    upstream backend_production {
            ip_hash;
        server ...
    }
    proxy_cache_path /mnt/nginx_cache levels=1:2
                     keys_zone=one:100m
                     inactive=7d max_size=10g;
    proxy_temp_path /var/www/nginx_temp;

    map $cookie_uslnn_env $backend {
        default      http://backend_production;
        testing      http://backend_testing;
        staging      http://backend_staging;
        production   http://backend_production;
    }

    server {
        location / {
            rewrite ^(.*)$ /$backend/$1;
        }
        location /testing/ {
            proxy_pass http://backend_testing;
        }
        location /staging/ {
            proxy_pass http://backend_staging;
        }
        location /production/ {
            proxy_pass http://backend_production;
            proxy_cache one;
            proxy_cache_key $my_cache_key;
            proxy_cache_valid  200 302 304 10m;
            proxy_cache_valid  301 1h;
            proxy_cache_valid  any 1m;
            proxy_cache_use_stale updating error timeout invalid_header 
http_500 http_502 http_503 http_504;
        }
        location /wp-admin {
            proxy_pass http://backend_production;
            proxy_read_timeout 300;
        }
    }
}

Thanks,
Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,47092#msg-47092
Posted by saltyflorida (Guest)
on 29.01.2010 08:14
(Received via mailing list)
saltyflorida Wrote:
-------------------------------------------------------
> "staging",
> > of your proxy_pass, proxy_cache_valid...
> > location   / {
> >     proxy_pass
> > 
> understand how this works. I tried to implement
>             ip_hash;
>     proxy_cache_path /mnt/nginx_cache levels=1:2
> 
>         location /production/ {
>         location /wp-admin {
>             proxy_pass http://backend_production;
>             proxy_read_timeout 300;
>         }
>     }
> }
> 
> Thanks,
> Eliot

Correction:
The configuration I tried looks like this:

http {
    upstream backend_testing {
            ip_hash;
        server ...
    }
    upstream backend_staging {
            ip_hash;
        server ...
    }
    upstream backend_production {
            ip_hash;
        server ...
    }
    proxy_cache_path /mnt/nginx_cache levels=1:2
                     keys_zone=one:100m
                     inactive=7d max_size=10g;
    proxy_temp_path /var/www/nginx_temp;

    map $cookie_uslnn_env $backend {
        default production;
        production production;
        testing testing;
        staging staging;
    }

    server {
        location / {
            rewrite ^(.*)$ /$backend/$1;
        }
        location /testing/ {
            proxy_pass http://backend_testing;
        }
        location /staging/ {
            proxy_pass http://backend_staging;
        }
        location /production/ {
            proxy_pass http://backend_production;
            proxy_cache one;
            proxy_cache_key $my_cache_key;
            proxy_cache_valid  200 302 304 10m;
            proxy_cache_valid  301 1h;
            proxy_cache_valid  any 1m;
            proxy_cache_use_stale updating error timeout invalid_header 
http_500 http_502 http_503 http_504;
        }
        location /wp-admin {
            proxy_pass http://backend_production;
            proxy_read_timeout 300;
        }
    }
}

Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,47093#msg-47093
Posted by Marcus Clyne (Guest)
on 29.01.2010 12:27
(Received via mailing list)
saltyflorida wrote:
>>>>         
>>     
>> variable
>>>
>>>
>>> }
>>> Marcus.
>> your suggestion, but I am
>>         server ...
>>                      keys_zone=one:100m
>>     server {
>>             proxy_pass http://backend_production;
>>             proxy_pass http://backend_production;
> The configuration I tried looks like this:
>     upstream backend_production {
>         production production;
>         }
>             proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_503 http_504;
>         }
>         location /wp-admin {
>             proxy_pass http://backend_production;
>             proxy_read_timeout 300;
>         }
>     }
> }
>   
Sorry, my fault.  That should have read  'proxy_pass
htttp://backend_production/;'.  The final slash 'deletes' the first part
of the location that's passed.

Note that you will want to add the slash for the /production/,
/testing/... blocks, but not for the /wp-admin block.

Marcus.
Posted by merlin corey (Guest)
on 29.01.2010 18:39
(Received via mailing list)
On Thu, Jan 28, 2010 at 4:49 PM, Marcus Clyne <ngx.eugaia@gmail.com> 
wrote:
>
> Marcus.
>
> _______________________________________________
> nginx mailing list
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx
>

And the configuration will be simpler and easier to understand six
months from now :O without any ifs or rewrites ;).  Also, the servers
can be moved to separate hardware (another tradition)!

> We are serving many domains with one server cluster and wanted to be able to test using the production domain names.

Use the power of NginX at your disposal!  *TELL* Wordpress MU what the
domain name is. ;)

fastcgi_param SERVER_NAME myawesomeproductiondomain.com;

-- Merlin
Posted by Laurence Rowe (Guest)
on 29.01.2010 20:20
(Received via mailing list)
I would take a look at HAProxy which has better support for this use
case, allowing for requests to be retried against another server if
their associated backend is down.

Laurence

2010/1/29 Marcus Clyne <ngx.eugaia@gmail.com>:
Posted by Marcus Clyne (Guest)
on 30.01.2010 00:02
(Received via mailing list)
Laurence Rowe wrote:
> I would take a look at HAProxy which has better support for this use
> case, allowing for requests to be retried against another server if
> their associated backend is down.
>   
I would agree that if you're just wanting to do proxying, then HAProxy
is probably a better way to go, however the above is also possible in
Nginx using upstreams.

Marcus.
Posted by Laurence Rowe (Guest)
on 30.01.2010 19:47
(Received via mailing list)
2010/1/29 Marcus Clyne <ngx.eugaia@gmail.com>:
> Laurence Rowe wrote:
>>
>> I would take a look at HAProxy which has better support for this use
>> case, allowing for requests to be retried against another server if
>> their associated backend is down.
>>
>
> I would agree that if you're just wanting to do proxying, then HAProxy is
> probably a better way to go, however the above is also possible in Nginx
> using upstreams.

The only option then for sticky sessions is ip_hash, not cookies.

Laurence
Posted by Marcus Clyne (Guest)
on 31.01.2010 01:01
(Received via mailing list)
>
> Hi,
>
> The only option then for sticky sessions is ip_hash, not cookies.
>

No, it's also possible to direct traffic to particular backend servers 
using
cookies too.

In fact there are more ways of directing traffic to backends/clusters 
with
Nginx than there are with HAProxy - in the sense of the number of ways 
of
choosing a cluster (which could just be one server) - but AFAIK there 
are
currently fewer ways of hashing / distributing over the servers in a
particular cluster of backends with Nginx than HAProxy (even if you 
include
the non-core modules).

If you did want high redundancy as well as sticky sessions, though, then
you'd probably want to store your key application data in something like
memcached and get your backend application to quiz that.

Marcus.
Posted by saltyflorida (Guest)
on 03.02.2010 06:31
(Received via mailing list)
Eugaia Wrote:
-------------------------------------------------------
> >>     
> >>     
> >>>>   
> >>> different definitions 
> >>>
> >>> location   /test/ {
> >>> cookie variable, so 
> >>> http://nginx.org/mailman/listinfo/nginx
> >> other than the root return a
> >>             ip_hash;
> >>     proxy_temp_path /var/www/nginx_temp;
> >>             rewrite ^(.*)$ /$backend/$1;
> >>             proxy_cache one;
> >>         location /wp-admin {
> >
> >         server ...
> >     map $cookie_uslnn_env $backend {
> >         location /testing/ {
> >             proxy_cache_valid  200 302 304 10m;
> >         }
> /production/, 
> /testing/... blocks, but not for the /wp-admin
> block.
> 
> Marcus.
> 
> 
> _______________________________________________
> nginx mailing list
> nginx@nginx.org
> http://nginx.org/mailman/listinfo/nginx

Marcus,
Thank you for the help. Sorry I did not reply sooner. Thank you
for explaining about the final slash. I tried adding the slash
in the /production/, /testing/, etc. blocks but then I got a
redirect loop. Is there something else I missed?

Eliot

Posted at Nginx Forum: 
http://forum.nginx.org/read.php?2,46979,49745#msg-49745