Wie wir developer.fastly.com von VCL auf Compute migriert haben

Wenn Sie mit Fastly arbeiten, verbringen Sie wahrscheinlich eine Menge Zeit in unserem Developer Hub. Vor einem Monat haben wir ihn von unserer VCL Plattform auf Compute migriert. Erfahren Sie, wie der Umzug lief und was Sie daraus lernen können.

Zunächst ist es wichtig zu betonen, dass eine Migration nicht zwingend der richtige Weg für Sie ist. Sowohl unsere VCL als auch Compute-Plattformen werden aktiv unterstützt und weiterentwickelt. Außerdem bietet VCL ein hervorragendes Setup-Erlebnis, das Ihnen großartige Vorlagen für schnelle Websites ganz ohne Code liefert.Wenn Sie aber komplexere Aufgaben auf der Edge erledigen möchten, benötigen Sie wahrscheinlich die allgemeineren Compute-Kapazitäten von Compute.

Bevor Sie sich aber neuen Dingen widmen, sollten Sie vielleicht das, was Sie bisher in VCL erledigt haben, auf Ihren Compute-Service übertragen. In diesem Blogpost zeige ich Ihnen, wie Sie alle von developer.fastly.com verwendeten Patterns von VCL auf Compute migrieren können – das perfekte Beispiel für eine Fallstudie also.

Vorbereitung

Da es sich bei dem bestehenden Service developer.fastly.com um VCL handelt (und um einen Service in der Produktivumgebung, den wir nicht kaputt machen wollen!), müssen wir zunächst einen neuen Compute Service zum Testen erstellen. Der erste Schritt besteht in der Installation der Fastly CLI und dem Aufsetzen eines neuen Projekts:

> fastly compute init
Creating a new Compute project.
Press ^C at any time to quit.
Name: [edge] developer-hub
Description: Fastly Developer Hub
Author: devrel@fastly.com
Language:
[1] Rust
[2] AssemblyScript (beta)
[3] JavaScript (beta)
[4] Other ('bring your own' Wasm binary)
Choose option: [1] 3
Starter kit:
[1] Default starter for JavaScript
A basic starter kit that demonstrates routing, simple synthetic responses and
overriding caching rules.
https://github.com/fastly/compute-starter-kit-javascript-default
Choose option or paste git URL: [1] 1
✓ Initializing...
✓ Fetching package template...
✓ Updating package manifest...
✓ Initializing package...
Initialized package developer-hub to:
~/repos/Developer-Hub/edge

Der Developer Hub ist größtenteils eine JavaScript Anwendung, die das Gatsby Framework verwendet. Daher haben wir uns entschieden, auch unseren Compute Code in JavaScript zu schreiben. Die durch den init-Befehl generierte Anwendung ist eine vollständige und funktionierende App. Es lohnt sich also, sie sofort in der Produktivumgebung bereitzustellen, um einen schönen Zyklus aus Entwicklung, Test und Wiederholung in Gang zu setzen:

> fastly compute publish
✓ Initializing...
✓ Verifying package manifest...
✓ Verifying local javascript toolchain...
✓ Building package using javascript toolchain...
✓ Creating package archive...
SUCCESS: Built package 'developer-hub' (pkg/developer-hub.tar.gz)
There is no Fastly service associated with this package. To connect to an existing service add the Service ID to the fastly.toml file, otherwise follow the prompts to create a service now.
Press ^C at any time to quit.
Create new service: [y/N] y
✓ Initializing...
✓ Creating service...
Domain: [some-funky-words.edgecompute.app]
Backend (hostname or IP address, or leave blank to stop adding backends):
✓ Creating domain some-funky-words.edgecompute.app'...
✓ Uploading package...
✓ Activating version...
Manage this service at:
https://manage.fastly.com/configure/services/*****************
View this service at:
https://some-funky-words.edgecompute.app
SUCCESS: Deployed package (service *****************, version 1)

Jetzt haben wir also einen funktionierenden Service, der von der Fastly Edge aus bedient wird, und können Änderungen in wenigen Sekunden bereitstellen. Lassen Sie uns mit der Migration beginnen!

Google Cloud Storage

Der Hauptinhalt des Developer Hubs ist die Gatsby Seite, die erstellt und in Google Cloud Storage bereitgestellt und anschließend statisch ausgeliefert wird. Fügen wir zunächst ein Backend hinzu:

