If you build software, the words it works on my machine have slipped out of your mouth at least once in your life. If you Google it you’ll probably find memes, stickers, and t-shirts joking about it. Developers have been haunted for decades by unexpected errors when running code on different machines. In the era of containers, you might think this is no longer an issue, but there are still drawbacks and challenges that need to be solved.
When I am coding I’m focused on just one thing: delivering value to my users as fast as possible. After many years working as a developer I can summarize that what I need to achieve that goal is to:
- Set up my development environment as quickly as possible and in a replicable way.
- Have a production-like replica of my application for development purposes.
- A blazing fast inner-loop development workflow.
In this blog post, I analyze the current alternatives for developing cloud-native applications and how they behave on replicability, developer/production parity, and fast inner loop development cycle. I will then introduce how we solve these challenges in Okteto using what we call the Cloud-Native Development methodology.
Last year we interviewed more than 200 different companies and software teams on how they build software. Based on these conversations we found out that the most common development workflows nowadays are:
- Traditional local development without containers.
- Container-driven development with tools like Docker Compose or Minikube.
- CI-driven development to perform end to end validation in the inner loop development cycle.
Let explain in more detail each of these solutions and how they behave concerning development/production parity, replicability, and fast inner loop development cycle.
Traditional local development
As developers, we tend to work like our service is an isolated entity running in our local machine.
I simply run my compiler or relay on a hot-reloader to automatically build for me as I code and test the results immediately in my browser or local command line.
This iterative process is probably the fastest it can be and you benefit from incremental builds and debuggers. But this approach is broken by design:
- The more local setup you need, the less replicable your development environment is.
- The way your application and its dependencies run in production has little in common with this local development setup. You are mocking your runtime dependencies everywhere.
This development workflow does not align well with the DevOps culture. Things will usually break in pre-production environments. Ops think Devs don’t know how to code and Devs think Ops don’t know how to run their code.
Working with containers gets me a step closer to replicating the complex architecture of an application in my local environment. Docker greatly alleviates certain pain points:
- Reduces the development/production parity.
- Provides a common environment for running my application across different operating systems, improving replicability.
Popular tools like Docker Compose or Minikube allow me to get a local version of my application up and running with a single command. But it also comes with downsides:
- Slower inner loop to build images and redeploy containers.
- Hard to integrate debuggers or other IDE extensions.
- Unable to run all my dependencies locally for complex applications, hence, degrading replicability.
- Unwanted overhead in my machine.
The ugly truth is that trying to run an entire stack locally as it will run in production is hard. Running a single service might be easy, but how about large microservice-based applications, service mesh, network configuration, API gateways, serverless, legacy services, heavy databases or CPU/memory intensive workloads? Madness.
So, how do I usually deal with dependencies I can’t run locally? You are forced to test your changes in a different environment. You merge your changes and somehow your application is updated in some sort of staging environment by a continuous integration job in hopefully a few minutes.
But have you ever struggled with bugs coming from CI, staging or even production that you cannot repro in your local setup? Since I’m unable to repro the bug, I need to rely on continuous integration again to validate my changes. Did it work? It didn’t? Start this slow process again… 😩.
The following table summarizes the behavior of these solutions versus development/production parity, replicability, and fast inner loop development cycle:
As you can see, none of these solutions is satisfactory for the properties we are looking for development environments. Devs happiness goes 👈👈👈 and Ops happiness goes 👉👉👉.
These solutions either slow my inner loop cycle down or postpone the real end-to-end test to a later phase. Trying to solve this problem led us to start Okteto. Our mission at Okteto is to take developers’ productivity to the next level. Our first three products, Okteto CLI, Okteto Cloud and Okteto Enterprise (the on-premise version of Okteto Cloud) are the first steps towards our vision of modern development workflows. Let me explain how Okteto solves the problems of dev/production parity, replicability, and a fast inner loop cycle:
Production-like development environments based on sandboxed Kubernetes namespaces
Okteto Cloud gives developers self-service access to Kubernetes namespaces in a shared development cluster. Each namespace is configured automatically to be isolated from other developers working on the same cluster. While Ops want developers to develop on a real environment, developers shouldn’t install Kubernetes locally as this requires hardware resources and deeper knowledge about this technology. In teams with more than 4 or 5 people and different operating systems, this would become a very cumbersome task.
One-click deployment of applications for pure replicability
Okteto Cloud makes launching your applications and development environments as easy as clicking a button. Developers can instantly deploy or upgrade their applications from an Application Catalog conveniently configured by the Ops Team (backed by Helm 3). Since apps are running in a shared cluster, developers take advantage of platform services running in the cluster such as Serverless Frameworks, Logs and Metrics Aggregators, Runtime Security Checkers like Falco, etc.
Fast inner loop: just code, build and test
Once your application is up and running, the Okteto CLI allows you to enable development mode on one or more components of your application and just focus on development. You code locally with the tools you know and love (including debuggers) and Okteto synchronizes the changes to update your application instantaneously. No commit, build or push required.
Containers and Kubernetes have taken our deployment techniques to the next level, but the development practices have not evolved at the same speed. Developers need access to replicable, production-like environments as part of the development cycle, but without having to give up a fast inner loop that is crucial for developer productivity.
If this problem sounds familiar to you, you should check out what we have built at Okteto. Take a look at our getting started guide and start developing at the speed of the cloud.
Do you have ideas, comments or feedback? Join us at the #okteto channel in the Kubernetes community Slack and share your thoughts with the community!