Deploying a Simple Crud on Rust

As promised, here is the web app after my brief rust intro. These making tags will serve as a reminder to create without perfection in mind: understand/make first, chisel later. This making tags also isn't meant to use as tutorials; it's more like here are some trials and tribulations, and some informal documentation meant for myself.

Creating a Web App

Here are some of the tooling/stack I went with. I started with the basic diesel example in actix/examples/diesel to bring up something with basic get/post features.

  • Actix

    • Here's a pretty comprehensive pros and cons of a couple of the biggest rust web frameworks. Actix is a popular/ergonomic async rust framework.
  • Sqlite

    • Bringing up anything else would incur more costs, and it's to set up and swap later
  • Diesel

    • (https://diesel.rs/guides/getting-started/) handles pretty much all the APIs that deals with a db including migration, ORM, etc. Some details on migration:
      • For now I am just writing migration by hand + diesel cli for migration runs.
  • Logging, linting, etc

    • A pretty good up-to-date list of rust tooling here (linting, benchmarking, logging, comments on antipatterns, etc)
  • Dev Tooling

    • Use systemfd cargo-watch to automatically rebuild your code and watch for change. systemfd works by creating a parllel process, and then works with conjunction with cargo watch to reload your app whenever you save. Sometimes the reload doesn't work as intended; I had a weird bug where I had to restart whenever I added a new endpoint.
    > cargo install systemfd cargo-watch
    > systemfd --no-pid -s http::5000 -- cargo watch -x run
    

And alas, our API works! I edited it to have posts and also support list and delete functions. I also created a tiny frontend to support the functionality. I also added an auth so that people can't just change my backend data willy nilly. Ask me for the auth token if you'd like to play with it.

  • Create:

    > curl -S -X POST --header "Content-Type: application/json" --data '{"text":"Hello World!"}' http://localhost:8080/post --header 'Authorization: Bearer ######'
    
    {"id":"3afdebd0-673f-4a93-96f0-69e2ab99c756","text":"Hello World!"}
    
  • Get:

    > curl -X GET http://localhost:8080/post/3afdebd0-673f-4a93-96f0-69e2ab99c756 --header 'Authorization: Bearer ######'
    
    {"id":"3afdebd0-673f-4a93-96f0-69e2ab99c756","text":"Hello World!"}
    
  • List:

    > curl -X GET http://localhost:8080/post/list --header 'Authorization: Bearer ######'
    
    [{"id": "3afdebd0-673f-4a93-96f0-69e2ab99c756", "text": "Hello World!"}]
    
  • Delete

    > curl -X DELETE http://localhost:8080/post/3afdebd0-673f-4a93-96f0-69e2ab99c756 --header 'Authorization: Bearer ######'
    
    {"id":"3afdebd0-673f-4a93-96f0-69e2ab99c756","text":"Hello World!"}
    

I'm not too familiar with common rust patterns yet but doing get/post/etc by resource is definitely a better pattern. Refer to the end of this post for other things I can do. I might write about it in another post, but that's more chiseling.

Deploying onto GKE

Ok, now let's deploy our service. A secondary motivation for this project is to try out GKE. I'm looking at two GCP products:

  1. App Engine (PaaS) uses a config file to do a single command deployment with gcloud app deploy. It abstracts out infra (gives you db, scalability, performance etc) at the expense of cost and some control.
  2. GKE (IaaS) works with docker images and is nicer for ops, and google has some nice kubernetes support (monitoring, UI, etc).

Looking at google trends, there is a clear winner. App engine seems really fast to set up, targeted at startups, but it just seems the paradigm of deployment with k8s/docker is just way more pervasive. Just going to take the road more travelled and deploy onto GKE. (though eks > gke)

While it was taking the entirity of my machine's resources to do a docker build ready for release ready image, I created a GCP account with $300 in free credits. Here are some nice benefits of google cloud:

  • Good documentation. (though most other documentation aside from the one provided by google is obsolete; gcp changes so quickly); I'm currently following this one.
  • Each project in gcp comes with its own container registry, docker push gcr.io/rust-post/rust-post-crud:v1 to load, and also easy API to fire.
  • Their installation management system is also quite nice, also their user interface is definitely more intuitive to use than AWS. Everything in the GKE view is really relevant, includes monitoring, and registry deployments, etc. Comes with VScode in the cloud.
  • Billing is really clear. Projects are isolated, and it's easy to stop billing on a per project basis. AWS billing is terrible. Bonus: no auto-pay at the end of free credits.
  • Note: the cheap tier for k8s my-first-cluster-1 is some shit.

We dockerize our application and load it into gcp's container registry. (not a small feat btw, this shit is gargantuan if not cached) -- beautiful!

> docker run gcr.io/rust-post/rust-post-crud:v1
Starting server at: 127.0.0.1:8080

The last step was kind of magical to me; I clicked some buttons in the gcp UI, mapped the LoadBalancerIP to A record of my domain, and it just worked. Hopefully my free resources on gke doesn't run out too quickly.

Concluding Thoughts

Here's a head nod to the things we can do to improve:

  • Using an actual persistent database and better generated migrations with schema builder in code
    • Use rust libraries like refinery and barrel with something like postgres
  • Fancier API to support more things, better structures
    • Option types and Result enums make return types pretty confusing
    • Better pattern from going from the business logic layer to the server response layer
    • Refactoring to have a lib.rs and a suite of tests against this business logic directory, something like ::posts::create, separate requests by resource, etc.
  • Docker optimizations
    • I'm using a somewhat naive way to send stuff into the docker context ... I was sending 2 gigabytes and everything was just so massive. Here's someone smarter's implementation that I'm not using.
  • A nice frontend to take advantage of all of this
    • The bearer token is something of an abomination, change that
    • The motivation is to use vue and materialize, but maybe that's for next time when I have more of a project direction.
  • Developer experience