fastly backend create --name gcs --address storage.googleapis.com --version active --autoclone

Jetzt bearbeiten wir die Hauptquelldatei der Compute-Anwendung – in diesem Fall src/index.js – um den Inhalt von GCS zu laden. Wir können damit beginnen, den gesamten Inhalt der Datei durch folgenden Code zu ersetzen:

const BACKENDS = {
GCS: "gcs",
}
const GCS_BUCKET_NAME = "fastly-developer-portal"
async function handleRequest(event) {
const req = event.request
const reqUrl = new URL(req.url)
let backendName
backendName = BACKENDS.GCS
reqUrl.pathname = "/" + GCS_BUCKET_NAME + "/production" + reqUrl.pathname
// Fetch the index page if the request is for a directory
if (reqUrl.pathname.endsWith("/")) {
reqUrl.pathname = reqUrl.pathname + "index.html"
}
const beReq = new Request(reqUrl, req);
let beResp = await fetch(beReq, {
backend: backendName,
cacheOverride: new CacheOverride(["GET", "HEAD", "FASTLYPURGE"].includes(req.method) ? "none" : "pass"),
})
if (backendName === BACKENDS.GCS && beResp.status === 404) {
// Try for a directory index if the original request didn't end in /
if (!reqUrl.pathname.endsWith("/index.html")) {
reqUrl.pathname += "/index.html"
const dirRetryResp = await fetch(new Request(reqUrl, req), { backend: BACKENDS.GCS })
if (dirRetryResp.status === 200) {
const origURL = new URL(req.url) // Copy of original client URL
return createRedirectResponse(origURL.pathname + "/")
}
}
}
return beResp
}
const createRedirectResponse = (dest) =>
new Response("", {
status: 301,
headers: { Location: dest },
})
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)))

Dieser Code implementiert das von uns empfohlene Muster für das Fronting eines öffentlichen GCS Buckets, aber gehen wir es einmal Schritt für Schritt durch:

  • Legen Sie einige Konstanten fest.Das BACKENDS-Objekt bietet uns eine gute Möglichkeit, das Routing für andere Backends, die später hinzukommen werden, zu erweitern. Der Wert „gcs“ hier entspricht dem Namen, den wir dem Backend zuvor gegeben haben.

  • Der Einfachheit halber weisen wir req die Client-Anfrage event.request zu. Mit der URL-Klasse können wir req.url in ein geparstes URL-Objekt, reqUrl verwandeln.

  • Um das richtige Objekt im GCS Bucket zu erreichen, setzten wir den Bucket-Namen an den Anfang des Pfades. In unserem Fall mussten wir auch „production“ (Produktivumgebung) hinzufügen, da wir auch Zweige, die nicht in der Produktivumgebung vorhanden sind, in unserem GCS Bucket speichern.

  • Wenn die eingehende Anfrage mit / endet, ist es sinnvoll, „index.html“ anzuhängen, um die Indexseiten des Verzeichnisses im Bucket zu finden.

  • Wenn die Antwort von Google eine 404-Fehlermeldung ist und die Client-Anfrage nicht mit einem Schrägstrich endet, fügen wir „/index.html“ zum Pfad hinzu und versuchen es noch einmal.Wenn es funktioniert, antworten wir mit einer externen Umleitung, um dem Client mitzuteilen, dass er einen Schrägstrich anhängen soll.

  • Zum Schluss hängen wir noch die Request-Handler-Funktion an das Client-Ereignis „fetch“ an.

Wir kompilieren und stellen bereit:

> fastly compute publish
✓ Initializing...
✓ Verifying package manifest...
✓ Verifying local javascript toolchain...
✓ Building package using javascript toolchain...
✓ Creating package archive...
SUCCESS: Built package 'developer-hub' (pkg/developer-hub.tar.gz)
✓ Uploading package...
✓ Activating version...
SUCCESS: Deployed package (service *****************, version 3)

Wenn wir nun some-funky-words.edgecompute.app in einem Browser laden, sehen wir den Developer Hub! Ein guter Anfang.

Nutzerdefinierte 404-Seite

Seiten, die existieren, funktionieren hervorragend. Wenn wir aber versuchen, eine Seite zu laden, die nicht existiert, passieren schlimme Dinge:

