Apart from all the recent discussions about Kubernetes and whether you should Dockerize your database or not, today I would like to show you why those two things might be good solutions when scalability and elasticity is a big requirement in your architecture.

The secret sauce here is simple: deploying both your application and database on Kubernetes and a combination of NoSQL and Spring Data.

 

Why NoSQL and Spring Data?

With document databases, you can avoid lots of unnecessary joins as the whole structure is stored in a single document. Therefore, it will naturally perform faster than a relational model as your data grows.

If you are using any of the JVM languages, Spring Data might be something quite familiar to you. Thus, you can quickly start with NoSQL even without any previous knowledge.

 

Why Kubernetes?

Kubernetes allows you to scale up and down your stateless application in a cloud-agnostic environment. In the last few versions, K8s also added the ability to run stateful applications such as databases in it, that is one of the (many) reasons why it is such a hot topic nowadays.

I have shown in my previous blog post how to deploy Couchbase on K8s and how to make it “elastic” by easily scaling up and down. If you haven’t read it yet, please spend a few extra minutes going through the video transcript as it is an important part of what we are going to talk about here.

 

Creating a User Profile Microservice

In the majority of the systems, the user (and all related entities) is the most frequently accessed data. Consequently, it is one of the first parts of the system that has to go through some sort of optimization as your data grows.

Adding a Cache layer is the first type of optimization we can think of. However, it is not the “final solution” yet. Things might get a little bit more complicated if you have thousands of users, or if you need to store user-related entities also in memory.

Managing massive amounts of user profiles is a well-known good fit for document databases. Just take a look at the Pokémon Go use case, for instance. Therefore, building a highly scalable and elastic User Profile Service seems to be a challenge good enough to demonstrate how to design a highly scalable microservice.

What you are going to need:

  • Couchbase
  • JDK and Lombok’s plugin for Eclipse or Intellij
  •  Maven
  • A Kubernetes cluster – I’m running this example on 3 nodes on AWS (I do not recommend using minikube). If you don’t know how to set up one, watch this video.

The Code

You can clone the whole project here:

 

Let’s start by creating our main entity called User:

 

In this entity we have two important properties:

  • securityRoles: All roles the user can play within the system.
  • preferences: All possible preferences the user might have, such as language, notifications, currency, etc.

Now, let’s play a little bit with our Repository. As we are using Spring Data,  you can use pretty much all its capabilities here:

If you want to know more about Couchbase and Spring Data, check out this tutorial.

We also implemented two other methods:

 

  • hasRole: Check if a user has a specified role:
  • findUsersByPreferencyName: As the name says, it finds all users that contain a given preference.

Notice that we are using N1QL syntax in the code above as it makes things much simpler to query than using plain JQL.

Additionally, you can run all tests to make sure that everything is working properly:

Don’t forget to change your application.properties with the correct credentials of your database:

In order to test our microservice, I added a few Restful endpoints:

 

Dockerizing your Microservice

First, change your application.properties to get the connection credentials from environment variables:

And now we can create our Dockerfile:

Then, we build and publish our image on Docker Hub:

  • Create your image:

 

  • Log into the Docker Hub from the command line

  •  Let’s grab the imageId of our recently created image:

  • Create your new tag using the imageId:

 

  • Finally, push your image:

Your image should now be available at Docker Hub:

 

Configuring the Database

I wrote a whole article about it here, but to keep it short. Just run the following commands inside the kubernetes directory.

After a while, all the 3 instances of our database should be running:

Let’s forward the Web Console’s port to our local machine:

And now we can access the web console at http://localhost:8091. You can log in using the username Administrator and the password password

Go to Security -> ADD USER with the following properties:

  • Username: couchbase-sample
  • Full Name: couchbase-sample
  • Password: couchbase-sample
  • Verify password: couchbase-sample
  • Roles: According to the image below:

OBS: In a production environment, please don’t add your app as an admin

 

