Software services architected as microservices, packaged in containers, and orchestrated with Kubernetes changes the game for continuous testing because of the excellent alignment with the five tenets of continuous testing.
The DevOps Institute describes continuous testing as an essential tenet of DevOps. Continuous testing allows for continuous assessment of incremental changes as part of establishing a culture and environment where building, testing and releasing software can happen rapidly, frequently, reliably and safely. In my book, Engineering DevOps, I explained that there are five tenets which are guiding principles for continuous testing as follows:
- Shift Left: Conduct each test as early in the pipeline as possible.
- Fail Early: Arrange the tests so the most likely problems are found at the earliest possible stage in the DevOps pipeline.
- Fail Often: Run tests frequently and with many different conditions.
- Test Fast: Arrange tests to run in quick cycles.
- Be Relevant: Focus on the most important tests and results.
The microservices approach aims to produce well bounded, networked, service-oriented modules. This is attractive to software application architectures because the individual microservices can be built and deployed as independent units. The smaller modules align well with the five tenets, which makes it easier and quicker to test compared to large monolithic applications.
Applications engineered as microservices have contracts that define how a containerized service behaves with other services that use it. This attribute of microservices reduces the dependence between microservices, which reduces the potential risk of unexpected interactions. This means that a substantial amount of high-quality testing of standalone microservices can be conducted early in the pipeline. Tests can be conducted, not just at the unit level, but at the integration level, as well. Specialty tests—such as user interface visual validation or security scans—can be performed with confidence that the application is being testing in the same way it will be used when deployed to production.
System-level testing applications, including regression, performance and reliability testing, in the later stages of pipelines can be a source of bottlenecks, because more complex configurations involving multiple services are needed. This testing can be accelerated when the dependencies between containerized microservices are well-understood, because only the configurations that are known to be affected by a change need to be deployed and tested in the staging environment.
Advanced deployment and testing patterns, such as feature-flag rollouts, canary rollouts and blue-green deployment methods, take advantage of the flexibility of containerized applications and further accelerate testing. Instead of waiting for sufficient testing to be performed before deployment to production, these patterns allow changes to be gradually and safely deployed without any downtime or interruptions for users. A subset of running application instances can be deployed and tested with a subset of users instead of updating all the application instances at one time. Progressively more of the running application instances can be deployed as the application changes are proven with more users and deployment variations, until they are all updated. With the blue-green pattern, a parallel set of production instances can be used to verify each new version of your application, and then switched with the existing production instances when the new version has been verified ready-to-deploy.
The structure of built container images can be tested very early in the pipeline to ensure commands run as expected, that files are in the correct location and have the correct content. Structure tests ensure commands run properly in the target image, make sure all expected files exist within the image, verify file contents are correct, verify metadata configuration data is correct, and verify license permissions on copyrighted files.
A well-configured and maintained test environment, one that closely mimics production and contains up-to-date code deployments, can provide a safe and sane way for testers to validate a scenario before it gets into the hands of a customer.
In my article 9Pillars of Containers Best Practices, I explained how containers allow the job of orchestrating many test configuration variations to be migrated from infrastructure teams to developers. Developers specify what their application needs in a container during the testing phase, and their continuous deployment tool builds and runs the container. Some of the container practices that help find problems early include the following:
- Place everything a service or application needs for testing in a container. Containers offer the opportunity to package test resources in special test containers so they can be conveniently and immutably invoked, on-demand, for testing changes with elastic scaling benefits.
- Test using the same container definition that is deployed.
- Test the container before pushing it to a shared environment.
Packaging a microservice into its own container is attractive because the service can be deployed immutably across any number of operating systems and infrastructures, scale easily and also can be reverted easily in the case of test failures.
Test environment management (TEM) systems can optimize the orchestration of test resources better with container test topologies that can be predefined and managed with Kubernetes across variations of operating systems and infrastructures.
The time between when a developer makes a code change and when you have a running version of the application should be as short as possible. In my prior article, 9 Pillars of Engineering DevOps With Kubernetes, I explained how, with Kubernetes, developers and testers can work better together to solve defects quickly and accurately, because developers can use the tester’s Kubernetes instance for debugging. This eliminates long delays associated with developers and testers trying to replicate each other’s test environments. Kubernetes also helps testers and developers quickly exchange precise information for application configurations.
By packaging each application in its own container, it makes it easy to know what is being tested. Engineers can create precisely the test environment that is needed—locally, on-demand and with precisely the same OS version and dependencies as it would have in production.
Ideally, pre-production clusters used for testing are identical to production clusters, but for cost purposes, pre-production clusters can be scaled-down replicas. Keeping the clusters similar ensures that any testing is done using the same or similar conditions to production. This reduces the probability of unexpected failures due to environmental differences when you deploy to production.
One of the most difficult tasks in maintaining a test environment is keeping the data for each instance of the test environment current. Containerization encourages managing test data as a part of the test suite. The database is also a part of the container stack, which allows the test team to populate the database more easily with relevant data as a part of initializing a test run.
What This Means
Containerizing services, together with Kubernetes orchestration, is a game changer for continuous testing. The five tenets of continuous testing – Shift left, fail early, fail often, test fast and be relevant – are all supported by containers and Kubernetes, which make it much easier to implement continuous testing.