Professional Documents
Culture Documents
Cloud Native App
Cloud Native App
EDITION v.1.0
PUBLISHED BY
Microsoft Developer Division, .NET, and Visual Studio product teams
A division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399
Copyright © 2020 by Microsoft Corporation
All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any
means without the written permission of the publisher.
This book is provided "as-is" and expresses the author's views and opinions. The views, opinions, and information
expressed in this book, including URL and other Internet website references, may change without notice.
Some examples depicted herein are provided for illustration only and are fictitious. No real association or
connection is intended or should be inferred.
Microsoft and the trademarks listed at https://www.microsoft.com on the "Trademarks" webpage are trademarks of
the Microsoft group of companies.
Mac and macOS are trademarks of Apple Inc.
The Docker whale logo is a registered trademark of Docker, Inc. Used by permission.
All other marks and logos are property of their respective owners.
Authors:
Editors:
Version
This guide has been written to cover .NET Core 3.1 version along with many additional updates related to the
same “wave” of technologies (that is, Azure and additional third-party technologies) coinciding in time with the
.NET Core 3.1 release.
NE XT
Introduction to cloud-native applications
5/19/2020 • 3 minutes to read • Edit Online
PR EVIO U S NE XT
Defining cloud native
5/19/2020 • 19 minutes to read • Edit Online
Stop what you're doing and text ten of your colleagues. Ask them to define the term "Cloud Native". Good chance
you'll get ten different answers.
Cloud native is all about changing the way you think about constructing critical business systems.
Cloud-native systems are designed to embrace rapid change, large scale, and resilience.
The Cloud Native Computing Foundation provides an official definition:
Cloud-native technologies empower organizations to build and run scalable applications in modern, dynamic
environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable
infrastructure, and declarative APIs exemplify this approach.
These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined
with robust automation, they allow engineers to make high-impact changes frequently and predictably with
minimal toil.
Applications have become increasingly complex with users demanding more and more. Users expect rapid
responsiveness, innovative features, and zero downtime. Performance problems, recurring errors, and the inability
to move fast are no longer acceptable. They'll easily move to your competitor.
Cloud native is much about speed and agility. Business systems are evolving from enabling business capabilities to
weapons of strategic transformation, accelerating business velocity and growth. It's imperative to get ideas to
market immediately.
Here are some companies who have implemented these techniques. Think about the speed, agility, and scalability
they've achieved.
C O M PA N Y EXP ERIEN C E
As you can see, Netflix, Uber, and WeChat expose systems that consist of hundreds of independent microservices.
This architectural style enables them to rapidly respond to market conditions. They can instantaneously update
small areas of a live, complex application, and individually scale those areas as needed.
The speed and agility of cloud native come about from a number of factors. Foremost is cloud infrastructure. Five
additional foundational pillars shown in Figure 1-3 also provide the bedrock for cloud-native systems.
Figure 1-3 . Cloud-native foundational pillars
Let's take some time to better understand the significance of each pillar.
The cloud…
Cloud-native systems take full advantage of the cloud service model.
Designed to thrive in a dynamic, virtualized cloud environment, these systems make extensive use of Platform as a
Service (PaaS) compute infrastructure and managed services. They treat the underlying infrastructure as
disposable - provisioned in minutes and resized, scaled, moved, or destroyed on demand – via automation.
Consider the widely accepted DevOps concept of Pets vs. Cattle. In a traditional data center, servers are treated as
Pets: a physical machine, given a meaningful name, and cared for. You scale by adding more resources to the same
machine (scaling up). If the server becomes sick, you nurse it back to health. Should the server become
unavailable, everyone notices.
The Cattle service model is different. You provision each instance as a virtual machine or container. They're identical
and assigned a system identifier such as Service-01, Service-02, and so on. You scale by creating more of them
(scaling out). When one becomes unavailable, nobody notices.
The cattle model embraces immutable infrastructure. Servers aren't repaired or modified. If one fails or requires
updating, it's destroyed and a new one is provisioned – all done via automation.
Cloud-native systems embrace the Cattle service model. They continue to run as the infrastructure scales in or out
with no regard to the machines upon which they're running.
The Azure cloud platform supports this type of highly elastic infrastructure with automatic scaling, self-healing, and
monitoring capabilities.
Modern design
How would you design a cloud-native app? What would your architecture look like? To what principles, patterns,
and best practices would you adhere? What infrastructure and operational concerns would be important?
The Twelve -Factor Application
A widely accepted methodology for constructing cloud-based applications is the Twelve-Factor Application. It
describes a set of principles and practices that developers follow to construct applications optimized for modern
cloud environments. Special attention is given to portability across environments and declarative automation.
While applicable to any web-based application, many practitioners consider Twelve-Factor as a solid foundation for
building cloud-native apps. Systems built upon these principles can deploy and scale rapidly and add features to
react quickly to market changes.
The following table highlights the Twelve-Factor methodology:
FA C TO R EXP L A N AT IO N
In the book, Beyond the Twelve-Factor App, author Kevin Hoffman details each of the original 12 factors (written in
2011). Additionally, he discusses three additional factors that reflect today's modern cloud application design.
N EW FA C TO R EXP L A N AT IO N
We'll refer to many of the 12+ factors in this chapter and throughout the book.
Critical Design Considerations
Beyond the guidance provided from the twelve-factor methodology, there are several critical design decisions you
must make when constructing distributed systems.
Communication
How will front-end client applications communicate with backed-end core services? Will you allow direct
communication? Or, might you abstract the back-end services with a gateway façade that provides flexibility,
control, and security?
How will back-end core services communicate with each other? Will you allow direct HTTP calls that lead to
coupling and impact performance and agility? Or might you consider decoupled messaging with queue and topic
technologies?
Communication is covered in detail Chapter 4, Cloud-Native Communication Patterns.
Resiliency
A microservices architecture moves your system from in-process to out-of-process network communication. In a
distributed architecture, what happens when Service B isn't responding to a network call from Service A? Or, what
happens when Service C becomes temporarily unavailable and other services calling it are blocked?
Resiliency is covered in detail Chapter 6, Cloud-Native Resiliency.
Distributed Data
By design, each microservice encapsulates its own data, exposing operations via its public interface. If so, how do
you query data or implement a transaction across multiple services?
Distributed data is covered in detail Chapter 5, Cloud-Native Data Patterns.
Identity
How will your service identify who is accessing it and what permissions they have?
Identity is covered in detail Chapter 8, Identity.
Microservices
Cloud-native systems embrace microservices, a popular architectural style for constructing modern applications.
Built as a distributed set of small, independent services that interact through a shared fabric, microservices share
the following characteristics:
Each implements a specific business capability within a larger domain context.
Each is developed autonomously and can be deployed independently.
Each is self-contained encapsulating its own data storage technology (SQL, NoSQL) and programming
platform.
Each runs in its own process and communicates with others using standard communication protocols such
as HTTP/HTTPS, WebSockets, or AMQP.
They compose together to form an application.
Figure 1-4 contrasts a monolithic application approach with a microservices approach. Note how the monolith is
composed of a layered architecture, which executes in a single process. It typically consumes a relational database.
The microservice approach, however, segregates functionality into independent services that include logic and
data. Each microservice hosts its own datastore.
Figure 1-4. Monolithic deployment versus microservices
Note how microservices promote the "One Codebase, One Application" principle from the Twelve-Factor
Application, discussed earlier in the chapter.
Factor #1 specifies "A single codebase for each microservice, stored in its own repository. Tracked with version
control, it can deploy to multiple environments."
Why microservices?
Microservices provide agility.
Earlier in the chapter, we compared an eCommerce application built as a monolith to that with microservices. In the
example, we saw some clear benefits:
Each microservice has an autonomous lifecycle and can evolve independently and deploy frequently. You
don't have to wait for a quarterly release to deploy a new features or update. You can update a small area of
a complex application with less risk of disrupting the entire system.
Each microservice can scale independently. Instead of scaling the entire application as a single unit, you
scale out only those services that require more processing power or network bandwidth. This fine-grained
approach to scaling provides for greater control of your system and helps to reduce overall costs as you
scale portions of your system, not everything.
An excellent reference guide for understanding microservices is .NET Microservices: Architecture for Containerized
.NET Applications. The book deep dives into microservices design and architecture. It's a companion for a full-stack
microservice reference architecture available as a free download from Microsoft.
Developing microservices
Microservices can be created with any modern development platform.
The Microsoft .NET Core platform is an excellent choice. Free and open source, it has many built-in features to
simplify microservice development. .NET Core is cross-platform. Applications can be built and run on Windows,
macOS, and most flavors of Linux.
.NET Core is highly performant and has scored well in comparison to Node.js and other competing platforms.
Interestingly, TechEmpower conducted an extensive set of performance benchmarks across many web application
platforms and frameworks. .NET Core scored in the top 10 - well above Node.js and other competing platforms.
.NET Core is maintained by Microsoft and the .NET community on GitHub.
Containers
Nowadays, it's natural to hear the term container mentioned in any conversation concerning cloud native. In the
book, Cloud Native Patterns, author Cornelia Davis observes that, "Containers are a great enabler of cloud-native
software." The Cloud Native Computing Foundation places microservice containerization as the first step in their
Cloud-Native Trail Map - guidance for enterprises beginning their cloud-native journey.
Containerizing a microservice is simple and straightforward. The code, its dependencies, and runtime are packaged
into a binary called a container image. Images are stored in a container registry, which acts as a repository or
library for images. A registry can be located on your development computer, in your data center, or in a public
cloud. Docker itself maintains a public registry via Docker Hub. The Azure cloud features a container registry to
store container images close to the cloud applications that will run them.
When needed, you transform the image into a running container instance. The instance runs on any computer that
has a container runtime engine installed. You can have as many instances of the containerized service as needed.
Figure 1-5 shows three different microservices, each in its own container, running on a single host.
Factor #2 specifies that "Each microservice isolates and packages its own dependencies, embracing changes
without impacting the entire system."
Containers support both Linux and Windows workloads. The Azure cloud openly embraces both. Interestingly, it's
Linux, not Windows Server, that has become the most popular operating system in Azure.
While several container vendors exist, Docker has captured the lion's share of the market. The company has been
driving the software container movement. It has become the de facto standard for packaging, deploying, and
running cloud-native applications.
Why containers?
Containers provide portability and guarantee consistency across environments. By encapsulating everything into a
single package, you isolate the microservice and its dependencies from the underlying infrastructure.
You can deploy that same container in any environment that has the Docker runtime engine. Containerized
workloads also eliminate the expense of pre-configuring each environment with frameworks, software libraries,
and runtime engines.
By sharing the underlying operating system and host resources, containers have a much smaller footprint than a
full virtual machine. The smaller size increases the density, or number of microservices, that a given host can run at
one time.
Container orchestration
While tools such as Docker create images and run containers, you also need tools to manage them. Container
management is done with a special software program called a container orchestrator. When operating at scale,
container orchestration is essential.
Figure 1-6 shows management tasks that container orchestrators provide.
TA SK S EXP L A N AT IO N
Note how orchestrators embrace the disposability and concurrency principles from the Twelve-Factor Application,
discussed earlier in the chapter.
Factor #9 specifies that "Service instances should be disposable, favoring fast startups to increase scalability
opportunities and graceful shutdowns to leave the system in a correct state. Docker containers along with an
orchestrator inherently satisfy this requirement."
Factor #8 specifies that "Services scale out across a large number of small identical processes (copies) as
opposed to scaling-up a single large instance on the most powerful machine available."
While several container orchestrators exist, Kubernetes has become the de facto standard for the cloud-native
world. It's a portable, extensible, open-source platform for managing containerized workloads.
You could host your own instance of Kubernetes, but then you'd be responsible for provisioning and managing its
resources - which can be complex. The Azure cloud features Kubernetes as a managed service, Azure Kubernetes
Service (AKS). A managed service allows you to fully leverage its features, without having to install and maintain it.
Azure Kubernetes Services is covered in detail Chapter 2, Scaling Cloud-Native Applications.
Backing services
Cloud-native systems depend upon many different ancillary resources, such as data stores, message brokers,
monitoring, and identity services. These services are known as backing services.
Figure 1-7 shows many common backing services that cloud-native systems consume.
Factor #6 specifies that, "Each microservice should execute in its own process, isolated from other running
services. Externalize required state to a backing service such as a distributed cache or data store."
You could host your own backing services, but then you'd be responsible for licensing, provisioning, and managing
those resources.
Cloud providers offer a rich assortment of managed backing services. Instead of owning the service, you simply
consume it. The provider operates the resource at scale and bears the responsibility for performance, security, and
maintenance. Monitoring, redundancy, and availability are built into the service. Providers fully support their
managed services - open a ticket and they fix your issue.
Cloud-native systems favor managed backing services from cloud vendors. The savings in time and labor are
great. The operational risk of hosting your own and experiencing trouble can get expensive fast.
A best practice is to treat a backing service as an attached resource, dynamically bound to a microservice with
information (a URL and credentials) stored in an external configuration. This guidance is spelled out in the Twelve-
Factor Application, discussed earlier in the chapter.
Factor #4 specifies that backing services "should be exposed via an addressable URL. Doing so decouples the
resource from the application, enabling it to be interchangeable."
Factor #3 specifies that "Configuration information is moved out of the microservice and externalized through
a configuration management tool outside of the code."
With this pattern, a backing service can be attached and detached without code changes. You might promote a
microservice from QA to a staging environment. You update the microservice configuration to point to the backing
services in staging and inject the settings into your container through an environment variable.
Cloud vendors provide APIs for you to communicate with their proprietary backing services. These libraries
encapsulate the plumbing and complexity. Communicating directly with these APIs will tightly couple your code to
the backing service. It's a better practice to insulate the implementation details of the vendor API. Introduce an
intermediation layer, or intermediate API, exposing generic operations to your service code. This loose coupling
enables you to swap out one backing service for another or move your code to a different public cloud without
having to make changes to the mainline service code.
Backing services are discussed in detail Chapter 5, Cloud-Native Data Patterns, and Chapter 4, Cloud-Native
Communication Patterns.
Automation
As you've seen, cloud-native systems embrace microservices, containers, and modern system design to achieve
speed and agility. But, that's only part of the story. How do you provision the cloud environments upon which these
systems run? How do you rapidly deploy app features and updates? How do you round out the full picture?
Enter the widely accepted practice of Infrastructure as Code, or IaC.
With IaC, you automate platform provisioning and application deployment. You essentially apply software
engineering practices such as testing and versioning to your DevOps practices. Your infrastructure and
deployments are automated, consistent, and repeatable.
Automating infrastructure
Tools like Azure Resource Manager, Terraform, and the Azure CLI, enable you to declaratively script the cloud
infrastructure you require. Resource names, locations, capacities, and secrets are parameterized and dynamic. The
script is versioned and checked into source control as an artifact of your project. You invoke the script to provision
a consistent and repeatable infrastructure across system environments, such as QA, staging, and production.
Under the hood, IaC is idempotent, meaning that you can run the same script over and over without side effects. If
the team needs to make a change, they edit and rerun the script. Only the updated resources are affected.
In the article, What is Infrastructure as Code, Author Sam Guckenheimer describes how, "Teams who implement IaC
can deliver stable environments rapidly and at scale. Teams avoid manual configuration of environments and
enforce consistency by representing the desired state of their environments via code. Infrastructure deployments
with IaC are repeatable and prevent runtime issues caused by configuration drift or missing dependencies. DevOps
teams can work together with a unified set of practices and tools to deliver applications and their supporting
infrastructure rapidly, reliably, and at scale."
Automating deployments
The Twelve-Factor Application, discussed earlier, calls for separate steps when transforming completed code into a
running application.
Factor #5 specifies that "Each release must enforce a strict separation across the build, release and run stages.
Each should be tagged with a unique ID and support the ability to roll back."
Modern CI/CD systems help fulfill this principle. They provide separate deployment steps and help ensure
consistent and quality code that's readily available to users.
Figure 1-8 shows the separation across the deployment process.
Figure 1-8 . Deployment steps in a CI/CD Pipeline
In the previous figure, pay special attention to separation of tasks.
The developer constructs a feature in their development environment, iterating through what is called the "inner
loop" of code, run, and debug. When complete, that code is pushed into a code repository, such as GitHub, Azure
DevOps, or BitBucket.
The push triggers a build stage that transforms the code into a binary artifact. The work is implemented with a
Continuous Integration (CI) pipeline. It automatically builds, tests, and packages the application.
The release stage picks up the binary artifact, applies external application and environment configuration
information, and produces an immutable release. The release is deployed to a specified environment. The work is
implemented with a Continuous Delivery(CD) pipeline. Each release should be identifiable. You can say, "This
deployment is running Release 2.1.1 of the application."
Finally, the released feature is run in the target execution environment. Releases are immutable meaning that any
change must create a new release.
Applying these practices, organizations have radically evolved how they ship software. Many have moved from
quarterly releases to on-demand updates. The goal is to catch problems early in the development cycle when
they're less expensive to fix. The longer the duration between integrations, the more expensive problems become
to resolve. With consistency in the integration process, teams can commit code changes more frequently, leading
to better collaboration and software quality.
Azure Pipelines
The Azure cloud includes a new CI/CD service entitled Azure Pipelines, which is part of the Azure DevOps offering
shown in Figure 1-9.
PR EVIO U S NE XT
Candidate apps for cloud native
5/19/2020 • 3 minutes to read • Edit Online
Look at the apps in your portfolio. How many of them qualify for a cloud-native architecture? All of them? Perhaps
some?
Applying a cost/benefit analysis, there's a good chance that most wouldn't support the hefty price tag required to
be cloud native. The cost of being cloud native would far exceed the business value of the application.
What type of application might be a candidate for cloud native?
Strategic enterprise system that need to constantly evolve business capabilities/features
An application that requires a high release velocity - with high confidence
A system with where individual features must release without a full redeployment of the entire system
An application developed by teams with expertise in different technology stacks
An application with components that must scale independently
Then there are legacy systems. While we'd all like to build new applications, we're often responsible for
modernizing legacy workloads that are critical to the business. Over time, a legacy application could be
decomposed into microservices, containerized, and ultimately "replatformed" into a cloud-native architecture.
Modernizing legacy apps
The free Microsoft e-book Modernize existing .NET applications with Azure cloud and Windows Containers
provides guidance for migrating on-premises workloads into cloud. Figure 1-10 shows that there isn't a single,
one-size-fits-all strategy for modernizing legacy applications.
Summary
In this chapter, we introduced cloud-native computing. We provided a definition along with the key capabilities that
drive a cloud-native application. We looked at the types of applications that might justify this investment and
effort.
With the introduction behind, we now dive into a much more detailed look at cloud native.
References
Cloud Native Computing Foundation
.NET Microservices: Architecture for Containerized .NET applications
Modernize existing .NET applications with Azure cloud and Windows Containers
Cloud Native Patterns by Cornelia Davis
Beyond the Twelve-Factor Application
What is Infrastructure as Code
Uber Engineering's Micro Deploy: Deploying Daily with Confidence
How Netflix Deploys Code
Overload Control for Scaling WeChat Microservices
PR EVIO U S NE XT
Introducing eShopOnContainers reference app
5/19/2020 • 3 minutes to read • Edit Online
Microsoft, in partnership with leading community experts, has produced a full-featured cloud-native microservices
reference application, eShopOnContainers. This application is built to showcase using .NET Core and Docker, and
optionally Azure, Kubernetes, and Visual Studio, to build an online storefront.
Understanding microservices
This book focuses on cloud-native applications built using Azure technology. To learn more about microservices
best practices and how to architect microservice-based applications, read the companion book, .NET Microservices:
Architecture for Containerized .NET Applications.
PR EVIO U S NE XT
Mapping eShopOnContainers to Azure Services
5/19/2020 • 6 minutes to read • Edit Online
Although not required, Azure is well-suited to supporting the eShopOnContainers because the project was built to
be a cloud-native application. The application is built with .NET Core, so it can run on Linux or Windows containers
depending on the Docker host. The application is made up of multiple autonomous microservices, each with its
own data. The different microservices showcase different approaches, ranging from simple CRUD operations to
more complex DDD and CQRS patterns. Microservices communicate with clients over HTTP and with one another
via message-based communication. The application supports multiple platforms for clients as well, since it adopts
HTTP as a standard communication protocol and includes ASP.NET Core and Xamarin mobile apps that run on
Android, iOS, and Windows platforms.
The application's architecture is shown in Figure 2-5. On the left are the client apps, broken up into mobile,
traditional Web, and Web Single Page Application (SPA) flavors. On the right are the server-side components that
make up the system, each of which can be hosted in Docker containers and Kubernetes clusters. The traditional
web app is powered by the ASP.NET Core MVC application shown in yellow. This app and the mobile and web SPA
applications communicate with the individual microservices through one or more API gateways. The API gateways
follow the "backends for front ends" (BFF) pattern, meaning that each gateway is designed to support a given
front-end client. The individual microservices are listed to the right of the API gateways and include both business
logic and some kind of persistence store. The different services make use of SQL Server databases, Redis cache
instances, and MongoDB/CosmosDB stores. On the far right is the system's Event Bus, which is used for
communication between the microservices.
API Gateway
The eShopOnContainers application has multiple front-end clients and multiple different back-end services. There's
no one-to-one correspondence between the client applications and the microservices that support them. In such a
scenario, there may be a great deal of complexity when writing client software to interface with the various back-
end services in a secure manner. Each client would need to address this complexity on its own, resulting in
duplication and many places in which to make updates as services change or new policies are implemented.
Azure API Management (APIM) helps organizations publish APIs in a consistent, manageable fashion. APIM
consists of three components: the API Gateway, and administration portal (the Azure portal), and a developer
portal.
The API Gateway accepts API calls and routes them to the appropriate back-end API. It can also provide additional
services like verification of API keys or JWT tokens and API transformation on the fly without code modifications
(for instance, to accommodate clients expecting an older interface).
The Azure portal is where you define the API schema and package different APIs into products. You also configure
user access, view reports, and configure policies for quotas or transformations.
The developer portal serves as the main resource for developers. It provides developers with API documentation,
an interactive test console, and reports on their own usage. Developers also use the portal to create and manage
their own accounts, including subscription and API key support.
Using APIM, applications can expose several different groups of services, each providing a back end for a particular
front-end client. APIM is recommended for complex scenarios. For simpler needs, the lightweight API Gateway
Ocelot can be used. The eShopOnContainers app uses Ocelot because of its simplicity and because it can be
deployed into the same application environment as the application itself. Learn more about eShopOnContainers,
APIM, and Ocelot.
Another option if your application is using AKS is to deploy the Azure Gateway Ingress Controller as a pod within
your AKS cluster. This allows your cluster to integrate with an Azure Application Gateway, allowing the gateway to
load-balance traffic to the AKS pods. Learn more about the Azure Gateway Ingress Controller for AKS.
Data
The various back-end services used by eShopOnContainers have different storage requirements. Several
microservices use SQL Server databases. The Basket microservice leverages a Redis cache for its persistence. The
Locations microservice expects a MongoDB API for its data. Azure supports each of these data formats.
For SQL Server database support, Azure has products for everything from single databases up to highly scalable
SQL Database elastic pools. Individual microservices can be configured to communicate with their own individual
SQL Server databases quickly and easily. These databases can be scaled as needed to support each separate
microservice according to its needs.
The eShopOnContainers application stores the user's current shopping basket between requests. This is managed
by the Basket microservice that stores the data in a Redis cache. In development, this cache can be deployed in a
container, while in production it can utilize Azure Cache for Redis. Azure Cache for Redis is a fully managed service
offering high performance and reliability without the need to deploy and manage Redis instances or containers on
your own.
The Locations microservice uses a MongoDB NoSQL database for its persistence. During development, the
database can be deployed in its own container, while in production the service can leverage Azure Cosmos DB's API
for MongoDB. One of the benefits of Azure Cosmos DB is its ability to leverage multiple different communication
protocols, including a SQL API and common NoSQL APIs including MongoDB, Cassandra, Gremlin, and Azure Table
Storage. Azure Cosmos DB offers a fully managed and globally distributed database as a service that can scale to
meet the needs of the services that use it.
Distributed data in cloud-native applications is covered in more detail in chapter 5.
Event Bus
The application uses events to communicate changes between different services. This functionality can be
implemented with a variety of implementations, and locally the eShopOnContainers application uses RabbitMQ.
When hosted in Azure, the application would leverage Azure Service Bus for its messaging. Azure Service Bus is a
fully managed integration message broker that allows applications and services to communicate with one another
in a decoupled, reliable, asynchronous manner. Azure Service Bus supports individual queues as well as separate
topics to support publisher-subscriber scenarios. The eShopOnContainers application would leverage topics with
Azure Service Bus to support distributing messages from one microservice to any other microservice that needed
to react to a given message.
Resiliency
Once deployed to production, the eShopOnContainers application would be able to take advantage of several
Azure services available to improve its resiliency. The application publishes health checks, which can be integrated
with Application Insights to provide reporting and alerts based on the app's availability. Azure resources also
provide diagnostic logs that can be used to identify and correct bugs and performance issues. Resource logs
provide detailed information on when and how different Azure resources are used by the application. You'll learn
more about cloud-native resiliency features in chapter 6.
PR EVIO U S NE XT
Deploying eShopOnContainers to Azure
5/19/2020 • 4 minutes to read • Edit Online
The eShopOnContainers application can be deployed to a variety of Azure platforms. The recommended approach
is to deploy the application to Azure Kubernetes Services (AKS). Helm, a Kubernetes deployment tool, is available to
reduce deployment complexity. Optionally, developers may implement Azure Dev Spaces for Kubernetes to
streamline their development process.
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.app.svc.marketing }}
labels:
app: {{ template "marketing-api.name" . }}
chart: {{ template "marketing-api.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app: {{ template "marketing-api.name" . }}
release: {{ .Release.Name }}
Note how the template describes a dynamic set of key/value pairs. When the template is invoked, values that
enclosed in curly braces are pulled in from other yaml-based configuration files.
You'll find the eShopOnContainers helm charts in the /k8s/helm folder. Figure 2-6 shows how the different
components of the application are organized into a folder structure used by helm to define and managed
deployments.
Note that version 3 of Helm officially removes the need for the Tiller server component. More information on
this enhancement can be found here.
Figure 2-7 . Developer Susie deploys her own version of the Bikes microservice and tests it.
At the same time, developer John is customizing the Reservations microservice and needs to test his changes. He
deploys his changes to his own dev space without conflicting with Susie's changes as shown in Figure 2-8. John
then tests his changes using his own URL that is prefixed with the name of his space ( john.s.dev.myapp.eus.azds.io).
Figure 2-8 . Developer John deploys his own version of the Reservations microservice and tests it without
conflicting with other developers.
Using Azure Dev Spaces, teams can work directly with AKS while independently changing, deploying, and testing
their changes. This approach reduces the need for separate dedicated hosted environments since every developer
effectively has their own AKS environment. Developers can work with Azure Dev Spaces using its CLI or launch
their application to Azure Dev Spaces directly from Visual Studio. Learn more about how Azure Dev Spaces works
and is configured.
PR EVIO U S NE XT
Centralized configuration
5/19/2020 • 3 minutes to read • Edit Online
Unlike a monolithic app in which everything runs within a single instance, a cloud-native application consists of
independent services distributed across virtual machines, containers, and geographic regions. Managing
configuration settings for dozens of interdependent services can be challenging. Duplicate copies of configuration
settings across different locations is error prone and difficult to manage. Centralized configuration is a critical
requirement for distributed cloud-native applications.
As discussed in Chapter 1, the Twelve-Factor App recommendations require strict separation between code and
configuration. Configuration must be stored externally from the application and read-in as needed. Storing
configuration values as constants or literal values in code is a violation. The same configuration values are often be
used by many services in the same application. Additionally, we must support the same values across multiple
environments, such as dev, testing, and production. The best practice is store them in a centralized configuration
store.
The Azure cloud presents several great options.
Configuration in eShop
The eShopOnContainers application includes local application settings files with each microservice. These files are
checked into source control, but don't include production secrets such as connection strings or API keys. In
production, individual settings may be overwritten with per-service environment variables. Injecting secrets in
environment variables is a common practice for hosted applications, but doesn't provide a central configuration
store. To support centralized management of configuration settings, each microservice includes a setting to toggle
between its use of local settings or Azure Key Vault settings.
References
The eShopOnContainers Architecture
Orchestrating microservices and multi-container applications for high scalability and availability
Azure API Management
Azure SQL Database Overview
Azure Cache for Redis
Azure Cosmos DB's API for MongoDB
Azure Service Bus
Azure Monitor overview
eShopOnContainers: Create Kubernetes cluster in AKS
eShopOnContainers: Azure Dev Spaces
Azure Dev Spaces
PR EVIO U S NE XT
Scaling cloud-native applications
5/19/2020 • 2 minutes to read • Edit Online
One of the most-often touted advantages of moving to a cloud hosting environment is scalability. Scalability, or the
ability for an application to accept additional user load without compromising performance for each user. It's most
often achieved by breaking up an application into small pieces that can each be given whatever resources they
require. Cloud vendors enable massive scalability anytime and anywhere in the world.
In this chapter, we discuss technologies that enable cloud-native applications to scale to meet user demand. These
technologies include:
Containers
Orchestrators
Serverless computing
PR EVIO U S NE XT
Leveraging containers and orchestrators
7/1/2020 • 12 minutes to read • Edit Online
Containers and orchestrators are designed to solve problems common to monolithic deployment approaches.
When using declarative configuration, you can preview the changes that will be made before committing them by
using kubectl diff -f FOLDERNAME against the folder where your configuration files are located. Once you're sure
you want to apply the changes, run kubectl apply -f FOLDERNAME . Add -R to recursively process a folder hierarchy.
You can also use declarative configuration with other Kubernetes features, one of which being deployments.
Declarative deployments help manage releases, updates, and scaling. They instruct the Kubernetes deployment
controller on how to deploy new changes, scale out load, or roll back to a previous revision. If a cluster is unstable,
a declarative deployment will automatically return the cluster back to a desired state. For example, if a node should
crash, the deployment mechanism will redeploy a replacement to achieve your desired state
Using declarative configuration allows infrastructure to be represented as code that can be checked in and
versioned alongside the application code. It provides improved change control and better support for continuous
deployment using a build and deploy pipeline.
Development resources
This section shows a short list of development resources that may help you get started using containers and
orchestrators for your next application. If you're looking for guidance on how to design your cloud-native
microservices architecture app, read this book's companion, .NET Microservices: Architecture for Containerized
.NET Applications.
Local Kubernetes Development
Kubernetes deployments provide great value in production environments, but can also run locally on your
development machine. While you may work on individual microservices independently, there may be times when
you'll need to run the entire system locally - just as it will run when deployed to production. There are several tools
that can help: Minikube and Docker Desktop. Visual Studio also provides tooling for Docker development.
Minikube
What is Minikube? The Minikube project says "Minikube implements a local Kubernetes cluster on macOS, Linux,
and Windows." Its primary goals are "to be the best tool for local Kubernetes application development and to
support all Kubernetes features that fit." Installing Minikube is separate from Docker, but Minikube supports
different hypervisors than Docker Desktop supports. The following Kubernetes features are currently supported by
Minikube:
DNS
NodePorts
ConfigMaps and secrets
Dashboards
Container runtimes: Docker, rkt, CRI-O, and containerd
Enabling Container Network Interface (CNI)
Ingress
After installing Minikube, you can quickly start using it by running the minikube start command, which
downloads an image and start the local Kubernetes cluster. Once the cluster is started, you interact with it using the
standard Kubernetes kubectl commands.
Docker Desktop
You can also work with Kubernetes directly from Docker Desktop on Windows. It is your only option if you're using
Windows Containers, and is a great choice for non-Windows containers as well. Figure 3-4 shows how to enable
local Kubernetes support when running Docker Desktop.
Figure 3-4 . Configuring Kubernetes in Docker Desktop.
Docker Desktop is the most popular tool for configuring and running containerized apps locally. When you work
with Docker Desktop, you can develop locally against the exact same set of Docker container images that you'll
deploy to production. Docker Desktop is designed to "build, test, and ship" containerized apps locally. It supports
both Linux and Windows containers. Once you push your images to an image registry, like Azure Container
Registry or Docker Hub, AKS can pull and deploy them to production.
Visual Studio Docker Tooling
Visual Studio supports Docker development for web-based applications. When you create a new ASP.NET Core
application, you have an option to configure it with Docker support, as shown in Figure 3-5.
Figure 3-5 . Visual Studio Enable Docker Support
When this option is selected, the project is created with a Dockerfile in its root, which can be used to build and
host the app in a Docker container. An example Dockerfile is shown in Figure 3-6.git
PR EVIO U S NE XT
Leveraging serverless functions
5/19/2020 • 4 minutes to read • Edit Online
In the spectrum from managing physical machines to leveraging cloud capabilities, serverless lives at the extreme
end. Your only responsibility is your code, and you only pay when your code runs. Azure Functions provides a way
to build serverless capabilities into your cloud-native applications.
What is serverless?
Serverless is a relatively new service model of cloud computing. It doesn't mean that servers are optional - your
code still runs on a server somewhere. The distinction is that the application team no longer concerns itself with
managing server infrastructure. Instead, the cloud vendor own this responsibility. The development team increases
its productivity by delivering business solutions to customers, not plumbing.
Serverless computing uses event-triggered stateless containers to host your services. They can scale out and in to
meet demand as-needed. Serverless platforms like Azure Functions have tight integration with other Azure
services like queues, events, and storage.
PR EVIO U S NE XT
Combining containers and serverless approaches
5/19/2020 • 2 minutes to read • Edit Online
Cloud-native applications typically implement services leveraging containers and orchestration. There are often
opportunities to expose some of the application's services as Azure Functions. However, with a cloud-native app
deployed to Kubernetes, it would be nice to leverage Azure Functions within this same toolset. Fortunately, you can
wrap Azure Functions inside Docker containers and deploy them using the same processes and tools as the rest of
your Kubernetes-based app.
When the project is created, it will include a Dockerfile and the worker runtime configured to dotnet . Now, you
can create and test your function locally. Build and run it using the docker build and docker run commands. For
detailed steps to get started building Azure Functions with Docker support, see the Create a function on Linux
using a custom image tutorial.
We've discussed containers in this chapter and in chapter 1. We've seen that containers provide many benefits to
cloud-native applications, including portability. In the Azure cloud, you can deploy the same containerized services
across staging and production environments. Azure provides several options for hosting these containerized
workloads:
Azure Kubernetes Services (AKS)
Azure Container Instance (ACI)
Azure Web Apps for Containers
Once authenticated, you can use docker commands to push container images to it. Before you can do so, however,
you must tag your image with the fully qualified name (URL) of your ACR login server. It will have the format
registryname.azurecr.io.
After you've tagged the image, you use the docker push command to push the image to your ACR instance.
After you push an image to the registry, it's a good idea to remove the image from your local Docker environment,
using this command:
As a best practice, developers shouldn't manually push images to a container registry. Instead, a build pipeline
defined in a tool like GitHub or Azure DevOps should be responsible for this process. Learn more in the Cloud-
Native DevOps chapter.
ACR Tasks
ACR Tasks is a set of features available from the Azure Container Registry. It extends your inner-loop development
cycle by building and managing container images in the Azure cloud. Instead of invoking a docker build and
docker push locally on your development machine, they're automatically handled by ACR Tasks in the cloud.
The following AZ CLI command both builds a container image and pushes it to ACR:
# build container image in ACR and push it into your container regsitry
az acr build --image sample/hello-world:v1 --registry myContainerRegistry008 --file Dockerfile .
As you can see from the previous command block, there's no need to install Docker Desktop on your development
machine. Additionally, you can configure ACR Task triggers to rebuild containers images on both source code and
base image updates.
"Imagine you're a new employee trying to fix a bug in a complex microservices application consisting of
dozens of components, each with their own configuration and backing services. To get started, you must
configure your local development environment so that it can mimic production including setting up your IDE,
building tool chain, containerized service dependencies, a local Kubernetes environment, mocks for backing
services, and more. With all the time involved setting up your development environment, fixing that first bug
could take days. Or you could use Dev Spaces and AKS."
The process for working with Azure Dev Spaces involves the following steps:
1. Create the dev space.
2. Configure the root dev space.
3. Configure a child dev space (for your own version of the system).
4. Connect to the dev space.
All of these steps can be performed using the Azure CLI and new azds command-line tools. For example, to create
a new Azure Dev Space for a given Kubernetes cluster, you would use a command like this one:
Next, you can use the azds prep command to generate the necessary Docker and Helm chart assets for running
the application. Then you run your code in AKS using azds up . The first time you run this command, the Helm
chart will be installed. The containers will be built and deployed according to your instructions. This task may take a
few minutes the first time it's run. However, after you make changes, you can connect to your own child dev space
using azds space select and then deploy and debug your updates in your isolated child dev space. Once you have
your dev space up and running, you can send updates to it by reissuing the azds up command or you can use
built-in tooling in Visual Studio or Visual Studio Code. With VS Code, you use the command palette to connect to
your dev space. Figure 3-12 shows how to launch your web application using Azure Dev Spaces in Visual Studio.
PR EVIO U S NE XT
Scaling containers and serverless applications
5/19/2020 • 2 minutes to read • Edit Online
There are two ways to scale an application: up or out. The former refers to adding capacity to a single resource,
while the latter refers to adding more resources to increase capacity.
PR EVIO U S NE XT
Other container deployment options
5/19/2020 • 2 minutes to read • Edit Online
Aside from Azure Kubernetes Service (AKS), you can also deploy containers to Azure App Service for Containers
and Azure Container Instances.
References
What is Kubernetes?
Installing Kubernetes with Minikube
MiniKube vs Docker Desktop
Visual Studio Tools for Docker
Understanding serverless cold start
Pre-warmed Azure Functions instances
Create a function on Linux using a custom image
Run Azure Functions in a Docker Container
Create a function on Linux using a custom image
Azure Functions with Kubernetes Event Driven Autoscaling
Canary Release
Azure Dev Spaces with VS Code
Azure Dev Spaces with Visual Studio
AKS Multiple Node Pools
AKS Cluster Autoscaler
Tutorial: Scale applications in AKS
Azure Functions scale and hosting
Azure Container Instances Docs
Deploy Container Instance from ACR
PR EVIO U S NE XT
Cloud-native communication patterns
5/19/2020 • 2 minutes to read • Edit Online
When constructing a cloud-native system, communication becomes a significant design decision. How does a
front-end client application communicate with a back-end microservice? How do back-end microservices
communicate with each other? What are the principles, patterns, and best practices to consider when
implementing communication in cloud-native applications?
Communication considerations
In a monolithic application, communication is straightforward. The code modules execute together in the same
executable space (process) on a server. This approach can have performance advantages as everything runs
together in shared memory, but results in tightly coupled code that becomes difficult to maintain, evolve, and scale.
Cloud-native systems implement a microservice-based architecture with many small, independent microservices.
Each microservice executes in a separate process and typically runs inside a container that is deployed to a cluster.
A cluster groups a pool of virtual machines together to form a highly available environment. They're managed with
an orchestration tool, which is responsible for deploying and managing the containerized microservices. Figure 4-1
shows a Kubernetes cluster deployed into the Azure cloud with the fully managed Azure Kubernetes Services.
PR EVIO U S NE XT
Front-end client communication
5/19/2020 • 9 minutes to read • Edit Online
In a cloud-native system, front-end clients (mobile, web, and desktop applications) require a communication
channel to interact with independent back-end microservices.
What are the options?
To keep things simple, a front-end client could directly communicate with the back-end microservices, shown in
Figure 4-2.
Ocelot Gateway
For simple .NET cloud-native applications, you might consider the Ocelot Gateway. Ocelot is an Open Source API
Gateway created for .NET microservices that require a unified point of entry into their system. It's lightweight, fast,
scalable.
Like any API Gateway, its primary functionality is to forward incoming HTTP requests to downstream services.
Additionally, it supports a wide variety of capabilities that are configurable in a .NET Core middleware pipeline. Its
feature set is presented in following table.
Routing Authentication
Each Ocelot gateway specifies the upstream and downstream addresses and configurable features in a JSON
configuration file. The client sends an HTTP request to the Ocelot gateway. Once received, Ocelot passes the
HttpRequest object through its pipeline manipulating it into the state specified by its configuration. At the end of
pipeline, Ocelot creates a new HTTPResponseObject and passes it to the downstream service. For the response,
Ocelot reverses the pipeline, sending the response back to client.
Ocelot is available as a NuGet package. It targets the NET Standard 2.0, making it compatible with both .NET Core
2.0+ and .NET Framework 4.6.1+ runtimes. Ocelot integrates with anything that speaks HTTP and runs on the
platforms which .NET Core supports: Linux, macOS, and Windows. Ocelot is extensible and supports many modern
platforms, including Docker containers, Azure Kubernetes Services, or other public clouds. Ocelot integrates with
open-source packages like Consul, GraphQL, and Netflix's Eureka.
Consider Ocelot for simple cloud-native applications that don't require the rich feature-set of a commercial API
gateway.
Real-time communication
Real-time, or push, communication is another option for front-end applications that communicate with back-end
cloud-native systems over HTTP. Applications, such as financial-tickers, online education, gaming, and job-progress
updates, require instantaneous, real-time responses from the back-end. With normal HTTP communication, there's
no way for the client to know when new data is available. The client must continually poll or send requests to the
server. With real-time communication, the server can push new data to the client at any time.
Real-time systems are often characterized by high-frequency data flows and large numbers of concurrent client
connections. Manually implementing real-time connectivity can quickly become complex, requiring non-trivial
infrastructure to ensure scalability and reliable messaging to connected clients. You could find yourself managing
an instance of Azure Redis Cache and a set of load balancers configured with sticky sessions for client affinity.
Azure SignalR Service is a fully managed Azure service that simplifies real-time communication for your cloud-
native applications. Technical implementation details like capacity provisioning, scaling, and persistent connections
are abstracted away. They're handled for you with a 99.9% service-level agreement. You focus on application
features, not infrastructure plumbing.
Once enabled, a cloud-based HTTP service can push content updates directly to connected clients, including
browser, mobile and desktop applications. Clients are updated without the need to poll the server. Azure SignalR
abstracts the transport technologies that create real-time connectivity, including WebSockets, Server-Side Events,
and Long Polling. Developers focus on sending messages to all or specific subsets of connected clients.
Figure 4-7 shows a set of HTTP Clients connecting to a Cloud-native application with Azure SignalR enabled.
Figure 4-7. Azure SignalR
Another advantage of Azure SignalR Service comes with implementing Serverless cloud-native services. Perhaps
your code is executed on demand with Azure Functions triggers. This scenario can be tricky because your code
doesn't maintain long connections with clients. Azure SignalR Service can handle this situation since the service
already manages connections for you.
Azure SignalR Service closely integrates with other Azure services, such as Azure SQL Database, Service Bus, or
Redis Cache, opening up many possibilities for your cloud-native applications.
PR EVIO U S NE XT
Service-to-service communication
5/19/2020 • 16 minutes to read • Edit Online
Moving from the front-end client, we now address back-end microservices communicate with each other.
When constructing a cloud-native application, you'll want to be sensitive to how back-end services communicate
with each other. Ideally, the less inter-service communication, the better. However, avoidance isn't always possible
as back-end services often rely on one another to complete an operation.
There are several widely accepted approaches to implementing cross-service communication. The type of
communication interaction will often determine the best approach.
Consider the following interaction types:
Query – when a calling microservice requires a response from a called microservice, such as, "Hey, give me
the buyer information for a given customer Id."
Command – when the calling microservice needs another microservice to execute an action but doesn't
require a response, such as, "Hey, just ship this order."
Event – when a microservice, called the publisher, raises an event that state has changed or an action has
occurred. Other microservices, called subscribers, who are interested, can react to the event appropriately.
The publisher and the subscribers aren't aware of each other.
Microservice systems typically use a combination of these interaction types when executing operations that require
cross-service interaction. Let's take a close look at each and how you might implement them.
Queries
Many times, one microservice might need to query another, requiring an immediate response to complete an
operation. A shopping basket microservice may need product information and a price to add an item to its basket.
There are a number of approaches for implementing query operations.
Request/Response Messaging
One option for implementing this scenario is for the calling back-end microservice to make direct HTTP requests to
the microservices it needs to query, shown in Figure 4-8.
Figure 4-8 . Direct HTTP communication
While direct HTTP calls between microservices are relatively simple to implement, care should be taken to
minimize this practice. To start, these calls are always synchronous and will block the operation until a result is
returned or the request times outs. What were once self-contained, independent services, able to evolve
independently and deploy frequently, now become coupled to each other. As coupling among microservices
increase, their architectural benefits diminish.
Executing an infrequent request that makes a single direct HTTP call to another microservice might be acceptable
for some systems. However, high-volume calls that invoke direct HTTP calls to multiple microservices aren't
advisable. They can increase latency and negatively impact the performance, scalability, and availability of your
system. Even worse, a long series of direct HTTP communication can lead to deep and complex chains of
synchronous microservices calls, shown in Figure 4-9:
Commands
Another type of communication interaction is a command. A microservice may need another microservice to
perform an action. The Ordering microservice may need the Shipping microservice to create a shipment for an
approved order. In Figure 4-12, one microservice, called a Producer, sends a message to another microservice, the
Consumer, commanding it to do something.
Events
Message queuing is an effective way to implement communication where a producer can asynchronously send a
consumer a message. However, what happens when many different consumers are interested in the same
message? A dedicated message queue for each consumer wouldn't scale well and would become difficult to
manage.
To address this scenario, we move to the third type of message interaction, the event. One microservice announces
that an action had occurred. Other microservices, if interested, react to the action, or event.
Eventing is a two-step process. For a given state change, a microservice publishes an event to a message broker,
making it available to any other interested microservice. The interested microservice is notified by subscribing to
the event in the message broker. You use the Publish/Subscribe pattern to implement event-based communication.
Figure 4-15 shows a shopping basket microservice publishing an event with two other microservices subscribing
to it.
Figure 4-15 . Event-Driven messaging
Note the event bus component that sits in the middle of the communication channel. It's a custom class that
encapsulates the message broker and decouples it from the underlying application. The ordering and inventory
microservices independently operate the event with no knowledge of each other, nor the shopping basket
microservice. When the registered event is published to the event bus, they act upon it.
With eventing, we move from queuing technology to topics. A topic is similar to a queue, but supports a one-to-
many messaging pattern. One microservice publishes a message. Multiple subscribing microservices can choose
to receive and act upon that message. Figure 4-16 shows a topic architecture.
PR EVIO U S NE XT
gRPC
7/1/2020 • 6 minutes to read • Edit Online
So far in this book, we've focused on REST-based communication. We've seen that REST is a flexible architectural
style that defines CRUD-based operations against entity resources. Clients interact with resources across HTTP with
a request/response communication model. While REST is widely implemented, a newer communication
technology, gRPC, has gained tremendous momentum across the cloud-native community.
What is gRPC?
gRPC is a modern, high-performance framework that evolves the age-old remote procedure call (RPC) protocol. At
the application level, gRPC streamlines messaging between clients and back-end services. Originating from
Google, gRPC is open source and part of the Cloud Native Computing Foundation (CNCF) ecosystem of cloud-
native offerings. CNCF considers gRPC an incubating project. Incubating means end users are using the technology
in production applications, and the project has a healthy number of contributors.
A typical gRPC client app will expose a local, in-process function that implements a business operation. Under the
covers, that local function invokes another function on a remote machine. What appears to be a local call
essentially becomes a transparent out-of-process call to a remote service. The RPC plumbing abstracts the point-
to-point networking communication, serialization, and execution between computers.
In cloud-native applications, developers often work across programming languages, frameworks, and technologies.
This interoperability complicates message contracts and the plumbing required for cross-platform communication.
gRPC provides a "uniform horizontal layer" that abstracts these concerns. Developers code in their native platform
focused on business functionality, while gRPC handles communication plumbing.
gRPC offers comprehensive support across most popular development stacks, including Java, JavaScript, C#, Go,
Swift, and NodeJS.
gRPC Benefits
gRPC uses HTTP/2 for its transport protocol. While compatible with HTTP 1.1, HTTP/2 features many advanced
capabilities:
A binary protocol for data transport - unlike HTTP 1.1, which sends data as clear text.
Multiplexing support for sending multiple parallel requests over the same connection - HTTP 1.1 limits
processing to one request/response message at a time.
Bidirectional full-duplex communication for sending both client requests and server responses simultaneously.
Built-in streaming enabling requests and responses to asynchronously stream large data sets.
gRPC is lightweight and highly performant. It can be up to 8x faster than JSON serialization with messages 60-80%
smaller. In Microsoft Windows Communication Foundation (WCF) parlance, gRPC performance exceeds the speed
and efficiency of the highly optimized NetTCP bindings. Unlike NetTCP, which favors the Microsoft stack, gRPC is
cross-platform.
Protocol Buffers
gRPC embraces an open-source technology called Protocol Buffers. They provide a highly efficient and platform-
neutral serialization format for serializing structured messages that services send to each other. Using a cross-
platform Interface Definition Language (IDL), developers define a service contract for each microservice. The
contract, implemented as a text-based .proto file, describes the methods, inputs, and outputs for each service. The
same contract file can be used for gRPC clients and services built on different development platforms.
Using the proto file, the Protobuf compiler, protoc , generates both client and service code for your target
platform. The code includes the following components:
Strongly typed objects, shared by the client and service, that represent the service operations and data elements
for a message.
A strongly typed base class with the required network plumbing that the remote gRPC service can inherit and
extend.
A client stub that contains the required plumbing to invoke the remote gRPC service.
At runtime, each message is serialized as a standard Protobuf representation and exchanged between the client
and remote service. Unlike JSON or XML, Protobuf messages are serialized as compiled binary bytes.
The book, gRPC for WCF Developers, available from the Microsoft Architecture site, provides in-depth coverage of
gRPC and Protocol Buffers.
gRPC usage
Favor gRPC for the following scenarios:
Synchronous backend microservice-to-microservice communication where an immediate response is required
to continue processing.
Polyglot environments that need to support mixed programming platforms.
Low latency and high throughput communication where performance is critical.
Point-to-point real-time communication - gRPC can push messages in real time without polling and has
excellent support for bi-directional streaming.
Network constrained environments – binary gRPC messages are always smaller than an equivalent text-based
JSON message.
At the time, of this writing, gRPC is primarily used with backend services. Most modern browsers can't provide the
level of HTTP/2 control required to support a front-end gRPC client. That said, there's an early initiative that enables
gRPC communication from browser-based apps built with JavaScript or Blazor WebAssembly technologies. The
gRPC-Web for .NET enables an ASP.NET Core gRPC app to support gRPC features in browser apps:
Strongly typed, code-generated clients
Compact Protobuf messages
Server streaming
gRPC implementation
The microservice reference architecture, eShop on Containers, from Microsoft, shows how to implement gRPC
services in .NET Core applications. Figure 4-22 presents the back-end architecture.
Figure 4-22 . Backend architecture for eShop on Containers
In the previous figure, note how eShop embraces the Backend for Frontends pattern (BFF) by exposing multiple API
gateways. We discussed the BFF pattern earlier in this chapter. Pay close attention to the Aggregator microservice
(in gray) that sits between the Web-Shopping API Gateway and backend Shopping microservices. The Aggregator
receives a single request from a client, dispatches it to various microservices, aggregates the results, and sends
them back to the requesting client. Such operations typically require synchronous communication as to produce an
immediate response. In eShop, backend calls from the Aggregator are performed using gRPC as shown in Figure
4-23.
Looking ahead
Looking ahead, gRPC will continue to gain traction for cloud-native systems. The performance benefits and ease of
development are compelling. However, REST will likely be around for a long time. It excels for publicly exposed APIs
and for backward compatibility reasons.
PR EVIO U S NE XT
Service Mesh communication infrastructure
5/19/2020 • 2 minutes to read • Edit Online
Throughout this chapter, we've explored the challenges of microservice communication. We said that development
teams need to be sensitive to how back-end services communicate with each other. Ideally, the less inter-service
communication, the better. However, avoidance isn't always possible as back-end services often rely on one another
to complete operations.
We explored different approaches for implementing synchronous HTTP communication and asynchronous
messaging. In each of the cases, the developer is burdened with implementing communication code.
Communication code is complex and time intensive. Incorrect decisions can lead to significant performance issues.
A more modern approach to microservice communication centers around a new and rapidly evolving technology
entitled Service Mesh. A service mesh is a configurable infrastructure layer with built-in capabilities to handle
service-to-service communication, resiliency, and many cross-cutting concerns. It moves the responsibility for
these concerns out of the microservices and into service mesh layer. Communication is abstracted away from your
microservices.
A key component of a service mesh is a proxy. In a cloud-native application, an instance of a proxy is typically
colocated with each microservice. While they execute in separate processes, the two are closely linked and share
the same lifecycle. This pattern, known as the Sidecar pattern, and is shown in Figure 4-24.
Summary
In this chapter, we discussed cloud-native communication patterns. We started by examining how front-end clients
communicate with back-end microservices. Along the way, we talked about API Gateway platforms and real-time
communication. We then looked at how microservices communicate with other back-end services. We looked at
both synchronous HTTP communication and asynchronous messaging across services. We covered gRPC, an
upcoming technology in the cloud-native world. Finally, we introduced a new and rapidly evolving technology
entitled Service Mesh that can streamline microservice communication.
Special emphasis was on managed Azure services that can help implement communication in cloud-native
systems:
Azure Application Gateway
Azure API Management
Azure SignalR Service
Azure Storage Queues
Azure Service Bus
Azure Event Grid
Azure Event Hub
We next move to distributed data in cloud-native systems and the benefits and challenges that it presents.
References
.NET Microservices: Architecture for Containerized .NET applications
Designing Interservice Communication for Microservices
Azure SignalR Service, a fully managed service to add real-time functionality
Azure API Gateway Ingress Controller
About Ingress in Azure Kubernetes Service (AKS)
gRPC Documentation
gRPC for WCF Developers
Comparing gRPC Services with HTTP APIs
Building gRPC Services with .NET video
PR EVIO U S NE XT
Distributed data
7/1/2020 • 10 minutes to read • Edit Online
As we've seen throughout this book, a cloud-native approach changes the way you design, deploy, and manage
applications. It also changes the way you manage and store data.
Figure 5-1 contrasts the differences.
Database-per-microservice, why?
This database per microservice provides many benefits, especially for systems that must evolve rapidly and
support massive scale. With this model...
Domain data is encapsulated within the service
Data schema can evolve without directly impacting other services
Each data store can independently scale
A data store failure in one service won't directly impact other services
Segregating data also enables each microservice to implement the data store type that is best optimized for its
workload, storage needs, and read/write patterns. Choices include relational, document, key-value, and even
graph-based data stores.
Figure 5-2 presents the principle of polyglot persistence in a cloud-native system.
Cross-service queries
While microservices are independent and focus on specific functional capabilities, like inventory, shipping, or
ordering, they frequently require integration with other microservices. Often the integration involves one
microservice querying another for data. Figure 5-3 shows the scenario.
Figure 5-3 . Querying across microservices
In the preceding figure, we see a shopping basket microservice that adds an item to a user's shopping basket.
While the data store for this microservice contains basket and line item data, it doesn't maintain product or pricing
data. Instead, those data items are owned by the catalog and pricing microservices. This presents a problem. How
can the shopping basket microservice add a product to the user's shopping basket when it doesn't have product
nor pricing data in its database?
One option discussed in Chapter 4 is a direct HTTP call from the shopping basket to the catalog and pricing
microservices. However, in chapter 4, we said synchronous HTTP calls couple microservices together, reducing
their autonomy and diminishing their architectural benefits.
We could also implement a request-reply pattern with separate inbound and outbound queues for each service.
However, this pattern is complicated and requires plumbing to correlate request and response messages. While it
does decouple the backend microservice calls, the calling service must still synchronously wait for the call to
complete. Network congestion, transient faults, or an overloaded microservice and can result in long-running and
even failed operations.
Instead, a widely accepted pattern for removing cross-service dependencies is the Materialized View Pattern,
shown in Figure 5-4.
Distributed transactions
While querying data across microservices is difficult, implementing a transaction across several microservices is
even more complex. The inherent challenge of maintaining data consistency across independent data sources in
different microservices can't be understated. The lack of distributed transactions in cloud-native applications
means that you must manage distributed transactions programmatically. You move from a world of immediate
consistency to that of eventual consistency.
Figure 5-5 shows the problem.
PR EVIO U S NE XT
Relational vs. NoSQL data
5/19/2020 • 20 minutes to read • Edit Online
Relational and NoSQL are two types of database systems commonly implemented in cloud-native apps. They're
built differently, store data differently, and accessed differently. In this section, we'll look at both. Later in this
chapter, we'll look at an emerging database technology called NewSQL.
Relational databases have been a prevalent technology for decades. They're mature, proven, and widely
implemented. Competing database products, tooling, and expertise abound. Relational databases provide a store of
related data tables. These tables have a fixed schema, use SQL (Structured Query Language) to manage data, and
support ACID guarantees.
No-SQL databases refer to high-performance, non-relational data stores. They excel in their ease-of-use, scalability,
resilience, and availability characteristics. Instead of joining tables of normalized data, NoSQL stores unstructured
or semi-structured data, often in key-value pairs or JSON documents. No-SQL databases typically don't provide
ACID guarantees beyond the scope of a single database partition. High volume services that require sub second
response time favor NoSQL datastores.
The impact of NoSQL technologies for distributed cloud-native systems can't be overstated. The proliferation of
new data technologies in this space has disrupted solutions that once exclusively relied on relational databases.
NoSQL databases include several different models for accessing and managing data, each suited to specific use
cases. Figure 5-9 presents four common models.
M O DEL C H A RA C T ERIST IC S
Key Value Store The simplest of the NoSQL databases, data is represented as a
collection of key-value pairs.
Graph Store Data is stored in a graph structure as node, edge, and data
properties.
Nowadays, care must be taken when conidering the CAP theorem constraints. A new type of database, called
NewSQL, has emerged which extends the relational database engine to support both horizontal scalability and
the scalable performance of NoSQL systems.
You have high volume workloads that require large scale Your workload volume is consistent and requires medium to
large scale
Your workloads don't require ACID guarantees ACID guarantees are required
Your data is dynamic and frequently changes Your data is predictable and highly structured
You need fast writes and write safety isn't critical Write safety is a requirement
Data retrieval is simple and tends to be flat You work with complex queries and reports
Your data requires a wide geographic distribution Your users are more centralized
Your application will be deployed to commodity hardware, Your application will be deployed to large, high-end hardware
such as with public clouds
In the next sections, we'll explore the options available in the Azure cloud for storing and managing your cloud-
native data.
Database as a Service
To start, you could provision an Azure virtual machine and install your database of choice for each service. While
you'd have full control over the environment, you'd forgo many built-in features of the cloud platform. You'd also
be responsible for managing the virtual machine and database for each service. This approach could quickly
become time-consuming and expensive.
Instead, cloud-native applications favor data services exposed as a Database as a Service (DBaaS). Fully managed
by a cloud vendor, these services provide built-in security, scalability, and monitoring. Instead of owning the
service, you simply consume it as a backing service. The provider operates the resource at scale and bears the
responsibility for performance and maintenance.
They can be configured across cloud availability zones and regions to achieve high availability. They all support
just-in-time capacity and a pay-as-you-go model. Azure features different kinds of managed data service options,
each with specific benefits.
We'll first look at relational DBaaS services available in Azure. You'll see that Microsoft's flagship SQL Server
database is available along with several open-source options. Then, we'll talk about the NoSQL data services in
Azure.
SQL API Proprietary API that supports JSON documents and SQL-
based queries
Gremlin API Supports Gremlin API with graph-based nodes and edge data
representations
Development teams can migrate existing Mongo, Gremlin, or Cassandra databases into Cosmos DB with minimal
changes to data or code. For new apps, development teams can choose among open-source options or the built-in
SQL API model.
Internally, Cosmos stores the data in a simple struct format made up of primitive data types. For each request,
the database engine translates the primitive data into the model representation you've selected.
In the previous table, note the Table API option. This API is an evolution of Azure Table Storage. Both share the same
underlying table model, but the Cosmos DB Table API adds premium enhancements not available in the Azure
Storage API. The following table contrasts the features.
A Z URE TA B L E STO RA GE A Z URE C O SM O S DB
Throughput Limit of 20,000 operations per table 10 Million operations per table
Global Distribution Single region with optional single Turnkey distributions to all regions with
secondary read region automatic failover
Indexing Available for partition and row key Automatic indexing of all properties
properties only
Microservices that consume Azure Table storage can easily migrate to the Cosmos DB Table API. No code changes
are required.
Tunable consistency
Earlier in the Relational vs. NoSQL section, we discussed the subject of data consistency. Data consistency refers to
the integrity of your data. Cloud-native services with distributed data rely on replication and must make a
fundamental tradeoff between read consistency, availability, and latency.
Most distributed databases allow developers to choose between two consistency models: strong consistency
and eventual consistency. Strong consistency is the gold standard of data programmability. It guarantees that a
query will always return the most current data - even if the system must incur latency waiting for an update to
replicate across all database copies. While a database configured for eventual consistency will return data
immediately, even if that data isn't the most current copy. The latter option enables higher availability, greater scale,
and increased performance.
Azure Cosmos DB offers five well-defined consistency models shown in Figure 5-13.
Constant Prefix Reads are still eventual, but data is returned in the ordering in
which it is written.
Session Guarantees you can read any data written during the current
session. It is the default consistency level.
In the article Getting Behind the 9-Ball: Cosmos DB Consistency Levels Explained, Microsoft Program Manager
Jeremy Likness provides an excellent explanation of the five models.
Partitioning
Azure Cosmos DB embraces automatic partitioning to scale a database to meet the performance needs of your
cloud-native services.
You manage data in Cosmos DB data by creating databases, containers, and items.
Containers live in a Cosmos DB database and represent a schema-agnostic grouping of items. Items are the data
that you add to the container. They're represented as documents, rows, nodes, or edges. All items added to a
container are automatically indexed.
To partition the container, items are divided into distinct subsets called logical partitions. Logical partitions are
populated based on the value of a partition key that is associated with each item in a container. Figure 5-14 shows
two containers each with a logical partition based on a partition key value.
NewSQL databases
NewSQL is an emerging database technology that combines the distributed scalability of NoSQL with the
ACID guarantees of a relational database. NewSQL databases are important for business systems that must
process high-volumes of data, across distributed environments, with full transactional support and ACID
compliance. While a NoSQL database can provide massive scalability, it does not guarantee data consistency.
Intermittent problems from inconsistent data can place a burden on the development team. Developers must
construct safeguards into their microservice code to manage problems caused by inconsistent data.
The Cloud Native Computing Foundation (CNCF) features several NewSQL database projects.
P RO JEC T C H A RA C T ERIST IC S
The open-source projects in the previous figure are available from the Cloud Native Computing Foundation. Three
of the offerings are full database products, which include .NET Core support. The other, Vitess, is a database
clustering system that horizontally scales large clusters of MySQL instances.
A key design goal for NewSQL databases is to work natively in Kubernetes, taking advantage of the platform's
resiliency and scalability.
NewSQL databases are designed to thrive in ephemeral cloud environments where underlying virtual machines
can be restarted or rescheduled at a moment’s notice. The databases are designed to survive node failures without
data loss nor downtime. CockroachDB, for example, is able to survive a machine loss by maintaining three
consistent replicas of any data across the nodes in a cluster.
Kubernetes uses a Services construct to allow a client to address a group of identical NewSQL databases processes
from a single DNS entry. By decoupling the database instances from the address of the service with which it's
associated, we can scale without disrupting existing application instances. Sending a request to any service at a
given time will always yield the same result.
In this scenario, all database instances are equal. There are no primary or secondary relationships. Techniques like
consensus replication found in CockroachDB allow any database node to handle any request. If the node that
receives a load-balanced request has the data it needs locally, it responds immediately. If not, the node becomes a
gateway and forwards the request to the appropriate nodes to get the correct answer. From the client's perspective,
every database node is the same: They appear as a single logical database with the consistency guarantees of a
single-machine system, despite having dozens or even hundreds of nodes that are working behind the scenes.
For a detailed look at the mechanics behind NewSQL databases, see the DASH: Four Properties of Kubernetes-
Native Databases article.
PR EVIO U S NE XT
Caching in a cloud-native app
5/19/2020 • 3 minutes to read • Edit Online
The benefits of caching are well understood. The technique works by temporarily copying frequently accessed data
from a backend data store to fast storage that's located closer to the application. Caching is often implemented
where...
Data remains relatively static.
Data access is slow, especially compared to the speed of the cache.
Data is subject to high levels of contention.
Why?
As discussed in the Microsoft caching guidance, caching can increase performance, scalability, and availability for
individual microservices and the system as a whole. It reduces the latency and contention of handling large
volumes of concurrent requests to a data store. As data volume and the number of users increase, the greater the
benefits of caching become.
Caching is most effective when a client repeatedly reads data that is immutable or that changes infrequently.
Examples include reference information such as product and pricing information, or shared static resources that
are costly to construct.
While microservices should be stateless, a distributed cache can support concurrent access to session state data
when absolutely required.
Also consider caching to avoid repetitive computations. If an operation transforms data or performs a complicated
calculation, cache the result for subsequent requests.
Caching architecture
Cloud native applications typically implement a distributed caching architecture. The cache is hosted as a cloud-
based backing service, separate from the microservices. Figure 5-15 shows the architecture.
PR EVIO U S NE XT
Elasticsearch in a cloud-native app
5/19/2020 • 2 minutes to read • Edit Online
Elasticsearch is a distributed search and analytics system that enables complex search capabilities across diverse
types of data. It's open source and widely popular. Consider how the following companies integrate Elasticsearch
into their application:
Wikipedia for full-text and incremental (search as you type) searching.
GitHub to index and expose over 8 million code repositories.
Docker for making its container library discoverable.
Elasticsearch is built on top of the Apache Lucene full-text search engine. Lucene provides high-performance
document indexing and querying. It indexes data with an inverted indexing scheme – instead of mapping pages to
keywords, it maps keywords to pages just like a glossary at the end of a book. Lucene has powerful query syntax
capabilities and can query data by:
Term (a full word)
Prefix (starts-with word)
Wildcard (using "*" or "?" filters)
Phrase (a sequence of text in a document)
Boolean value (complex searches combining queries)
While Lucene provides low-level plumbing for searching, Elasticsearch provides the server that sits on top of
Lucene. Elasticsearch adds higher-level functionality to simplify working Lucene, including a RESTful API to access
Lucene's indexing and searching functionality. It also provides a distributed infrastructure capable of massive
scalability, fault tolerance, and high availability.
For larger cloud-native applications with complex search requirements, Elasticsearch is available as managed
service in Azure. The Microsoft Azure Marketplace features preconfigured templates which developers can use to
deploy an Elasticsearch cluster on Azure.
From the Microsoft Azure Marketplace, developers can use preconfigured templates built to quickly deploy an
Elasticsearch cluster on Azure. Using the Azure-managed offering, you can deploy up to 50 data nodes, 20
coordinating nodes, and three dedicated master nodes.
Summary
This chapter presented a detailed look at data in cloud-native systems. We started by contrasting data storage in
monolithic applications with data storage patterns in cloud-native systems. We looked at data patterns
implemented in cloud-native systems, including cross-service queries, distributed transactions, and patterns to
deal with high-volume systems. We contrasted SQL with NoSQL data. We looked at data storage options available
in Azure that include both Microsoft-centric and open-source options. Finally, we discussed caching and
Elasticsearch in a cloud-native application.
References
Command and Query Responsibility Segregation (CQRS) pattern
Event Sourcing pattern
Why isn't RDBMS Partition Tolerant in CAP Theorem and why is it Available?
Materialized View
All you really need to know about open source databases
Compensating Transaction pattern
Saga Pattern
Saga Patterns | How to implement business transactions using microservices
Compensating Transaction pattern
Getting Behind the 9-Ball: Cosmos DB Consistency Levels Explained
Exploring the different types of NoSQL Databases Part II
On RDBMS, NoSQL and NewSQL databases. Interview with John Ryan
SQL vs NoSQL vs NewSQL: The Full Comparison
DASH: Four Properties of Kubernetes-Native Databases
CockroachDB
TiDB
YugabyteDB
Vitess
Elasticsearch: The Definitive Guide
Introduction to Apache Lucene
PR EVIO U S NE XT
Cloud-native resiliency
5/19/2020 • 2 minutes to read • Edit Online
Resiliency is the ability of your system to react to failure and still remain functional. It's not about avoiding failure,
but accepting failure and constructing your cloud-native services to respond to it. You want to return to a fully
functioning state quickly as possible.
Unlike traditional monolithic applications, where everything runs together in a single process, cloud-native
systems embrace a distributed architecture as shown in Figure 6-1:
P O L IC Y EXP ERIEN C E
Timeout Places limit on the duration for which a caller can wait for a
response.
Note how in the previous figure the resiliency policies apply to request messages, whether coming from an
external client or back-end service. The goal is to compensate the request for a service that might be momentarily
unavailable. These short-lived interruptions typically manifest themselves with the HTTP status codes shown in the
following table.
H T T P STAT US C O DE C A USE
Question: Would you retry an HTTP Status Code of 403 - Forbidden? No. Here, the system is functioning properly,
but informing the caller that they aren't authorized to perform the requested operation. Care must be taken to
retry only those operations caused by failures.
As recommended in Chapter 1, Microsoft developers constructing cloud-native applications should target the .NET
Core platform. Version 2.1 introduced the HTTPClientFactory library for creating HTTP Client instances for
interacting with URL-based resources. Superseding the original HTTPClient class, the factory class supports many
enhanced features, one of which is tight integration with the Polly resiliency library. With it, you can easily define
resiliency policies in the application Startup class to handle partial failures and connectivity issues.
Next, let's expand on retry and circuit breaker patterns.
Retry pattern
In a distributed cloud-native environment, calls to services and cloud resources can fail because of transient (short-
lived) failures, which typically correct themselves after a brief period of time. Implementing a retry strategy helps a
cloud-native service mitigate these scenarios.
The Retry pattern enables a service to retry a failed request operation a (configurable) number of times with an
exponentially increasing wait time. Figure 6-2 shows a retry in action.
Building a reliable application in the cloud is different from traditional on-premises application development. While
historically you purchased higher-end hardware to scale up, in a cloud environment you scale out. Instead of trying
to prevent failures, the goal is to minimize their effects and keep the system stable.
That said, reliable cloud applications display distinct characteristics:
They're resilient, recover gracefully from problems, and continue to function.
They're highly available (HA) and run as designed in a healthy state with no significant downtime.
Understanding how these characteristics work together - and how they affect cost - is essential to building a
reliable cloud-native application. We'll next look at ways that you can build resiliency and availability into your
cloud-native applications leveraging features from the Azure cloud.
PR EVIO U S NE XT
Resilient communications
5/19/2020 • 3 minutes to read • Edit Online
Throughout this book, we've embraced a microservice-based architectural approach. While such an architecture
provides important benefits, it presents many challenges:
Out-of-process network communication. Each microservice communicates over a network protocol that
introduces network congestion, latency, and transient faults.
Service discovery. How do microservices discover and communicate with each other when running across a
cluster of machines with their own IP addresses and ports?
Resiliency. How do you manage short-lived failures and keep the system stable?
Load balancing. How does inbound traffic get distributed across multiple instances of a microservice?
Security. How are security concerns such as transport-level encryption and certificate management
enforced?
Distributed Monitoring. - How do you correlate and capture traceability and monitoring for a single request
across multiple consuming microservices?
You can address these concerns with different libraries and frameworks, but the implementation can be expensive,
complex, and time-consuming. You also end up with infrastructure concerns coupled to business logic.
Service mesh
A better approach is an evolving technology entitled Service Mesh. A service mesh is a configurable infrastructure
layer with built-in capabilities to handle service communication and the other challenges mentioned above. It
decouples these concerns by moving them into a service proxy. The proxy is deployed into a separate process
(called a sidecar) to provide isolation from business code. However, the sidecar is linked to the service - it's created
with it and shares its lifecycle. Figure 6-7 shows this scenario.
PR EVIO U S NE XT
Monitoring and health
5/19/2020 • 2 minutes to read • Edit Online
Microservices and cloud-native applications go hand in hand with good DevOps practices. DevOps is many things
to many people but perhaps one of the better definitions comes from cloud advocate and DevOps evangelist
Donovan Brown:
"DevOps is the union of people, process, and products to enable continuous delivery of value to our end users."
Unfortunately, with terse definitions, there's always room to say more things. One of the key components of
DevOps is ensuring that the applications running in production are functioning properly and efficiently. To gauge
the health of the application in production, it's necessary to monitor the various logs and metrics being produced
from the servers, hosts, and the application proper. The number of different services running in support of a cloud-
native application makes monitoring the health of individual components and the application as a whole a critical
challenge.
PR EVIO U S NE XT
Observability patterns
5/19/2020 • 7 minutes to read • Edit Online
Just as patterns have been developed to aid in the layout of code in applications, there are patterns for operating
applications in a reliable way. Three useful patterns in maintaining applications have emerged: logging ,
monitoring , and aler ts .
Figure 7-4 . Logs from various sources are ingested into a centralized log store.
Challenges with detecting and responding to potential app health
issues
Some applications aren't mission critical. Maybe they're only used internally, and when a problem occurs, the user
can contact the team responsible and the application can be restarted. However, customers often have higher
expectations for the applications they consume. You should know when problems occur with your application
before users do, or before users notify you. Otherwise, the first you know about a problem may be when you
notice an angry deluge of social media posts deriding your application or even your organization.
Some scenarios you may need to consider include:
One service in your application keeps failing and restarting, resulting in intermittent slow responses.
At some times of the day, your application's response time is slow.
After a recent deployment, load on the database has tripled.
Implemented properly, monitoring can let you know about conditions that will lead to problems, letting you
address underlying conditions before they result in any significant user impact.
Monitoring cloud-native apps
Some centralized logging systems take on an additional role of collecting telemetry outside of pure logs. They can
collect metrics, such as time to run a database query, average response time from a web server, and even CPU load
averages and memory pressure as reported by the operating system. In conjunction with the logs, these systems
can provide a holistic view of the health of nodes in the system and the application as a whole.
The metric-gathering capabilities of the monitoring tools can also be fed manually from within the application.
Business flows that are of particular interest such as new users signing up or orders being placed, may be
instrumented such that they increment a counter in the central monitoring system. This unlocks the monitoring
tools to not only monitor the health of the application but the health of the business.
Queries can be constructed in the log aggregation tools to look for certain statistics or patterns, which can then be
displayed in graphical form, on custom dashboards. Frequently, teams will invest in large, wall-mounted displays
that rotate through the statistics related to an application. This way, it's simple to see the problems as they occur.
Cloud-native monitoring tools provide real-time telemetry and insight into apps regardless of whether they're
single-process monolithic applications or distributed microservice architectures. They include tools that allow
collection of data from the app as well as tools for querying and displaying information about the app's health.
PR EVIO U S NE XT
Logging with Elastic Stack
5/19/2020 • 4 minutes to read • Edit Online
There are many good centralized logging tools and they vary in cost from being free, open-source tools, to more
expensive options. In many cases, the free tools are as good as or better than the paid offerings. One such tool is a
combination of three open-source components: Elastic search, Logstash, and Kibana.
Collectively these tools are known as the Elastic Stack or ELK stack.
Elastic Stack
The Elastic Stack is a powerful option for gathering information from a Kubernetes cluster. Kubernetes supports
sending logs to an Elasticsearch endpoint, and for the most part, all you need to get started is to set the
environment variables as shown in Figure 7-5:
KUBE_LOGGING_DESTINATION=elasticsearch
KUBE_ENABLE_NODE_LOGGING=true
Figure 7-6 . An example of a Kibana dashboard showing the results of a query against logs that are ingested from
Kubernetes
Figure 7-7 . Serilog config for writing log information directly to logstash over HTTP
Logstash would use a configuration like the one shown in Figure 7-8.
input {
http {
#default host 0.0.0.0:8080
codec => json
}
}
output {
elasticsearch {
hosts => "elasticsearch:9200"
index=>"sales-%{+xxxx.ww}"
}
}
Elastic search
Elastic search is a powerful search engine that can index logs as they arrive. It makes running queries against the
logs quick. Elastic search can handle huge quantities of logs and, in extreme cases, can be scaled out across many
nodes.
Log messages that have been crafted to contain parameters or that have had parameters split from them through
Logstash processing, can be queried directly as Elasticsearch preserves this information.
A query that searches for the top 10 pages visited by [email protected] , appears in Figure 7-9.
"query": {
"match": {
"user": "[email protected]"
}
},
"aggregations": {
"top_10_pages": {
"terms": {
"field": "page",
"size": 10
}
}
}
Figure 7-9 . An Elasticsearch query for finding top 10 pages visited by a user
References
Install Elastic Stack on Azure
PR EVIO U S NE XT
Monitoring in Azure Kubernetes Services
5/19/2020 • 2 minutes to read • Edit Online
The built-in logging in Kubernetes is primitive. However, there are some great options for getting the logs out of
Kubernetes and into a place where they can be properly analyzed. If you need to monitor your AKS clusters,
configuring Elastic Stack for Kubernetes is a great solution.
Log.Finalize()
Logging is one of the most overlooked and yet most important parts of deploying any application at scale. As the
size and complexity of applications increase, then so does the difficulty of debugging them. Having top quality logs
available makes debugging much easier and moves it from the realm of "nearly impossible" to "a pleasant
experience".
PR EVIO U S NE XT
Azure Monitor
5/19/2020 • 4 minutes to read • Edit Online
No other cloud provider has as mature of a cloud application monitoring solution as that found in Azure. Azure
Monitor is an umbrella name for a collection of tools designed to provide visibility into the state of your system,
insights into any problems, and optimization of your application.
Figure 7-12 . Azure Monitor, a collection to tools to provide insight into how a cloud-native application is
functioning.
Reporting data
Once the data is gathered, it can be manipulated, summarized, and plotted into charts, which allow users to
instantly see when there are problems. These charts can be gathered into dashboards or into Workbooks, a multi-
page report designed to tell a story about some aspect of the system.
No modern application would be complete without some artificial intelligence or machine learning. To this end,
data can be passed to the various machine learning tools in Azure to allow you to extract trends and information
that would otherwise be hidden.
Application Insights provides a powerful query language called Kusto that can be used to find records, summarize
them, and even plot charts. For instance, this query will locate all the records for the month of November 2007,
group them by state, and plot the top 10 as a pie chart.
StormEvents
| where StartTime >= datetime(2007-11-01) and StartTime < datetime(2007-12-01)
| summarize count() by State
| top 10 by count_
| render piechart
Dashboards
There are several different dashboard technologies that may be used to surface the information from Azure
Monitor. Perhaps the simplest is to just run queries in Application Insights and plot the data into a chart.
Figure 7-14 . An example of Application Insights charts embedded in the main Azure Dashboard.
These charts can then be embedded in the Azure portal proper through use of the dashboard feature. For users
with more exacting requirements, such as being able to drill down into several tiers of data, Azure Monitor data is
available to Power BI. Power BI is an industry-leading, enterprise class, business intelligence tool that can aggregate
data from many different data sources.
Alerts
Sometimes, having data dashboards is insufficient. If nobody is awake to watch the dashboards, then it can still be
many hours before a problem is addressed, or even detected. To this end, Azure Monitor also provides a top notch
alerting solution. Alerts can be triggered by a wide range of conditions including:
Metric values
Log search queries
Activity Log events
Health of the underlying Azure platform
Tests for web site availability
When triggered, the alerts can perform a wide variety of tasks. On the simple side, the alerts may just send an e-
mail notification to a mailing list or a text message to an individual. More involved alerts might trigger a workflow
in a tool such as PagerDuty, which is aware of who is on call for a particular application. Alerts can trigger actions
in Microsoft Flow unlocking near limitless possibilities for workflows.
As common causes of alerts are identified, the alerts can be enhanced with details about the common causes of the
alerts and the steps to take to resolve them. Highly mature cloud-native application deployments may opt to kick
off self-healing tasks, which perform actions such as removing failing nodes from a scale set or triggering an
autoscaling activity. Eventually it may no longer be necessary to wake up on-call personnel at 2AM to resolve a
live-site issue as the system will be able to adjust itself to compensate or at least limp along until somebody arrives
at work the next morning.
Azure Monitor automatically leverages machine learning to understand the normal operating parameters of
deployed applications. This enables it to detect services that are operating outside of their normal parameters. For
instance, the typical weekday traffic on the site might be 10,000 requests per minute. And then, on a given week,
suddenly the number of requests hits a highly unusual 20,000 requests per minute. Smart Detection will notice this
deviation from the norm and trigger an alert. At the same time, the trend analysis is smart enough to avoid firing
false positives when the traffic load is expected.
References
Azure Monitor
PR EVIO U S NE XT
Identity
5/19/2020 • 2 minutes to read • Edit Online
Most software applications need to have some knowledge of the user or process that is calling them. The user or
process interacting with an application is known as a security principal, and the process of authenticating and
authorizing these principals is known as identity management, or simply identity. Simple applications may include
all of their identity management within the application, but this approach doesn't scale well with many applications
and many kinds of security principals. Windows supports the use of Active Directory to provide centralized
authentication and authorization.
While this solution is effective within corporate networks, it isn't designed for use by users or applications that are
outside of the AD domain. With the growth of Internet-based applications and the rise of cloud-native apps,
security models have evolved.
In today's cloud-native identity model, architecture is assumed to be distributed. Apps can be deployed anywhere
and may communicate with other apps anywhere. Clients may communicate with these apps from anywhere, and
in fact, clients may consist of any combination of platforms and devices. Cloud-native identity solutions leverage
open standards to achieve secure application access from clients. These clients range from human users on PCs or
phones, to other apps hosted anywhere online, to set-top boxes and IOT devices running any software platform
anywhere in the world.
Modern cloud-native identity solutions typically leverage access tokens that are issued by a secure token
service/server (STS) to a security principal once their identity is determined. The access token, typically a JSON
Web Token (JWT), includes claims about the security principal. These claims will minimally include the user's
identity but may also include additional claims that can be used by applications to determine the level of access to
grant the principal.
Typically, the STS is only responsible for authenticating the principal. Determining their level of access to resources
is left to other parts of the application.
References
Microsoft identity platform
PR EVIO U S NE XT
Authentication and authorization in cloud-native
apps
5/19/2020 • 2 minutes to read • Edit Online
Authentication is the process of determining the identity of a security principal. Authorization is the act of granting
an authenticated principal permission to perform an action or access a resource. Sometimes authentication is
shortened to AuthN and authorization is shortened to AuthZ . Cloud-native applications need to rely on open
HTTP-based protocols to authenticate security principals since both clients and applications could be running
anywhere in the world on any platform or device. The only common factor is HTTP.
Many organizations still rely on local authentication services like Active Directory Federation Services (ADFS).
While this approach has traditionally served organizations well for on premises authentication needs, cloud-native
applications benefit from systems designed specifically for the cloud. A recent 2019 United Kingdom National
Cyber Security Centre (NCSC) advisory states that "organizations using Azure AD as their primary authentication
source will actually lower their risk compared to ADFS." Some reasons outlined in this analysis include:
Access to full set of Microsoft credential protection technologies.
Most organizations are already relying on Azure AD to some extent.
Double hashing of NTLM hashes ensures compromise won't allow credentials that work in local Active
Directory.
References
Authentication basics
Access tokens and claims
It may be time to ditch your on premises authentication services
PR EVIO U S NE XT
Azure Active Directory
5/19/2020 • 2 minutes to read • Edit Online
Microsoft Azure Active Directory (Azure AD) offers identity and access management as a service. Customers use it
to configure and maintain who users are, what information to store about them, who can access that information,
who can manage it, and what apps can access it. AAD can authenticate users for applications configured to use it,
providing a single sign-on (SSO) experience. It can be used on its own or be integrated with Windows AD running
on premises.
Azure AD is built for the cloud. It's truly a cloud-native identity solution that uses a REST-based Graph API and
OData syntax for queries, unlike Windows AD, which uses LDAP. On premises Active Directory can sync user
attributes to the cloud using Identity Sync Services, allowing all authentication to take place in the cloud using
Azure AD. Alternately, authentication can be configured via Connect to pass back to local Active Directory via ADFS
to be completed by Windows AD on premises.
Azure AD supports company branded sign-in screens, multi-factory authentication, and cloud-based application
proxies that are used to provide SSO for applications hosted on premises. It offers different kinds of security
reporting and alert capabilities.
References
Microsoft identity platform
PR EVIO U S NE XT
IdentityServer for cloud-native applications
7/1/2020 • 3 minutes to read • Edit Online
IdentityServer is an open-source authentication server that implements OpenID Connect (OIDC) and OAuth 2.0
standards for ASP.NET Core. It's designed to provide a common way to authenticate requests to all of your
applications, whether they're web, native, mobile, or API endpoints. IdentityServer can be used to implement Single
Sign-On (SSO) for multiple applications and application types. It can be used to authenticate actual users via sign-
in forms and similar user interfaces as well as service-based authentication that typically involves token issuance,
verification, and renewal without any user interface. IdentityServer is designed to be a customizable solution. Each
instance is typically customized to suit an individual organization and/or set of applications' needs.
Getting started
IdentityServer4 is open-source and free to use. You can add it to your applications using its NuGet packages. The
main package is IdentityServer4 that has been downloaded over four million times. The base package doesn't
include any user interface code and only supports in memory configuration. To use it with a database, you'll also
want a data provider like IdentityServer4.EntityFramework that uses Entity Framework Core to store configuration
and operational data for IdentityServer. For user interface, you can copy files from the Quickstart UI repository into
your ASP.NET Core MVC application to add support for sign in and sign out using IdentityServer middleware.
Configuration
IdentityServer supports different kinds of protocols and social authentication providers that can be configured as
part of each custom installation. This is typically done in the ASP.NET Core application's Startup class in the
ConfigureServices method. The configuration involves specifying the supported protocols and the paths to the
servers and endpoints that will be used. Figure 8-2 shows an example configuration taken from the
IdentityServer4 Quickstart UI project:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.Authority = "https://demo.identityserver.io/";
options.ClientId = "implicit";
options.ResponseType = "id_token";
options.SaveTokens = true;
options.CallbackPath = new PathString("/signin-idsrv");
options.SignedOutCallbackPath = new PathString("/signout-callback-idsrv");
options.RemoteSignOutPath = new PathString("/signout-idsrv");
JavaScript clients
Many cloud-native applications leverage server-side APIs and rich client single page applications (SPAs) on the
front end. IdentityServer ships a JavaScript client ( oidc-client.js ) via NPM that can be added to SPAs to enable
them to use IdentityServer for sign in, sign out, and token-based authentication of web APIs.
References
IdentityServer documentation
Application types
JavaScript OIDC client
PR EVIO U S NE XT
Security
5/19/2020 • 2 minutes to read • Edit Online
Not a day goes by where the news doesn't contain some story about a company being hacked or somehow losing
their customers' data. Even countries aren't immune to the problems created by treating security as an
afterthought. For years, companies have treated the security of customer data and, in fact, their entire networks as
something of a "nice to have". Windows servers were left unpatched, ancient versions of PHP kept running and
MongoDB databases left wide open to the world.
However, there are starting to be real-world consequences for not maintaining a security mindset when building
and deploying applications. Many companies learned the hard way what can happen when servers and desktops
aren't patched during the 2017 outbreak of NotPetya. The cost of these attacks has easily reached into the billions,
with some estimates putting the losses from this single attack at 10 billion US dollars.
Even governments aren't immune to hacking incidents. The city of Baltimore was held ransom by criminals making
it impossible for citizens to pay their bills or use city services.
There has also been an increase in legislation that mandates certain data protections for personal data. In Europe,
GDPR has been in effect for more than a year and, more recently, California passed their own version called CCDA,
which comes into effect January 1, 2020. The fines under GDPR can be so punishing as to put companies out of
business. Google has already been fined 50 million Euros for violations, but that's just a drop in the bucket
compared with the potential fines.
In short, security is serious business.
PR EVIO U S NE XT
Azure security for cloud-native apps
5/19/2020 • 24 minutes to read • Edit Online
Cloud-native applications can be both easier and more difficult to secure than traditional applications. On the
downside, you need to secure more smaller applications and dedicate more energy to build out the security
infrastructure. The heterogeneous nature of programming languages and styles in most service deployments also
means you need to pay more attention to security bulletins from many different providers.
On the flip side, smaller services, each with their own data store, limit the scope of an attack. If an attacker
compromises one system, it's probably more difficult for the attacker to make the jump to another system than it is
in a monolithic application. Process boundaries are strong boundaries. Also, if a database backup leaks, then the
damage is more limited, as that database contains only a subset of data and is unlikely to contain personal data.
Threat modeling
No matter if the advantages outweigh the disadvantages of cloud-native applications, the same holistic security
mindset must be followed. Security and secure thinking must be part of every step of the development and
operations story. When planning an application ask questions like:
What would be the impact of this data being lost?
How can we limit the damage from bad data being injected into this service?
Who should have access to this data?
Are there auditing policies in place around the development and release process?
All these questions are part of a process called threat modeling. This process tries to answer the question of what
threats there are to the system, how likely the threats are, and the potential damage from them.
Once the list of threats has been established, you need to decide whether they're worth mitigating. Sometimes a
threat is so unlikely and expensive to plan for that it isn't worth spending energy on it. For instance, some state
level actor could inject changes into the design of a process that is used by millions of devices. Now, instead of
running a certain piece of code in Ring 3, that code is run in Ring 0. This allows an exploit that can bypass the
hypervisor and run the attack code on the bare metal machines, allowing attacks on all the virtual machines that
are running on that hardware.
The altered processors are difficult to detect without a microscope and advanced knowledge of the on silicon
design of that processor. This scenario is unlikely to happen and expensive to mitigate, so probably no threat model
would recommend building exploit protection for it.
More likely threats, such as broken access controls permitting Id incrementing attacks (replacing Id=2 with
Id=3 in the URL) or SQL injection, are more attractive to build protections against. The mitigations for these
threats are quite reasonable to build and prevent embarrassing security holes that smear the company's
reputation.
Penetration testing
As applications become more complicated the number of attack vectors increases at an alarming rate. Threat
modeling is flawed in that it tends to be executed by the same people building the system. In the same way that
many developers have trouble envisioning user interactions and then build unusable user interfaces, most
developers have difficulty seeing every attack vector. It's also possible that the developers building the system
aren't well versed in attack methodologies and miss something crucial.
Penetration testing or "pen testing" involves bringing in external actors to attempt to attack the system. These
attackers may be an external consulting company or other developers with good security knowledge from another
part of the business. They're given carte blanche to attempt to subvert the system. Frequently, they'll find extensive
security holes that need to be patched. Sometimes the attack vector will be something totally unexpected like
exploiting a phishing attack against the CEO.
Azure itself is constantly undergoing attacks from a team of hackers inside Microsoft. Over the years, they've been
the first to find dozens of potentially catastrophic attack vectors, closing them before they can be exploited
externally. The more tempting a target, the more likely that eternal actors will attempt to exploit it and there are a
few targets in the world more tempting than Azure.
Monitoring
Should an attacker attempt to penetrate an application, there should be some warning of it. Frequently, attacks can
be spotted by examining the logs from services. Attacks leave telltale signs that can be spotted before they
succeed. For instance, an attacker attempting to guess a password will make many requests to a login system.
Monitoring around the login system can detect weird patterns that are out of line with the typical access pattern.
This monitoring can be turned into an alert that can, in turn, alert an operations person to activate some sort of
countermeasure. A highly mature monitoring system might even take action based on these deviations proactively
adding rules to block requests or throttle responses.
Built-in security
Azure is designed to balance usability and security for the majority of users. Different users are going to have
different security requirements, so they need to fine-tune their approach to cloud security. Microsoft publishes a
great deal of security information in the Trust Center. This resource should be the first stop for those professionals
interested in understanding how the built-in attack mitigation technologies work.
Within the Azure portal, the Azure Advisor is a system that is constantly scanning an environment and making
recommendations. Some of these recommendations are designed to save users money, but others are designed to
identify potentially insecure configurations, such as having a storage container open to the world and not
protected by a Virtual Network.
Security Principals
The first component in RBAC is a security principal. A security principal can be a user, group, service principal, or
managed identity.
Roles
A security principal can take on many roles or, using a more sartorial analogy, wear many hats. Each role defines a
series of permissions such as "Read messages from Azure Service Bus endpoint". The effective permission set of a
security principal is the combination of all the permissions assigned to all the roles that security principal has.
Azure has a large number of built-in roles and users can define their own roles.
Scopes
Roles can be applied to a restricted set of resources within Azure. For instance, applying scope to the previous
example of reading from a Service Bus queue, you can narrow the permission to a single queue: "Read messages
from Azure Service Bus endpoint blah.servicebus.windows.net/queue1 "
The scope can be as narrow as a single resource or it can be applied to an entire resource group, subscription, or
even management group.
When testing if a security principal has a certain permission, the combination of role and scope are taken into
account. This combination provides a powerful authorization mechanism.
Deny
Previously, only "allow" rules were permitted for RBAC. This behavior made some scopes complicated to build. For
instance, allowing a security principal access to all storage accounts except one required granting explicit
permission to a potentially endless list of storage accounts. Every time a new storage account was created, it would
have to be added to this list of accounts. This added management overhead that certainly wasn't desirable.
Deny rules take precedence over allow rules. Now representing the same "allow all but one" scope could be
represented as two rules "allow all" and "deny this one specific one". Deny rules not only ease management but
allow for resources that are extra secure by denying access to everybody.
Checking access
As you can imagine, having a large number of roles and scopes can make figuring out the effective permission of a
service principal quite difficult. Piling deny rules on top of that, only serves to increase the complexity. Fortunately,
there's a permissions calculator that can show the effective permissions for any service principal. It's typically found
under the IAM tab in the portal, as shown in Figure 10-3.
Kubernetes
Within Kubernetes, there's a similar service for maintaining small pieces of secret information. Kubernetes Secrets
can be set via the typical kubectl executable.
Creating a secret is as simple as finding the base64 version of the values to be stored:
Then adding it to a secrets file named secret.yml for example that looks similar to the following example:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
Finally, this file can be loaded into Kubernetes by running the following command:
Figure 9-5 . SSL labs report showing a score of A for a Service Bus endpoint.
While this level of encryption isn't going to be sufficient for all time, it should inspire confidence that Azure TLS
connections are quite secure. Azure will continue to evolve its security standards as encryption improves. It's nice
to know that there's somebody watching the security standards and updating Azure as they improve.
At rest
In any application, there are a number of places where data rests on disk. The application code itself is loaded from
some storage mechanism. Most applications also use some kind of database such as SQL Server, Cosmos DB, or
even the amazingly price-efficient Table Storage. These databases all use heavily encrypted storage to ensure that
nobody other than the applications with proper permissions can read your data. Even the system operators can't
read data that has been encrypted. So customers can remain confident their secret information remains secret.
Storage
The underpinning of much of Azure is the Azure Storage engine. Virtual machine disks are mounted on top of
Azure Storage. Azure Kubernetes Services run on virtual machines that, themselves, are hosted on Azure Storage.
Even serverless technologies, such as Azure Functions Apps and Azure Container Instances, run out of disk that is
part of Azure Storage.
If Azure Storage is well encrypted, then it provides for a foundation for most everything else to also be encrypted.
Azure Storage is encrypted with FIPS 140-2 compliant 256-bit AES. This is a well-regarded encryption technology
having been the subject of extensive academic scrutiny over the last 20 or so years. At present, there's no known
practical attack that would allow someone without knowledge of the key to read data encrypted by AES.
By default, the keys used for encrypting Azure Storage are managed by Microsoft. There are extensive protections
in place to ensure to prevent malicious access to these keys. However, users with particular encryption
requirements can also provide their own storage keys that are managed in Azure Key Vault. These keys can be
revoked at any time, which would effectively render the contents of the Storage account using them inaccessible.
Virtual machines use encrypted storage, but it's possible to provide another layer of encryption by using
technologies like BitLocker on Windows or DM-Crypt on Linux. These technologies mean that even if the disk
image was leaked off of storage, it would remain near impossible to read it.
Azure SQL
Databases hosted on Azure SQL use a technology called Transparent Data Encryption (TDE) to ensure data remains
encrypted. It's enabled by default on all newly created SQL databases, but must be enabled manually for legacy
databases. TDE executes real-time encryption and decryption of not just the database, but also the backups and
transaction logs.
The encryption parameters are stored in the master database and, on startup, are read into memory for the
remaining operations. This means that the master database must remain unencrypted. The actual key is managed
by Microsoft. However, users with exacting security requirements may provide their own key in Key Vault in much
the same way as is done for Azure Storage. The Key Vault provides for such services as key rotation and revocation.
The "Transparent" part of TDS comes from the fact that there aren't client changes needed to use an encrypted
database. While this approach provides for good security, leaking the database password is enough for users to be
able to decrypt the data. There's another approach that encrypts individual columns or tables in a database. Always
Encrypted ensures that at no point the encrypted data appears in plain text inside the database.
Setting up this tier of encryption requires running through a wizard in SQL Server Management Studio to select
the sort of encryption and where in Key Vault to store the associated keys.
Figure 9-6 . Selecting columns in a table to be encrypted using Always Encrypted.
Client applications that read information from these encrypted columns need to make special allowances to read
encrypted data. Connection strings need to be updated with Column Encryption Setting=Enabled and client
credentials must be retrieved from the Key Vault. The SQL Server client must then be primed with the column
encryption keys. Once that is done, the remaining actions use the standard interfaces to SQL Client. That is, tools
like Dapper and Entity Framework, which are built on top of SQL Client, will continue to work without changes.
Always Encrypted may not yet be available for every SQL Server driver on every language.
The combination of TDE and Always Encrypted, both of which can be used with client-specific keys, ensures that
even the most exacting encryption requirements are supported.
Cosmos DB
Cosmos DB is the newest database provided by Microsoft in Azure. It has been built from the ground up with
security and cryptography in mind. AES-256bit encryption is standard for all Cosmos DB databases and can't be
disabled. Coupled with the TLS 1.2 requirement for communication, the entire storage solution is encrypted.
Figure 9-7 . The flow of data encryption within Cosmos DB.
While Cosmos DB doesn't provide for supplying customer encryption keys, there has been significant work done
by the team to ensure it remains PCI-DSS compliant without that. Cosmos DB also doesn't support any sort of
single column encryption similar to Azure SQL's Always Encrypted yet.
Keeping secure
Azure has all the tools necessary to release a highly secure product. However, a chain is only as strong as its
weakest link. If the applications deployed on top of Azure aren't developed with a proper security mindset and
good security audits, then they become the weak link in the chain. There are many great static analysis tools,
encryption libraries, and security practices that can be used to ensure that the software installed on Azure is as
secure as Azure itself. Examples include static analysis tools, encryption libraries, and security practices.
PR EVIO U S NE XT
DevOps
5/19/2020 • 21 minutes to read • Edit Online
The favorite mantra of software consultants is to answer "It depends" to any question posed. It isn't because
software consultants are fond of not taking a position. It's because there's no one true answer to any questions in
software. There's no absolute right and wrong, but rather a balance between opposites.
Take, for instance, the two major schools of developing web applications: Single Page Applications (SPAs) versus
server-side applications. On the one hand, the user experience tends to be better with SPAs and the amount of
traffic to the web server can be minimized making it possible to host them on something as simple as static
hosting. On the other hand, SPAs tend to be slower to develop and more difficult to test. Which one is the right
choice? Well, it depends on your situation.
Cloud-native applications aren't immune to that same dichotomy. They have clear advantages in terms of speed of
development, stability, and scalability, but managing them can be quite a bit more difficult.
Years ago, it wasn't uncommon for the process of moving an application from development to production to take a
month, or even more. Companies released software on a 6-month or even every year cadence. One needs to look
no further than Microsoft Windows to get an idea for the cadence of releases that were acceptable before the ever-
green days of Windows 10. Five years passed between Windows XP and Vista, a further 3 between Vista and
Windows 7.
It's now fairly well established that being able to release software rapidly gives fast-moving companies a huge
market advantage over their more sloth-like competitors. It's for that reason that major updates to Windows 10
are now approximately every six months.
The patterns and practices that enable faster, more reliable releases to deliver value to the business are collectively
known as DevOps. They consist of a wide range of ideas spanning the entire software development life cycle from
specifying an application all the way up to delivering and operating that application.
DevOps emerged before microservices and it's likely that the movement towards smaller, more fit to purpose
services wouldn't have been possible without DevOps to make releasing and operating not just one but many
applications in production easier.
Figure 10-1 - DevOps and microservices.
Through good DevOps practices, it's possible to realize the advantages of cloud-native applications without
suffocating under a mountain of work actually operating the applications.
There's no golden hammer when it comes to DevOps. Nobody can sell a complete and all-encompassing solution
for releasing and operating high-quality applications. This is because each application is wildly different from all
others. However, there are tools that can make DevOps a far less daunting proposition. One of these tools is known
as Azure DevOps.
Azure DevOps
Azure DevOps has a long pedigree. It can trace its roots back to when Team Foundation Server first moved online
and through the various name changes: Visual Studio Online and Visual Studio Team Services. Through the years,
however, it has become far more than its predecessors.
Azure DevOps is divided into five major components:
GitHub Actions
Founded in 2009, GitHub is a widely popular web-based repository for hosting projects, documentation, and code.
Many large tech companies, such as Apple, Amazon, Google, and mainstream corporations use GitHub. GitHub
uses the open-source, distributed version control system named Git as its foundation. On top, it then adds its own
set of features, including defect tracking, feature and pull requests, tasks management, and wikis for each code
base.
As GitHub evolves, it too is adding DevOps features. For example, GitHub has its own continuous
integration/continuous delivery (CI/CD) pipeline, called GitHub Actions . GitHub Actions is a community-powered
workflow automation tool. It lets DevOps teams integrate with their existing tooling, mix and match new products,
and hook into their software lifecycle, including existing CI/CD partners."
GitHub has over 40 million users, making it the largest host of source code in the world. In October of 2018,
Microsoft purchased GitHub. Microsoft has pledged that GitHub will remain an open platform that any developer
can plug into and extend. It continues to operate as an independent company. GitHub offers plans for enterprise,
team, professional, and free accounts.
Source control
Organizing the code for a cloud-native application can be challenging. Instead of a single giant application, the
cloud-native applications tend to be made up of a web of smaller applications that talk with one another. As with
all things in computing, the best arrangement of code remains an open question. There are examples of successful
applications using different kinds of layouts, but two variants seem to have the most popularity.
Before getting down into the actual source control itself, it's probably worth deciding on how many projects are
appropriate. Within a single project, there's support for multiple repositories, and build pipelines. Boards are a little
more complicated, but there too, the tasks can easily be assigned to multiple teams within a single project. It's
possible to support hundreds, even thousands of developers, out of a single Azure DevOps project. Doing so is
likely the best approach as it provides a single place for all developer to work out of and reduces the confusion of
finding that one application when developers are unsure in which project in which it resides.
Splitting up code for microservices within the Azure DevOps project can be slightly more challenging.
Task management
Managing tasks in any project can be difficult. Up front there are countless questions to be answered about the
sort of workflows to set up to ensure optimal developer productivity.
Cloud-native applications tend to be smaller than traditional software products or at least they're divided into
smaller services. Tracking of issues or tasks related to these services remains as important as with any other
software project. Nobody wants to lose track of some work item or explain to a customer that their issue wasn't
properly logged. Boards are configured at the project level but within each project, areas can be defined. These
allow breaking down issues across several components. The advantage to keeping all the work for the entire
application in one place is that it's easy to move work items from one team to another as they're understood better.
Azure DevOps comes with a number of popular templates pre-configured. In the most basic configuration, all that
is needed to know is what's in the backlog, what people are working on, and what's done. It's important to have
this visibility into the process of building software, so that work can be prioritized and completed tasks reported to
the customer. Of course, few software projects stick to a process as simple as to do , doing , and done . It doesn't
take long for people to start adding steps like QA or Detailed Specification to the process.
One of the more important parts of Agile methodologies is self-introspection at regular intervals. These reviews
are meant to provide insight into what problems the team is facing and how they can be improved. Frequently, this
means changing the flow of issues and features through the development process. So, it's perfectly healthy to
expand the layouts of the boards with additional stages.
The stages in the boards aren't the only organizational tool. Depending on the configuration of the board, there's a
hierarchy of work items. The most granular item that can appear on a board is a task. Out of the box a task
contains fields for a title, description, a priority, an estimate of the amount of work remaining and the ability to link
to other work items or development items (branches, commits, pull requests, builds, and so forth). Work items can
be classified into different areas of the application and different iterations (sprints) to make finding them easier.
Figure 10-5 - Task in Azure DevOps.
The description field supports the normal styles you'd expect (bold, italic underscore and strike through) and the
ability to insert images. This makes it a powerful tool for use when specifying work or bugs.
Tasks can be rolled up into features, which define a larger unit of work. Features, in turn, can be rolled up into
epics. Classifying tasks in this hierarchy makes it much easier to understand how close a large feature is to rolling
out.
CI/CD pipelines
Almost no change in the software development life cycle has been so revolutionary as the advent of continuous
integration (CI) and continuous delivery (CD). Building and running automated tests against the source code of a
project as soon as a change is checked in catches mistakes early. Prior to the advent of continuous integration
builds, it wouldn't be uncommon to pull code from the repository and find that it didn't pass tests or couldn't even
be built. This resulted in tracking down the source of the breakage.
Traditionally shipping software to the production environment required extensive documentation and a list of
steps. Each one of these steps needed to be manually completed in a very error prone process.
variables:
version: 9.2.0.$(Build.BuildNumber)
solution: Portals.sln
artifactName: drop
buildPlatform: any cpu
buildConfiguration: release
pool:
name: Hosted VS2017
demands:
- msbuild
- visualstudio
- vstest
steps:
- task: NuGetToolInstaller@0
displayName: 'Use NuGet 4.4.1'
inputs:
versionSpec: 4.4.1
- task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
restoreSolution: '$(solution)'
- task: VSBuild@1
displayName: 'Build solution'
inputs:
solution: '$(solution)'
msbuildArgs: '-p:DeployOnBuild=true -p:WebPublishMethod=Package -p:PackageAsSingleFile=true -
p:SkipInvalidConfigurations=true -p:PackageLocation="$(build.artifactstagingdirectory)\\"'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: VSTest@2
displayName: 'Test Assemblies'
inputs:
testAssemblyVer2: |
**\$(buildConfiguration)\**\*test*.dll
!**\obj\**
!**\*testadapter.dll
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: CopyFiles@2
displayName: 'Copy UI Test Files to: $(build.artifactstagingdirectory)'
inputs:
SourceFolder: UITests
TargetFolder: '$(build.artifactstagingdirectory)/uitests'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
ArtifactName: '$(artifactName)'
condition: succeededOrFailed()
PR EVIO U S NE XT
Feature flags
5/19/2020 • 3 minutes to read • Edit Online
In chapter 1, we affirmed that cloud native is much about speed and agility. Users expect rapid responsiveness,
innovative features, and zero downtime. Feature flags are a modern deployment technique that helps increase
agility for cloud-native applications. They enable you to deploy new features into a production environment, but
restrict their availability. With the flick of a switch, you can activate a new feature for specific users without
restarting the app or deploying new code. They separate the release of new features from their code deployment.
Feature flags are built upon conditional logic that control visibility of functionality for users at runtime. In modern
cloud-native systems, it's common to deploy new features into production early, but test them with a limited
audience. As confidence increases, the feature can be incrementally rolled out to wider audiences.
Other use cases for feature flags include:
Restrict premium functionality to specific customer groups willing to pay higher subscription fees.
Stabilize a system by quickly deactivating a problem feature, avoiding the risks of a rollback or immediate
hotfix.
Disable an optional feature with high resource consumption during peak usage periods.
Conduct experimental feature releases to small user segments to validate feasibility and popularity.
Feature flags also promote trunk-based development. It's a source-control branching model where developers
collaborate on features in a single branch. The approach minimizes the risk and complexity of merging large
numbers of long-running feature branches. Features are unavailable until activated.
if (featureFlag) {
// Run this code block if the featureFlag value is true
} else {
// Run this code block if the featureFlag value is false
}
[FeatureGate(MyFeatureFlags.FeatureA)]
public class ProductController : Controller
{
...
}
[FeatureGate(MyFeatureFlags.FeatureA)]
public IActionResult UpdateProductStatus()
{
return ObjectResult(ProductDto);
}
PR EVIO U S NE XT
Infrastructure as code
5/19/2020 • 5 minutes to read • Edit Online
Cloud-native systems embrace microservices, containers, and modern system design to achieve speed and agility.
They provide automated build and release stages to ensure consistent and quality code. But, that's only part of the
story. How do you provision the cloud environments upon which these systems run?
Modern cloud-native applications embrace the widely accepted practice of Infrastructure as Code, or IaC . With
IaC, you automate platform provisioning. You essentially apply software engineering practices such as testing and
versioning to your DevOps practices. Your infrastructure and deployments are automated, consistent, and
repeatable. Just as continuous delivery automated the traditional model of manual deployments, Infrastructure as
Code (IaC) is evolving how application environments are managed.
Tools like Azure Resource Manager (ARM), Terraform, and the Azure Command Line Interface (CLI) enable you to
declaratively script the cloud infrastructure you require.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "",
"apiProfile": "",
"parameters": { },
"variables": { },
"functions": [ ],
"resources": [ ],
"outputs": { }
}
Terraform
Cloud-native applications are often constructed to be cloud agnostic . Being so means the application isn't tightly
coupled to a particular cloud vendor and can be deployed to any public cloud.
Terraform is commercial templating tool that can provision cloud-native applications across all the major cloud
players: Azure, Google Cloud Platform, AWS, and AliCloud. Instead of using JSON as the template definition
language, it uses the slightly more terse YAML.
An example Terraform file that does the same as the previous Resource Manager template (Figure 10-15) is shown
in Figure 10-16:
provider "azurerm" {
version = "=1.28.0"
}
- task: AzureCLI@2
displayName: Azure CLI
inputs:
azureSubscription: <Name of the Azure Resource Manager service connection>
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
az --version
az account show
PR EVIO U S NE XT
Cloud Native Application Bundles
5/19/2020 • 3 minutes to read • Edit Online
A key property of cloud-native applications is that they leverage the capabilities of the cloud to speed up
development. This design often means that a full application uses different kinds of technologies. Applications may
be shipped in Docker containers, some services may use Azure Functions, while other parts may run directly on
virtual machines allocated on large metal servers with hardware GPU acceleration. No two cloud-native
applications are the same, so it's been difficult to provide a single mechanism for shipping them.
The Docker containers may run on Kubernetes using a Helm Chart for deployment. The Azure Functions may be
allocated using Terraform templates. Finally, the virtual machines may be allocated using Terraform but built out
using Ansible. This is a whole mess of technologies and there has been no way to package them all together into a
reasonable package. Until now.
Cloud Native Application Bundles (CNABs) are a joint effort by a number of community-minded companies such
as Microsoft, Docker, and HashiCorp to develop a specification to package distributed applications.
The effort was announced in December of 2018, so there's still a fair bit of work to do to expose the effort to the
greater community. However, there's already an open specification and a reference implementation known as
Duffle. This tool, which was written in Go, is a joint effort between Docker and Microsoft.
The CNABs can contain different kinds of installation technologies. This allows things like Helm Charts, Terraform
templates, and Ansible Playbooks to coexist in the same package. Once built, the packages are self-contained and
portable; they can be installed from a USB stick. The packages are cryptographically signed to ensure they
originate from the party they claim.
The core of a CNAB is a file called bundle.json . This file defines the contents of the bundle, be they Terraform or
images or anything else. Figure 11-9 defines a CNAB that invokes some Terraform. Notice, however, that it actually
defines an invocation image that is used to invoke the Terraform. When packaged up, the Docker file that is located
in the cnab directory is built into a Docker image, which will be included in the bundle. Having Terraform installed
inside a Docker container in the bundle means that users don't need to have Terraform installed on their machine
to run the bundling.
{
"name": "terraform",
"version": "0.1.0",
"schemaVersion": "v1.0.0-WD",
"parameters": {
"backend": {
"type": "boolean",
"defaultValue": false,
"destination": {
"env": "TF_VAR_backend"
}
}
},
"invocationImages": [
{
"imageType": "docker",
"image": "cnab/terraform:latest"
}
],
"credentials": {
"tenant_id": {
"env": "TF_VAR_tenant_id"
},
"client_id": {
"env": "TF_VAR_client_id"
},
"client_secret": {
"env": "TF_VAR_client_secret"
},
"subscription_id": {
"env": "TF_VAR_subscription_id"
},
"ssh_authorized_key": {
"env": "TF_VAR_ssh_authorized_key"
}
},
"actions": {
"status": {
"modifies": true
}
}
}
DevOps Decisions
There are so many great tools in the DevOps space these days and even more fantastic books and papers on how
to succeed. A favorite book to get started on the DevOps journey is The Phoenix Project, which follows the
transformation of a fictional company from NoOps to DevOps. One thing is for certain: DevOps is no longer a "nice
to have" when deploying complex, Cloud Native Applications. It's a requirement and should be planned for and
resourced at the start of any project.
References
Azure DevOps
Azure Resource Manager
Terraform
Azure CLI
PR EVIO U S NE XT
Summary
5/19/2020 • 3 minutes to read • Edit Online
PR EVIO U S