Deploying your Microservice

First, let’s create a Kubernetes secret where we will store the password to connect to our database:

Run the following command to create the secret:

The file spring-boot-app.yaml is the one responsible for deploying our app. Let’s take a look at its contents:

I would like to highlight some important parts of this file:

  • replicas: 2 -> Kubernetes will launch 2 instances of our app
  •  image: deniswsrosa/kubernetes-starter-kit -> The docker image we have created before.
  • containers: name: -> Here is where we define the name of the container running our application. You will use this name in Kubernetes whenever you want to define how many instances should be running, autoscaling strategies, load balancing, etc.
  • env: -> Here is where we define the environment variables of our app. Note that we are also referring to the secret we created before.

Run the following command to deploy our app:

In a few seconds, you will notice that both instances of your application are already running:

Finally, let’s expose our microservice to the outside world. There are dozens of different possibilities of how it can be done. In our case, let’s simply create a Load Balancer:

The selector is one of the most important parts of the file above. It is where we define the containers to which the traffic will be redirected. In this case, we are just pointing to the app we have deployed before.

Run the following command to create our load balancer:

The load balancer will take a few minutes to be up and redirecting traffic to our pods. You can run the following command to check its status:

As you can see in the image above, our Load Balancer is accessible at ad84a916d65ad11e884a20266aaa53c9-1223617270.us-west-2.elb.amazonaws.com, and the targetPort 8080 will redirect traffic to two endpoints: 10.2.1.6:8080 and 10.2.2.7:8080

Finally, we can access our application and start sending requests to it:

  • Inserting a new user:
  • Searching for users:

 

What about being elastic?

This is where things get really interesting. What if we need to scale up the whole of our microservice? Let’s say the Black Friday is coming and we need to prepare our infrastructure to support this massive flow of users coming to our website. Well, that is an easy problem to solve:

  •  To scale up our application, we just need to change the number of replicas in the spring-boot-app.yaml file.

    And then, run the following command:

Is there anything missing? Yes. What about our database? We should scale it up as well:

  • Change the size attribute in the couchbase-cluster.yaml file:

 

Finally, run the following command:

 

How can I Scale it down?

Scaling down is as easy as scaling up; you just need to change both couchbase-cluster.yaml and spring-boot-app.yaml:

  • couchbase-cluster.yaml

  • spring-boot-app.yaml:

 

And run the following commands:

 

Auto-scaling microservices on Kubernetes

I will make a deep dive into this topic in part 2 of this article. In the meantime, you can check this video about pod autoscaling.

Troubleshooting your Kubernetes deployment

If your Pods fail to start, there are many ways to troubleshoot the problem. In the case below, both applications failed to start:

Since they are part of the deployment, let’s describe the deployment to try understanding what is happening:

Well, nothing is really relevant in this case. Let’s look at one of the pod’s logs then:

Gotcha! The application did not start because we forgot to create the user on Couchbase. By just creating the user, the pods will be up in a few seconds:

Conclusion

Databases are stateful applications, and scaling them is not as fast as scaling stateless ones (and probably it will never be), but if you need to make a truly elastic architecture, you should plan to scale all components of your infrastructure. Otherwise, you are just creating bottleneck somewhere else.

In this article, I tried to show just a small introduction about how you can make both your application and database on Kubernetes elastic. However, it is not a production-ready architecture yet. There are a plenty of other things to consider still, and I will address some of them in the upcoming articles.

In the meantime, if you have any questions, tweet me at @deniswsrosa or leave a comment below.

Posted by Denis Rosa, Developer Advocate, Couchbase

Denis Rosa is a Developer Advocate for Couchbase and lives in Munich - Germany. He has a solid experience as a software engineer and speaks fluently Java, Python, Scala and Javascript. Denis likes to write about search, Big Data, AI, Microservices and everything else that would help developers to make a beautiful, faster, stable and scalable app.

Leave a reply