"wait-for", a tiny utility to wait for TCP resources to be ready

Use it in your Kubernetes clusters to wait for resources to be ready, like SQL Databases

5 minutes read, 910 words
Posted on January 23, 2022

The screen of my Macbook Pro when I was writing some Go code in 2018

I know I’m not the first one to create this and, in fact, there are a plethora of options out there to use as of right now. Still, I wrote my own version of wait-for, and there are a few differences that make it to be a little more useful than the alternatives.

For those of you who have never heard about this, wait-for is a very simple and tiny application with a unique purpose: it allows you to define several TCP endpoints – that is, endpoints like a MySQL Database (or MariaDB if you’re in line with the new waves), or even NoSQL ones, like Redis or MongoDB – and wait for them to be ready.

Now ready is a strong word, so to put it in clear terms, wait-for will wait until a connection can be established against a TCP endpoint, and that’s it, it will allow you to configure some basic automation, needed in environments like docker-compose or Kubernetes.

How to check in a Kubernetes cluster if your database is ready?

Databases in Kubernetes and how to wait for them to be ready

The best example is often used in Kubernetes environments where you want to wait for a MariaDB database to be available to connect to it or fail fast if it’s not. The easiest way to do this is by putting this application in an init container and then to probe every second if the database has finally come online.

More Kubernetes-savvy people might work this issue out in different ways, for example:

  • They could build their application so that the startup process checks for the Database to be ready and crash the container if it’s not. Kubernetes will reschedule the app until it either gives up restarting it or until the database has come online.
  • They could also configure the health checks to validate if the database is active, by just requesting it. This works well, too, since you can mark your application as “unhealthy”, which is especially useful when monitoring is active, and you want to be warned of this situation.

While those are great arguments against wait-for, this app is meant to be a stop-gap and not a one-size-fits-all solution. Often teams migrating their apps to Kubernetes environments might not have the time nor the ability to “patch” the application before it makes it to Kubernetes just to implement this feature.

Example YAML manifest for Kubernetes

The following YAML, taken as-is from the wait-for documentation page, outlines a very basic use case scenario:

 pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: init-container-demo
spec:
  initContainers:
  - name: wait-for
    image: ghcr.io/patrickdappollonio/wait-for:latest
    env:
    - name: POSTGRES_HOST
      value: "postgres.default.svc.cluster.local:5432"
    command:
      - /wait-for
    args:
      - --host="google.com:443"
      - --host="mysql.example.com:3306"
      - --host="$(POSTGRES_HOST)"
      - --verbose
  containers:
  - name: nginx-container
    image: nginx

The highlighted lines above show how to use an init container before the original application starts. In this case, we’re hoping nginx comes up after. Right before we boot it, we create an init container that pings google.com on port 443 (for https), mysql.example.com on port 3306, and postgres.default.svc.cluster.local on port 5432 – note for this last one, the setting is coming as an environment variable fed to the init container.

What do you get out of the box?

Out of the box, you can expect:

  • Proper exit codes for when connecting to those resources succeeded vs failed. It’s amazing how many of the solutions do not account for this, which is critical to ensure the next container, your app container, either runs or gets cancelled. That, or in CI/CD environments where a non-zero exit code allows to mark the pipeline as “failed”.
  • The ability to validate multiple hosts at once. You can call the --host flag multiple times, and for each, configure the appropriate port.
  • A configurable time between connections. This is a simple one, and the long-story-short is that you might not want to be making requests every second to a service that’s taking its precious time to boot. Perhaps you want to try every 5 seconds instead? That’s configurable using the --every flag.

If there are other features you would like to see, please open an issue with your request! Happy to discuss those further!

Some things you should know…

There are a couple of caveats that I want to wait to see addressed in the open and gauge the interest to know what people would expect here:

  • The Docker image is a scratch image, and that means there’s no terminal or shell interpreter like bash or sh. It’s often, personally, not needed, and you can get environment variable injection from Kubernetes itself as shown above. Still, if there’s a need to have either sh or bash, I’m down to hear the recommendations.
    • Due to this, by the way, I’ve already received feedback to allow the option to “pass” a command to execute when all resources are available. I might implement this either as a wait-for -h "example.com:443" -- echo "Success!" or something along those lines. It gets challenging if you also want a command to execute on failures.
  • If your database or third party resource requires authentication, wait-for might not work for your use case. It will 100% work to validate if the database can receive connections, but it will not go through the authentication process – it will close the connection as soon as a socket can be established.

Other than that, if you encounter any issues or there’s a feature you would like to see added, please don’t hesitate and open an issue!

Link: wait-for on Github

Share this: