Varnish: Backend fetch failed while using Nginx + Docker

时间:2017-08-23 16:39:27

标签: docker nginx varnish

I am a little lost as I'm experiencing a 503-error with Varnish, whilst using it in a docker-compose container.

varnishlog returns this:

varnish_1  | *   << BeReq    >> 3
varnish_1  | -   Begin          bereq 2 pass
varnish_1  | -   Timestamp      Start: 1503505759.849314 0.000000 0.000000
varnish_1  | -   BereqMethod    GET
varnish_1  | -   BereqURL       /
varnish_1  | -   BereqProtocol  HTTP/1.1
varnish_1  | -   BereqHeader    Host: localhost
varnish_1  | -   BereqHeader    Pragma: no-cache
varnish_1  | -   BereqHeader    Cache-Control: no-cache
varnish_1  | -   BereqHeader    Upgrade-Insecure-Requests: 1
varnish_1  | -   BereqHeader    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36
varnish_1  | -   BereqHeader    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
varnish_1  | -   BereqHeader    Accept-Encoding: gzip, deflate, br
varnish_1  | -   BereqHeader    Accept-Language: de,de-DE;q=0.8,en-US;q=0.6,en;q=0.4
varnish_1  | -   BereqHeader    X-Forwarded-For: 172.18.0.1, 172.18.0.1
varnish_1  | -   BereqHeader    Via: varnish-default
varnish_1  | -   BereqHeader    startedAt: 1503505759.849
varnish_1  | -   BereqHeader    X-Varnish: 3
varnish_1  | -   VCL_call       BACKEND_FETCH
varnish_1  | -   VCL_return     fetch
varnish_1  | -   FetchError     no backend connection
varnish_1  | -   Timestamp      Beresp: 1503505759.849366 0.000052 0.000052
varnish_1  | -   Timestamp      Error: 1503505759.849370 0.000056 0.000004
varnish_1  | -   BerespProtocol HTTP/1.1
varnish_1  | -   BerespStatus   503
varnish_1  | -   BerespReason   Service Unavailable
varnish_1  | -   BerespReason   Backend fetch failed
varnish_1  | -   BerespHeader   Date: Wed, 23 Aug 2017 16:29:19 GMT
varnish_1  | -   BerespHeader   Server: Varnish
varnish_1  | -   VCL_call       BACKEND_ERROR
varnish_1  | -   BerespHeader   Content-Type: text/html; charset=utf-8
varnish_1  | -   BerespHeader   Retry-After: 5
varnish_1  | -   VCL_return     deliver
varnish_1  | -   Storage        malloc Transient
varnish_1  | -   ObjProtocol    HTTP/1.1
varnish_1  | -   ObjStatus      503
varnish_1  | -   ObjReason      Backend fetch failed
varnish_1  | -   ObjHeader      Date: Wed, 23 Aug 2017 16:29:19 GMT
varnish_1  | -   ObjHeader      Server: Varnish
varnish_1  | -   ObjHeader      Content-Type: text/html; charset=utf-8
varnish_1  | -   ObjHeader      Retry-After: 5
varnish_1  | -   Length         278
varnish_1  | -   BereqAcct      0 0 0 0 0 0
varnish_1  | -   End

This is my default.vcl's content:

# varnish version 4
vcl 4.0;
import std;
import directors;

# backend start

backend default {
  .host = "web";
  .port = "80";
  .connect_timeout = 30s;
  .first_byte_timeout = 600s;
  .between_bytes_timeout = 1s;
  .probe = {
    .url = "/robots.txt";
    .interval = 10s;
    .timeout = 5s;
    .window = 5;
    .threshold = 3;
  }
}

# backend end


#############
# Housekeeping

# init start
sub vcl_init {
  new magento2 = directors.fallback();
  magento2.add_backend(default);
}
# init end

sub vcl_fini {
  return (ok);
}

#############
# Client side

acl purge {
    "web";
    "php";
    "localhost";
}

