Serverless Server-side Swift using Google Cloud Run

Christian Weinberger
5 min readMay 27, 2019

--

Part 1: Deploying a Server-side Swift application

Serverless

Serverless is there to make our developer’s life easier and costs more manageable. By offering us infrastructure and platforms as a service on a per-use basis we don’t have to deal with configuring or monitoring servers as well as thinking too much about scaling. Often these services are offered by cloud providers as FaaS (Function As A Service), e.g. AWS Lambda, Azure Cloud Functions etc.

Apart from the usual drawbacks when going for a serverless architecture (see more below) you are often limited to a small amount of supported programming languages such as python, ruby, nodejs and go (IBM supports serverside swift functions).

Google Cloud Run

Well, Google introduced a new solution Google Cloud Run that provides you with a fully managed serverless execution environment for containerized apps. This means you can run any container — with the language of your choice — on their platform.

Google Cloud Run Overview

As of writing this you can try out Google Cloud Platform for free, getting $300 free credit to spend over 12 months. https://cloud.google.com/free/

Running Server-side Swift on Google Cloud Run

I’m choosing Vapor as my weapon of choice when it comes to server-side Swift, but this should also work with alternative frameworks such as Kitura or Perfect.

The following steps are required:

  • Setup an account on Google Cloud platform (not covered in this tutorial)
  • Create a project on Google Cloud platform
  • Install Google Cloud toolbox (CLI)
  • Setup your Vapor project
  • Create and configure the Dockerfile
  • Build your project and push the image to Google Cloud Container Registry
  • Deploy the image to Google Cloud Run

Okay, let’s get our hands dirty! 🙌

Create project on GCR (or use an existing one)

For testing out things it is always nice to create a new project that you can easily delete later (and with it, all its resources).

  1. Go to https://console.cloud.google.com/projectcreate
  2. Enter a name for your project, create the project
  3. Remember the project id, e.g. gcr-blog-example

Install Google Cloud toolbox

I’m assuming you are on a Mac and have homebrew installed.

  1. Install the Google Cloud SDK command line interface:
    $ brew install homebrew/cask/google-cloud-sdk
  2. Initialize gcloud, providing access to your Google Cloud account and select your project:
    $ gcloud init
  3. Install the beta components needed for Google Cloud Run:
    $ gcloud components install beta
    $ gcloud components update

Setup your Vapor project

I’m assuming you are already having the Vapor toolbox installed. If not, please follow the instructions here (macOS).

Note: you can get the source code on Github: https://github.com/cweinberger/gcr-example-vapor

  1. Create a new Vapor project (using the default api template):
    $ vapor new gcr-example-vapor
  2. Change directory $ cd gcr-example-vapor and run $ vapor xcode -y to generate the Xcode project and open it
  3. Let’s collect some information about the container we’re currently on. Change your boot.swift to:
import Vaporinternal struct ContainerInfo: Content {
static let date: Date = Date()
static let containerID: Int = Int.random(in: 0...Int.max)
let creationDate = ContainerInfo.date
let currentDate = Date()
let uuid = ContainerInfo.containerID
}
/// Called after your application has initialized.
public func boot(_ app: Application) throws {
// Your code here
_ = ContainerInfo()
}

4. In the bottom ofroutes.swift add this route:

router.get("gcr-example") { req in
return ContainerInfo()
}

5. Run the project locally (CMD+R) — make sure to select the Run target.

6. Try our new endpoint! In terminal enter $ curl localhost:8080/gcr-example:

{"currentDate":"2019-05-22T04:26:49Z","uuid":"1939D9E0-A67A-412D-9C4F-55722EFA1751","creationDate":"2019-05-22T04:17:18Z"}

Note that the default template from Vapor uses SQLite as Fluent driver which doesn’t make a lot of sense when working with horizontally scaled systems. If you need to persist your data (across nodes) you should use a different solution, e.g. Google Cloud SQL, or any separate database server.

Create and configure the Dockerfile

The default Vapor template already includes a Dockerfile 🎉. Let’s take this one as a basis and adjust to fit our (Google’s) needs:

  1. Create a copy of the Dockerfile shipped with the example: $ cp web.Dockerfile Dockerfile
  2. Change the ENV in line 30 to development
  3. Google Cloud Run expects containers to listen on port 8080, therefore change the the port from 80 to 8080 in line 32
# You can set the Swift version to what you need for your app. Versions can be found here: https://hub.docker.com/_/swift
FROM swift:5.0 as builder
# For local build, add `--build-arg env=docker`
# In your application, you can use `Environment.custom(name: "docker")` to check if you're in this env
ARG env
RUN apt-get -qq update && apt-get install -y \
libssl-dev zlib1g-dev \
&& rm -r /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN mkdir -p /build/lib && cp -R /usr/lib/swift/linux/*.so* /build/lib
RUN swift build -c release && mv `swift build -c release --show-bin-path` /build/bin
# Production image
FROM ubuntu:18.04
ARG env
# DEBIAN_FRONTEND=noninteractive for automatic UTC configuration in tzdata
RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
libatomic1 libicu60 libxml2 libcurl4 libz-dev libbsd0 tzdata \
&& rm -r /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /build/bin/Run .
COPY --from=builder /build/lib/* /usr/lib/
# Uncomment the next line if you need to load resources from the `Public` directory
#COPY --from=builder /app/Public ./Public
# Uncomment the next line if you are using Leaf
#COPY --from=builder /app/Resources ./Resources
ENV ENVIRONMENT=development
ENTRYPOINT ./Run serve --env $ENVIRONMENT --hostname 0.0.0.0 --port 8080

4. (Optional) If you have Docker installed, you can test locally now by building and running it:
$ docker build -t gcr-example-vapor .
$ docker run -it -p 8080:8080 gcr-example-vapor

This will make our endpoint available at localhost:8080/gcr-example. You can skip this step if you don’t have Docker.

Build your image & deploy to Google Cloud Run

Now we are almost done! We are using the CLI tool gcloud that we installed earlier to build and upload our image to Google Cloud Registry. Then we will deploy it using Google Cloud Run.

  1. Submit the build:
    $ gcloud builds submit --tag gcr.io/gcr-blog-example/gcr-example-vapor
  2. Deploy and run it on Google Cloud Run:
    $ gcloud beta run deploy --image gcr.io/gcr-blog-example/gcr-example-vapor
    (replace gcr-blog-example with your project id and gcr-example-vapor with a container name of your choice)
  3. In the output you will see the URL, e.g. https://gcr-example-vapor-{some-id}-uc.a.run.app.

We are done now, you can try it out with curl or in the browser of your choice:

Interesting: creationDate and uuid never change 🤔. That’s something for the next blog posts 🕵️‍♂️.

Up next

This was as easy as it should be when dealing with serverless applications. In less than 10 minutes we were able to build & configure a project and deploy it into the wild using Google Cloud Run.

In the next articles we will be focussing on advantages and disadvantages of this architecture, have a deep look into performance and create more elaborate examples.

Thanks for reading!
– Christian

Links

--

--