Service Mesh With Istio on Kubernetes in 5 Steps
In this tutorial, we'll discover how to make services that can communicate with one another using Istio and Kubernetes.
Join the DZone community and get the full member experience.
Join For FreeIn this article, I'm going to show you some basic and more advanced samples that illustrate how to use the Istio platform in order to provide communication between microservices deployed on Kubernetes. Following the description on Istio website, it is:
An open platform to connect, manage, and secure microservices. Istio provides an easy way to create a network of deployed services with load balancing, service-to-service authentication, monitoring, and more, without requiring any changes in service code.
Istio provides mechanisms for traffic management like request routing, discovery, load balancing, handling failures, and fault injection. Additionally, you may enable istio-auth, which provides RBAC (Role-Based Access Control) and Mutual TLS Authentication. In this article, we will discuss only traffic management mechanisms.
Step 1. Installing Istio on the Minikube Platform
The most comfortable way to test Istio locally on Kubernetes is through Minikube. I have already described how to configure Minikube on your local machine in this article: Microservices with Kubernetes and Docker. When installing Istio on Minikube we should first enable some Minikube's plugins during startup.
minikube start --extra-config=controller-manager.ClusterSigningCertFile="/var/lib/localkube/certs/ca.crt" --extra-config=controller-manager.ClusterSigningKeyFile="/var/lib/localkube/certs/ca.key" --extra-config=apiserver.Admission.PluginNames=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
Istio is installed in a dedicated namespace called istio-system
, but is able to manage services from all other namespaces. First, you should go to the release page and download the installation file corresponding to your OS. For me, it is Windows, and all the next steps will be described with the assumption that we are using exactly this OS. After running Minikube it would be useful to enable Docker on Minikube's VM. Thanks to that, you will be able to execute docker
commands.
@FOR /f "tokens=* delims=^L" %i IN ('minikube docker-env') DO @call %i
Now, extract the Istio files to your local filesystem. The file istioctl.exe
, which is available under the ${ISTIO_HOME}/bin
directory, should be added to your PATH. Istio contains some installation files for Kubernetes platform in ${ISTIO_HOME}/install/kubernetes
. To install Istio's core components on Minikube just apply the following YAML definition file.
kubectl apply -f install/kubernetes/istio.yaml
Now, you have Istio's core components deployed on your Minikube instance. These components are:
Envoy - an open-source edge and service proxy designed for cloud-native applications. Istio uses an extended version of the Envoy proxy. If you are interested in details about Envoy and microservices, read my article Envoy Proxy with Microservices, which describes how to integrate the Envoy gateway with service discovery.
Mixer - a platform-independent component responsible for enforcing access control and usage policies across the service mesh.
Pilot - provides service discovery for the Envoy sidecars and traffic management capabilities for intelligent routing and resiliency.
The configuration provided inside istio.yaml
deploys some pods and services related to the components mentioned above. You can verify the installation using the kubectl
command or just by visiting the Web Dashboard available after executing the command minikube dashboard
.
Step 2. Building Sample Applications Based on Spring Boot
Before we start to configure any traffic rules with Istio, we need to create sample applications that will communicate with each other. These are really simple services. The source code of these applications is available on my GitHub account inside the repository sample-istio-services. There are two services: caller-service
and callme-service
. Both of them expose an endpoint ping which prints the application's name and version. Both of these values are taken from the Spring Boot build-info
file, which is generated during the application build. Here's the implementation of the endpoint GET /callme/ping
.
@RestController
@RequestMapping("/callme")
public class CallmeController {
private static final Logger LOGGER = LoggerFactory.getLogger(CallmeController.class);
@Autowired
BuildProperties buildProperties;
@GetMapping("/ping")
public String ping() {
LOGGER.info("Ping: name={}, version={}", buildProperties.getName(), buildProperties.getVersion());
return buildProperties.getName() + ":" + buildProperties.getVersion();
}
}
And here's the implementation of the endpoint GET /caller/ping
. It calls the GET /callme/ping
endpoint using Spring RestTemplate
. We are assuming that callme-service
is available under the address callme-service:8091
on Kubernetes. This service will be exposed inside the Minikube node under port 8091.
@RestController
@RequestMapping("/caller")
public class CallerController {
private static final Logger LOGGER = LoggerFactory.getLogger(CallerController.class);
@Autowired
BuildProperties buildProperties;
@Autowired
RestTemplate restTemplate;
@GetMapping("/ping")
public String ping() {
LOGGER.info("Ping: name={}, version={}", buildProperties.getName(), buildProperties.getVersion());
String response = restTemplate.getForObject("http://callme-service:8091/callme/ping", String.class);
LOGGER.info("Calling: response={}", response);
return buildProperties.getName() + ":" + buildProperties.getVersion() + ". Calling... " + response;
}
}
The sample applications have to be started on a Docker container. Here's the Dockerfile that is responsible for building an image with the caller-service
application.
FROM openjdk:8-jre-alpine
ENV APP_FILE caller-service-1.0.0-SNAPSHOT.jar
ENV APP_HOME /usr/app
EXPOSE 8090
COPY target/$APP_FILE $APP_HOME/
WORKDIR $APP_HOME
ENTRYPOINT ["sh", "-c"]
CMD ["exec java -jar $APP_FILE"]
A similar Dockerfile
is available for callme-service
. Now, the only thing we have to is to build Docker images.
docker build -t piomin/callme-service:1.0 .
docker build -t piomin/caller-service:1.0 .
There is also version 2.0.0-SNAPSHOT
of callme-service
available in branch v2
. Switch to this branch, build the whole application, and then build a Docker image with the 2.0
tag. Why do we need version 2.0? I'll describe it in the next section.
docker build -t piomin/callme-service:2.0 .
Step 3. Deploying the Sample Applications on Minikube
Before we start deploying our applications on Minikube, let's take a look at the sample system architecture visible on the following diagram. We are going to deploy callme-service
in two versions: 1.0
and 2.0
. The application caller-service
is just calling callme-service, so it does not know anything about different versions of the target service. If we would like to route traffic between two versions of callme-service
in proportions 20% to 80%, we have to configure the proper Istio routerule. Also, because Istio Ingress is not supported on Minikube, we will just use Kubernetes Service. If we need to expose it outside the Minikube cluster, we should set the type to NodePort
.
Let's proceed to the deployment phase. Here's the deployment definition for callme-service
in version 1.0
.
apiVersion: v1
kind: Service
metadata:
name: callme-service
labels:
app: callme-service
spec:
type: NodePort
ports:
- port: 8091
name: http
selector:
app: callme-service
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: callme-service
spec:
replicas: 1
template:
metadata:
labels:
app: callme-service
version: v1
spec:
containers:
- name: callme-service
image: piomin/callme-service:1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8091
Before deploying it on Minikube, we have to inject some Istio properties. The command below prints a new version of the deployment definition enriched with Istio configuration. We may copy it and save it as deployment-with-istio.yaml
.
istioctl kube-inject -f deployment.yaml
Now, let's apply the configuration to Kubernetes.
kubectl apply -f deployment-with-istio.yaml
The same steps should be performed for caller-service
, and also for version 2.0
of callme-service
. All YAML configuration files are committed together with the applications and are located in the root directory of every application's module. If you have successfully deployed all the required components, you should see the following elements in your Minikube's dashboard.
Step 4. Applying Istio Routing Rules
Istio provides a simple Domain-specific language (DSL) that allows you configure some interesting rules that control how requests are routed within your service mesh. I'm going to show you the following rules:
- Split traffic between different service versions
- Injecting the delay in the request path
- Injecting HTTP error as a response from the service
Here's sample route rule definition for callme-service
. It splits traffic in proportions 20:80 between versions 1.0 and 2.0 of the service. It also adds a 3-second delay in 10% of the requests,= and returns an HTTP 500 error code for 10% of the requests.
apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
name: callme-service
spec:
destination:
name: callme-service
route:
- labels:
version: v1
weight: 20
- labels:
version: v2
weight: 80
httpFault:
delay:
percent: 10
fixedDelay: 3s
abort:
percent: 10
httpStatus: 500
Let's apply a new route rule to Kubernetes.
kubectl apply -f routerule.yaml
Now, we can easily verify that rule by executing the command istioctl get routerule
.
Step 5. Testing the Solution
Before we start testing, let's deploy Zipkin on Minikube. Istio provides the deployment definition file zipkin.yaml
inside the directory ${ISTIO_HOME}/install/kubernetes/addons
.
kubectl apply -f zipkin.yaml
Let's take a look at the list of services deployed on Minikube. The API provided by the application caller-service is available under port 30873.
We may easily test the service for a web browser by calling the URL http://192.168.99.100:30873/caller/ping. It prints the name and version of the service, and also the name and version of callme-service invoked by caller-service. Because 80% of traffic is routed to version 2.0 of callme-service, you will probably see the following response:
However, sometimes version 1.0 of callme-service may be called...
... or Istio can simulate HTTP 500 code.
You can easily analyze traffic statistics with the Zipkin console.
Or just take a look at the logs generated by the pods.
Published at DZone with permission of Piotr Mińkowski, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Transactional Outbox Patterns Step by Step With Spring and Kotlin
-
RAML vs. OAS: Which Is the Best API Specification for Your Project?
-
How To Scan and Validate Image Uploads in Java
-
Build a Simple Chat Server With gRPC in .Net Core
Comments