Transcript
This is modernizing applications with containers. In this presentation, we’ll talk about container ideals and monolithic application realities. What are the benefits of containers? What are the best practices around containers? And what are the realities of monolithic applications? We’ll then talk about modernization of monolithic applications and containers. What are the strategies around modernizing applications? How do you triage applications for modernization? How do you compare services to monoliths and methods of decomposing monoliths into services? And then we’ll wrap up with the summary.
Table of Contents
- Container Ideals and Monolithic realities (0:35)
- Monolithic Application Reality (2:26)
- Docker Container Best Practices (5:53)
- Modernization Strategies (9:48)
- Benefits of Services vs Monoliths (19:49)
- Challenges/Pitfalls of Services (22:13)
- How do we split a monolithic application into services? (24:56)
- Summary (30:21)
- Learn more
Container Ideals and Monolithic realities (0:35)
When we talk about containerization, there’s a number of different benefits for both developers and for deployment. At the core, containers provide process level isolation. This means that each container can run isolated from other containers on the same system. This has a number of benefits, including how you can pack resources onto a single system and have multiple things running without interfering with each other. Containers include all the dependency the application requires, decoupling the application from the host. This means that you don’t have to worry about what’s running on the host machine or how it’s running on the host machine affecting the container. So no more, “it works on my machine.” Containers allow easy application movement and distribution. A container image can be run in the same way on multiple different pieces of hardware and have them actually work the same way. This means you can pick up that container image and move it around as you need to and have consistency in how that container runs. There’s then the Open Container Initiative standard that is supported by all the major DevOps tooling and cloud vendors. This means when you build a container image that complies with the OCI standards, you can run it anywhere and use any of the major DevOps tools with it with confidence. And finally, developers have a better experience working with containers to build applications for all the reasons we just mentioned before and makes working with your application much more straightforward for an individual developer or a cross developer team when coding the application.
Monolithic Application Reality (2:26)
So now let’s compare that to the monolithic application reality that many customers have to deal with. So we look at an application. This application was first deployed to production in 2010 with additional functionality added over time. The application is considered mission critical for the business. So the application is something that’s not going to go anywhere. It’s something that we have to deal with. So we’re going to start off with a Java application. So we have the Java virtual machine as the lowest layer here. Then we have the application server with Java EE services. Layered on top of that is the spring framework, which makes use of those Java EE services. Then we have frameworks like Log4J for instance, Lucene for search, more Java libraries that are needed, lots of other supporting files on top of that. And then finally, we have an eCommerce Java application with search, inventory, sales, CRM, reporting, and support functionality that have been built into it over time. So this is the application that we’re trying to support. That’s running as a monolith. We now look at it as a holistic system. We see that there’s a variety of other systems that it depends upon, whether it be back in databases, whether it be MQ calls to a backend system, or even external web service calls via SOAP, since this is an application that’s over a decade old. So this is the whole of the application that we’re working with. So this application has to be 12.5GB in size given all the components that are involved in it. So this is a very large monolithic application that is the reality for many people to have things like this.
All right, so what is the business impact of having a monolithic application like we just saw? The first is deployment frequency. When you have such a large application, it is difficult to get it deployed. That means the schedule could be quarterly to six months or more between deployments because you have so many things you’re trying to line up with it because everything has to go at one time. So that means there’s a higher lead time for changes. So even a small change could be quarterly or more to actually get it out to production. The next thing is scaling. So if you want to scale this monolithic application, you’ve got to clone the entire thing to scale it. That’s the only choice you have available for you is a horizontal type scaling. The next piece is lower reliability. A problem anywhere in the application has the ability to affect the whole. So if there’s a memory leak and one small function somewhere, it could bring the entire application down because it’s all one process. And then a more complex developer experience, you can’t just develop pieces of the application. It’s all or nothing, which means you have to have everything set up and running to actually be able to do any kind of development with it. And all of this leads to higher cost. Larger teams, more operations cost, more hardware, everything costs more with this monolithic application.
Docker Container Best Practices (5:53)
So let’s talk about container best practices. So when we talk about containers, what we want is for each container to do one thing and do it well. So the idea here is that containers are going to focus on a particular aspect, something that needs to be done that’s going to be self-contained. And that container can do that one thing. It doesn’t need to do anything beyond that. The next thing is don’t ship developer tooling into production. If you have developer tools that you need in development, make sure those don’t go into a production image. Reduce the size of the image where possible. There’s no point including extra stuff into your production environment. At best, it’s simply extra bytes that are up there. At worst, it could be a security issue or cause other problems. Include only what is needed to run the application. So again, if there’s something that’s in that image that isn’t required to run the application, it needs to be removed. And then finally start simple and get more complicated as needed. And this really applies to how you work with containers across your entire SDLC. You don’t need to have extra pieces if you don’t need them at the moment. You can certainly add them later.
Okay, so why are we talking about these best practices? Well, let’s go back and look at our application. Here is our application. And the question then becomes, can I containerize this? Can I put this into a container given what we were just talking about? So if I were to put this application into a container, how would this line up with our best practices? Each container should do one thing and do it well. No, that’s not what this is doing at all. Don’t ship developer tooling into production. And I’m actually not sure if there’s any developer tooling in that. We’re going to have to go through and look and find out. Reduce the size of the image where possible. No, we’re not aligning with that either. Include only what is needed to run the application. No, we’re not doing that. And then start simple and get more complicated as needed. We’re certainly not doing that. So we’re not meeting almost any of our container best practices if we were to containerize this monolith. So the question then becomes, should we do it? And the answer I’m going to give is yes, you can containerize this monolithic application. You can put the entire thing into a container.
So the question that becomes if it doesn’t comply with any of the best practices, why would I do that? Let’s go back to our benefits of containerization. Containers provide process level isolation so we can pack more resources. Yes, this is still true, even if that’s a monolith that’s inside that container. Containers include all the dependencies the application requires decoupling the app from the host. No more “works on my machine.” Yes, if this whole monolith is inside of a container, that means that it’s isolated from any other JVM versions you have your machine, paths, any of that kind of stuff. So that benefit is still there. Containers allow easy application movement and distribution. Yes, if the monolith is in that particular container, you can pick up that image, move it around and run it and it will run the same way. Containers use the OCI standards, so it means it could be deployed anywhere. Yes, this is still true, even if it’s a monolith that’s inside that container. And developers have a better experience working with containers to build applications. Yes, this is still true, even though it’s a monolith. So even though we’re not complying with the best practices, containerizing a monolith can actually provide you benefits outside of that.
Modernization Strategies (9:48)
So now let’s talk about modernization strategies. We have this monolithic application. We can put it inside of a container. Putting it inside a container isn’t really modernizing it per se. Perhaps there’s other strategies that containerization can help with. So the first thing that we’re going to talk about here, just to keep in the back of your mind as we’re having this conversation, is Conway’s law. Conway’s law says that any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure. What this means is that if you have different departments who are working on a particular application, each of those departments are going to get represented in that particular application. And you need to keep this in mind as you’re looking at modernizing applications such that you don’t repeat mistakes of trying to replicate your organization versus replicating the domain that the application needs to fulfill.
Okay, application modernization strategies. This was first popularized by Gartner as the five R’s. There’s a variety of versions out there today if you go look at it. But the five R’s we’re going to talk about are Re-hosting – this is minimal to no changes, a lift and shift of the application. Refactor – making some very light modifications to the application, perhaps paying down some technical debt. Rearchitect – significant modifications or splitting of the application into components. Rebuild – re-writing, redesigning the application from the ground up, and finally Replace – retire the application or replace with other systems. For this presentation we’ll be focusing on Rehost, Refactor, Rearchitect, and Rebuild as the options.
All right, so the first thing we need to think about is triage. What are the considerations for modernizing an application? Typically when you’re doing a modernization effort, there’s not an application you’re focused on. There could be dozens or hundreds of applications that could need modernization. And there needs to be some kind of thought process as to which applications get modernized in which way, in which priority. So what are some of those considerations? The first are the business priorities. What are the business priorities and how do they align to modernizing this particular application? Is this application important to the business priorities? And if so, how is modernization going to apply to that? Or is this application not important to the business priorities? Perhaps it is more important to the operational priorities? How does that affect the prioritization? The next is application knowledge. So the application in our example is over a decade old. Does anyone really understand how the application works or how it’s deployed? And if the answer is no to either of those questions, it really forces you to take a step back and think about the level of effort that’s going to be required for different types of modernization strategies. The next is the application tech stack. Again, if this application is older, does it comply with the current application technology standards? And if the answer is no, what does that mean to your modernization strategy for this application? The next is the application lifespan. Is the application still needed in its current form? If the application is old enough and things have changed enough, maybe that affects your strategy and your prioritization for this application. Organizational capacity – how much capacity do the development, testing and operations teams have to work on this application? Everybody has a day job they need to be doing. Is there actually going to be enough capacity to do modernization of this particular application in a time frame that you want? And then finally is the cost and risk. What are the cost and risk of running the current model at the way it is versus the cost and risk of modernizing the application? Regardless of what modernization strategy you have, there’s going to be a cost involved and there’s going to be some level of risk involved as well. And measuring and comparing those to the current is an important part of the triage. So once we’ve gone through the triage and determined what’s the level of effort going to be and what the priority is of modernizing this application, we can then look at the different strategies and how they apply.
So the first we’re going to look at is the Rehost. This is a very basic lift and shift of the application typically required when the hardware it’s currently running on is going out of support or for other reasons there needs to be a change in the infrastructure that it’s actually living on. So with our Docker container holding the monolith, this becomes extremely easy. We can pick up that container image. We could go deposit it in this case on the little clouds down there and run it in that cloud environment. It runs exactly the same way it would run On Prem. It still needs the connections to the databases to the MQ and to the web services. But the application itself will run exactly the same as it did before. So containerization makes this lift and shift easy.
All right, the next one is Refactor. Now we’re going to make some light modifications to the application itself. So in this case we are going to look at reducing some technical debt, perhaps update some versions of things and make some minor changes where it makes sense. So let’s look at what we’re doing here. The first thing on the left hand side is we’re changing out that SOAP call that existed before to REST. So modernizing the API that’s being used for those calls that need to be made. The next thing we’re doing is looking at that reporting functionality that was built into the application and removing that and taking it out to a third party reporting tool that then has access to the back end databases. Notice this third party reporting tool can be in its own container as well. So this way we don’t have extraneous functionality in the application that is not critical for the application to run.
All right, so the next strategy is Rearchitecting. This is significant modifications or splitting of the application into components. So why should we split an application into components or services? Services can have a number of features that are going to help us. The first is that each service is independently deployable. This has a number of immediate benefits as far as teams, timetables, technologies, all those pieces. So having individual independent pieces is going to have a number of ripple effects to both development and deployment. These services are loosely coupled to other services. The loosely here is incredibly important. We’ll talk more about that here in just a minute. So typically services are defined by a business function or capability. So you may have something like accounts or a customer or that kind of thing that you want to define that is going to be defined by a service or multiple services depending upon how you want to do it. So each service has a single responsibility model. So it’s responsible for its particular components and handles the business logic and data associated to it. As I just said, they actually contain their own business logic and data management for their single responsibility. And they communicate with each other via some kind of API or broker.
So what is the right scope for a service? You know, is the service big? Is it small? What should it be? Well, there’s a number of different scales or scopes of services that we talk about. The first one we’ve already been talking about, which is the macroservice or monolith. All business services are deployed together tightly coupled. So this is the single process monolith version of things. The next is microservices. Microservices are typically widely talked about when we talk about services. They cover a business component. They’re independently deployed, loosely coupled and communicate via API. There’s also other scales. There is a mini service, which is a group of microservices that serve a business need or perhaps a process. And then there’s nanoscale, which is single business function, very fine grain scope. You can think of like an inventory widget on a page being a nanoservice that just shows availability of inventory of a particular item as defined as a service. All right. So what is the right scope? There is no one right scope for a service. The right scope is the one that is just big enough to give you real value and not bigger. So, you know, this is going to vary for each application and each service that you’re creating. So you can’t just say everything’s going to be a microservice or everything’s going to be a nanoservice. You really got to look at what it is you’re trying to do, what value you’re getting out of that service, and does that value make sense for what the effort you’re having to put in to maintain that service?
Benefits of Services vs Monoliths (19:49)
So what are the benefits of services versus monoliths? Well, we already talked about several of these, but let’s go back through them. So smaller independent development teams, because each team can work on a particular service versus working on the entire monolith, that means those teams can work independently of each other and not have to be as large. The next thing is independent service release schedules. This means faster time to market for changes because those independent services can release on their own schedules. Scale services independently – so if you need to scale up the customer service, but not the product service, you don’t have to scale the entire application now. This means lower resource cost overall. The next is smaller scope developer experience. Again, I don’t have to run the entire application on my machine, which means I can have a laptop, for instance, running parts of the application, just the pieces I need to be able to get my work done. I could do things like blue/green deployments, because now I have individual services, I could actually go out there and deploy different versions of independent services and see what the reaction is versus having to try to do that at the monolith level. Reusability services – the classic example here is a tax calculation service or an inventory service, where you might need that same service in multiple places and you could reuse that service. Tech agnostic services – so now because these services are independent of each other, I could now start considering separate tech stacks for individual services where it makes sense. And then better application reliability, better control of failure modes. So now if I have an issue like a memory leak in one small service, I don’t have to worry about it taking down the entire application. I can have a failure mode where part of the application is not working, but the rest of it still is so that some business can get done while the remediation is happening for the service that failed.
Challenges/Pitfalls of Services (22:13)
Alright, so those are all the benefits of services. What are some of the challenges and pitfalls of services? So surely it’s not all positive looking at services. Well, the first one is complexity. This is a big one. The more pieces you break an application into means the more moving parts that you have to take care of as part of this. So there is technological complexity, there is process complexity and there’s organizational complexity that is a part of changing a model to services based architecture. This is a very, very big thing that has to be considered as you’re making this type of move. The next is latency. Network hops are real. So, if you have multiple services running on the same machine, you have API calls that are happening between them. Those API calls could take only a few microseconds. You think, oh, that’s not a big deal. But if you have dozens or possibly hundreds of those service calls that are happening to fulfill one request, that time adds up. And so you need to think about that as the number of services grow. Distributive monoliths. So if services are created that are not loosely coupled, but instead are tightly coupled, therefore meaning that individual services can’t run unless they have all the other services around them, then you really haven’t created independent services. What you’ve created is an distributed monolith, which simply is that same issue that you’re having before, except now it spread across multiple machines and multiple groups instead of one particular application. Too fine-grain services; why don’t we make everything service? Why don’t we make every icon on every field on every page of service? Well, are you getting business value out of those services? Does it make sense to have services at that level? Or is it simply adding complexity to do that? And then technology is the goal. No business person that I’m aware of comes out to their investors and says, Istio is our goal or this orchestration technology is our goal or that type of thing. The business is interested in business results. And so anytime you look at technology as being the goal of what you’re trying to do versus the business outcome, that can be a challenge.
How do we split a monolithic application into services? (24:56)
Right, so the next thing is how do we split a monolithic application into services? So we have our monolithic application. We now want to break it into services. How do we do that? So one of the patterns that is widely used is called the Strangler Fig Pattern and with a name like that, we need to go into a little bit of background as to why it has that name. This is from Martin Fowler who came up with this pattern. And he was talking about one of his vacations where they came upon these huge Strangler Figs, they seed in the upper branches of a tree and gradually build root systems all the way down to the soil. Once they get to the soil, they start growing around the tree and killing it as they’re growing. And so Martin felt this was a metaphor to describe a way of rewriting an important system. So an alternative route to an application rewrite is to gradually create a new system around the edges of the old, letting it grow slowly until the old system is strangled.
Okay, so first thing is we need to identify and create services from a monolith. So we need to figure out what these services are going to be. So we need to identify logical components within our application. What is it going to be like: customer, product, inventory, sale, it could be a variety of different things. We need to understand the dependencies between those components there. At the beginning, there’s going to be all kinds of connections between those services. We need to understand what that looks like within that monolith. And then we need to find and remove duplication of functionalities between components in the monolith. Once we’ve done that and we have something that more closely resembles an API, we could then create groupings of those components into services and then determine the granularity of those services, anything from macro to nano. And then create an API or broker to remotely call those services.
So we’ve now taken our e-commerce application and we’ve broken it down into some components. So we have orders, searches, customers, payments, inventory, and support cases. So these are the components that we have lined out and we figured out the calls between these, removed duplicated functionality. And we feel we’re ready to try to start breaking these pieces out. So this is what the application looks like inside the container. What we’re going to do first is add a proxy in front of this application. The proxy is going to allow us to split out these individual components without affecting the end user experience. So then we could take a component that we feel is the most isolated from the other ones. So in this case, we’ll take payments and we’ll split it out into its own container. So now we have the payments running in two places. We have it running in the original application and we have it running in a separate container and we have the API between those running through the proxy. Once we’ve done this and verified that the payments service in the new container is working properly and is servicing the traffic, we could then go in and remove the payments functionality or component from the model. And so this is giving us the ability to slowly split out these individual services in the Strangler Fig Pattern. And so eventually what we wind up with are each of the different services now running in their own containers. And we also see here the third party reporting container from our earlier example.
Okay, so we’ve gone through the Rearchitect piece of this, significant modifications or splitting the application. The next is Rebuilding. So now we’re going to completely rewrite, redesign the application. So now we can make wholesale changes to what we were doing versus what we were trying to do before, which was taking what we had and modernize it into new technologies.
So now if we look at it, suddenly the functionality has a bunch of changes to it. Not only do we have different languages other than Java in it; we have Golang for orders and payment system. We had existing reviews and discussions and social media integrations that didn’t exist before in our application that we’re going to add in as PHP components. We have third party CRM search and reporting functionality we’re going to add in. We’re going to have an AI/ML chatbot for our customer success and we’re going to use a Rust service for inventory. So now we see that we have services again, but we have things that look much different than when we rearchitected the application before.
Okay, so we’ve covered Rehost, Refactor, Rearchitect and Rebuild and we’re not covering Replace because Replace is simply getting rid of the application and starting with something else.
Summary (30:21)
So let’s wrap up here. There are a lot of monolithic applications out there. Modernization will continue for years to come. There is no one right path to modernization. You have to determine what the best path for your organization and for each application is through a triage and prioritization process. Containers help with the modernization process regardless of where you are on that journey. Containers provide value regardless if you’re containerizing a monolith or a microservice. Thank you very much.
Learn more
- New to Docker? Get started.
- Deep dive into Docker products with featured guides.
- Subscribe to the Docker Newsletter.
- Get the latest release of Docker Desktop.
- Have questions? The Docker community is here to help.