Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(router): aws lambda support #446

Merged
merged 54 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
33c7381
feat(router): AWS lambda support
JivusAyrus Jan 18, 2024
3895efa
Merge branch 'main' into dustin/eng-4657-router-on-aws-go-lambda
StarpTech Jan 19, 2024
7a37787
chore: improve
StarpTech Jan 20, 2024
c8cef16
chore: improve
StarpTech Jan 20, 2024
8d3eacd
chore: improve
StarpTech Jan 20, 2024
eacb90f
chore: improve
StarpTech Jan 20, 2024
3f5ed94
chore: improve
StarpTech Jan 20, 2024
67adad1
chore: improve
StarpTech Jan 20, 2024
141f15f
chore: improve
StarpTech Jan 20, 2024
bc5017f
chore: improve
StarpTech Jan 20, 2024
94f67c5
chore: improve
StarpTech Jan 20, 2024
e7bb5e5
chore: improve
StarpTech Jan 20, 2024
a508cf0
chore: improve
StarpTech Jan 20, 2024
7301b68
chore: improve
StarpTech Jan 20, 2024
58c6674
chore: improve
StarpTech Jan 20, 2024
3509833
chore: improve
StarpTech Jan 20, 2024
0722aba
chore: improve
StarpTech Jan 20, 2024
5b4dec7
chore: improve
StarpTech Jan 20, 2024
420eb6d
chore: improve
StarpTech Jan 20, 2024
3e1af44
chore: improve
StarpTech Jan 20, 2024
10ef1e8
chore: improve
StarpTech Jan 20, 2024
85e4b62
chore: improve
StarpTech Jan 20, 2024
dd3a9c9
chore: improve
StarpTech Jan 20, 2024
20569df
chore: improve
StarpTech Jan 20, 2024
c7a1cc0
chore: improve
StarpTech Jan 20, 2024
20d223e
chore: improve
StarpTech Jan 20, 2024
01b1721
chore: improve
StarpTech Jan 20, 2024
efd61ff
chore: improve
StarpTech Jan 20, 2024
4940bf8
chore: improve
StarpTech Jan 20, 2024
ef2fdc3
chore: improve
StarpTech Jan 20, 2024
9da7e48
chore: improve
StarpTech Jan 20, 2024
6da3a9a
chore: improve
StarpTech Jan 20, 2024
b250a4e
chore: improve
StarpTech Jan 20, 2024
4dbb604
chore: improve
StarpTech Jan 20, 2024
e318583
chore: improve
StarpTech Jan 20, 2024
2b9e98f
chore: improve
StarpTech Jan 20, 2024
f956ab3
chore: improve
StarpTech Jan 20, 2024
1133453
chore: improve
StarpTech Jan 20, 2024
e51618e
chore: improve
StarpTech Jan 20, 2024
04ef435
chore: improve
StarpTech Jan 20, 2024
a31e215
chore: improve
StarpTech Jan 20, 2024
b0f67d5
chore: improve
StarpTech Jan 21, 2024
5c48f5b
chore: improve
StarpTech Jan 21, 2024
ef70b71
chore: improve
StarpTech Jan 21, 2024
5d4a73c
chore: improve
StarpTech Jan 21, 2024
e28b0ec
Merge branch 'main' into dustin/eng-4657-router-on-aws-go-lambda
StarpTech Jan 22, 2024
fac988a
chore: improve
StarpTech Jan 22, 2024
35a1f0e
chore: improve
StarpTech Jan 22, 2024
982a10b
chore: improve
StarpTech Jan 22, 2024
46f58f2
chore: revert
StarpTech Jan 22, 2024
45b289b
chore: revert
StarpTech Jan 22, 2024
771c91c
chore: revert
StarpTech Jan 22, 2024
b41e021
chore: revert
StarpTech Jan 22, 2024
97126b1
fix: race when swapping server
StarpTech Jan 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ controlplane:
- "controlplane/**/*"
graphqlmetrics:
- "graphqlmetrics/**/*"
aws-lambda-router:
- "aws-lambda-router/**/*"
63 changes: 63 additions & 0 deletions .github/workflows/aws-lambda-router-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: AWS Lambda Router CI
on:
pull_request:
paths:
- "aws-lambda-router/**/*"
- "router-tests/**/*"
- ".github/workflows/aws-lambda-router-ci.yaml"

concurrency:
group: ${{github.workflow}}-${{github.head_ref}}
cancel-in-progress: true

env:
CI: true

jobs:
build_test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
# The go install / version instructions are inside the Makefile, so we need to cache the Makefile.
key: ${{ runner.os }}-go-${{ hashFiles('aws-lambda-router/go.sum') }}-makefile-${{ hashFiles('Makefile') }}
restore-keys: |
${{ runner.os }}-go-

- uses: ./.github/actions/go
with:
cache-dependency-path: router/go.sum

