Top Banner
Solving anything in VCL Andrew Betts, Financial Times
62

Solving anything in VCL

Apr 16, 2017

Download

Technology

Fastly
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Solving anything in VCL

Solving anything in VCLAndrew Betts, Financial Times

Page 2: Solving anything in VCL

Who is this guy?1.Helped build the original HTML5

web app for the FT2.Created our Origami component

system3.Ran FT Labs for 3 years4.Now working with Nikkei to rebuild

nikkei.com5.Also W3C Technical Architecture

Group6.Live in Tokyo, Japan

2

Pic of me.

Page 3: Solving anything in VCL

Nikkei1.Largest business

newspaper in Japan2.Globally better known

for the Nikkei 225 stock index

3.Around 3 million readers

Page 4: Solving anything in VCL

4

Page 5: Solving anything in VCL

5

Page 6: Solving anything in VCL

6

Page 7: Solving anything in VCL

7

Page 8: Solving anything in VCL

8

Page 9: Solving anything in VCL

Coding on the edge

9

Page 10: Solving anything in VCL

Benefits of edge code10

1.Smarter routing2.Faster authentication3.Bandwidth management4.Higher cache hit ratio

Page 11: Solving anything in VCL

Edge side includes11

<esi:include src="http://example.com/1.html" alt="http://bak.example.com/2.html" onerror="continue"/>

index.html

my-news.html

Cache-control: max-age=86400

Cache-control: private

Server

Page 12: Solving anything in VCL

The VCL way1.Request and response bodies are opaque2.Everything happens in metadata3.Very restricted: No loops or variables4.Extensible: some useful Fastly extensions include geo-ip and

crypto5.Incredibly powerful when used creatively

12

Page 13: Solving anything in VCL

SOA RoutingSend requests to multiple microservice backends

This is great if...You have a microservice

architectureMany backends, one domainYou add/remove services

regularly

1

Page 14: Solving anything in VCL

SOA Routing in VCL14

Front page

Article page

Timeline

Content API

Choose a backend based on a path

match of the request URL

/article/123

Page 15: Solving anything in VCL

SOA Routing in VCL15

[ { name, paths, host, useSsl, }, …]

{{#each backends}} backend {{name}} { .port = "{{p}}"; .host = "{{h}}"; }{{/each}}

let vclContent = vclTemplate(data);

fs.writeFileSync( vclFilePath, vclContent, 'UTF-8');

services.json

Defines all the backends and paths that they control.

routing.vcl.handlebars

VCL template with Handlebars placeholders for backends & routing

build.js

Task script to merge service data into VCL template

Page 16: Solving anything in VCL

SOA Routing: key tools and techniques●Choose a backend:set req.backend = {{backendName}};

●Match a route pattern:if (req.url ~ "{{pattern}}")

●Remember to set a Host header:set req.http.Host = "{{backendhost}}";

●Upload to Fastly using FT Fastly tools○ https://github.com/Financial-Times/fastly-tools

16

Page 17: Solving anything in VCL

service-registry.json17

[ { "name": "front-page", "paths": [ "/(?qs)", "/.resources/front/(**)(?qs)" ], "hosts": [ "my-backend.ap-northeast-1.elasticbeanstalk.com" ] }, { "name": "article-page", ... }]

Common regex patterns simplified into shortcuts

Page 18: Solving anything in VCL

routing.vcl.handlebars18

{{#each backends}}backend {{name}} { .port = "{{port}}"; .host = "{{host}}"; .ssl = {{use_ssl}}; .probe = { .request = "GET / HTTP/1.1" "Host: {{host}}" "Connection: close"; }}{{/each}}

sub vcl_recv { {{#each routes}} if (req.url ~ "{{pattern}}") { set req.backend = {{backend}}; {{#if target}} set req.url = regsub(req.url, "{{pattern}}", "{{target}}"); {{/if}}

{{!-- Fastly doesn't support the host_header property in backend definitions --}} set req.http.Host = "{{backendhost}}"; } {{/each}} return(lookup);}

Page 19: Solving anything in VCL

build.js19

const vclTemplate = handlebars.compile(fs.readFileSync('routing.vcl.handlebars'), 'UTF-8'));const services = require('services.json');

// ... transform `services` into `viewData`

let vclContent = vclTemplate(viewData);fs.writeFileSync(vclFilePath, vclContent, 'UTF-8');

Page 20: Solving anything in VCL

UA TargetingReturn user-agent specific responses without destroying your cache hit ratio

This is great if...You have a response that is

tailored to different device types

There are a virtually infinite number of User-Agent values

2

Page 21: Solving anything in VCL

21

Polyfill screenshot

Page 22: Solving anything in VCL

UA Targeting22

/normalizeUA

/polyfill.js?ua=ie/11

/polyfill.js

Add the normalised User-Agent to the URL and restart the original request

Add a Vary: User-Agent header to the response before sending it back to the browser

We call this a preflight request

Page 23: Solving anything in VCL

UA targeting: key tools and techniques●Remember something using request headers:set req.http.tmpOrigURL = req.url;

●Change the URL of the backend request:set req.url = "/api/normalizeUA?ua=" req.http.User-Agent;

●Reconstruct original URL adding a backend response header:set req.url = req.http.tmpOrigURL "?ua=" resp.http.NormUA;

●Restart to send the request back to vcl_recv:restart;

23

Page 24: Solving anything in VCL

ua-targeting.vcl24

sub vcl_recv { if (req.url ~ "^/v2/polyfill\." && req.url !~ "[\?\&]ua=") { set req.http.X-Orig-URL = req.url; set req.url = "/v2/normalizeUa?ua=" urlencode(req.http.User-Agent); }}

sub vcl_deliver { if (req.url ~ "^/v\d/normalizeUa" && resp.status == 200 && req.http.X-Orig-URL) { set req.http.Fastly-force-Shield = "1"; if (req.http.X-Orig-URL ~ "\?") { set req.url = req.http.X-Orig-URL "&ua=" resp.http.UA; } else {

set req.url = req.http.X-Orig-URL "?ua=" resp.http.UA; } restart; } else if (req.url ~ "^/v\d/polyfill\..*[\?\&]ua=" && req.http.X-Orig-URL && req.http.X-Orig-URL !~ "[\?\&]ua=") { add resp.http.Vary = "User-Agent"; } return(deliver);}

Page 25: Solving anything in VCL

AuthenticationImplement integration with your federated identity system entirely in VCL

This is great if...You have a federated login

system using a protocol like OAuth

You want to annotate requests with a simple verified authentication state

3

Page 26: Solving anything in VCL

Magic circa 200126

<?phpecho $_SERVER['PHP_AUTH_USER'];?>

http://intranet/my/example/app

Page 27: Solving anything in VCL

New magic circa 201627

app.get('/', (req, res) => { res.end(req.get('Nikkei-UserID'));});

Page 28: Solving anything in VCL

Authentication28

/article/123

Decode+verifyauth cookie!

Nikkei-UserID: andrew.bettsNikkei-UserRank: premium

Vary: Nikkei-UserRank

Article

Cookie: Auth=a139fm24...

Cache-control: private

Page 29: Solving anything in VCL

Authentication: key tools and techniques●Get a cookie by name: req.http.Cookie:MySiteAuth●Base64 normalisation:digest.base64url_decode(), digest.base64_decode

●Extract the parts of a JSON Web Token (JWT):regsub({{cookie}}, "(^[^\.]+)\.[^\.]+\.[^\.]+$", "\1");

●Check JWT signature: digest.hmac_sha256_base64()●Set trusted headers for backend use:req.http.Nikkei-UserID = regsub({{jwt}}, {{pattern}}, "\1");

29

Page 30: Solving anything in VCL

authentication.vcl30

if (req.http.Cookie:NikkeiAuth) { set req.http.tmpHeader = regsub(req.http.Cookie:NikkeiAuth, "(^[^\.]+)\.[^\.]+\.[^\.]+$", "\1"); set req.http.tmpPayload = regsub(req.http.Cookie:NikkeiAuth, "^[^\.]+\.([^\.]+)\.[^\.]+$", "\1");

set req.http.tmpRequestSig = digest.base64url_decode( regsub(req.http.Cookie:NikkeiAuth, "^[^\.]+\.[^\.]+\.([^\.]+)$", "\1") );

set req.http.tmpCorrectSig = digest.base64_decode( digest.hmac_sha256_base64("{{jwt_secret}}", req.http.tmpHeader "." req.http.tmpPayload) );

if (req.http.tmpRequestSig != req.http.tmpCorrectSig) { error 754 "/login; NikkeiAuth=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT"; }

... continues ...

Page 31: Solving anything in VCL

authentication.vcl (cont)31

set req.http.tmpPayload = digest.base64_decode(req.http.tmpPayload);

set req.http.Nikkei-UserID = regsub(req.http.tmpPayload, {"^.*?"sub"\s*:\s*"(\w+)".*?$"}, "\1"); set req.http.Nikkei-Rank = regsub(req.http.tmpPayload, {"^.*?"ds_rank"\s*:\s*"(\w+)".*?$"}, "\1");

unset req.http.base64_header; unset req.http.base64_payload; unset req.http.signature; unset req.http.valid_signature; unset req.http.payload;

} else {

set req.http.Nikkei-UserID = "anonymous"; set req.http.Nikkei-Rank = "anonymous";}

Page 32: Solving anything in VCL

Feature flagsDark deployments and easy A/B testing without reducing front end perf or cache efficiency

This is great if...You want to serve different

versions of your site to different users

Test new features internally on prod before releasing them to the world

4

Page 33: Solving anything in VCL

33

Page 34: Solving anything in VCL

34

Now you see it...

Page 35: Solving anything in VCL

Feature flags parts35

●A flags registry - a JSON file will be fine○ Include all possible values of each flag and what percentage of the audience it

applies to

○ Publish it statically - S3 is good for that

●A flag toggler tool○ Reads the JSON, renders a table, writes an override cookie with chosen values

●An API○ Reads the JSON, responds to requests by calculating a user's position number

on a 0-100 line and matches them with appropriate flag values

●VCL○ Merges flag data into requests

Page 36: Solving anything in VCL

Feature flags36

Flags API

Article

Merge the flags response with the override cookie, set as HTTP header, restart original request...

Decode+verifyauth cookie!

/article/123

Cookie: Flgs-Override= Foo=10;

/api/flags?userid=6453

Flgs: highlights=true; Foo=42;

Flgs: highlights=true; Foo=42; Foo=10

Vary: Flgs

Page 37: Solving anything in VCL

ExpressJS flags middleware37

app.get('/', (req, res) => { if (req.flags.has('highlights')) { // Enable highlights feature }});

HTTP/1.1 200 OKVary: Nikkei-Flags...

Middleware provides convenient interface to flags header

Invoking the middleware on a request automatically applies a Vary header to the response

Page 38: Solving anything in VCL

Dynamic backendsOverride backend rules at runtime without updating your VCL

This is great if...You have a bug you can't

reproduce without the request going through the CDN

You want to test a local dev version of a service with live integrations

5

Page 39: Solving anything in VCL

Dynamic backends39

Developer laptop

Dynamic backend proxy(node-http-proxy)

Check forwarded IP is whitelisted or auth header is also present

GET /article/123Backend-Override: article -> fc57848a.ngrok.io

Detect override header, if path

would normally be routed to article,

change it to override proxy

instead.

ngrok

fc57848a.ngrok.i

o

Normal production backends

Page 40: Solving anything in VCL

Dynamic backends: key tools and techniques●Extract backend to override:

set req.http.tmpORBackend = regsub(req.http.Backend-Override, "\s*\-\>.*$", "");

●Check whether current backend matchesif (req.http.tmpORBackend == req.http.tmpCurrentBackend) {

●Use node-http-proxy for the proxy app○ Remember res.setHeader('Vary', 'Backend-Override');○ I use {xfwd: false, changeOrigin: true, hostRewrite: true}

40

Page 41: Solving anything in VCL

Debug headersCollect request lifecycle information in a single HTTP response header

This is great if...You find it hard to understand

what path the request is taking through your VCL

You have restarts in your VCL and need to see all the individual backend requests, not just the last one

6

Page 42: Solving anything in VCL

42

The VCL flow

Page 43: Solving anything in VCL

43

The VCL flow

Page 44: Solving anything in VCL

44

The VCL flow

Page 45: Solving anything in VCL

Debug journey45

vcl_recv { set req.http.tmpLog = if (req.restarts == 0, "", req.http.tmpLog ";"); # ... routing ... set req.http.tmpLog = req.http.tmpLog " {{backend}}:" req.url;}vcl_fetch { set req.http.tmpLog = req.http.tmpLog " fetch"; ... }vcl_hit { set req.http.tmpLog = req.http.tmpLog " hit"; ... }vcl_miss { set req.http.tmpLog = req.http.tmpLog " miss"; ... }vcl_pass { set req.http.tmpLog = req.http.tmpLog " pass"; ... }vcl_deliver { set resp.http.CDN-Process-Log = req.http.tmpLog;}

Page 46: Solving anything in VCL

Debug journey46

CDN-Process-Log: apigw:/flags/v1/rnikkei/allocate?output=diff&segid=foo&rank=X HIT (hits=2 ttl=1.204/5.000 age=4 swr=300.000 sie=604800.000); rnikkei_front_0:/ MISS (hits=0 ttl=1.000/1.000 age=0 swr=300.000 sie=86400.000)

Page 47: Solving anything in VCL

RUM++Resource Timing API + data Fastly exposes in VCL. And no backend.

This is great if...You want to track down hotspots

of slow response timesYou'd like to understand how

successfully end users are being matched to their nearest PoPs

7

Page 48: Solving anything in VCL

Resource timing on front end48

var rec = window.performance.getEntriesByType("resource").find(rec => rec.name.indexOf('[URL]') !== -1)

;

(new Image()).src = '/sendBeacon'+'?dns='+(rec.domainLookupEnd-rec.domainLookupStart)+'&connect='+(rec.connectEnd-rec.connectStart)+'&req='+(rec.responseStart-rec.requestStart)+'&resp='+(rec.responseEnd-rec.responseStart)

;

Page 49: Solving anything in VCL

Add CDN data in VCL & respond with synthetic

49

sub vcl_recv { if (req.url ~ "^/sendBeacon") { error 204 "No content"; }}

Page 50: Solving anything in VCL

RUM++50

/sendBeacon?foo=42&...

No backend request!

204 No Content

Write logs in 1 minute batches to Amazon S3

Use an 'error' response to return a 204!

Page 51: Solving anything in VCL

51

Custom format string for log entries

Page 52: Solving anything in VCL

Crunch the data52

Page 53: Solving anything in VCL

Beyond ASCIIUse these encoding tips to embed non-ASCII content in your VCL file.

This is great if...Your users don't speak English,

but you can only write ASCII in VCL files

8

Page 54: Solving anything in VCL

Everyone does UTF-8 now, right?54

synthetic {"Responsive Nikkeiアルファプログラムのメンバーの皆様、アルファバージョンのサイトにアクセスできない場合、 [email protected] までその旨連絡ください。 "};

Page 55: Solving anything in VCL

55

Page 56: Solving anything in VCL

Quick conversion56

"string" .split('') .map( char => char.codePointAt(0) < 128 ? char : "&#"+char.codePointAt(0)+";" ) .join('');

Page 57: Solving anything in VCL

"Fixed"57

synthetic {"Responsive Nikkei&#12450;&#12523;&#12501;&#12449;&#12503;&#12525;&#12464;&#12521;&#12512;&#12398;&#12513;&#12531;&#12496;&#12540;&#12398;&#30342;&#27096;&#12289;&#12450;&#12523;&#12501;&#12449;&#12496;&#12540;&#12472;&#12519;&#12531;&#12398;&#12469;&#12452;&#12488;&#12395;&#12450;&#12463;&#12475;&#12473;&#12391;&#12365;&#12394;&#12356;&#22580;&#21512;&#12289;[email protected] &#12414;&#12391;&#12381;&#12398;&#26088;&#36899;&#32097;&#12367;&#12384;&#12373;&#12356;&#12290;"};

Page 58: Solving anything in VCL

"Fixed"58

synthetic digest.base64decode({"IlJlc3BvbnNpdmUgTmlra2Vp44Ki44Or44OV44Kh44OX44Ot44Kw44Op44Og44Gu44Oh44Oz44OQ44O844Gu55qG5qeY44CB44Ki44Or44OV44Kh44OQ44O844K444On44Oz44Gu44K144Kk44OI44Gr44Ki44Kv44K744K544Gn44GN44Gq44GE5aC05ZCI44CBcm5mZWVkYmFja0BuZXgubmlra2VpLmNvLmpwIOOBvuOBp+OBneOBruaXqOmAo+e1oeOBj+OBoOOBleOBhOOAgiI="});

Page 59: Solving anything in VCL

59

Here's what you always wanted! Hugs,

Page 60: Solving anything in VCL

I have 68 backends

60

Varnish transaction ID!!

Page 61: Solving anything in VCL

Varnishlog to the rescue

A way to submit a varnish transaction ID to the API, and get all varnishlog events relating to that transaction, including related (backend) transactions

61

> fastly log 1467852934

17 SessionOpen c 66.249.72.22 47013 :8017 ReqStart c 66.249.72.22 47013 146785293417 RxRequest c GET17 RxURL c /articles/12317 RxProtocol c HTTP/1.117 RxHeader c Host: www.example.com ...

Page 62: Solving anything in VCL

Thanks for listening

62

Andrew [email protected]@triblondon

Get the slidesbit.ly/ft-fastly-altitude-2016