My personal webspace

A webspace for innovation, free thinking, and procrastination

Google App Engine made easy

Turns out, GAE can run a Docker container, which makes life super easy. Some basic rules:

So its very limited in what it can do, but basically with the structure

.
├── app.yaml
└── Dockerfile

You can easily make an app. You might need to add persistence (i.e., databases) through their service, but you could at least bring small apps to play with for very low budgets ($4/month, for the smallest contained).

An example; minimal Rust webserver

Disclaimer; I like it simple, so we’re not going to integrate the Docker packaging with the binary creation. Just play along.

First, make our Rust app:

cargo new rust-gae

cd rust-gae

Now, quickly setup a basic web server; we’ll use Hyper and tokio.

cat <<EOF > src/main.rs
#![deny(warnings)]

use std::convert::Infallible;

use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};

async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from("Hello World!")))
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    pretty_env_logger::init();

    // For every connection, we must make a `Service` to handle all
    // incoming HTTP requests on said connection.
    let make_svc = make_service_fn(|_conn| {
        // This is the `Service` that will handle the connection.
        // `service_fn` is a helper to convert a function that
        // returns a Response into a `Service`.
        async { Ok::<_, Infallible>(service_fn(hello)) }
    });

    let addr = ([0, 0, 0, 0], 8080).into();

    let server = Server::bind(&addr).serve(make_svc);

    println!("Listening on http://{}", addr);

    server.await?;

    Ok(())
}
EOF

Make sure to setup the Cargo.toml file! Default for everything, but add 3 libraries.

# This attempts to append the depends to the bottom of the Cargo.toml; if the base
# template changes, you may have to do this manually.
cat <<EOF >> Cargo.toml
hyper = "0.13"
tokio = { version = "0.2", features = ["full"] }
pretty_env_logger = "0.4.0"
EOF

If you need to, add the muslc component. This lets us make the Docker file really small, and really simple.

rustup target add x86_64-unknown-linux-musl

Then, build our application!

cargo build --target x86_64-unknown-linux-musl --release

Finally, let’s make a Docker image. First, make a directory and copy the binary we just build in.

mkdir dist
cd dist
cp ../target/x86_64-unknown-linux-musl/release/rust-gae .

Then make our Dockerfile

cat <<EOF > Dockerfile
FROM scratch
COPY ./rust-gae .

EXPOSE 8080
ENTRYPOINT ["./rust-gae"]
EOF

Verify that it builds with docker build ., and we’re in business!.

Last thing to do is setup app.yaml, and do the work of setting up a GCP project. Basically, you want to be at a point where the command gcloud app deploy works. Follow the Google documentation for that bit, since it’s not my specialty.

Setup the app.yaml file

cat <<EOF > app.yaml
env: flex

# This bit here is optional, but makes it so only a single instance is brought up.
# This keeps cost down, hence when it's here. Remove it if you have a real need :).
instance_class: B1
manual_scaling:
  instances: 1
EOF

And finally, use gcloud app deploy to deploy it. You should be provided with a URL, and see magic when you go to it.

Good luck out there!

Still here?

Try to bring down that app. I started polling at 120 requests per second, at which point I think the limiting factor was actually my machine/internet. A more complex application might be a bit slower, but still, for a dirt cheap machine 120 RPS isn’t bad. That equates to roughly 10_368_000 a day. Need more? Scale up.


Content © 2022 Charles Hathaway