Learn about Google's new service launched recently – Google Cloud Run – and how it allows you to run server-side swift server-less 😱. In part 1 we will cover the basics and deploy a Vapor application in less than 10 minutes.
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).
- Go to https://console.cloud.google.com/projectcreate
- Enter a name for your project, create the project
- 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.
-
Install the Google Cloud SDK command line interface:
$ brew install homebrew/cask/google-cloud-sdk
-
Initialize
gcloud
, providing access to your Google Cloud account and select your project:$ gcloud init
-
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
-
Create a new Vapor project (using the default api template):
$ vapor new gcr-example-vapor
-
Generate the Xcode project and open it:
$ vapor xcode -y
-
Let's collect some information about the container we're currently on. Change your
boot.swift
to:import Vapor internal 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() }
-
In the bottom of
routes.swift
add this route:router.get("gcr-example") { req in return ContainerInfo() }
-
Run the project locally (CMD+R) — make sure to select the
Run
target. -
Try our new endpoint! In terminal enter:
$ curl localhost:8080/gcr-example
-
Result:
{ "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:
-
Create a copy of the Dockerfile shipped with the example:
$ cp web.Dockerfile Dockerfile
-
Change the
ENV
in line 30 todevelopment
-
Google Cloud Run expects containers to listen on port
8080
, therefore change the the port from80
to8080
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
-
(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.
-
Submit the build:
$ gcloud builds submit --tag gcr.io/gcr-blog-example/gcr-example-vapor
-
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 andgcr-example-vapor
with a container name of your choice) -
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
- Github repository with all sources: https://github.com/cweinberger/gcr-example-vapor
- Nodes Engineering Blog: https://engineering.nodesagency.com
- Vapor: https://vapor.codes
- Google Cloud Platform: https://cloud.google.com/
- Google Cloud Run: https://cloud.google.com/run/
Author
Christian Weinberger
Technical Director