How to route requests in varnish on the basis of request parameters (path, query), onto a changing list of backend servers.
Where each request can be handled by a particular backend server.
A simple example
Here's a simple example where you'll inspect req.url and look for the right path and querystring. Based on the value a backend will be selected:
vcl 4.1;
backend backend1 {
.host = "backend1.example.com";
.port = "80";
}
backend backend2 {
.host = "backend2.example.com";
.port = "80";
}
sub vcl_recv {
if(req.url ~ "/\?action=foo") {
set req.backend_hint = backend1;
} else {
set req.backend_hint = backend2;
}
}
You can make the logic as complex as you want and add is many if-statements as you want.
Limitations
There are 3 very specific limitations to the example above:
The backends are static and should be explicitly defined in VCL
DNS hostnames are resolved at compile time and DNS changes aren't noticed at runtime
You cannot use request or runtime information to compose the hostname of your backends
I noticed the term a changing list of backends in your question. I'm not sure to what extent static backends will affect you. But if they do, the only real solution is to use dynamic backends in Varnish Enterprise.
Dynamic backends in Varnish Enterprise
Varnish Enterprise, the commercial version of Varnish offers a dynamic backends module that circumvents these limitations.
Backends can be defined on-the-fly and DNS changes are spotted when the DNS TTL expires.
Here's an example:
vcl 4.1;
import goto;
backend default none;
sub vcl_backend_fetch {
if(bereq.url ~ "^/(images|videos|static)/\?country=(us|uk|fr|jp)") {
}
set bereq.backend = goto.dns_backend(regsub(bereq.url,"^/(images|videos|static)/\?country=(us|uk|fr|jp)","\1.\2.backend.example.com"));
}
When the request URL is /images/?country=uk, the backend would become images.uk.backend.example.com.
This is just a hypothetical example. The point I'm trying to make is that you can define backends on the fly and that DNS changes are spotted at runtime.
See https://docs.varnish-software.com/varnish-cache-plus/vmods/goto/ if you want to learn more about dynamic backends in Varnish Enterprise.
Varnish Enterprise disclaimer
This post is not a Varnish Enterprise advertisement. If you can solve your issues with open source Varnish, that's absolutely fine.
If Varnish Cache would limit you in your use case, Varnish Enterprise is there to solve that problem.
Although Varnish Enterprise is commercial software that requires purchasing a license, there are cheap ways to get started.
If you're running or testing your application on Cloud platforms such as, AWS, Azure or Google Cloud, you can use one of our Varnish Enterprise machine images without having to purchase a license up front.
License fees are billed per hour by the Cloud platform and there are free trials available. Some platforms even have a cheaper "developer edition" that allow you to test Varnish Enterprise on smaller Cloud instances.
Related
Brief history: I recently moved to new AWS account. And I ported all settings to new account - Route53, CF distributions, S3 buckets, new EC2 instance, RDS etc.
I've managed to make most of it work except for accessing APIs hosted on an EC2 instance on Node running Express server. Note: I AM able to access via ip address (e.g. ipaddress:8000/api/v1). However, not with the domain name - theplaybook.rocks/v1/ap1.
The apex domain works properly (the frontend hosted on S3 with static website hosting and served via CF distribution configured with DNS records) - [theplaybook.rocks][1] works fine.
In the CF distribution I have 2 Origins
S3 static website - configured with behaviour default path
EC2 Custom Origin - configured with behaviour /api/* path
Alternate domain names - configured
Security policy - TLSV1 (Tried 1.1 as well)
Origin Protocol Policy (for EC2 origin) - HTTP only
Behaviour for EC2 origin (points I think may help in answering this problem):
Viewer Protocol Policy - HTTP & HTTPS
Cache Based on Selected Request Headers - ALL
Forward Cookies - ALL
Query String Forwarding and Caching - Forward All, cache based on all
It seems like the CF is unable (or doesn't want to) connect to the Custom Origin.
TLDR:-
http://3.15.153.208:8000/api/v1 works (public IP - so problem is probably not with node/express and ports).
http://theplaybook.rocks/api/v1/ does not work, actually it used to work from different AWS account, just not in new configuration.
PS:- I had the same problem in the old account. And I solved it somehow. And I can't remember how I solved it anymore.
Please help... and I can share more info... let me know. Tks.
Try adding an alternate domain name to your cloud front distribution: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html
Solved. Finally.
I changed HTTP port to 8000 (instead of 80) in Origin settings and it started working immediately.
Note: Previously this was not the case. I never used to put 8000 port here. Not sure why now.
Thanks everyone who glanced by this post :-D
I would like to configure varnish as a cache for one of my websites for a temporary heavy load.
I set up several virtual machines with varnish that should cache my main website.
As my main server hosts several websites the apache server is configured to be a virtual host server.
So I defined the main website domainname in the default.vcl instead of an IP address, hoping varnish would propagate the requests properly to the right apache virtualhost.
But it seems it just uses the IP address, and is caching the apache default page that appears when sending a http request to my main website ip address.
Is there a way to configure varnish so it calls my backend using the right url, and not an IP:port ?
my varnish config looks like :
backend default{
.host = "www.myvhost.com";
.port = "80";
}
instead of :
backend default{
.host = "my.ip";
.port = "80";
}
because I need the varnish instances : http://www1.myvhost.com, http://www2.myvhost.com ... to cache the main server http://www.myvhost.com
but not http://myip:80/
Thanks for your help
You probably ean the varnish is the reverse proxy not the apache
As my main server hosts several websites the apache server is
configured to be a reverse proxy.
You can configure your hostname in the backend definition of your default.vcl
https://github.com/mattiasgeniar/varnish-4.0-configuration-templates/blob/master/default.vcl
All your traffic is going to point on the varnish for all your vhosts, excepts if you have several IP on your server.
I think what you are looking for is flitering queries for a single domain. You can achieve this by filtering on the host.
sub vcl_recv {
if (req.http.host ~ "(www\.)?yourdomain\.com") {
return(pass);
}
}
All traffic not on your domain will be directly pass to the backend without lookup
From experimenting with Azure load-balancing set, it seems that x-forwarded-for header is not used (as it would be expected in regular load-balancer), rather they preserve the original client IP.
E.g.:
app.get('/my-ip', function(req, res) {
winston.log('/my-ip', 'x-forwarded', req.headers['x-forwarded-for'] || 'none', 'remoteAddress', req.connection.remoteAddress || 'none');
res.end();
});
With the result:
/my-ip x-forwarded none remoteAddress MY_CORRECT_IP
Can this behavior be confirmed and relied upon?
You are confusing proxies with load balancing. Proxies use x-forwarded, load balancers do not (by default). Load balancers work at a lower level in the OSI stack (although you might find all kinds of things calling themselves load balancers that really aren't).
The key difference here is that a proxy actually interprets your HTTP request, typically caching it in the process, before forwarding it with it's altered headers. A load balancer doesn't have to (though it can). They just re-route packets. Some more advanced load balancers support adding this header, but it's never the default configuration. Proxies typically have this header on by default, and support removing it.
The reason load balancers don't typically need this header is that a load balancer is basically a router, as such it maintains the original source ip information of the packets by default. A proxy, on the other hand acts as destination for the original request, then it issues a new request to the new destination, thus the original packet information is typically lost. Like, if you worked at a mail forwarding facility, and you opened peoples mail, read it, then put it in a new envelope with your return address.
I am attempting to set up my first Varnish Cache server and I have a couple questions for any person(s) experienced.
1.) I am running Varnish as a stand alone server. Do I need Apache also installed on the same server. Ultimately the actual site that will be behind Varnish is not on this server.
2.) Do I point the domain to Varnish and then set the config to point to the ip address of the server that is hosting the site? If so, how do you point it to the right site?
3.) If Varnish is standalone and I have an Apache content server, can they both be port 80 and just change the ip address in the default.vcl
backend default {
.host = "198.221.134.235";
.port = "80";
}
Sorry for the basic questions. I have been on Google all weekend and I found plenty of information on how to install and config Varnish but it seems like the site you want to Cache is on the same server since all of them are changing the port Apache listens to and that seems like it would mean the site is living on the same server.
And if you have any good sites with information, please feel free to share them! Thanks again!
No, Varnish and Apache (or any other HTTP/webserver) can run on a separate server.
Indeed, point the domain to the IP of Varnish and setup a backend as described in the documentation: https://www.varnish-cache.org/docs/3.0/tutorial/backend_servers.html. The IP
of your webserver will be the IP of the backend.
Correct, as long as Apache and Varnish are on separate servers they both can listen on port 80
If I am not mistaken you will have the following setup:
DNS example.com => 1.1.1.1
IP 1.1.1.1:80: Varnish (backend: 1.1.1.2:80)
IP 1.1.1.2:80: Apache
I have a Grails App and I am looking to lock it down by ip so that only a number of IP ranges can access the application. I have used the Spring Security command within Config to achieve this:
grails.plugins.springsecurity.ipRestrictions
Then when running the App in the cloud (Jelastic) even though I am on one of the IP's I listed it doesn’t let me access the areas I want. I then put some code in the app shown below to pull back the address of the Client and it shows the address of maybe the cloud proxy server instead of the Client using the App:
request.getRemoteAddr()
I think that it wont let me access the areas I want as its reading my IP as the IP of the cloud proxy, I have also tried running the commands below to see if any of them return my actual IP, however they were all null :S
request.getHeader("X-Forwarded-For");
request.getHeader("Proxy-Client-IP");
request.getHeader("WL-Proxy-Client-IP")
request.getHeader("HTTP_CLIENT_IP")
request.getHeader("HTTP_X_FORWARDED_FOR")
I just need to know if there is some way of restricting this application down in the cloud by Client IP instead of it using the IP of the Cloud Proxy? Thanks in advance
All requests to Jelastic instances are coming through infrastructure global Resolver.
So, you are right, request.getRemoteAddr() returns IP of Resolver and it's not recognized by your allowed list.
Workaround for this is purchasing external IP for your app server in Jelastic. In this case all requests will come directly to your instance.
I also recommend you get on board the dedicated Jelastic Community in order to share your experience and get help from others.
Have you configured a nginx instance in front of your tomcat? I am not sure if there are any jelastic specifics, but you have to configure nginx so that it passes the ip to the proxied service, see http://wiki.nginx.org/HttpRealIpModule
You could e.g. set a custom header, if you don't want to overwrite the defaults:
proxy_set_header X-Real-IP $remote_addr;
Have you tried dumping the headers you actually get in your app?
On Cloudfoundry.com I can see that I get 'x-forwarded-for'
class HeaderController {
def headerTest = {
def headerNames = request.headerNames.collect{ it }
headerNames.each {
render "$it : ${request.getHeader(it)}\n"
}
render "Remote addr : ${request.getRemoteAddr()}\n"
render "Forward addr : ${request.getHeader('x-forwarded-for' )}\n"
}
}
In Jelastic cloud 'x-forwarded-for' displays your IP as well.
As a follow up I suggest you to check the related topic on Jelastic Community
So supposedly you'd have to set your IP restriction config in a way that it checks the value of 'x-forwarded-for'.