Istio is an open source project to better manage service mesh in the world of microservices. It puts together many new concepts, packages, and approaches to enhance the experience of controlling and monitoring microservices. One of the new concepts is “Mixer.”
The Istio Mixer, as its name suggests, can take in different configurations and merge them with a different data source, then dispatch them to different channels. This is a very useful feature because it allows users to create a wide range of policies that meet their specific needs. However, the Mixer isn’t a completely simple tool; with increased capabilities comes increased complexity.
The Mixer currently supports three categories of policies:
- Precondition checking, such as whether the source service is on the destination service’s allowlist.
- Quota management, such as rate limits.
- Telemetry reporting, such as logging a sending date to zipkin and displaying through graphana.
The traditional policy format, which consists of match and action (a simple ACL-based approach), cannot accommodate the diversity of the rules. With so many new features and terms it can be confusing for users.
In this tutorial, we’ll examine the three major parts of a Mixer configuration: the Adapter/Handler, the Instance, and the Rule.
NOTE: The current version being referred to is Alpha version 0.2. Some terminology from version 0.1 has been deprecated. For example, in Alpha version 0.1 “Aspect” was a major concept, but it was difficult to position the concept while specifying both
adapter in the API.
Think of an adapter as a plugin. It’s the code block that performs the logical function and can be invoked by Mixer framework. The image below will help you understand the plugin structure.
While some adapters may have connections to backend services like Prometheus or New Relic, others can perform the whole functionality within the adapter code itself, like quota or blocklist. Below is the list of the current adapters available:
- kubernetes Config
- list Config
- memquota Config
- prometheus Config
- stackdriver Config
- statsd Config
- stdio Config
- svcctrl Config
- denier Config
Of course, as with any plugin framework, you can create your own adapter. That task is beyond the scope of this guide. Handler is an adapter with operational parameters. One way to see this is to take Adapter as the class definition and Handler as the concrete object. In fact, in the configuration file, we only define the handler, which refers to the corresponding adapter. The following is an example of a quota handler.
metadata: name: handler namespace: istio-config-default spec: quotas: - name: requestcount.quota.istio-config-default maxAmount: 5000 validDuration: 1s
In this quota handler, we specify 5000 requests per second. The operational parameters are defined by the specific adapter. Each handler configuration is therefore different.
Instance deals with data weaving. Envoy collects runtime data, but a handler only cares about part of it, so it’s important to pass useful information. There are grpc proto files called templates associated with the instances. Each instance has to fall into one of the templates. The current templates are:
Next, let’s take a look at how the date is specified in the instance:
“Dimension” actually means compound keys. The attributes are the keys, and are the information weaved mainly by Envoy. They are dispatched into each adapter and can be “cherry-picked” by the instance configuration. This quota instance categorizes the
destinationVersion of each request into one compound key. When the handler gets the data, it will put each request into different buckets based on the compound key. The quota of 5000/sec is applied on those buckets individually.
Note in the diagram each attribute has a name and expression. The names are fixed; that is, they cannot be changed. The list of names are available here. The value type, that is the int or string, is also fixed. However, the values are not bound. We can change the expression to get a different value. For example, instead of
source: source.labels[“app”] we can use
source: request.headers[“X-Forwarded-For”]. This will change the value from source label to source IP address. The “|” means alternative value if the previous value doesn’t exist.
Rules are used to specify when the instance data will be sent to handlers. You might ask: didn’t we just specify the dimension of the instance? Why do we need rules? Well, as it turns out, the relationship between handler and instance is many to many. So, you need rules to specify when to invoke what. Let’s look at an example:
apiVersion: "config.istio.io/v1alpha2" kind: rule metadata: name: promtcp namespace: istio-system spec: match: context.protocol == "tcp" actions: - handler: handler.prometheus instances: - tcpbytesreceived.metric - tcpbytessent.metric
In each rule, there is a match, which specifies a condition, and actions, which specify, well, the actions. In this example, when the protocol is TCP, the handler handler.prometheus will be called by two instances:
Sometimes we want to apply to all situations. In those cases, we can have an empty match or write
In summary, the rule defines when, the instance defines what (data), and the handler defines where (adapter) to dispatch the information.
Next, we’ll go through an example to illustrate those concepts. Our task is: use denier adapter (refer to the “list of adapters” image above) to deny all the requests if the user agent is
Before we start, it’s important to know that the Istio API follows the Kubernetes API format. There are certain structures and keywords in the format:
spec are the common ones.
The first step is to create a handler of denier adapter.
apiVersion: “config.istio.io/v1alpha2” kind: denier metadata: name: denyall namespace: istio-system spec: status: code: 7 message: Not allowed
The current API version is config.istio.io/v1alpha2. “Kind” is the place to specify adaptor. “Name” is the name of the handler. “Namespace” specifies which namespace the handler can be applied on. The “istio-system” is the super namespace that can be applied on all namespaces in the cluster. Under “spec”, the content is required by the denier adapter.
The second step is to create an instance:
apiVersion: “config.istio.io/v1alpha2” kind: checknothing metadata: name: denyrequest namespace: istio-system spec:
The “kind” here specifies the proto template “checknothing”. The name of this instance is “denyrequest”. Since we don’t need to check anything, the “spec” is left empty; that is, no information is going to be passed on to the handler. The rest are the same as the handler config.
Lastly, let’s create the rule:
apiVersion: "config.istio.io/v1alpha2" kind: rule metadata: name: mixerdenysome namespace: istio-system spec: match: match(request.headers["user-agent"], "curl*") actions: - handler: denyall.denier instances: - denyrequest.checknothing
So far, we should know the “kind” value is “rule”. In “match”, since curl has different versions, it’s important to use partial match here. In “actions”, note that the reference to the handler is the name of handler plus the name of adapter(“kind” in the handler). And the reference to the instance is the name of the instance plus the template name of instance(“kind” in the instance).
When we combine all three parts into a
.yaml file and run
istioctl apply -f xxx.yaml with it, the mixer config is created by Istio.
We hope this tutorial make things a little bit easier for you when writing a Mixer config! IBM Cloud is a great place to start your Istio journey.