sub vcl_recv {
  call custom_ctrl;

  if (req.method == "PURGE") {
      if (client.ip !~ purge) {
          return (synth(405, "Method not allowed"));
      }
      if (!req.http.X-Magento-Tags-Pattern) {
          return (synth(400, "X-Magento-Tags-Pattern header required"));
      }
      ban("obj.http.X-Magento-Tags ~ " + req.http.X-Magento-Tags-Pattern);
      return (synth(200, "Purged"));
  }

  /* We do not support SPDY or HTTP/2.0 */
  if (req.method == "PRI") {
    return (synth(405));
  }

  if (req.restarts == 0) {
    /* set X-Forwarded-For */
    if (req.http.X-Forwarded-For) {
      set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
    } else {
      set req.http.X-Forwarded-For = client.ip;
    }
    /* set Via */
    if (req.http.Via) {
      set req.http.Via = req.http.Via + ", varnish-default";
    } else {
      set req.http.Via = "varnish-default";
    }
    set req.http.startedAt = std.time2real(now, 0.0);
  }



  /* backend selector */
  if (req.url ~ "^/max") {
    set req.backend_hint = magento2.backend();
  }

  if (req.method != "GET" &&
    req.method != "HEAD" &&
    req.method != "PUT" &&
    req.method != "POST" &&
    req.method != "TRACE" &&
    req.method != "OPTIONS" &&
    req.method != "DELETE") {
      /* Non-RFC2616 or CONNECT which is weird. */
      return (pipe);
  }

  /* Implementing websocket support (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html) */
  if (req.http.Upgrade ~ "(?i)websocket") {
    return (pipe);
  }


  if (req.method != "GET" && req.method != "HEAD") {
    /* We only deal with GET and HEAD by default */
    return (pass);
  }

  /* Not cacheable */
  if (req.http.Authorization) {
    return (pass);
  }

  # no cache request
  if (req.http.Cache-Control == "no-cache" || req.url ~ "cache-control=no-cache") {
    return (pass);
  }

  # Send Surrogate-Capability headers to announce ESI support to backend
  # set req.http.Surrogate-Capability = "key=ESI/1.0";

  # sort the query string
  set req.url = std.querysort(req.url);

  # Bypass shopping cart, checkout and search requests
  if (req.url ~ "/checkout" || req.url ~ "/catalogsearch") {
      return (pass);
  }

  # normalize url in case of leading HTTP scheme and domain
  set req.url = regsub(req.url, "^http[s]?://", "");

  # Cookies
  std.collect(req.http.Cookie);

  # Compression filter. See https://www.varnish-cache.org/trac/wiki/FAQ/Compression
  if (req.http.Accept-Encoding) {
      if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf|flv)$") {
          # No point in compressing these
          unset req.http.Accept-Encoding;
      } elsif (req.http.Accept-Encoding ~ "gzip") {
          set req.http.Accept-Encoding = "gzip";
      } elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") {
          set req.http.Accept-Encoding = "deflate";
      } else {
          # unkown algorithm
          unset req.http.Accept-Encoding;
      }
  }
  # Remove Google gclid parameters to minimize the cache objects
  set req.url = regsuball(req.url,"\?gclid=[^&]+$",""); # strips when QS = "?gclid=AAA"
  set req.url = regsuball(req.url,"\?gclid=[^&]+&","?"); # strips when QS = "?gclid=AAA&foo=bar"
  set req.url = regsuball(req.url,"&gclid=[^&]+",""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz"

  # static files are always cacheable. remove SSL flag and cookie
  if (req.url ~ "^/(pub/)?(media|static)/.*\.(ico|css|js|jpg|jpeg|png|gif|tiff|bmp|mp3|ogg|svg|swf|woff|woff2|eot|ttf|otf)$") {
    unset req.http.Https;
    unset req.http.X-Forwarded-Proto;
    unset req.http.Cookie;
  }

  return (hash);
}


sub vcl_pipe {
  # By default Connection: close is set on all piped requests, to stop
  # connection reuse from sending future requests directly to the
  # (potentially) wrong backend. If you do want this to happen, you can undo
  # it here.
  # unset bereq.http.connection;
  if (req.http.upgrade) {
    set bereq.http.upgrade = req.http.upgrade;
  }
  return (pipe);
}


sub vcl_pass {
  return (fetch);
}


sub vcl_hash {

  if (req.http.host) {
    hash_data(req.http.host);
  } else {
    hash_data(server.ip);
  }

  if (req.http.cookie ~ "X-Magento-Vary=") {
    hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1"));
  }

  # For multi site configurations to not cache each other's content
  if (req.http.host) {
    hash_data(req.http.host);
  } else {
    hash_data(server.ip);
  }

  # To make sure http users don't see ssl warning
  if (req.http.X-Forwarded-Proto) {
    hash_data(req.http.X-Forwarded-Proto);
  }

  return (lookup);
}


sub vcl_purge {
  return (synth(200, "Purged"));
}


sub vcl_hit {
  if (obj.ttl > 0s) {
    # A pure unadultered hit, deliver it
    return (deliver);
  }
  # backend is healthy
  if (std.healthy(req.backend_hint)) {
    # set the stale
    if(obj.ttl + std.duration(std.integer(regsub(obj.http.Cache-Control, "[\s\S]*m-stale=(\d)+[\s\S]*", "\1"), 2) + "s", 2s) > 0s){
      return (deliver);
    }
  } else if (obj.ttl + obj.grace > 0s) {
    # Object is in grace, deliver it
    # Automatically triggers a background fetch
    return (deliver);
  }

  # fetch & deliver once we get the result
  return (miss);
}


sub vcl_miss {
  return (fetch);
}


