Use Google Cloud Run with FastAPI

For a webservice project idea that only should serve an atom feed I tried to find an alternative to use a virtual server. Google Cloud Run seems to be a good alternative. So I clicked a free account and tried it.

The easiest way to use the Google Cloud is by using their sdk. On ArchLinux install the community package. The sdk documentation shows how to do this for other Linux distributions and other operating systems.

First step is to login:

gcloud init

And to configure a bit:

gcloud config set run/platform managed
gcloud config set run/region europe-west4

For cloud run we need a Dockerfile to run the code:

FROM tiangolo/uvicorn-gunicorn:python3.8-slim
RUN pip install --no-cache-dir fastapi
COPY ./app /app

The base Docker container used is https://hub.docker.com/r/tiangolo/uvicorn-gunicorn/. And of course a bit of Python code to with the api. The framework used is FastAPI which similar to Flask, but build on Python 3 concepts. This is placed in an app directory and named main.py:

from fastapi import FastAPI
app = FastAPI()

@app.get("/")
async def hello():
    return {"message": "Hello World"}

Now build the container on the Google Cloud. (Replace <PROJECT_ID> with your Google Cloud project ID)

gcloud builds submit --tag eu.gcr.io/<PROJECT_ID>/helloworld

And run the build artifact on Google Cloud Run.

gcloud run deploy hello1 --image eu.gcr.io/<PROJECT_ID>/helloworld --allow-unauthenticated

The last command returns the URL were the code is deployed with a *.run.app domain. Of course we want to run the code with our own domain. The Google Cloud can handle this even with automatic SSL certificates.

First verify your domain:

gcloud domains verify <DOMAIN>

Then add the TXT entry shown to your DNS configuration. Additionally a CNAME for the domain to use, for example:

CNAME hello.madflex.de.  ghs.googlehosted.com.

DNS may take a few minutes for the verification and CNAME registration to propagate. When this has happenend register the domain mapping:

gcloud beta run domain-mappings create --service hello1 --domain hello.madflex.de

Now the FastAPI minimal api is running at https://hello.madflex.de until I delete the Google Cloud Run service for it. 🙂

Install ArchLinux on Hetzner Cloud

This is my protocol of an Archlinux installation on a CX11 in the Hetzner Cloud.

First Step: Create a new Server

  • create with any OS.

  • activate rescue mode (linux64)

Second Step: Install Archlinux

  • ssh as root to server (now in rescue mode)

  • run installimage and choose ArchLinux

  • I only changed the HOSTNAME in the install.conf

  • reboot

Third Step: post-install

  • ssh as root to server (now with ArchLinux)

  • add more locales in /etc/locale.gen and run locale-gen

  • set root password with passwd

  • install some packages (i.e. cronie sudo tinc git …) using pacman -S

  • enable cronie: systemctl enable cronie

  • visudo and enable wheel group

  • add user: useradd -m -G wheel -s /bin/bash myusername

  • set password for user: passwd myusername

  • setup tinc (sth for another blogpost)

Use docker ARGs for conditionals on build

Problem

Run some script on docker build but only on production and not on development.

Solution

Using ARG in Dockerfile and docker-compose.yml. The "main" is an example for production and "main_dev" for development.

Dockerfile:

FROM python:3

ARG DEV
RUN if [ ! "$DEV" = "yes" ]; then echo "DEV is NOT set"; else echo "DEV is set"; fi

CMD [ "echo", "OK" ]

docker-compose.yml:

version: "3.8"
services:
  main:
    build:
      context: .
  main_dev:
    build:
      context: .
      args:
         DEV: "yes"

Now lets run the examples:

docker-compose build --no-cache main results in

Building main
Step 1/4 : FROM python:3
 ---> 1f88553e8143
Step 2/4 : ARG DEV
 ---> Running in a369f15bdbba
Removing intermediate container a369f15bdbba
 ---> d33834a028c4
Step 3/4 : RUN if [ ! "$DEV" = "yes" ]; then echo "DEV is NOT set"; else echo "DEV is set"; fi
 ---> Running in c47c9d3ae979
DEV is NOT set
Removing intermediate container c47c9d3ae979
 ---> d93839918c95
Step 4/4 : CMD [ "echo", "OK" ]
 ---> Running in 1280176dc942
Removing intermediate container 1280176dc942
 ---> ac023ae1b6fa

Successfully built ac023ae1b6fa
Successfully tagged dockerarg_main:latest

and

docker-compose build --no-cache main_dev results in

Building main_dev
Step 1/4 : FROM python:3
 ---> 1f88553e8143
Step 2/4 : ARG DEV
 ---> Running in a9bbe1cbf494
Removing intermediate container a9bbe1cbf494
 ---> f28af462f46a
Step 3/4 : RUN if [ ! "$DEV" = "yes" ]; then echo "DEV is NOT set"; else echo "DEV is set"; fi
 ---> Running in 908227c7de9c
DEV is set
Removing intermediate container 908227c7de9c
 ---> 4f4caad6140a
Step 4/4 : CMD [ "echo", "OK" ]
 ---> Running in 0f83a6b15abf
Removing intermediate container 0f83a6b15abf
 ---> 19bf8758a2ae

Successfully built 19bf8758a2ae
Successfully tagged dockerarg_main_dev:latest

This seems to work as seen with DEV is NOT set vs DEV is set.