- name: Install tools
run: make setup-build-tools

- name: Generate code
run: make generate-go

- name: Check if git is not dirty after generating files
run: git diff --no-ext-diff --exit-code

- name: Install dependencies
working-directory: ./aws-lambda-router
run: go mod download

- name: Run linters on router
uses: ./.github/actions/go-linter
with:
working-directory: ./aws-lambda-router

- name: Test
working-directory: ./aws-lambda-router
run: make test

- name: Build
working-directory: ./aws-lambda-router
run: make build
57 changes: 57 additions & 0 deletions .github/workflows/aws-router-binary-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Build and Release AWS Router Binaries
on:
release:
types: [published]
#workflow_dispatch:

permissions:
contents: write
packages: write

jobs:
releases-matrix:
if: startsWith(github.event.release.tag_name, 'aws-lambda-router@')
name: Build and Release AWS Router Binaries
runs-on: ubuntu-latest
timeout-minutes: 30

strategy:
matrix:
# build and publish in parallel: linux/386, linux/amd64, linux/arm64, darwin/amd64, darwin/arm64
goos: [linux, darwin]
goarch: ["386", amd64, arm64]
exclude:
- goarch: "386"
goos: darwin

steps:
- name: Checkout repository
uses: actions/checkout@v3

- uses: ./.github/actions/go
with:
cache-dependency-path: router/go.sum

- uses: winterjung/split@v2
id: split
with:
separator: "@"
msg: "${{ github.event.release.tag_name }}"

- uses: wangyoucao577/go-release-action@v1
name: Build and attach binaries to GitHub Release
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
# Where to run `go build .`
project_path: "aws-lambda-router/cmd"
# Convention from AWS Lambda
binary_name: "bootstrap"
pre_command: export CGO_ENABLED=0
build_flags: -trimpath
# -w = omits the DWARF symbol table, effectively removing debugging information. Reduces binary size by ~30%.
ldflags: -w -extldflags -static -X module github.com/wundergraph/cosmo/aws-lambda-router/internal.Version=${{ steps.split.outputs._1 }}
overwrite: true
extra_files: LICENSE
#release_tag: router@0.14.0
3 changes: 3 additions & 0 deletions aws-lambda-router/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.aws-sam
bootstrap
lambda.zip
30 changes: 30 additions & 0 deletions aws-lambda-router/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.PHONY: build

VERSION?=dev
build:
CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/aws-lambda-router/internal.Version=$(VERSION)" -a -o bootstrap cmd/main.go

build-sam:
rm -rf .aws-sam && sam build --parallel && cp router.json .aws-sam/build/Api/router.json

dev: build-sam
sam local start-api -p 3003 --shutdown

deploy: build-sam
sam deploy

lint:
cd adapter && go vet ./...
cd adapter && staticcheck ./...

test:
go test -v ./...

fetch-router-config:
wgc router fetch production -o router.json

sync:
sam sync --watch

create-lambda-zip: build fetch-router-config
zip -r lambda.zip bootstrap router.json
86 changes: 86 additions & 0 deletions aws-lambda-router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# aws-lambda-router

<p align="center">
<img width="550" src="cover.png"/>
</p>