sub vcl_deliver {
  # Happens when we have all the pieces we need, and are about to send the
  # response to the client.
  #
  # You can do accounting or modifying the final object here.
  set resp.http.X-Hits = obj.hits;
  set req.http.varnishUse = now - std.real2time(std.real(req.http.startedAt, 0.0), now);
  if (resp.http.Server-Timing) {
    if (std.real(req.http.varnishUse, 0) > 0) {
      set resp.http.Server-Timing = "9=" + req.http.varnishUse + ";Varnish," + resp.http.Server-Timing;
    } else {
      set resp.http.Server-Timing = "9=0.000;Varnish," + resp.http.Server-Timing;
    }
  } else {
    if (std.real(req.http.varnishUse, 0) > 0) {
      set resp.http.Server-Timing = "9=" + req.http.varnishUse + ";Varnish";
    } else {
      set resp.http.Server-Timing = "9=0.000;Varnish";
    }
  }
  return (deliver);
}



# custom control
sub custom_ctrl{
  #响应healthy检测
  if(req.url == "/ping"){
    return(synth(701));
  }
  if(req.url == "/varnish/version") {
    return(synth(702));
  }
}


sub vcl_synth {
  if (resp.status == 701) {
    synthetic("pong");
  } elsif (resp.status == 702) {
    synthetic("2017-08-20T16:27:55.780Z");
  }
  set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
  set resp.status = 200;
  set resp.http.Content-Type = "text/plain; charset=utf-8";
  return (deliver);
}


###############
# Backend Fetch

sub vcl_backend_fetch {
  return (fetch);
}



sub vcl_backend_response {
  if (bereq.uncacheable) {
    return (deliver);
  }

  if (beresp.http.content-type ~ "text") {
      set beresp.do_esi = true;
  }

  # cache only successfully responses and 404s
  if (beresp.status != 200 && beresp.status != 404) {
      set beresp.ttl = 0s;
      set beresp.uncacheable = true;
      return (deliver);
  } elsif (beresp.http.Cache-Control ~ "private") {
      set beresp.uncacheable = true;
      set beresp.ttl = 86400s;
      return (deliver);
  }

  if (beresp.http.X-Magento-Debug) {
      set beresp.http.X-Magento-Cache-Control = beresp.http.Cache-Control;
  }

  # validate if we need to cache it and prevent from setting cookie
  # images, css and js are cacheable by default so we have to remove cookie also
  if (beresp.ttl > 0s && (bereq.method == "GET" || bereq.method == "HEAD")) {
      unset beresp.http.set-cookie;
      if (bereq.url !~ "\.(ico|css|js|jpg|jpeg|png|gif|tiff|bmp|gz|tgz|bz2|tbz|mp3|ogg|svg|swf|woff|woff2|eot|ttf|otf)(\?|$)") {
          set beresp.http.Pragma = "no-cache";
          set beresp.http.Expires = "-1";
          set beresp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
          set beresp.grace = 1m;
      }
  }

  # the response body is text, do gzip (judge by response header Content-Type)
  if (beresp.http.Content-Type ~ "text" || beresp.http.Content-Type ~ "application/javascript" || beresp.http.Content-Type ~ "application/json") {
    set beresp.do_gzip = true;
  }

  # The following scenarios set uncacheable
  if (beresp.ttl <= 0s ||
    beresp.http.Set-Cookie ||
    beresp.http.Surrogate-Control ~ "no-store" ||
    (!beresp.http.Surrogate-Control &&
      beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
    beresp.http.Vary == "*"){
    # Hit-For-Pass
    set beresp.uncacheable = true;
    set beresp.ttl = 300s;
    set beresp.grace = 0s;
    return (deliver);
  }

  # Pause ESI request and remove Surrogate-Control header
  if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
    unset beresp.http.Surrogate-Control;
    set beresp.do_esi = true;
  }

  return (deliver);
}

You might wonder why there is .host = "web" in the config - this is due to the fact that docker-compose magically creates configs for each node in the docker-compose.yml file for linked services, which is in my case web as the servicename for the nginx instance.

I'm able to ping web from the varnish container, so the linking is okay. Also, this is the preferred and purposed usage of docker containers.

nginx is listening on Port 80 (like varnish, internally). I tried nginx on 8080 before, but it kept somehow listening to tcp:80 and there were no error output, so I gave it a try to map both services on port 80 and it seems to work fine; however, the problem exists if I switch nginx to port 8080. Keep in mind that nginx is always listening to port 80 if you use FROM nginx in your Dockerfile for the web-container, so this also might not be the problem.

Happy to provide more information.

1 个答案:

答案 0 :(得分:0)

这个Github-Issue中的答案为我解决了这个问题。因为看起来调查会产生问题。我不知道为什么,但使用问题本身标记的较小的配置文件正在工作。

https://github.com/magento/magento2/issues/10165