The Meshery v0.5.0 release includes two new libraries: MeshKit and Meshery Adapter Library.
These two libraries improve contributor experience and development speed by reducing the burden of sustaining the plethora of Meshery adapters, allowing contributors to focus on exposing a service mesh's differentiated value, instead of having to redundantly implement plumbing for managing service meshes.
MeshKit
MeshKit was formerly named gokit
and was renamed recently to align with the other Meshery components' names (and avoid confusion with the go-kit
project). MeshKit can be considered a derivative of go-kit
with specific focus on service mesh management.
In the Meshery v0.5.0 release, MeshKit has been enhanced and expanded substantially. Considering that the MeshKit library provides broadly useful functionality, it is used in a growing number of Meshery components. It is intended to be one of the top level libraries in the Meshery ecosystem.
MeshKit is a toolkit for Layer5’s microservices, and is positioned to become Layer5’s middleware component for Layer5’s microservices, leveraging other libraries like go-kit/kit
. In complement to functionality provided by a service mesh, its purpose is to provide implementations for common cross-cutting concerns like error handling, logging, and tracing. Uniform error handling and logging across all Meshery components helps to implement efficient tooling for observability, monitoring and troubleshooting. The library provides some common data models for Meshery, notably for Service Mesh Interface conformance testing, and Kubernetes' kubeconfig
.
Another central component in Meshkit is the utils
package.
This package provides a Kubernetes and a Helm client that implements functionality based on the Go libraries of these tools. The API exposed by these libraries is quite low-level, and the higher-level functions of the utils
package simplifies usage of Kubernetes and Helm clients significantly.
Another advantage MeshKit that it is not necessary to use the command line versions of these tools, providing a more tailored experience for developers,
and better logging and error handling integration.
1package main23import (4 "os"56 meshkitlogger "github.com/layer5io/meshkit/logger"7 meshkitkubernetes "github.com/layer5io/meshkit/utils/kubernetes"8 "k8s.io/client-go/kubernetes"9)1011func main() {12 // nginx contains the deployment manifest for nginx.13 nginx := `apiVersion: apps/v114kind: Deployment15metadata:16 name: nginx-deployment17spec:18 selector:19 matchLabels:20 app: nginx21 replicas: 2 # tells deployment to run 2 pods matching the template22 template:23 metadata:24 labels:25 app: nginx26 spec:27 containers:28 - name: nginx29 image: nginx:1.14.230 ports:31 - containerPort: 8032`3334 // Create an instance of the meshkit logger handler.35 log, err := meshkitlogger.New("ExampleApp",36 meshkitlogger.Options{Format: meshkitlogger.JsonLogFormat, DebugLevel: false})37 if err != nil {38 os.Exit(1)39 }40 log.Info("successfully instantiated meshkit logger")4142 // Detect kubeconfig on the local system.43 config, err := meshkitkubernetes.DetectKubeConfig()44 if err != nil {45 log.Error(err)46 os.Exit(1)47 }48 log.Info(config.Host)4950 // Create Kubernetes client set for the detected kubeconfig using the Kubernetes Go client library.51 clientset, err := kubernetes.NewForConfig(config)52 if err != nil {53 log.Error(err)54 os.Exit(1)55 }5657 // Create an instance of the meshkit Kubernetes client ...58 client, err := meshkitkubernetes.New(clientset, *config)59 if err != nil {60 log.Error(err)61 os.Exit(1)62 }6364 // ... and use it to deploy nginx to the cluster.65 err2 := client.ApplyManifest([]byte(nginx), meshkitkubernetes.ApplyOptions{66 Namespace: "default",67 Update: true,68 Delete: false,69 })70 if err2 != nil {71 log.Error(err2)72 os.Exit(1)73 }74 log.Info("successfully applied the manifest")75}
Meshery Adapters
Meshery adapters are management plane components and manage the lifecycle of service meshes. This includes installation and deletion, configuration, and verification that an installation follows recommended practices. In addition, Meshery adapters can assess to what extent a service mesh complies to the Service Mesh Interface standard. Meshery adapters support management of multiple versions of their respective service mesh and also come bundled with sample applications that can be deployed for easy and quick exploration of service mesh capabilities.
A Meshery adapter is a gRPC server that exposes the MeshServiceServer
interface:
1// MeshServiceServer is the server API for MeshService service.2type MeshServiceServer interface {3 CreateMeshInstance(context.Context, *CreateMeshInstanceRequest) (*CreateMeshInstanceResponse, error)4 MeshName(context.Context, *MeshNameRequest) (*MeshNameResponse, error)5 ApplyOperation(context.Context, *ApplyRuleRequest) (*ApplyRuleResponse, error)6 SupportedOperations(context.Context, *SupportedOperationsRequest) (*SupportedOperationsResponse, error)7 StreamEvents(*EventsRequest, MeshService_StreamEventsServer) error8}
CreateMeshInstance
sets up the Kubernetes client. It does not, as the name might imply, create an instance of a service mesh.MeshName
returns the name of the mesh, configured in the adapter.SupportedOperations
returns all supported operations, configured in the adapter. An operation is e.g. the installation of a service mesh.ApplyOperation
executes the operation specified in the request. It is one of the supported operations.StreamEvents
allows sending events from the server to the client.
This API is one of the extension points of Meshery, making it easy to add support for new service meshes to Meshery. Meshery adapters abstract away differences in installation and configuration of the various service meshes.
In general, the various service mesh implementations are installed and configured in their own way. For instance, some service meshes have their own installer, like istioctl
for Istio, while others use Helm charts, like Consul. One of the purposes of Meshery adapters is to abstract these differences away.
Meshery Adapter Library
As can be expected, adapters for the various meshes have a lot of code in common. Initially, this common code was copied from one adapter implementation to the next. The question arose whether common code should be factored out to one or several libraries. After some discussion, the community decided to move some of the more general code to Meshkit, and adapter specific code to a new library.
Thus, the Meshery Adapter Library was born.
It reduces the amount of boilerplate code in the adapters substantially, making adapter code easier to follow. This is especially valuable in an open source community where typically many developers contribute, for varying amounts of time. For the same reasons, it is important such libraries are easily understandable.
Also, it means new adapters can be implemented quickly, as only configuration and operations that differ between services meshes need to be implemented.
The initial commit was submitted on October 6th, 2020 based on a refactoring effort in the adapter for the Kuma service mesh. Within a few months, several adapters have been refactored or implemented from scratch based on the Meshery Adapter Library.
The main purpose of the Meshery Adapter Library is to:
- provide a set of interfaces, some with default implementations, to be used and extended by adapters.
- implement cross-cutting concerns like logging, error handling, and tracing.
- provide a mini framework implementing the gRPC server that allows plugging in the mesh specific configuration and operations implemented in the adapters.
The core interface in the library is the adapter Handler
interface:
1// Interface Handler is extended by adapters, and used in package api/grpc that implements the MeshServiceServer.2type Handler interface {3 // GetName returns the name of the adapter.4 GetName() string5 // CreateInstance instantiates clients used in deploying and managing mesh instances, e.g. Kubernetes clients.6 CreateInstance([]byte, string, *chan interface{}) error7 // ApplyOperation applies an adapter operation. This is adapter specific and needs to be implemented by each adapter.8 ApplyOperation(context.Context, OperationRequest) error9 // ListOperations list all operations an adapter supports.10 ListOperations() (Operations, error)1112 // Need not implement this method and can be reused13 StreamErr(*Event, error) // Streams an error event, e.g. to a channel14 StreamInfo(*Event) // Streams an informational event, e.g. to a channel15}
It corresponds closely to the gRPC API discussed above, and indeed these methods are called in the implementation of the MeshServiceServer
interface. This implementation is also part of the Meshery Adapter Library.
Using struct
embedding, an adapter extends the default implementation Adapter
of the interface Handler
from the library.
Usually, it is sufficient that this adapter handler overrides only the ApplyOperation
function from the default implementation.
(There, it is a no-op implementation.)
The figure below illustrates this and the usage of the library in an adapter.
In the main
package of the adapter, the default configuration provider Viper
from the library is instantiated, and reads the adapter specific configuration. This includes a specification of all available operations. As configuration providers are implementations of an interface, you can choose any of the providers from the library, or implement your own.
Next, an instance of the adapter handler is created. Other components, for instance a logger, may be created depending on needs and requirements, which is symbolize by the three dots in the figure.
The service
is a struct that holds all the parameters that specify an adapter service, like the port the gRPC server is running on, and the instance of the adapter handler created in a previous step. This struct is passed to the Start
function from the library. This Start
function wraps the gRPC server, wiring up all necessary components, and starts the service. The developer does not need to touch any gRPC code.
Conclusion
Extracting common code from adapters to the two new libraries has proven to be a worthwhile investment. It led to cleaner code as well as cleaner application architecture, shortened implementation time for new adapters considerably, and upleveled the quality of Meshery's adapters through consistency of implementation.
P.S. If these topics excite you and you want to explore the beautiful realm of service meshes, come and say "Hi" on the community Slack and you are sure to be warmly welcomed. 😀