This is the [AWS Lambda](https://aws.amazon.com/lambda/) version of the WunderGraph Cosmo [Router](https://wundergraph.com/cosmo/features/router). Please [contact](https://wundergraph.com/contact/sales) us if you have any questions or production use case.
Why AWS lambda? Because it's cheap and scales automatically. You only pay for what you use. No need to manage servers or containers. It also integrates well with the rest of the AWS ecosystem.

Status: **Beta**

Demo: [https://zqadzbqwsi.execute-api.us-west-1.amazonaws.com/Prod/](https://zqadzbqwsi.execute-api.us-west-1.amazonaws.com/Prod/) (Playground)

## Features

- [X] GraphQL Queries
- [X] GraphQL Mutations
- [X] Telemetry Flushing after each request
- [X] Schema Usage Tracking after each request
- [ ] Subscription: Not implemented. Please [talk to us](https://wundergraph.com/contact/sales) if you need this.

## Requirements

* AWS CLI already configured with Administrator permission
* [Docker installed](https://www.docker.com/community-edition)
* [Golang](https://golang.org)
* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)

# Setup process

## Cosmo Cloud

First signup For Cosmo Cloud and follow the [onboarding](https://cosmo-docs.wundergraph.com/tutorial/cosmo-cloud-onboarding) process.

Run `make fetch-router-config` to fetch the latest router configuration from Cosmo Cloud. We assume that you have named your graph `production`.
The file is stored in `router.json` and copied to the Lambda build directory on each build.

## Local development

Build the router and start the API Gateway locally:

```bash
make dev
```

Open [http://127.0.0.1:3003/](http://127.0.0.1:3003/) in your browser and you should see the GraphQL Playground.

### Deploy on code change

This will upload the code to AWS without performing a CloudFormation deployment. This is useful for development.

```bash
make sync
```

## Deploying application

Ensure that the following environment variables are set in [template.yaml](template.yaml):

- `STAGE` - The name of the stage, which API Gateway uses as the first path segment in the invoke Uniform Resource Identifier (URI) e.g. `Prod` for `/Prod`.
- `GRAPH_API_TOKEN` - The API token for your graph. You can find this in the Cosmo Cloud dashboard.

*For production use cases, we recommend to use [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) to store the `GRAPH_API_TOKEN`.*

```bash
make deploy
```

The command will package and deploy your application with the SAM CLI to AWS.
You can find your API Gateway Endpoint URL in the output values displayed after deployment.

# User Guide

You don't have to build the router yourself. You can download the latest release and follow the instructions below.

1. Download the Lambda Router binary from the official [Router Releases](https://github.com/wundergraph/cosmo/releases?q=aws-lambda-router&expanded=true) page.
2. Create a .zip archive with the binary and the `router.json` file. You can download the latest `router.json` with [`wgc federated-graph fetch`](https://cosmo-docs.wundergraph.com/cli/federated-graph/fetch).

The .zip archive should look like this:
```bash
.
└── myFunction.zip/
├── bootstrap # Extracted from the Router release archive
└── router.json # Downloaded with `wgc federated-graph fetch`
```
3. Deploy the .zip archive to AWS Lambda. You can use SAM CLI or the AWS console. Alternatively, you can use your IaC tool of choice.
99 changes: 99 additions & 0 deletions aws-lambda-router/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
"context"
"errors"
"fmt"
"github.com/akrylysov/algnhsa"
"github.com/aws/aws-lambda-go/lambda"
"github.com/wundergraph/cosmo/aws-lambda-router/internal"
"github.com/wundergraph/cosmo/router/core"
"github.com/wundergraph/cosmo/router/pkg/config"
"github.com/wundergraph/cosmo/router/pkg/logging"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net/http"
"os"
"time"
)

const (
telemetryServiceName = "aws-lambda-router"
routerConfigPath = "router.json"
)

var (
defaultSampleRate = 0.2 // 20% of requests will be sampled
enableTelemetry = os.Getenv("DISABLE_TELEMETRY") != "true"
devMode = os.Getenv("DEV_MODE") == "true"
stage = os.Getenv("STAGE")
graphApiToken = os.Getenv("GRAPH_API_TOKEN")
httpPort = os.Getenv("HTTP_PORT")
)

func main() {
ctx := context.Background()

logger := logging.New(false, false, zapcore.InfoLevel)
logger = logger.With(
zap.String("service_version", internal.Version),
)
defer func() {
if err := logger.Sync(); err != nil {
fmt.Println("Could not sync logger", err)
}
}()

r := internal.NewRouter(
internal.WithGraphApiToken(graphApiToken),
internal.WithLogger(logger),
internal.WithRouterConfigPath(routerConfigPath),
internal.WithTelemetryServiceName(telemetryServiceName),
internal.WithStage(stage),
internal.WithTraceSampleRate(defaultSampleRate),
internal.WithEnableTelemetry(enableTelemetry),
internal.WithHttpPort(httpPort),
internal.WithRouterOpts(core.WithDevelopmentMode(devMode)),
internal.WithRouterOpts(
core.WithEngineExecutionConfig(config.EngineExecutionConfiguration{
EnableSingleFlight: true,
EnableRequestTracing: devMode,
EnableExecutionPlanCacheResponseHeader: devMode,
MaxConcurrentResolvers: 1024,
}),
),
)

svr, err := r.NewServer(ctx)
if err != nil {
logger.Fatal("Could not create server", zap.Error(err))
}

// Set the server to ready
svr.HealthChecks().SetReady(true)

// If HTTP_PORT is set, we assume we are running the router without lambda
if httpPort != "" {
if err := svr.HttpServer().ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Fatal("Could not start server", zap.Error(err))
}
return
}

lambdaHandler := algnhsa.New(svr.HttpServer().Handler, nil)
lambda.StartWithOptions(lambdaHandler,
lambda.WithContext(ctx),
// Registered an internal extensions which gives us 500ms to shutdown
// This mechanism does not replace telemetry flushing after a request
// https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html#runtimes-lifecycle-extensions-shutdown
lambda.WithEnableSIGTERM(func() {
logger.Debug("Server shutting down")
sCtx, cancel := context.WithTimeout(context.Background(), 400*time.Millisecond)
defer cancel()
if err := r.Shutdown(sCtx); err != nil {
panic(err)
}
logger.Debug("Server shutdown")
}),
)
}
Binary file added aws-lambda-router/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading