Spring Boot Library for integration with Istio
source link: https://piotrminkowski.wordpress.com/2020/06/10/spring-boot-library-for-integration-with-istio/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Spring Boot Library for integration with Istio
In this article I’m going to present an annotation-based Spring Boot library for integration with Istio. The Spring Boot Istio library provides auto-configuration, so you don’t have to do anything more than including it to your dependencies to be able to use it.
The library is using Istio Java Client me.snowdrop:istio-client
for communication with Istio API on Kubernetes. The following picture illustrates an architecture of presented solution on Kubernetes. The Spring Boot Istio is working just during application startup. It is able to modify existing Istio resources or creating new if there are no matching rules found.
Source code
The source code of library is available on my GitHub repository https://github.com/piomin/spring-boot-istio.git.
How to use it
To use in your Spring Boot application you include the following dependency.
<
dependency
>
<
groupId
>com.github.piomin</
groupId
>
<
artifactId
>spring-boot-istio</
artifactId
>
<
version
>0.1.0.RELEASE</
version
>
</
dependency
>
After that you should annotate one of your class with @EnableIstio
. The annotation contains several fields used for Istio DestinationRule
and VirtualService
objects.
@RestController
@RequestMapping
(
"/caller"
)
@EnableIstio
(version =
"v1"
, timeout =
3
, numberOfRetries =
3
)
public
class
CallerController {
private
static
final
Logger LOGGER = LoggerFactory.getLogger(CallerController.
class
);
@Autowired
BuildProperties buildProperties;
@Autowired
RestTemplate restTemplate;
@Value
(
"${VERSION}"
)
private
String version;
@GetMapping
(
"/ping"
)
public
String ping() {
LOGGER.info(
"Ping: name={}, version={}"
, buildProperties.getName(), version);
String response = restTemplate.getForObject(
"http://callme-service:8080/callme/ping"
, String.
class
);
LOGGER.info(
"Calling: response={}"
, response);
return
"I'm caller-service "
+ version +
". Calling... "
+ response;
}
}
The name of Istio objects is generated basing on spring.application.name
. So you need to provide that name in your application.yml
.
spring:
application:
name: caller-service
Currently there are five available fields that may be used for @EnableIstio
.
@Target
({ElementType.TYPE})
@Retention
(RetentionPolicy.RUNTIME)
@Documented
public
@interface
EnableIstio {
int
timeout()
default
0
;
String version()
default
""
;
int
weight()
default
0
;
int
numberOfRetries()
default
0
;
int
circuitBreakerErrors()
default
0
;
}
Here’s the detailed description of available parameters.
version
– it indicates the version of IstioSubset
. We may define multiple versions of the same application. The name of label isversion
weight
– it sets a weight assigned to theSubset
indicated by theversion
labeltimeout
– a total read timeout in seconds on the client side – including retriesnumberOfRetries
– it enables retry mechanism. By default we are retrying all 5XX HTTP codes. The timeout of a single retry is calculated astimeout / numberOfRetries
circuitBreakerErrors
– it enables circuit breaker mechanism. It is based of a number of consecutive HTTP 5XX errors. If circuit is open for a single application it is ejected from the pool for 30 seconds
How it works
Here’s the Deployment
definition of our sample service. It should be labelled with the same version
as set inside @EnableIstio
.
apiVersion: apps/v1
kind: Deployment
metadata:
name: caller-service
spec:
replicas:
1
selector:
matchLabels:
app: caller-service
template:
metadata:
name: caller-service
labels:
app: caller-service
version: v1
spec:
containers:
- name: caller-service
image: piomin/caller-service
imagePullPolicy: IfNotPresent
ports:
- containerPort:
8080
Let’s deploy our sample application on Kubernetes. After deploy we may verify the status of Deployment
.
The name of created DestinationRule
is a concatenation of spring.application.name
property value and word -destination
.
The name of created VirtualService
is a concatenation of spring.application.name
property value and word -route
. Here’s the definition of VirtualService
created for annotation @EnableIstio(version = "v1", timeout = 3, numberOfRetries = 3)
and caller-service
application.
How it is implemented
We need to define bean that implements BeanPostProcessor
interface. On application startup it is trying to find the annotation @EnableIstio
. If such annotation exists it takes a value of its fields and then it is creating new Istio objects or editing the currently existing objects.
public
class
EnableIstioAnnotationProcessor
implements
BeanPostProcessor {
private
final
Logger LOGGER = LoggerFactory.getLogger(EnableIstioAnnotationProcessor.
class
);
private
ConfigurableListableBeanFactory configurableBeanFactory;
private
IstioClient istioClient;
private
IstioService istioService;
public
EnableIstioAnnotationProcessor(ConfigurableListableBeanFactory configurableBeanFactory, IstioClient istioClient, IstioService istioService) {
this
.configurableBeanFactory = configurableBeanFactory;
this
.istioClient = istioClient;
this
.istioService = istioService;
}
public
Object postProcessBeforeInitialization(Object bean, String beanName)
throws
BeansException {
EnableIstio enableIstioAnnotation = bean.getClass().getAnnotation(EnableIstio.
class
);
if
(enableIstioAnnotation !=
null
) {
LOGGER.info(
"Istio feature enabled: {}"
, enableIstioAnnotation);
Resource<DestinationRule, DoneableDestinationRule> resource = istioClient.v1beta1DestinationRule()
.withName(istioService.getDestinationRuleName());
if
(resource.get() ==
null
) {
createNewDestinationRule(enableIstioAnnotation);
}
else
{
editDestinationRule(enableIstioAnnotation, resource);
}
Resource<VirtualService, DoneableVirtualService> resource2 = istioClient.v1beta1VirtualService()
.withName(istioService.getVirtualServiceName());
if
(resource2.get() ==
null
) {
createNewVirtualService(enableIstioAnnotation);
}
else
{
editVirtualService(enableIstioAnnotation, resource2);
}
}
return
bean;
}
}
We are using API provided by Istio Client library. It provides a set of builders dedicated for creating elements of Istio objects.
private
void
createNewDestinationRule(EnableIstio enableIstioAnnotation) {
DestinationRule dr =
new
DestinationRuleBuilder()
.withMetadata(istioService.buildDestinationRuleMetadata())
.withNewSpec()
.withNewHost(istioService.getApplicationName())
.withSubsets(istioService.buildSubset(enableIstioAnnotation))
.withTrafficPolicy(istioService.buildCircuitBreaker(enableIstioAnnotation))
.endSpec()
.build();
istioClient.v1beta1DestinationRule().create(dr);
LOGGER.info(
"New DestinationRule created: {}"
, dr);
}
private
void
editDestinationRule(EnableIstio enableIstioAnnotation, Resource<DestinationRule, DoneableDestinationRule> resource) {
LOGGER.info(
"Found DestinationRule: {}"
, resource.get());
if
(!enableIstioAnnotation.version().isEmpty()) {
Optional<Subset> subset = resource.get().getSpec().getSubsets().stream()
.filter(s -> s.getName().equals(enableIstioAnnotation.version()))
.findAny();
resource.edit()
.editSpec()
.addAllToSubsets(subset.isEmpty() ? List.of(istioService.buildSubset(enableIstioAnnotation)) :
Collections.emptyList())
.editOrNewTrafficPolicyLike(istioService.buildCircuitBreaker(enableIstioAnnotation)).endTrafficPolicy()
.endSpec()
.done();
}
}
private
void
createNewVirtualService(EnableIstio enableIstioAnnotation) {
VirtualService vs =
new
VirtualServiceBuilder()
.withNewMetadata().withName(istioService.getVirtualServiceName()).endMetadata()
.withNewSpec()
.addToHosts(istioService.getApplicationName())
.addNewHttp()
.withTimeout(enableIstioAnnotation.timeout() ==
0
?
null
:
new
Duration(
0
, (
long
) enableIstioAnnotation.timeout()))
.withRetries(istioService.buildRetry(enableIstioAnnotation))
.addNewRoute().withNewDestinationLike(istioService.buildDestination(enableIstioAnnotation)).endDestination().endRoute()
.endHttp()
.endSpec()
.build();
istioClient.v1beta1VirtualService().create(vs);
LOGGER.info(
"New VirtualService created: {}"
, vs);
}
private
void
editVirtualService(EnableIstio enableIstioAnnotation, Resource<VirtualService, DoneableVirtualService> resource) {
LOGGER.info(
"Found VirtualService: {}"
, resource.get());
if
(!enableIstioAnnotation.version().isEmpty()) {
istioClient.v1beta1VirtualService().withName(istioService.getVirtualServiceName())
.edit()
.editSpec()
.editFirstHttp()
.withTimeout(enableIstioAnnotation.timeout() ==
0
?
null
:
new
Duration(
0
, (
long
) enableIstioAnnotation.timeout()))
.withRetries(istioService.buildRetry(enableIstioAnnotation))
.editFirstRoute()
.withWeight(enableIstioAnnotation.weight() ==
0
?
null
: enableIstioAnnotation.weight())
.editOrNewDestinationLike(istioService.buildDestination(enableIstioAnnotation)).endDestination()
.endRoute()
.endHttp()
.endSpec()
.done();
}
}
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK