There was a time where it was commonplace for companies to deploy new features on a monthly, bi-monthly, and, in some cases, even quarterly basis.
Long gone are the days where companies can deploy on such an extended schedule. Customers expect features to be delivered faster, and with higher quality. And this is where continuous delivery comes in.
Continuous delivery is a way of building software, such that it can be deployed to a specified environment, whenever you want to. And deploy only the highest quality versions to production. And ideally with one command or button push.
With this level of ease for a deployment, not only will you be able to deliver features to users faster, you'll also be able to fix bugs faster. And with all the layers of testing that exist between the continuous integration and continuous delivery processes, the software being delivered will be of higher quality.
Continuous delivery is not only for companies that are considered to be 'unicorns', it's within the grasp of all of us. In this Course, we'll take a look at what's involved with continuous delivery, and see an example.
This introductory Course will be the foundation for future, more advanced Courses, that will dive into building a complete continuous delivery process. Before we can start trying to implement tools, we need to make sure that we have an understanding of the problem we need to solve. And we need to know what kind of changes to our application may be required to support continuous delivery.
Understanding the aspects of the continuous delivery process can help developers and operations engineers to gain a more complete picture of the DevOps philosophy. Continuous delivery covers topics from development through deployment and is a topic that all software engineers should have experience with.
By the end of this Course, you'll be able to:
- Define continuous delivery and continuous deployment
- Describe some of the code level changes that will help support continuously delivery
- Describe the pros and cons for monoliths and microservices
- Explain blue / green & canary deployments
- Explain the pros and cons of mutable and immutable servers
- Identify some of the tools that are used for continuous delivery
This is a beginner level Course for people with:
- Development experience
- Operations experience
What You'll Learn
|Lecture||What you'll learn|
|Intro||What will be covered in this Course|
|What is Continuous Delivery?||What Continuous Delivery is and why it's valuable|
|Coding for Continuous Delivery||What type of code changes may be required to support constant delivery|
|Architecting for Continuous Delivery||What sort of architectural changes may be required to support continuous delivery|
|Mutable vs. Immutable Servers||What are the pros and cons for mutable and immutable servers|
|Deployment Methods||How we can get software to production without downtime|
|Continuous Delivery Tools||What sort of tools are available for creating a continuous delivery process|
|Putting it All Together||What a continuous delivery process looks like|
|Summary||A review of the Course|
If you have thoughts or suggestions for this Course, please contact Cloud Academy at email@example.com.
Welcome back to Introduction to Continuous Delivery, I'm Ben Lambert, and I'll be your instructor for this lecture.
I mentioned previously that Continuous Delivery is a way of building software in such a way that it can always be deployed. Now that means that code has to support this constant deployment. In order to accommodate always being deployable, we as developers need to ensure that we're writing code in such a way that works well with this. While there are a lot of potential considerations, we're going to focus on three of them.
We're going to talk about:
- feature toggles
- inversion of control
- defensive coding
If you've taken the Introduction to Continuous Integration course, then you're at least familiar with inversion of control.
Let's start with feature toggles. If you have a feature that takes a while to implement, then it will require some way of being deployable while it's an incomplete state. The optimal solution is to develop the code in a very modular and incremental way. And by doing this, you're not really introducing any additional technical debt.
For new functionality, this method is not too difficult. However it becomes more difficult when we need to make changes to existing code. Especially when the changes we're making span over a multiple sprint period. So, how do we deal with features that span across multiple sprints? We can use a technique called feature toggles. Feature toggles are a way to have your code checked and see if a given feature is enabled, and if so it'll allow the code to execute.
Now there are different types of feature toggles for different scenarios, however, I'm going to focus on release toggles. Release toggles are a method that allow developers to conditionally execute code based on the state of the toggle. What this means is, as developers, we can build our code to check and see if a feature is enabled. And if so, then we can execute the code, and if not we can just ignore it. Allowing new features that are not complete to be deployed and not break anything.
Here we have the most basic of examples. It shows that on line 13 we're checking to see if the advanced search feature is enabled, and if so we're going to return the results of the new advanced search. And if not, it's going to stick with the existing version. Now, this isn't a working version, but the concept is valid.
Alright, now before you go off and wrap everything in a million feature toggles there's a downside you need to think about first. Release toggles are meant to be short lived, and not used indefinitely. They're a short term step to help ensure that you can continue to deploy code to your main line while creating features that go across multiple sprints. And the result of these toggles are what we call technical debt. Technical debt is a concept where developers save time in the short term by doing something the easy way, but have to spend more time later to redo things the correct way. And the more technical debt that builds up, the more difficult a project can become to work on over time. So using release toggles creates technical debt, because we have to remove them later once the feature is complete. We also have to consider how these toggles impact testing, because for each toggle there are two states that need to be tested, and it grows with every new toggle. At a certain point, it becomes something that won't scale well, so we have to take care to make sure that we're using these release toggles only when needed.
One final note on toggles, if you're going to use them then you should have some sort of mechanism in place that can identify the state of all of the toggles. Debugging software can be difficult enough, adding in all these hidden toggles into the mix can be downright painful.
Okay, next up, let's talk about inversion of control and why it helps to make our code better suited for continuous delivery. Inversion of control is a mechanism for increasing the modularity of your code. Its goal is to allow you to be able to replace a component with other components that share the same interface so that you can just swap out components when you need to. The more modular your code, the more testable it is. And the easier it tends to be to test just the things that have changed. There are a lot of different implementations of inversion of control, though they all help you by making your code more modular.
Let's have a generic look at IOC, which is an abbreviation for Inversion of Control through the dependency injection pattern. Here you can see that we have some software modules that handle file storage. We have one for AWS, one for Azure, one for Google Cloud Storage, and one that's an In-Memory Storage. And they're all built based on the same generic file storage interface. Which ensures that these can be swapped out at any time. And we also have our core application, and it needs to be able to use remote file storage. If we hard-coded it to use one, or even conditionally to be able to use any of those storage modules, we'd make testing more difficult.
However, if we told our application that it should just expect some module that implements the generic file storage interface, then we can swap out the implementation whenever we need to. Now this has a lot of value for extensibility because we can create new modules to handle file storage as long as they implement that same interface. And because all of these modules should behave the same way at the interface level, we can test them in a uniform way. This also ensures that our code is written to be testable. Unit tests become reasonably simple when you can swap out an actual module for a fake version that's built to be using the same interface. So by using the in-memory version it allows us to unit test rather quickly. So inversion of control is one way to make modular code, and modular code is easier to work on, and easier to test.
Alright, our final consideration is going to be secure coding practices. Now, it's not uncommon for security to take a back seat in the software world. For the most part, people don't want to think about security until after their systems have been compromised. So if security is already being ignored, speeding up the delivery process is not going to help.
Secure programming is one of those topics that could take up an entire course, and if that's something you'd like to hear more about let us know at firstname.lastname@example.org. However, for now, we're just going to take a look at a few of the OWASP top 10. OWASP is a not-for-profit organization that helps to promote information security practices. In 2013 they updated their list of the Top 10 Software Vulnerabilities, and based on their website they're looking to do the same for either 2016 or 2017.
First up is injection issues. Injection issues take on different forms, however, the gist remains the same. If a malicious user can inject some code or command into an input field and have your code execute it, then they are one step closer to gaining access that they shouldn't have. Here's an example of SQL injection. You can see that the SQL is being dynamically constructed, and is accepting input from a user, and adding it to that SQL command. So the result here is that SQL will be executed that you didn't expect to be. This next one is an ORM injection and it's very similar. Looking at this example you can see that it looks a lot like the SQL injection from before. This is an example of injecting into the rails all around. There are other types of injection, however, they're all fairly similar.
If your code accepts untrusted user input, this input could come from anywhere, a URL param, a post-body, a cookie value, etc., then you need to consider that input to be malicious. If a user can manipulate the value in any way, then that value needs to be treated as malicious, and you'll need to sanitize the input. Which would be different depending on how you're going to use it. Next on the list, weak authentication and session management.
So, we're doing web development, and you're using the HTTP protocol, which is a stateless protocol. That means if you want to have users be able to log in, and be remembered when they return, then you need some sort of mechanism to track this. Now just about every web framework in existence handles this via some sort of session token. A valid session token is as good as a username and password. So you as the developer need to protect this token on the user's behalf. For this, start with ensuring that the session tokens are set to HTTP only. This will protect from some cross-site scripting attacks where the cookie could be stolen. This will be a different setting for each web framework, so you'll have to look in to how to do it for yours. Next up, use SSL. Not just on the login page, but throughout the entire site. If you don't then the token can be found if somebody's sniffing on the network for it.
Also, try not to store untrusted, un-sanitized data in your database. Maybe your application isn't susceptible, however, consider that this data could end up displayed in web-based logs or similar tools, and if it executes code on some admin dashboard, and sends the session token off for a malicious user to use, this could be quite damaging. Like I mentioned before, this isn't the lesson on secure development, so I'm not going to go into anymore detail. Though I did want to cover it here in part, because security needs to be baked into the entire development, deployment, and operations pipeline. I recommend checking out the OWASP Top 10, and find some resources that help to better contextualize it for the language that you most often program in.
So, I know we've considered a lot in this lecture, so let's summarize.
Feature toggles, and in particular release toggles are useful in small well thought out doses.
Modular coding practices help to make code that's easier to work on, and easier to test.
And things like dependency injection can help with that. And last, security is everyone's job, and that includes the developers.
In our next lecture we'll talk about some of the architectural requirements for continuous delivery.
Okay, if you're ready, let's get started.
Ben Lambert is a software engineer and was previously the lead author for DevOps and Microsoft Azure training content at Cloud Academy. His courses and learning paths covered Cloud Ecosystem technologies such as DC/OS, configuration management tools, and containers. As a software engineer, Ben’s experience includes building highly available web and mobile apps. When he’s not building software, he’s hiking, camping, or creating video games.