404 return

Gatsby generiert eine „Seite nicht gefunden“-Seite und stellt sie als „404.html“ im GCS Bucket bereit. Wir müssen diesen Inhalt nur ausliefern, wenn die Client-Anfrage eine 404-Antwort von Google auslöst hat:

if (backendName === BACKENDS.GCS && beResp.status === 404) {
// ... existing code here ... //
// Use a custom 404 page
if (!reqUrl.pathname.endsWith("/404.html")) {
debug("Fetch custom 404 page")
const newPath = "/" + GCS_BUCKET_NAME + "/404.html"
beResp = await fetch(new Request(newPath, req), { backend: BACKENDS.GCS })
beResp = new Response(beResp.body, { status: 404, headers: beResp.headers })
}
}

Dabei senden wir aber nicht die beResp zurück, die wir direkt vom Origin-Server erhalten, da ihr Statuscode 200 (OK) lautet. Stattdessen erstellen wir eine neue „404“-Antwort unter Verwendung des Bodystreams aus der GCS-Antwort. Dieses Pattern wird in unserem Leitfaden zur Integration von Backends im Detail erläutert.

Wie gehabt führen wir zur Bereitstellung fastly compute publish aus. Unsere „Seite nicht gefunden“-Fehlermeldungen sehen jetzt schon viel besser aus:

Custom 404

Redirects

Natürlich gibt es auf developer.fastly.com auch eine Menge Umleitungen, die als nächstes von VCL migriert werden müssen. Wir verwenden bereits Edge Dictionaries, um sie zu speichern – eines für exakte Umleitungen und eines für Präfix-Umleitungen. Diese können beim Start des Request Handlers geladen werden:

