Josh Frazier Josh Frazier is a Senior Data Engineer at Figure Eight, a human-in-the-loop machine learning and artificial intelligence company based in San Francisco. He first moved to the Bay Area five years ago, after honing his skills at small software outfits in his native Maryland. He has worked for software companies ranging from mobile analytics and security start-ups to industry leaders in virtualization. When he’s not writing code, he enjoys reading, studying mathematics and physics, and traveling with his wife.

When I started at Figure Eight, I was given the usual “getting started” materials for a new engineer; architecture diagrams, Confluence pages, GitHub repos, etc. After some time getting my usual development environment set up, I began working from a Confluence page designed to get new engineers up and running with at least some of the microservices in our architecture. The page walks the user through initial dependencies and set up before moving on to checking out several repos and running their application stacks. We were using docker-compose, so the idea here was to run a single docker-compose up command in each repo, and the application stack would come up.

Right away we had a bit of a problem: even with nothing else running on a MacBook Pro, running even a small number of microservices, along with their databases, messaging queues, and redis instances is going to cause performance issues.

Distributed systems and microservice architectures present real challenges for engineers following local development paradigms.

The resource constraints of a local development stack in a system this complex aside, I ran into some trouble with race conditions: microservice A would come up and attempt to connect to a RabbitMQ that hadn’t been provisioned yet, or microservice B would come up before its Postgres database was bootstrapped. After running into these gotchas for a few deployments, I wrote a script that would bring up the infrastructure components first, then check to see if those services were healthy before attempting to bring up our Figure Eight applications. This did the trick (though obviously didn’t make my RAM very happy) so I checked this script in and articulated it to the team as a useful way to get around some of the headaches of running our stack locally.

This story probably sounds familiar in the age of microservices, containers, and container orchestration technologies. This certainly isn’t the first company for which I have worked that has faced similar problems; distributed systems and microservice architectures present real challenges for engineers following local development paradigms.

Enter the DevSpace

Based on a recommendation from another engineering team member, our core platform team decided to vet the Kubernetes developer tool DevSpace as a possible solution to our local development woes. It promised Kubernetes resource management with a more reasonable learning curve, secure multi-tenancy with namespace isolation that provides sandboxed deployments for engineers, and hot code reloading from local machines to pods running on a remote cluster.

Getting Started, Again

After identifying a desirable candidate from our stack of microservices, I set about implementing a POC. From the root directory of the repo, I was able to run

devspace init 1 devspace init

and generate a DevSpace configuration yaml similar to the one pictured below in a matter of a minute or so. This command will walk the user through connecting their namespace to their deployment(s), and the cluster on which pods will be deployed. Ours is a Docker shop, so this step was quite simple, we point the primary deployment for the project to our ./Dockerfile at the root of the repo.

Sample devspace config:

Once this was hooked up, I was able to run

devspace deploy 1 devspace deploy

and had a pod running, based on the dockerfile in my repo, in short order.

Getting a list of deployments for a given project is straightforward:

devspace list deployments 1 devspace list deployments

Which will yield something like this:

The reader will note that at no point during this process did we define a Helm chart for either of these deployments, DevSpace handles configuring our deployments for us.

For relatively simple applications, without any external dependencies, this initialization process is all that is required to get the application up and running in Kubernetes using DevSpace. Obviously, this is not the case for most applications. My pod came up, couldn’t find a Postgres database it needed to do its work, and promptly died.

Dependencies

One of our past local environment sins that I wanted to avoid with this new technology was managing dependency components on a per-application basis. All of our applications use Postgres databases, and yet in each project, we were launching a different Postgres instance with every

docker-compose up 1 docker - compose up

This pattern is fine for one-off testing of a single application, but breaks down when we start talking about deploying other micro-services to a local machine or cluster (queue the port definition collisions).

My idea was to centralize deployments for what I refer to as “infrastructure components” into their own repo, so that they can be managed in a single place.

DevSpace provides some “canned” database management systems out of the box with the component flag, and they are very easy to add to a devspace.yaml for deployment definitions:

devspace add deployment <name_of_service> —component=<DBMS_NAME> 1 devspace add deployment & lt ; name_of_service & gt ; — component =& lt ; DBMS_NAME & gt ;

To get a listing of available components, simply run:

devspace list available-components 1 devspace list available - components

Adding a component:

So we created a repo, which has its own devspace.yaml file, specifically for deploying our infrastructure before we deploy any micro-services. When a new space is brought up, the engineer simply goes into this repo, runs

devspace deploy 1 devspace deploy

and has the backbone of our platform deployed. No need to craft any Helm charts, or craft and manage any yaml files for our microservices or databases.

Now, when we deploy microservices, we can have them check their dependencies before they are deployed. If their dependencies don’t exist, we fail fast, and provide valuable feedback to the user.

Being able to deploy with a single command is just part of what the DevSpace CLI tool brings to the table. DevSpace is a powerful tool for managing Kubernetes deployments without much prior knowledge and has helped us to provide our platform in a box to new engineers starting at Figure Eight. Now, on day one at Figure Eight, new engineers can get the first-hand experience with deploying our entire platform, and valuable insight into which microservices depend upon which infrastructure components.

Implications, and the Problem of Shared Development Environments.

The capabilities that DevSpace has unlocked for us go beyond giving engineers their own sandboxes, and the ability to administer them.

Before adopting DevSpace, doing integration testing for even minor changes meant negotiating time on our shared development environments, so that these changes could be vetted in a real-world scenario without adversely affecting someone else’s testing. This is a common problem, and one that we sought to address with our implementation of DevSpace, and the processes and infrastructure we built around it.

Naturally, we wanted the bells and whistles that DevSpace gave us, meaning that the ability to test code changes in a fully running environment before those changes even hit our testing environments is extremely compelling. The desire for this capability drove how we thought about this project, and we hope others can learn from our experiences.