47,99 €
Microservices architecture allows developers to build and maintain applications with ease, and enterprises are rapidly adopting it to build software using Spring Boot as their default framework. With this book, you’ll learn how to efficiently build and deploy microservices using Spring Boot.
This microservices book will take you through tried and tested approaches to building distributed systems and implementing microservices architecture in your organization. Starting with a set of simple cooperating microservices developed using Spring Boot, you’ll learn how you can add functionalities such as persistence, make your microservices reactive, and describe their APIs using Swagger/OpenAPI. As you advance, you’ll understand how to add different services from Spring Cloud to your microservice system. The book also demonstrates how to deploy your microservices using Kubernetes and manage them with Istio for improved security and traffic management. Finally, you’ll explore centralized log management using the EFK stack and monitor microservices using Prometheus and Grafana.
By the end of this book, you’ll be able to build microservices that are scalable and robust using Spring Boot and Spring Cloud.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 683
Veröffentlichungsjahr: 2019
Copyright © 2019 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author(s), nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Commissioning Editor: Richa TripathiAcquisition Editor:Shriram ShekharContent Development Editor:Tiksha SarangSenior Editor: Rohit SinghTechnical Editor: Gaurav GalaCopy Editor: Safis EditingProject Coordinator:Prajakta NaikProofreader: Safis EditingIndexer:Rekha NairProduction Designer:Jyoti Chauhan
First published: September 2019
Production reference: 1190919
Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK.
ISBN 978-1-78961-347-6
www.packt.com
Packt.com
Subscribe to our online digital library for full access to over 7,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Get a free eBook or video every month
Fully searchable for easy access to vital information
Copy and paste, print, and bookmark content
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.packt.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.
At www.packt.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
Magnus Larsson has been in the IT industry for more than 30 years, working as a consultant for large companies in Sweden such as Volvo, Ericsson, and AstraZeneca. He has seen a lot of different communication technologies come and go over the years, such as RPC, CORBA, SOAP, and REST. In the past, he struggled with the challenges associated with distributed systems as there was no substantial help from the software available at that time. This has, however, changed dramatically over the last few years with the introduction of open source projects such as Spring Cloud, Netflix OSS, Docker, and Kubernetes. Over the last five years, Magnus has been helping customers use these new software technologies and has also done several presentations and blog posts on the subject.
Erik Lupander is a software architect and developer with over 15 years of professional experience.
He holds an M.Sc. in applied informatics from the University of Gothenburg. While Java Virtual Machine-based languages and architecture have been his bread and butter, Erik is a polyglot software craftsman at heart who, among other technologies, has embraced Go and microservice architecture.
He has spoken at software conferences on topics ranging from OpenGL ES and big data to Go and microservices, and was a technical reviewer for Building Microservices with Go, by Nic Jackson.
He lives just outside Gothenburg, Sweden, with his wife and two children, and is currently employed by Callista Enterprise AB, a Swedish consultancy specializing in software architecture.
If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
Title Page
Copyright and Credits
Hands-On Microservices with Spring Boot and Spring Cloud
About Packt
Why subscribe?
Contributors
About the author
About the reviewer
Packt is searching for authors like you
Preface
Who this book is for
What this book covers
To get the most out of this book
Download the example code files
Download the color images
Code in Action
Conventions used
Get in touch
Reviews
Section 1: Getting Started with Microservice Development Using Spring Boot
Introduction to Microservices
Technical requirements
My way into microservices
Benefits of autonomous software components
Challenges with autonomous software components
Enter microservices
A sample microservice landscape
Defining a microservice
Challenges with microservices
Design patterns for microservices
Service discovery
Problem
Solution
Solution requirements
Edge server
Problem 
Solution
Solution requirements
Reactive microservice
Problem
Solution
Solution requirements
Central configuration
Problem
Solution
Solution requirements
Centralized log analysis
Problem
Solution
Distributed tracing
Problem
Solution
Solution requirements
Circuit Breaker
Problem
Solution
Solution requirements 
Control loop
Problem
Solution
Solution requirements
Centralized monitoring and alarms
Problem
Solution
Solution requirements 
Software enablers
Other important considerations
Summary
Introduction to Spring Boot
Technical requirements
Learning about Spring Boot
Convention over configuration and fat JAR files
Code examples for setting up a Spring Boot application
The magic @SpringBootApplication annotation
Component scanning
Java-based configuration
Beginning with Spring WebFlux
Code examples of setting up a REST service using Spring WebFlux
Starter dependencies
Property files
Sample RestController
Exploring SpringFox
Understanding Spring Data
Entity
Repositories
Understanding Spring Cloud Stream
Code examples for sending and receiving messages with Spring Cloud Stream
Learning about Docker
Summary
Questions
Creating a Set of Cooperating Microservices
Technical requirements
Tool installation
Installing Homebrew
Using Homebrew to install Java, curl, jq, and the Spring Boot CLI
Using an IDE
Accessing the source code
Introducing the microservice landscape
Information handled by microservices
Product service
Review service
Recommendation service
Product composite service
Infrastructure-related information
Temporarily replacing a discovery service
Generating skeleton microservices
Using Spring Initializr to generate skeleton code
Setting up multi-project builds in Gradle
Adding RESTful APIs
Adding an API and a util project
The api project
The util project
Implementing our API
Adding a composite microservice
API classes
Properties
Integration component
Composite API implementation 
Adding error handling
The global REST controller exception handler
Error-handling in API implementations
Error-handling in the API client
Testing APIs manually
Preventing slow lookup of the localhost hostname
Adding automated microservice tests in isolation
Adding semi-automated tests of a microservice landscape
Trying out the test script
Summary
Questions
Deploying Our Microservices Using Docker
Technical requirements
Introduction to Docker
Running our first Docker commands
Challenges with running Java in Docker
Java without Docker
Java in Docker
CPU
Memory
Problems with Docker and Java SE 9 (or older)
Using Docker with one microservice
Changes in source code
Building a Docker image
Starting up the service
Running the container detached
Managing a landscape of microservices using Docker Compose
Changes in the source code
Starting up the microservice landscape
Testing them all together automatically
Troubleshooting a test run
Summary
Questions
Adding an API Description Using OpenAPI/Swagger
Technical requirements
Introduction to using SpringFox
Changes in the source code
Adding dependencies to the Gradle build files
Adding configuration and general API documentation to Product Composite Service Application
Adding API-specific documentation to ProductCompositeService
Adding textual descriptions of the API to the property file 
Building and starting the microservice landscape
Trying out the Swagger documentation
Summary
Questions
Adding Persistence
Technical requirements
But first, let's see where we are heading
Adding a persistence layer to the core microservices
Adding dependencies
Storing data with entity classes
Defining repositories in Spring Data
Writing automated tests that focus on persistence
Using the persistence layer in the service layer
Log the database connection URL
Adding new APIs
The use of the persistence layer
Declaring a Java bean mapper
Updating the service tests
Extending the composite service API
Adding new operations in the composite service API
Adding methods in the integration layer 
Implementing the new composite API operations
Updating the composite service tests
Adding databases to the Docker Compose landscape
The Docker Compose configuration
Database connect configuration
The MongoDB and MySQL CLI tools
Manual tests of the new APIs and the persistence layer
Updating the automated tests of the microservice landscape
Summary
Questions
Developing Reactive Microservices
Technical requirements
Choosing between non-blocking synchronous APIs and event-driven asynchronous services
Developing non-blocking synchronous REST APIs using Spring
An introduction to Spring Reactor
Non-blocking persistence using Spring Data for MongoDB
Changes in the test code
Non-blocking REST APIs in the core services
Changes in the APIs
Changes in the service implementations
Changes in the test code
Dealing with blocking code
Non-blocking REST APIs in the composite services
Changes in the API
Changes in the integration layer
Changes in the service implementation
Changes in the test code
Developing event-driven asynchronous services
Configuring Spring Cloud Stream to handle challenges with messaging
Consumer groups
Retries and dead-letter queues 
Guaranteed order and partitions
Defining topics and events
Changes in the Gradle build files
Publishing events in the composite service
Declaring message sources and publishing events in the integration layer
Adding configuration for publishing events
Changes in the test code
Consuming events in the core services
Declaring message processors
Changes in the service implementations
Adding configuration for consuming events
Changes in the test code
Running manual tests of the reactive microservice landscape
Saving events
Adding a health API
Using RabbitMQ without using partitions
Using RabbitMQ with two partitions per topic
Using Kafka with two partitions per topic
Running automated tests of the reactive microservice landscape
Summary
Questions
Section 2: Leveraging Spring Cloud to Manage Microservices
Introduction to Spring Cloud
Technical requirements
The evolution of Spring Cloud
Using Netflix Eureka as a discovery service
Using Spring Cloud Gateway as an edge server
Using Spring Cloud Config for centralized configuration
Using Resilience4j for improved resilience
Sample usage of the circuit breaker in Resilience4j
Using Spring Cloud Sleuth and Zipkin for distributed tracing
Summary
Questions
Adding Service Discovery Using Netflix Eureka and Ribbon
Technical requirements
Introducing service discovery
The problem with DNS-based service discovery
Challenges with service discovery
Service discovery with Netflix Eureka in Spring Cloud
Setting up a Netflix Eureka server
Connecting microservices to a Netflix Eureka server
Setting up configuration for use in the development process
Eureka configuration parameters
Configuring the Eureka server
Configuring clients to the Eureka server
Trying out the discovery service
Scaling up
Scaling down
Disruptive tests with the Eureka server
Stopping the Eureka server
Stopping a review instance 
Starting up an extra instance of the product service
Starting up the Eureka server again
Summary
Questions
Using Spring Cloud Gateway to Hide Microservices Behind an Edge Server
Technical requirements
Adding an edge server to our system landscape
Setting up a Spring Cloud Gateway
Adding a composite health check
Configuring a Spring Cloud Gateway
Routing rules
Routing requests to the product-composite API
Routing requests to the Eureka server's API and web page
Routing requests with predicates and filters
Trying out the edge server
Examining what is exposed outside the Docker engine
Trying out the routing rules
Calling the product composite API through the edge server
Calling Eureka through the edge server
Routing based on the host header
Summary
Questions
Securing Access to APIs
Technical requirements
Introduction to OAuth 2.0 and OpenID Connect
Introduction to OAuth 2.0
Introducing OpenID Connect
Securing the system landscape
Adding an authorization server to our system landscape
Protecting external communication with HTTPS
Replacing a self-signed certificate in runtime
Securing access to the discovery service, Netflix Eureka
Changes in the Eureka server
Changes in Eureka clients
Testing the protected Eureka server
Authenticating and authorizing API access using OAuth 2.0 and OpenID Connect
Changes in both the edge server and the product-composite service
Changes in the product-composite service
Changes in the test script
Testing with the local authorization server
Building and running the automated tests
Acquiring access tokens
Acquiring access tokens using the password grant flow
Acquiring access tokens using the implicit grant flow
Acquiring access tokens using the code grant flow
Calling protected APIs using access tokens
Testing with an OpenID Connect provider – Auth0
Setting up an account and OAuth 2.0 client in Auth0
Applying the necessary changes to use Auth0 as an OpenID provider
Changing the configuration in the OAuth resource servers
Changing the test script so it acquires access tokens from Auth0
Running the test script with Auth0 as the OpenID Connect provider
Acquiring access tokens using the password grant flow
Acquiring access tokens using the implicit grant flow
Acquiring access tokens using the authorization code grant flow
Calling protected APIs using the Auth0 access tokens
Getting extra information about the user
Summary
Questions
Centralized Configuration
Technical requirements
Introduction to the Spring Cloud Configuration server
Selecting the storage type of the configuration repository
Deciding on the initial client connection
Securing the configuration
Securing the configuration in transit
Securing the configuration at rest
Introducing the config server API
Setting up a config server
Setting up a routing rule in the edge server 
Configuring the config server for use with Docker
Configuring clients of a config server
Configuring connection information
Moving the partitioning configuration from Docker Compose files to the configuration repository
Structuring the configuration repository
Trying out the Spring Cloud Configuration server
Building and running automated tests
Getting the configuration using the config server API
Encrypting and decrypting sensitive information
Summary
Questions
Improving Resilience Using Resilience4j
Technical requirements
Introducing the Resilience4j circuit breaker and retry mechanism
Introducing the circuit breaker
Introducing the retry mechanism
Adding a circuit breaker and retry mechanism to the source code
Adding programmable delays and random errors
Changes in the API definitions
Changes in the product composite microservice
Changes in the product microservice
Adding a circuit breaker
Adding dependencies to the build file
Adding the circuit breaker and timeout logic
Adding fast fail fallback logic
Adding configuration
Adding a retry mechanism
Adding the retry annotation
Handling retry-specific exceptions
Adding configuration
Adding automated tests
Trying out the circuit breaker and retry mechanism
Building and running the automated tests
Verifying that the circuit is closed under normal operations
Forcing the circuit breaker to open when things go wrong
Closing the circuit breaker again
Trying out retries caused by random errors
Summary
Questions
Understanding Distributed Tracing
Technical requirements
Introducing distributed tracing with Spring Cloud Sleuth and Zipkin
Adding distributed tracing to the source code
Adding dependencies to build files
Adding configuration for Spring Cloud Sleuth and Zipkin
Adding Zipkin to the Docker Compose files
Trying out distributed tracing
Starting up the system landscape with RabbitMQ as the queue manager
Sending a successful API request
Sending an unsuccessful API request
Sending an API request that triggers asynchronous processing
Monitoring trace information passed to Zipkin in RabbitMQ
Using Kafka as a message broker
Summary
Questions
Section 3: Developing Lightweight Microservices Using Kubernetes
Introduction to Kubernetes
Technical requirements
Introducing Kubernetes concepts
Introducing Kubernetes API objects
Introducing Kubernetes runtime components
Creating a Kubernetes cluster using Minikube
Working with Minikube profiles
Working with Kubernetes CLI, kubectl
Working with kubectl contexts
Creating a Kubernetes cluster
Trying out a sample deployment
Managing a Kubernetes cluster
Hibernating and resuming a Kubernetes cluster
Terminating a Kubernetes cluster
Summary
Questions
Deploying Our Microservices to Kubernetes
Technical requirements
Replacing Netflix Eureka with Kubernetes services
Introducing Kustomize
Setting up common definitions in the base folder
Deploying to Kubernetes for development and test
Building Docker images
Deploying to Kubernetes
Changes in the test script for use with Kubernetes
Reaching the internal actuator endpoint using Docker Compose
Reaching the internal actuator endpoint using Kubernetes
Choosing between Docker Compose and Kubernetes
Testing the deployment
Deploying to Kubernetes for staging and production
Changes in the source code
Deploying to Kubernetes
Performing a rolling upgrade
Preparing the rolling upgrade
Upgrading the product service from v1 to v2
Rolling back a failed deployment
Cleaning up
Summary
Questions
Implementing Kubernetes Features as an Alternative
Technical requirements
Replacing the Spring Cloud Config Server
Changes in the source code to replace the Spring Cloud Config Server
Replacing the Spring Cloud Gateway
Changes in the source code for Spring Cloud Gateway
Testing with Kubernetes ConfigMaps, secrets, and ingress resource
Walking through the deploy script
Running commands for deploying and testing
Automating the provision of certificates 
Deploying the Cert Manager and defining Let's Encrypt issuers
Creating an HTTP tunnel using ngrok
Provisioning certificates with the Cert Manager and Let's Encrypt
Using Let's Encrypt's staging environment
Using Let's Encrypt's production environment
Cleaning up
Verifying that microservices work without Kubernetes
Changes in the source code for Docker Compose
Testing with Docker Compose
Summary
Questions
Using a Service Mesh to Improve Observability and Management
Technical requirements
Introduction to service mesh using Istio
Injecting Istio proxies into existing microservices
Introducing Istio API objects
Introducing runtime components in Istio 
Changes in the microservice landscape 
Kubernetes Ingress resources are replaced with Istio Ingress Gateway as an edge server
Simplifying the system landscape and replacing Zipkin with Jaeger
Deploying Istio in a Kubernetes cluster
Setting up access to Istio services
An added bonus from using the minikube tunnel command
Creating the service mesh
Source code changes
Updating the deployment scripts to inject Istio proxies
Changing the file structure of the Kubernetes definition files
Adding Kubernetes definition files for Istio
Running commands to create the service mesh
Observing the service mesh
Securing a service mesh
Protecting external endpoints with HTTPS and certificates
Authenticating external requests using OAuth 2.0/OIDC access tokens
Protecting internal communication using mutual authentication (mTLS)
Ensuring that a service mesh is resilient
Testing resilience by injecting faults
Testing resilience by injecting delays
Performing zero-downtime deployments
Source code changes
Service and deployment objects for concurrent versions of microservices
Added Kubernetes definition files for Istio
Deploying v1 and v2 versions of the microservices with routing to the v1 version
Verifying that all traffic initially goes to the v1 version of the microservices
Running canary tests
Running blue/green tests
A short introduction to the kubectl patch command
Performing the blue/green deployment
Running tests with Docker Compose
Summary
Questions
Centralized Logging with the EFK Stack
Technical requirements
Configuring Fluentd
Introducing Fluentd
Configuring Fluentd
Deploying the EFK stack on Kubernetes
Building and deploying our microservices
Deploying Elasticsearch and Kibana
A walkthrough of the definition files
Running the deploy commands
Deploying Fluentd
A walkthrough of the definition files
Running the deploy commands
Trying out the EFK stack
Initializing Kibana
Analyzing the log records
Discovering the log records from microservices
Performing root cause analyses
Summary
Questions
Monitoring Microservices
Technical requirements
Introduction to performance monitoring using Prometheus and Grafana
Changes in source code for collecting application metrics
Building and deploying the microservices
Monitoring microservices using Grafana dashboards
Installing a local mail server for tests
Starting up the load test
Using Kiali's built-in Grafana dashboards
Importing existing Grafana dashboards
Developing your own Grafana dashboards
Examining Prometheus metrics
Creating the dashboard
Creating an empty dashboard
Creating a new panel for the circuit breaker metric
Creating a new panel for the retry metric
Arranging the panels
Trying out the new dashboard
Testing the circuit breaker metrics
Testing the retry metrics
Setting up alarms in Grafana
Setting up a mail-based notification channel
Setting up an alarm on the circuit breaker
Trying out the circuit breaker alarm
Summary
Questions
Other Books You May Enjoy
Leave a review - let other readers know what you think
This book is about building production-ready microservices using Spring Boot and Spring Cloud. Five years ago, when I began to explore microservices, I was looking for a book like this.
This book has been developed after I learned about, and mastered, open source software used for developing, testing, deploying, and managing landscapes of cooperating microservices.
This book primarily covers Spring Boot, Spring Cloud, Docker, Kubernetes, Istio, the EFK stack, Prometheus, and Grafana. Each of these open source tools works great by itself, but it can be challenging to understand how to use them together in an advantageous way. In some areas, they complement each other, but in other areas they overlap, and it is not obvious which one to choose for a particular situation.
This is a hands-on book that describes step by step how to use these open source tools together. This is the book I was looking for five years ago when I started to learn about microservices, but with updated versions of the open source tools it covers.
This book is for Java and Spring developers and architects who want to learn how to break up their existing monoliths into microservices and deploy them either on-premises or in the cloud, using Kubernetes as a container orchestrator and Istio as a service mesh. No familiarity with microservices architecture is required to get started with this book.
Chapter 1, Introduction to Microservices, will help you understand the basic premise of the book, microservices, along with the essential concepts and design patterns that go along with it.
Chapter 2, Introduction to Spring Boot, will get you introduced to Spring Boot and the other open source projects that will be used in the first part of the book: Spring WebFlux for developing RESTful APIs, SpringFox for producing OpenAPI- or Swagger-based documentation for the APIs, Spring Data for storing data in SQL and NoSQL databases, Spring Cloud Stream for message-based microservices, and Docker to run the microservices as containers.
Chapter 3, Creating a Set of Cooperating Microservices, will teach you how to create a set of cooperating microservices from scratch. You will use Spring Initializr to create skeleton projects based on Spring Framework 5.1 and Spring Boot 2.1. The idea is to create three core services (that will handle their own resources) and one composite service that uses the three core services to aggregate a composite result. Toward the end of the chapter, you will learn how to add very basic RESTful APIs based on Spring WebFlux. In the next chapter, more and more functionality will be added to these microservices.
Chapter 4, Deploying Our Microservices Using Docker, will teach you how to deploy microservices using Docker. You will learn how to add Dockerfiles and docker-compose files in order to start up the whole microservice landscape with a single command. Then, you will learn how to use multiple Spring profiles to handle configurations with and without Docker.
Chapter 5, Adding an API Description Using OpenAPI/Swagger, will get you up to speed with documenting the APIs exposed by a microservice using OpenAPI/Swagger. You will use the SpringFox framework to annotate the services to create OpenAPI- or Swagger-based API documentation on the fly. The key highlight will be how the APIs can be tested in a web browser using SpringFox Swagger UI.
Chapter 6, Adding Persistence, will show you how to add persistence to the data of the microservice. You will use Spring Data to set up and access data in a MongoDB document database for two of the core microservices and access data in a MySQL relational database using the Java Persistence API (JPA) for the remaining microservice.
Chapter 7, Developing Reactive Microservices, will teach you why and when a reactive approach is of importance and how to develop end-to-end reactive services. You will learn how to develop and test both non-blocking synchronous RESTful APIs and asynchronous event-driven services. You will also learn how to use the reactive non-blocking driver for MongoDB and use conventional blocking code for MySQL.
Chapter 8, Introduction to Spring Cloud, will introduce you to Spring Cloud and the components of Spring Cloud that will be used in this book.
Chapter 9, Adding Service Discovery Using Netflix Eureka and Ribbon, will show you how to use Netflix Eureka and Ribbon in Spring Cloud to add service discovery capabilities. This will be achieved by adding a Netflix Eureka-based service discovery server to the system landscape. You will then configure the microservices to use Netflix Ribbon to find other microservices. You will understand how microservices are registered automatically and how traffic through Netflix Ribbon is automatically load balanced to new instances when they become available.
Chapter 10, Using Spring Cloud Gateway to Hide Microservices Behind an Edge Server, will guide you through how to hide the microservices behind an edge server using Spring Cloud Gateway and only expose selected APIs to external consumers. You will also learn how to hide the internal complexity of the microservices from external consumers. This will be achieved by adding a Spring Cloud Gateway-based edge server to the system landscape and configuring it to only expose the public APIs.
Chapter 11, Securing Access to APIs, will explain how to protect exposed APIs using OAuth 2.0 and OpenID Connect. You will learn how to add an OAuth 2.0 authorization server based on Spring Security to the system landscape, and how to configure the edge server and the composite service to require valid access tokens issued by that authorization server. You will learn how to expose the authorization server through the edge server and secure its communication with external consumers using HTTPS. Finally, you will learn how to replace the internal OAuth 2.0 authorization server with an external OpenID Connect provider from Auth0.
Chapter 12, Centralized Configuration, will deal with how to collect the configuration files from all the microservices in one central repository and use the configuration server to distribute the configuration to the microservices at runtime. You will also learn how to add a Spring Cloud Config Server to the system landscape and configure all microservices to use the Spring Config Server to get its configuration.
Chapter 13, Improving Resilience Using Resilience4j, will explain how to use the capabilities of Resilience4j to prevent, for example, the "chain of failure" anti-pattern. You will learn how to add a retry mechanism and a circuit breaker to the composite service, and how to configure the circuit breaker to fast fail when the circuit is open, and how to utilize a fallback method to create a best-effort response.
Chapter 14, Understanding Distributed Tracing, will show you how to use Zipkin to collect and visualize tracing information. You will also use Spring Cloud Sleuth to add trace IDs to requests so that request chains between cooperating microservices can be visualized.
Chapter 15, Introduction to Kubernetes, will explain the core concepts of Kubernetes and how to perform a sample deployment. You will also learn how to set up Kubernetes locally for development and testing purposes using Minkube.
Chapter 16, Deploying Our Microservices to Kubernetes, will show how to deploy microservices on Kubernetes. You will also learn how to use Kustomize to configure the deployment in Kubernetes for different runtime environments, such as test and production environments. Finally, you will learn how to replace Netflix Eureka with the built-in support in Kubernetes for service discovery, based on Kubernetes services objects and the kube-proxy runtime component.
Chapter 17, Implementing Kubernetes Features as an Alternative, will explain how to use Kubernetes features as an alternative to the Spring Cloud services introduced in the previous chapters. You will learn why and how to replace Spring Cloud Config Server with Kubernetes secrets and config maps. You will also learn why and how to replace Spring Cloud Gateway with Kubernetes ingress objects and how to add the Cert Manager to automatically provision and rotate certificates from Let's Encrypt's for HTTPS endpoints.
Chapter 18, Using a Service Mesh to Improve Observability and Management, will introduce the concept of a service mesh and will explain how to use Istio to implement a service mesh in runtime using Kubernetes. You will learn how to use a service mesh to further improve the resilience, security, traffic management, and observability of the microservice landscape.
Chapter 19, Centralized Logging with the EFK Stack, will explain how to use Elasticsearch, Fluentd, and Kibana (the EFK stack) to collect, store, and visualize log streams from microservices. You will learn how to deploy the EFK stack in Minikube and how to use it to analyze collected log records and find log output from all microservices involved in the processing of a request that spans several microservices. You will also learn how to perform root cause analysis using the EFK stack.
Chapter 20, Monitoring Microservices, will show you how to monitor the microservices deployed in Kubernetes using Prometheus and Grafana. You will learn how to use both existing dashboards in Granfana to monitor different types of metrics, and you will also learn how to create your own dashboards. Finally, you will learn how to create alerts in Grafana that will be used to send emails with alerts when configured thresholds are passed for selected metrics.
Assessments, is uploaded on the GitHub repository containing the answers to the questions asked in the respective chapters.
A good understanding of Java 8, along with a basic knowledge of Spring Framework, is required. A general understanding of the challenges of distributed systems, in addition to some experience of running your own code in production, will also be beneficial.
You can download the example code files for this book from your account at www.packt.com. If you purchased this book elsewhere, you can visit www.packtpub.com/support and register to have the files emailed directly to you.
You can download the code files by following these steps:
Log in or register at
www.packt.com
.
Select the
Support
tab.
Click on
Code Downloads
.
Enter the name of the book in the
Search
box and follow the onscreen instructions.
Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:
WinRAR/7-Zip for Windows
Zipeg/iZip/UnRarX for Mac
7-Zip/PeaZip for Linux
The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Hands-On-Microservices-with-Spring-Boot-and-Spring-Cloud. In case there's an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
We also provide a PDF file that has color images of the screenshots/diagrams used in this book. You can download it here: https://static.packt-cdn.com/downloads/9781789613476_ColorImages.pdf.
To see the code being executed, please visit the following: http://bit.ly/2kn7mSp.
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: "To use the local filesystem, the config server needs to be launched with the Spring profile, native, enabled"
A block of code is set as follows:
management.endpoint.health.show-details: "ALWAYS"management.endpoints.web.exposure.include: "*"logging.level.root: info
When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:
backend: serviceName: auth-server
servicePort: 80
- path: /product-composite
Any command-line input or output is written as follows:
brew install kubectl
Bold: Indicates a new term, an important word, or words that you see on screen. For example, words in menus or dialog boxes appear in the text like this. Here is an example: "As seen in the preceding screenshot Chrome reports: This certificate is valid!"
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, mention the book title in the subject of your message and email us at [email protected].
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in, and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!
For more information about Packt, please visit packt.com.
In this section, you will learn how to use some of the most important features of Spring Boot to develop microservices.
This section includes the following chapters:
Chapter 1
,
Introduction to Microservices
Chapter
2
,
Introduction to Spring Boot
Chapter
3
,
Creating a Set of Cooperating Microservices
Chapter
4
,
Deploying Our Microservices Using Docker
Chapter
5
,
Adding API Description Using OpenAPI/Swagger
Chapter
6
,
Adding Persistence
Chapter
7
,
Developing Reactive Microservices
This book does not blindly praise microservices. Instead, it's about how we can use their benefits while being able to handle the challenges of building scalable, resilient, and manageable microservices.
As an introduction to this book, the following topics will be covered in this chapter:
How I learned about microservices and what experience I have of their
benefits
and
challenges
What is a microservice-based architecture?
Challenges with microservices
Design patterns for handling challenges
Software enablers that can help us handle these challenges
Other important considerations that aren't covered in this book
No installations are required for this chapter. However, you may be interested in taking a look at the C4 model conventions, https://c4model.com, since the illustrations in this chapter are inspired by the C4 model.
This chapter does not contain any source code.
When I first learned about the concept of microservices back in 2014, I realized that I had been developing microservices (well, kind of) for a number of years without knowing it was microservices I was dealing with. I was involved in a project that started in 2009 where we developed a platform based on a set of separated features. The platform was delivered to a number of customers that deployed it on-premise. To make it easy for the customers to pick and choose what features they wanted to use from the platform, each feature was developed as an autonomous software component; that is, it had its own persistent data and only communicated with other components using well-defined APIs.
Since I can't discuss specific features in this project's platform, I have generalized the names of the components, which are labeled from Component A to Component F. The composition of the platform into a set of components is illustrated as follows:
Each component is developed using Java and the Spring Framework, and is packaged as a WAR file and deployed as a web app in a Java EE web container, for example, Apache Tomcat. Depending on the customer's specific requirements, the platform can be deployed on single or multiple servers. A two-node deployment may look as follows:
Decomposing the platform's functionality into a set of autonomous software components provides a number of benefits:
A customer can deploy parts of the platform in its own system landscape, integrating it with its existing systems using its well-defined APIs. The following is an example where one customer decided to deploy
Component A
,
Component
B
,
Component
D
, and
Component
E
from the platform and integrate them with two existing systems in the customer's system landscape,
System A
and
System B
:
Another customer can choose to replace parts of the platform's functionality with implementations that already exist in the customer's system landscape, potentially requiring some adoption of the existing functionality in the platform's APIs. The following is an example where a customer has replaced
Component C
and
Com
ponent F
in the platform with their own implementation:
Each component in the platform can be delivered and upgraded separately. Thanks to using
well-defined APIs, o
ne component can be upgraded to a new version without being dependent on the
life cycle
of the other components. The following is an example where
Component A
has been upgraded from version
v1.1
to
v1.2
.
Component B
, which calls
Component A
, does not need to be upgraded since it uses a
well-defined API; that is, it's still the same after the upgrade (or it's at least backward-compatible):
Thanks to the use of well-defined APIs, each component in the platform can also be scaled out to multiple servers independently of the other components. Scaling can be done either to meet high availability requirements or to handle higher volumes of requests. Technically, this is achieved by
manually
setting up load balancers in front of a number of servers, each running a
Java EE web container. An example where
Component A
has been scaled out to three instances looks as follows:
We also learned that decomposing the platform introduced a number of new challenges that we were not exposed (at least not to the same degree) when developing more traditional, monolithic applications:
Adding new instances to a component required manually configuring load balancers and manually setting up new nodes. This work was both
time-consuming and
error-prone.
The platform was initially prone to errors in the other systems it was communicating with. If a system stopped responding to requests that were sent from the platform in a timely fashion, the platform quickly ran out of crucial resources, for example, OS threads, specifically when exposed to a large number of concurrent requests. This caused components in the platform to hang or even crash. Since most of the communication in the platform is based on synchronous communication, one component crashing can lead to cascading failures; that is, clients of the crashing components could also crash after a while. This is known as a
chain of failures
.
Keeping the configuration consistent and up to date in all the instances of the components quickly became a problem, causing a lot of manual and repetitive work. This led to quality problems from time to time.
Monitoring the state of the platform in terms of latency issues and hardware usage (for example, usage of CPU, memory, disks, and the network) was more complicated compared to monitoring a single instance of a monolithic application.
Collecting log files from a number of distributed components and correlating related log events from the components was also difficult but feasible since the number of components was fixed and known in advance.
Over time, we addressed most of the challenges that were mentioned in the preceding list with a mix of in-house-developed tools and well-documented instructions for handling these challenges manually. The scale of the operation was, in general, at a level where manual procedures for releasing new versions of the components and handling runtime issues were acceptable, even though they were not desirable.
Learning about microservice-based architectures in 2014 made me realize that other projects had also been struggling with similar challenges (partly for other reasons than the ones I described earlier, for example, the large cloud service providers meeting web-scale requirements). Many microservice pioneers had published details of lessons they'd learned. It was very interesting to learn from these lessons.
Many of the pioneers initially developed monolithic applications that made them very successful from a business perspective. But over time, these monolithic applications became more and more difficult to maintain and evolve. They also became challenging to scale beyond the capabilities of the largest machines available (also known as vertical scaling). Eventually, the pioneers started to find ways to split monolithic applications into smaller components that could be released and scaled independently of each other. Scaling small components can be done horizontally, that is, deploying a component on a number of smaller servers and placing a load balancer in front of it. If done in the cloud, the scaling capability is potentially endless – it is just a matter of how many virtual servers you bring in (given that your component can scale out on a huge number of instances, but more on that later on).
In 2014, I also learned about a number of new open source projects that delivered tools and frameworks that simplified the development of microservices and could be used to handle the challenges that come with a microservice-based architecture. Some of these are as follows:
Pivotal released
Spring Cloud
, which wraps parts of the
Netflix OSS
in order to provide capabilities such as dynamic service discovery, configuration management, distributed tracing, circuit breaking, and more.
I also learned about
Docker
and the container revolution, which is great for minimizing the gap between development and production. Being able to package a component not only as a deployable runtime
artifact
(for example, a Java,
war
or,
jar
file) but as a complete image ready to be launched as a container (for example, an isolated process) on a server running Docker was a great step forward for development and testing.
A container engine, such as Docker, is not enough to be able to use containers in a production environment. Something is needed that, for example, can ensure that all the containers are up and running and that they can scale out containers on a number of servers, thereby providing high availability and/or increased compute resources. These types of product became known as
container orchestrators
.
A number of products have evolved over the last few years, such as Apache Mesos, Docker in Swarm mode, Amazon ECS, HashiCorp Nomad, and
Kubernetes
. Kubernetes was initially developed by Google. When Google released v1.0, they also donated Kubernetes to CNCF (
https
://www.cncf.io/
). During 2018, Kubernetes became kind of a de facto standard, available both pre-packaged for on-premise use and available as a service from most major cloud providers.
I have recently started to learn about the concept of a
service mesh
and how a service mesh can complement a container orchestrator to further offload microservices from responsibilities to make them manageable and resilient.
Since this book can't cover all aspects of the technologies I just mentioned, I will focus on the parts that have proven to be useful in customer projects I have been involved in since 2014. I will describe how they can be used together to create cooperating microservices that are manageable, scalable, and resilient.
Each chapter in this book will address a specific concern. To demonstrate how things fit together, I will use a small set of cooperating microservices that we will evolve throughout this book:
Now that we know the how and what of microservices, let's start to look into how a microservice can be defined.
To me, a microservice architecture is about splitting up monolithic applications into smaller components, which achieves two major goals:
Faster development, enabling continuous deployments
Easier to scale, manually or automatically
A microservice is essentially an autonomous software component that is independently upgradeable and scalable. To be able to act as an autonomous component, it must fulfill certain criteria as follows:
It must conform to a shared-nothing architecture; that is,
microservices
don't share data in databases with each other!
It must only communicate through well-defined interfaces, for example, using synchronous services or preferably by sending messages to each other using APIs and message formats that are stable, well-documented, and evolve by following a defined versioning strategy.
It must be deployed as separate runtime processes. Each instance of a microservice runs in a separate
runtime process, for example, a Docker container.
Microservice instances are stateless so that incoming requests to a microservice can be handled by any of its instances.
Using a set of microservices, we can deploy to a number of smaller servers instead of being forced to deploy to a single big server, like we have to do when deploying a monolithic application.
Given that the preceding criteria have been fulfilled, it is easier to scale up a single microservice into more instances (for example, using more virtual servers) compared to scaling up a big monolithic application. Utilizing auto-scaling capabilities that are available in the cloud is also a possibility, but not typically feasible for a big monolithic application. It's also easier to upgrade or even replace a single microservice compared to upgrading a big monolithic application.
This is illustrated by the following diagram, where a monolithic application has been divided into six microservices, all of which have been deployed into one separate server. Some of the microservices have also been scaled up independently of the others:
A very frequent question I receive from customers is, How big should a microservice be?
I try to use the following rules-of-thumb:
Small enough to fit in the head of a developer
Big enough to not jeopardize performance (that is, latency) and/or data consistency (SQL foreign keys between data that's stored in different microservices are no longer something you can take for granted)
So, to summarize, a microservice architecture is, in essence, an architectural style where we decompose a monolithic application into a group of cooperating autonomous software components. The motivation is to enable faster development and to make it easier to scale the application.
Next, we will move on to understand some of the challenges that we will face when it comes to microservices.
In the Challenges with autonomous software components section, we have already seen some of the challenges that autonomous software components can bring (and they all apply to microservices as well) as follows:
Many small components that use synchronous communication can cause
a
chain of failure
problem, especially under high load.
Keeping the configuration up to date for
m
any small components can be challenging.
It's hard to track a request that's being processed and involves many components, for example, when performing root cause analysis, where each component stores log events locally.
Analyzing the usage of hardware resources on a component level can be challenging as well.
Manual
configuration and management
of m
any small components can become costly and error-prone.
Another downside (but not always obvious initially) of decomposing an application into a group of autonomous components is that they form a distributed system. Distributed systems are known to be, by their nature, very hard to deal with. This has been known for many years (but in many cases neglected until proven differently). My favorite quote to establish this fact is from Peter Deutsch who, back in 1994, stated the following:
The 8 fallacies of distributed computing: Essentially everyone, when they first build a distributed application, makes the following eight assumptions. All prove to be false in the long run and all cause big trouble and painful learning experiences:
The network is reliableLatency is zeroBandwidth is infiniteThe network is secureTopology doesn't changeThere is one administratorTransport cost is zeroThe network is homogeneousNote: The eighth fallacy was actually added by James Gosling at a later date. For more details, please go to https://www.rgoarchitects.com/Files/fallacies.pdf.
In general, building microservices-based on these false assumptions leads to solutions that are prone to both temporary network glitches and problems that occur in other microservice instances. When the number of microservices in a system landscape increases, the likelihood of problems also goes up. A good rule of thumb is to design your microservice architecture based on the assumption that there is always something going wrong in the system landscape. The microservice architecture needs to be designed to handle this, in terms of detecting problems and restarting failed components but also on the client-side so that requests are not sent to failed microservice instances. When problems are corrected, requests to the previously failing microservice should be resumed; that is, microservice clients need to be resilient. All of these need, of course, to be fully automated. With a large number of microservices, it is not feasible for operators to handle this manually!
The scope of this is large, but we will limit ourselves for now and move on to study design patterns for microservices.
This topic will cover using design patterns to mitigate challenges with microservices, as described in the preceding section. Later in this book, we will see how we can implement these design patterns using Spring Boot, Spring Cloud, and Kubernetes.
The concept of design patterns is actually quite old; it was invented by Christopher Alexander back in 1977. In essence, a design pattern is about describing a reusable solution to a problem when given a specific context.
The design patterns we will cover are as follows:
Service discovery
Edge server
Reactive microservices
Central configuration
Centralized log analysis
Distributed tracing
Circuit Breaker
Control loop
Centralized monitoring and alarms
We will use a lightweight approach to describing design patterns, and focus on the following:
The problem
A solution
Requirements for the solution
Later in this book, we will delve more deeply into how to apply these design patterns. The context for these design patterns is a system landscape of cooperating microservices where the microservices communicate with each other using either synchronous requests (for example, using HTTP) or by sending asynchronous messages (for example, using a message broker).
The servicediscovery pattern has the following problem, solution, and solution requirements.
How can clients find microservices and their instances?
Microservices instances are typically assigned dynamically allocated IP addresses when they start up, for example, when running in containers. This makes it difficult for a client to make a request to a microservice that, for example, exposes a REST API over HTTP. Consider the following diagram:
Add a new component – a service discovery service – to the system landscape, which keeps track of currently available microservices and the IP addresses of its instances.
Some solution requirements are as follows:
Automatically register/unregister microservices and their instances as they come and go.
The client must be able to make a request to a logical endpoint for the microservice. The request will be routed to one of the microservices available instances.
Requests to a microservice must be load-balanced over the available instances.
We must be able to detect instances that are not currently healthy; that is, requests will not be routed to them.
Implementation notes: As we will see, this design pattern can be implemented using two different strategies:
Client-side routing
: T
he client uses a library that communicates with the s
ervice discovery
service to find out the proper instances to send the requests to.
Server-side routing
: T
he infrastructure of the s
ervice discovery
service also exposes a reverse proxy that all requests are sent to. The reverse proxy forwards the requests to a proper microservice instance on behalf of the client.
The edge server pattern has the following problem, solution, and solution requirements.
In a system landscape of microservices, it is in many cases desirable to expose some of the microservices to the outside of the system landscape and hide the remaining microservices from external access. The exposed microservices must be protected against requests from malicious clients.
Add a new component, an Edge Server, to the system landscape that all incoming requests will go through:
Implementation notes: An edge server typically behaves like a reverse proxy and can be integrated with a discovery service to provide dynamic load balancing capabilities.
Some solution requirements are as follows:
Hide internal services that should not be exposed outside their context; that is, only route requests to microservices that are configured to allow external requests.
Expose external services and protect them from malicious requests; that is, use standard protocols and best practices such as OAuth, OIDC, JWT tokens, and API keys to ensure that the clients are trustworthy.
The reactive microservice pattern has the following problem, solution, and solution requirements.
Traditionally, as Java developers, we are used to implementing synchronous communication using blocking I/O, for example, a RESTful JSON API over HTTP. Using a blocking I/O means that a thread is allocated from the operating system for the length of the request. If the number of concurrent requests goes up (and/or the number of involved components in a request, for example, a chain of cooperating microservices, goes up), a server might run out of available threads in the operating system, causing problems ranging from longer response times to crashing servers.
Also, as we already mentioned in this chapter, overusing blocking I/O can make a system of microservices prone to errors. For example, an increased delay in one service can cause clients to run out of available threads, causing them to fail. This, in turn, can cause their clients to have the same types of problem, which is also known as a chain of failures. See the Circuit Breaker section for how to handle a chain-of-failure-related problem.
Use non-blocking I/O to ensure that no threads are allocated while waiting for processing to occur in another service, that is, a database or another microservice.
Some solution requirements are as follows:
Whenever feasible, use an asynchronous programming model; that is, send messages without waiting for the receiver to process them.
If a synchronous programming model is preferred, ensure that reactive frameworks are used that can execute synchronous requests using non-blocking I/O, that is, without allocating a thread while waiting for a response. This will make the microservices easier to scale in order to handle an increased workload.
Microservices must also be designed to be resilient, that is, capable of producing a response, even if a service that it depends on fails. Once the failing service is operational again, its clients must be able to resume using it, which is known as self-healing.
The central configuration pattern has the following problem, solution, and solution requirements.
An application is, traditionally, deployed together with its configuration, for example, a set of environment variables and/or files containing configuration information. Given a system landscape based on a microservice architecture, that is, with a large number of deployed microservice instances, some queries arise:
How do I get a complete picture of the configuration that is in place for all the running microservice instances?
How do I update the configuration and make sure that all the affected
microservice instances
