If you haven’t ever used Resizin, it’s a small, very nifty tool to manipulate your pictures in the cloud. Throw a picture at it, give it a few variables in a query string and voila, you have your picture back with different size, width and gravity, … whatever you would like. It was entirely brewed in Ackee and we are using it in all the major projects we worked on, e.g.: FlashNews or FlashSport.

examples of resizing photos from Resizin

Migration design

Resizin is not the youngest project. The first version was running on GKE before the control plane got billed. There was no need to ask questions about billing efficiency because Cloud Run was not yet in the cloud and App Engine was more expensive. We also encountered some issues with horizontal pod scaling because HPA was not enabled by default back then. That’s how old it is.

With 2.24 EUR per day for the control plane and 3 EUR per day for instances, we started to consider a change. The last nail in the coffin was an expired node commitment. With that, we started estimating how much it would cost to run the entire setup on Cloud Run. After a few inconclusive estimates and a few beers, we decided to check in production. What harm could be done if you try the Cloud Run for a day and switch back once the error rate goes above, let’s say, 10 percent?

Issues on the way

It turned out that Resizin had a tremendous requirement: If there is no SQL connection to check client setup, check the client configuration in the files. If that is also missing, let’s return 404 for the request. The problem is that Cloud Run does not really wait on async awaits of SQL connectors’ availability. The first requests of each started container returned 404 for all the clients kept in the database.

It turned out that GKE never had that issue because health checks were waiting for the container to be fully available. You do not have that luxury with Cloud Run. To share some code with you, the change was quite simple:


-   const client = getDynamicClients()[clientKey] || staticClients[clientKey];
+   const client = (await getDynamicClients)()[clientKey] || staticClients[clientKey];


One other thing was that we didn’t want to pay for the GCP VPC connector. That’s a thing you need to connect to your Cloud SQL instance via IP. If you can handle a socket, that’s a win because those are free. Of course, we would rather spend the money on the beer, so we chose a socket. To make the setup interoperability with IP, we used a switch:


const knexConnection = {
    user: config.sql.user,
    password: config.sql.password,
    database: config.sql.database,
    charset: config.sql.charset,
    timezone: config.sql.timezone,

const knexConfig = config.sql.socketPath
    client: 'mysql',
    connection: extend({
        socketPath: config.sql.socketPath || "",
    }, knexConnection),
    client: 'mysql',
    connection: extend({
        host: config.sql.host || 'localhost',
        port: config.sql.port,
    }, knexConnection),


GKE to Cloud Run: Was it worth it?

As you might have noticed, this blog post has an underlying theme. That is, of course, beer. We estimated that switching our workload from GKE to Cloud Run saved us 6 EUR daily. The current price of the common Pilsner Urquell Beer Light Lager is 1.17 EUR (at Tesco). The switch helped us to get richer daily by five beers. But on a serious note, go ahead if you have a workload that does not have problems with cold starts, is stateless, and keeps all data in GCS buckets or Cloud SQL.

Based on our research before migration, each app instance can efficiently handle only one core and up to 2 GiB of memory. That’s a reasonable small container instance in Cloud Run, giving us a sustainable base for horizontal scaling. No large jumps in billing once the application scales up.

During the majority of the life cycle, the active container count circles around 3–5 instances:  

graph showing


I can do nothing but recommend the switch from GKE to Cloud Run. If you have any questions about migration, don’t hesitate to ask. Otherwise, like, comment and subscribe.

Are you interested in working together? We wanna know more. Let’s discuss it in person!

Get in touch >