async function handleRequest(event) {
const req = event.request
const reqUrl = new URL(req.url)
const reqPath = reqUrl.pathname
const dictExactRedirects = new Dictionary("exact_redirects")
const dictPrefixRedirects = new Dictionary("prefix_redirects")
​​ // Exact redirects
const normalizedReqPath = reqPath.replace(/\/$/, "")
const redirDest = dictExactRedirects.get(normalizedReqPath)
if (redirDest) {
return createRedirectResponse(redirDest)
}
// Prefix redirects
let redirSrc = String(normalizedReqPath)
while (redirSrc.includes("/")) {
const redirDest = dictPrefixRedirects.get(redirSrc)
if (redirDest) {
return createRedirectResponse(redirDest + reqPath.slice(redirSrc.length))
}
redirSrc = redirSrc.replace(/\/[^/]*$/, "")
}

Die exakten Umleitungen sind ziemlich einfach, aber für die Präfix-Umleitungen müssen wir eine Schleife durchlaufen: Wir entfernen ein URL-Segment nach dem anderen, bis der Pfad leer ist. Anschließend schlagen wir schrittweise kürzere Präfixe im Dictionary nach. Wenn wir einen finden, wird der nicht übereinstimmende Teil der Client-Anfrage an die Umleitung angehängt (eine Umleitung von /source => /destination leitet also eine Anfrage für /source/foo auf /destination/foo um).

Kanonisierung des Hostnamens

Irgendwann kam uns die Idee, dass developer.fastly.com auch als fastly.dev verfügbar sein sollte. Daher leitet unser VCL Service Nutzer, die fastly.dev aufrufen, zu developer.fastly.com um. Lassen Sie uns das als nächstes migrieren. Zuerst legen wir ganz oben in der Datei fest, was die CNAME-Domain für die Website ist:

const PRIMARY_DOMAIN = "developer.fastly.com"

Dann lesen wir im Request-Handler den Host-Header ab und leiten ihn bei Bedarf um. Beim Testen haben wir diesen auf etwas ähnliches wie random-funky-words.edgecompute.appgesetzt.Dies sollte oberhalb des Umleitungscodes stehen, gleich nach den Meldungen am Anfang des Request Handlers:

async function handleRequest(event) {
// ... existing code ... //
const hostHeader = req.headers.get("host")
// Canonicalize hostname
if (!req.headers.has("Fastly-FF") && hostHeader !== PRIMARY_DOMAIN) {
return createRedirectResponse("https://" + PRIMARY_DOMAIN + reqPath)
}

Ihr VCL Service führt möglicherweise auch eine TLS-Umleitung für unsichere HTTP-Anfragen durch. Compute kümmert sich darum automatisch und gibt Anfragen nicht mehr an Ihren Code weiter, bis eine sichere Verbindung besteht – das brauchen Sie also nicht zu migrieren.

Um diesen Code zu testen, muss dem Service eine zweite, Nicht-CNAME-Domain hinzugefügt werden. Wir machen das mit fastly domain create:

fastly domain create --name testing-fastly-devhub.global.ssl.fastly.net --version latest --autoclone

Dies ist ein wirklich guter Anwendungsfall für von Fastly zugewiesene Domains, sodass Sie sich (noch) nicht mit DNS herumschlagen müssen. Wenn wir nun im Browser zu testing-fastly-devhub.global.ssl.fastly.net gehen, leitet uns unser Service zu unserer primären Domain um.

Antwort-Header

Im nächsten Schritt haben wir uns den Änderungen zugewandt, die unser VCL Service an den Antworten vornimmt, die wir von GCS erhalten. Google fügt den Antworten eine Reihe von Headern hinzu, die wir dem Client nicht zeigen wollen, zum Beispiel x-goog-generation. Da das Backend in Zukunft noch mehr davon hinzufügen könnte, ist es sinnvoll, die Header anhand einer Allowlist zu filtern. Zunächst legen wir oben in der Datei fest, welche Antwort-Header zulässig sind:

const ALLOWED_RESP_HEADERS = [
"cache-control",
"content-encoding",
"content-type",
"date",
"etag",
"vary",
]

Dann können wir direkt nach dem Abruf im Backend einen Code einfügen, um die zurückerhaltenen Header zu filtern:

// Filter backend response to retain only allowed headers
beResp.headers.keys().forEach((k) => {
if (!ALLOWED_RESP_HEADERS.includes(k)) beResp.headers.delete(k)
})

Umgekehrt gibt es einige Header, die wir in den Client-Antworten haben wollen, zum Beispiel Content-Security-Policy. Diese müssen wir also hinzufügen. Viele davon müssen nur bei HTML-Antworten vorhanden sein:

if ((beResp.headers.get("content-type") || "").includes("text/html")) {
beResp.headers.set("Content-Security-Policy", "default-src 'self'; scrip...")
beResp.headers.set("X-XSS-Protection", "1")
beResp.headers.set("Referrer-Policy", "origin-when-cross-origin")
beResp.headers.append(
"Link",
"</fonts/CircularStd-Book.woff2>; rel=preload; as=font; crossorigin=anonymous," +
"</fonts/Lexia-Regular.woff2>; rel=preload; as=font; crossorigin=anonymous," +
"<https://www.google-analytics.com>; rel=preconnect"
)
beResp.headers.set("alt-svc", `h3-29=":443";ma=86400,h3-27=":443";ma=86400`)
beResp.headers.set("Strict-Transport-Security", "max-age=86400")
}

Das Hinzufügen und Entfernen von Headern ist sowohl bei VCL als auch bei Compute-Services ein sehr häufiger Anwendungsfall.

Weiterleitende Backends

Eine weitere Aufgabe unseres VCL Service ist es, manche Anfragen an andere Backends als GCS weiterzuleiten, damit wir Cloud-Funktionen oder andere Services von Drittanbietern aufrufen können. Wir verwenden zum Beispiel Swiftype für unsere „Developer Hub“ Suchmaschine. Damit die Such-API innerhalb der Domain developer.fastly.com verfügbar ist, können wir ein neues Backend hinzufügen und dann bestimmte Anfragepfade einrichten, um dieses Backend anzusprechen.

Zunächst verwenden wir die Fastly CLI, um das Backend hinzuzufügen:

fastly backend create --name swiftype --address search-api.swiftype.com --version active --autoclone

Dann fügen wir eine Konstante in unseren Quellcode ein, die es uns ermöglicht, auf dieses Backend zu verweisen:

const BACKENDS = {
GCS: "gcs",
SWIFTYPE: "swiftype",
}

Schließlich aktualisieren wir den Auswahlcode im Backend und fügen eine Routing-Logik hinzu, um gegebenenfalls den Swiftype auszuwählen:

let backendName
if (reqPath === "/api/internal/search") {
backendName = BACKENDS.SWIFTYPE
reqUrl.pathname = "/api/v1/public/engines/search.json"
} else {
backendName = BACKENDS.GCS
reqUrl.pathname = "/" + GCS_BUCKET_NAME + "/production" + reqUrl.pathname
// Fetch the index page if the request is for a directory
if (reqUrl.pathname.endsWith("/")) {
reqUrl.pathname = reqUrl.pathname + "index.html"
}
}

Sie können dies für alle Ihre Backends wiederholen. Für Sentry und Formkeep, verwenden wir beispielsweise dieselbe Methode.Mehr zu diesem Pattern in Kürze.

Bereitstellung unter Verwendung von GitHub

Unser VCL Service wurde nicht in der Versionskontrolle verwaltet; dies ist also eine gute Gelegenheit ist, das zu beheben. Die Compute Version wird zusammen mit der Hauptquelle des Developer Hubs gespeichert, sodass wir die Edge-Versionen mit den Backend-Updates koordinieren und sie abhängig voneinander machen können, damit wir keine Änderungen an der Edge-Logik ausliefern, wenn wir sie im Backend nicht bereitgestellt haben.

Wir haben unserem CI-Workflow zwei Aufgaben hinzugefügt. Eine, um die Edge-Anwendung für PRs zu erstellen:

build-fastly:
name: C@E build
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Fastly CLI
uses: fastly/compute-actions/setup@main
- name: Install edge code dependencies
run: cd edge && npm install
- name: Build Compute@Edge Package
uses: fastly/compute-actions/build@main
with:
project_directory: ./edge/

Und eine zweite, um die Edge-Anwendung bereitzustellen, sobald die GCS-Bereitstellung abgeschlossen ist:

update-edge-app:
name: Deploy C@E app
runs-on: ubuntu-latest
if: ${{ github.ref_name == 'production' }}
needs: deploy
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Install edge code dependencies
run: cd edge && npm install
- name: Deploy to Compute@Edge
uses: fastly/compute-actions@main
with:
project_directory: ./edge/
env:
FASTLY_API_TOKEN: ${{ secrets.FASTLY_API_TOKEN }}

Nächste Schritte

Nach Abschluss der Migration verfügen wir nun über dieselben Funktionen wie beim VCL Service. Da der Developer Hub jetzt aber von Compute gesteuert wird, können wir alle unsere Cloud-Funktionen direkt in unseren Edge-Compute-Code verschieben und damit auch schwierigere Aufgaben auf der Edge erledigen.

Die Migration von Services auf Compute könnte einfacher sein, als Sie denken. Wenn Sie Ihre eigene Migration planen, lesen Sie unseren Leitfaden für die Migration von VCL zu Compute, der viele der Muster abdeckt, die wir für den Developer Hub konvertieren mussten. In unserer Lösungsbibliothek finden Sie außerdem Demos, Beispielcode und Tutorials für eine breite Palette von Anwendungsfällen.

Sie arbeiten noch nicht mit Compute? Finden Sie heraus, wie unsere serverlose Edge-Compute-Plattform Ihnen helfen kann, schnellere, sicherere und leistungsfähigere Anwendungen auf der Edge zu entwickeln.

Andrew Betts
Principal Developer Advocate
Veröffentlicht am

Lesedauer: 6 Min.

Sie möchten sich mit einem Experten austauschen?
Sprechen Sie mit einem Experten
Diesen Beitrag teilen
Andrew Betts
Principal Developer Advocate

Andrew Betts ist Principal Developer Advocate bei Fastly und arbeitet mit Entwicklern auf der ganzen Welt zusammen, um das Web schneller, sicherer, zuverlässiger und einfacher zu machen. Er gründete ein Web-Beratungsunternehmen, das schließlich von der Financial Times übernommen wurde, leitete das Team, das die bahnbrechende HTML5-Web-Anwendung der FT entwickelte und gründete die Labs-Abteilung der FT. Außerdem ist Andrew ein gewähltes Mitglied der W3C Technical Architecture Group, einem Gremium aus neun Personen, das die Entwicklung des World Wide Web vorantreibt.

Sie möchten loslegen?

Setzen Sie sich mit uns in Verbindung oder erstellen Sie einen Account.