Primaza

Multi Cluster Service Consumption Framework

The Problem

  • Modern application stacks are increasingly complex
  • Managing connections to all your supporting Services becomes more and more challenging… and that’s before you introduce additional clouds and hybrid environments
  • No platform enables easy consumption of hybrid cloud services
  • Needed to provide Scalability, Security and Portability
  • Previous solutions (OSBAPI, Service Binding, etc.) gained moderate traction
    • Replace one complexity with another
    • Heavyweight, onerous, exacting implementations
  • Customers are left to implement ad-hoc solutions
  • Manual steps to provision and consume cloud service (not scalable)
  • Custom IaC to provide the scale required (boilerplate imperative code)
  • Too much time spent on non-value added activities

The Solution

  • Separated logical environments that can span across different Kubernetes clusters
  • An automatically collated Catalog that appropriately exposes available Services to Applications
  • Leverages industry best practices to securely deliver service endpoint data to applications
  • A platform that enables hybrid cloud services consumption in a portable, secured and scalable way

Benefits to Service Providers

  • No action required!
  • Continue to focus on delivering their cloud service with well-defined APIs
  • Continue to work with Service Kubernetes APIs and Controllers

Benefits to Administrators

  • Automatic addition of services to the catalog (via Service Class) - manual catalog management still possible (via Registered Service)
  • Service Classes are easy to create, and can be built by the service provider, administrators, or third-parties
  • Popular Service Classes delivered by Primaza out of the box

Benefits to Developer

  • Service Catalog makes it easy to discover new services and see what's available in the environment
  • Developers can automatically and easily claim services from the catalog
  • Claims can be requested for multiple environments (CI/CD capable)
  • Developers can discover/learn via provided sandbox

Implementation

Primaza's architecture is composed by the following elements:

  • Primaza's Control Plane: manages environments, services and claims
  • Application agents: binds applications to services
  • Service agents: discover services

image

Primaza defines the following entities and controllers to provide the above described features.

Entities:

  • Cluster Environment: represents an development environment on a Kubernetes Cluster.
  • Registered Service: represents running instance of a software service.
  • Service Binding: projects secrets referenced by ServiceBinding resources to application compute resources.
  • Service Class: defines how a registered service can be automatically generated from a service
  • Service Claim: represents a claim for Registered Service.
  • Service Catalog: represents group of Registered Services.

Architecture

Primaza's architecture is composed by the following elements:

  • Primaza's Control Plane: manages environments, services and claims
  • Application agents: binds applications to services
  • Service agents: discover services

To better describe the Primaza architecture the following three concepts need to be introduced:

  • Primaza's Control Plane Namespace: a namespace where Primaza is installed
  • Application Namespace: a namespace configured to host the Primaza's Application Agent
  • Service Namespace: a namespace configured to host the Primaza's Service Agent

For how Primaza is designed, these three concepts may apply at the same time to a single namespace. In other words, you can install Primaza, Primaza's Application Agent, and Primaza's Service Agent in the same Kubernetes namespace.

In the following picture you find a simplified diagrams of the agents-based architecture.

image

To allow agents to perform operations in the namespace, they need an Service Account with the right permissions.

primazactl is an in-development companion tool to help administrators configuring clusters and namespaces.

In the following picture, you find a detailed representation of all the resources created in a Primaza environment.

image

Control Plane

The Primaza's Control Plane is the core of Primaza.

It contains all the logic for the management of the following resources:

  • Cluster Environment: represents an development environment on a Kubernetes Cluster.
  • Registered Service: represents running instance of a software service.
  • Service Binding: projects secrets referenced by ServiceBinding resources to application compute resources.
  • Service Class: defines how a registered service can be automatically generated from a service
  • Service Claim: represents a claim for Registered Service.
  • Service Catalog: represents group of Registered Services.

Agents

Primaza Agents are pushed into namespaces by Primaza. Two kinds of Primaza Agents are defined:

  • Application agent: is published into application namespaces and binds applications to services.
  • Service agent: is published into service namespace and discovers services.

image

To allow agents to perform operations in the namespace, they need an identity (Service Account) with the right permissions.

primazactl is an companion tool to help administrators configuring clusters and namespaces.

Configure agent

Application agent and Service agent deployment are configured in Primaza's Control Plane at runtime. There exists a ConfigMap named primaza-manager-config in Primaza's Control Plane namespace.

This ConfigMap consists of following keys:

  • agentapp-image: Application agent image
  • agentsvc-image: Service agent image
  • agentapp-manifest: Application agent manifest
  • agentsvc-manifest: Service agent manifest

By default the ConfigMap is populated using the make target agents-configmap executed at runtime. This ConfigMap is used in primaza-controller-manager deployment as environment variables. An user can modify the values of primaza-manager-config and then delete the primaza-controller-manager pod in Primaza's Control Plane namespace. This would reset the environment variables defined in primaza-manager-config deployment.

Application Agent

Application Agents are installed into ClusterEnvironment's Application Namespaces. Target namespaces need to be already configured to allow agents to run.

Application Agents need to access resources in the namespace they're published into. More specifically, an Application Agent requires the following resources to exists into the namespace:

  • A Role granting
    • full access to leases.coordination.k8s.io
    • read access to servicebindings.primaza.io
    • read access and update rights for deployments.apps
    • create right for events
  • A Service Account for the agent
  • A RoleBinding that binds the ServiceAccount to the Role
  • A Secret with the kubeconfig to communicate back with Primaza's Control Plane

To prepare Application Namespaces you can use primazactl.

When a ServiceBinding is created in an Application Namespace, the Application Agent looks for resources mentioned in its specification.

Primaza Application Agent runs a dynamic informer for Application resources mentioned in the ServiceBinding's specification. The informer monitors changes to the Application matching the ServiceBinding specifications and updates the ServiceBinding's status accordingly.

  • If the Application Resource mentioned in ServiceBinding specification is updated or created, the secret referenced by ServiceBinding resource will be projected into all the matching applications.
  • If the Application Resource is deleted and no matching workloads are found in the namespace, then the ServiceBinding status condition Reason is updated to NoMatchingWorkloads.

Binding a Service

When a ServiceBinding is created (or updated) into an Application namespace, the Application Agent gets the data from the secrets and project them into applications specified in the ServiceBinding instance.

Currently the secret data is being projected as volume mounts. SERVICE_BINDING_ROOT points to the environment variable in the container which is used as the volume mount path. In the absence of this environment variable, /bindings is used as the volume mount path.

Please refer to https://github.com/servicebinding/spec#reconciler-implementation for more information.

Claiming a Service

Claiming from Primaza's Control Plane Namespace

When a ServiceClaim is created in Primaza's Control Plane's Namespace, Primaza builds the ServiceBinding and Service Endpoint Definition Secrets and pushes them resources to all the application namespaces of matching ClusterEnvironments. The ServiceClaim controller in Primaza Control Plane watches RegisteredService resources. Any change made to ServiceEndpointDefinition values in RegisteredService are propagated to the secret and ServiceBinding resource in application namespace by Primaza.

Claiming from Application Namespace

When a ServiceClaim is created in Primaza's Control Plane's Namespace, the Application Agent forwards this ServiceClaim to Primaza's Control Plane. The Control Plane, in turn, builds the ServiceBinding and Service Endpoint Definition Secrets and pushes them back to the Application Namespace.

Service Agent

Service Agents are installed into ClusterEnvironment's Service Namespaces. Target namespaces need to be already configured to allow agents to run. Service Agents just need to access resources in the namespace they're published into.

More specifically, a Service agent requires the following resources to exists into the namespace:

  • A Role granting
    • full access to leases.coordination.k8s.io
    • create right for events
  • A Service Account for the agent
  • A RoleBinding that binds the ServiceAccount to the Role
  • A Secret with the kubeconfig to communicate back with Primaza's Control Plane

To prepare Service Namespaces you can use primazactl.

When a ServiceClass is created, the Service Agent looks for resources matching its specification.

A Role needs to be created which allows to retrieve, list and watch ServiceClass resources as Primaza's Service Agent runs a dynamic informer for each resource.

The informer monitors changes to resources matching the ServiceClass specifications and updates the RegisteredServices on Primaza control plane.

Service Discovery

The Service Agent monitors all the resources specified in Service Classes existing in its namespace. When a resource matching a Service Class is created, updated, or deleted, the Service Agent is notified and will create a RegisteredService in Primaza's Control Plane.

Namespace Reconciliation

There two types of Namespaces defined in Primaza, where the reconciliation happens. Namespaces are reconciled by the ClusterEnvironment controllers which runs in the Primaza's Control Plane.

  • Application Namespace reconciliation: Creation, Update and Deletion of the resources in application namespace
  • Service Namespace reconciliation: Creation, Update and Deletion of the resources in service namespace

During the Namespace Lifecycle, Primaza's permissions in the namespace are checked, permissions for talking with Primaza's Control Plane are set, and then namespace agent is deployed into the namespace.

Later, during Agent deletion, its permissions on Primaza's Control Plane are revoked.

Application Namespace Reconciliation

The Lifecycle of a Application Namespace includes the creation, update and deletion of Primaza's resources.

Application Namespaces are reconciled by the ClusterEnvironment controllers. This controller runs in the Primaza's Control Plane.

Events on Application Namespaces are related to ones on ClusterEnvironments.

  • Application Namespace Creation: when an application namespace is added to the applicationNamespaces list of a ClusterEnvironment, the ClusterEnvironment controller checks the permissions in the target namespace and tries to push the Application Agent in it. Finally, it will push in the namespace the ServiceCatalog for the given environment and all the ServiceBinding generated from ServiceClaims declaring a labelMatch or that are targeting the namespace in the applicationContext property.

  • Application Namespace Update: when an update event on the Application Namespace parent ClusterEnvironment occurs, all application namespaces are reconciled as described in the Application Namespace Creation and Application Namespace Deletion.

  • Application Namespace Deletion: when an Application Namespace is deleted from its parent ClusterEnvironment's applicationNamespaces list or when the ClusterEnvironment itself is deleted, the Application Agent is deleted from the target namespace. As a result, this will trigger a deletion of all the Primaza's resources in the namespace. This deletion is actually relying on Kubernetes' ownership.

Service Namespace Reconciliation

The Lifecycle of a Service Namespace includes the creation, update and deletion of Primaza's resources.

Namespaces are reconciled by the ClusterEnvironment controllers. This controller runs in the Primaza's Control Plane.

Events on Service Namespaces are related to ones on ClusterEnvironments.

  • Service Namespace Creation: when a service namespace is added to the serviceNamespaces list of a ClusterEnvironment, the ClusterEnvironment controller checks the permissions in the target namespace and tries to push the Service Agent in it. Finally, it will push in the namespace all the matching ServiceClasses for the given environment.

  • Service Namespace Update: when an update event on the Service Namespace parent ClusterEnvironment occurs, all service namespaces are reconciled as described in the Service Namespace Creation and Service Namespace Deletion.

  • Service Namespace Deletion: when an Service Namespace is deleted from its parent ClusterEnvironment's serviceNamespaces list or when the ClusterEnvironment itself is deleted, the Service Agent is deleted from the target namespace. As a result, this will trigger a deletion of all the Primaza's resources in the namespace. This deletion is actually relying on Kubernetes' ownership.

Resources

In the following you find a list of all the resources needed for a Primaza Tenant.

Tenant Architecture image

Tenant Namespace

The Tenant namespace is the namespace in which the Primaza Control Plane is installed.

Control Plane

Resources created in the Primaza's Tenant namespace for executing the Primaza Control Plane:

  • Primaza Control Plane's namespace is named after the tenant name: <tenant>
  • Application Agent Role: primaza-claimer
  • Service Agent Role: primaza-reporter

Remote Cluster

Resources created in the Primaza Tenant namespace when a Remote cluster is joined to the Primaza Tenant:

  • ClusterEnvironment: <cluster environment name>
  • Kubeconfig Secret for ClusterEnvironment: primaza-kubeconfig-<cluster environment name>

Application Agent

Resources created in the Primaza Tenant namespace when an Application Namespace is configured:

  • Application Agent's Service Account: primaza-app-<cluster environment name>-<namespace>
  • Access Token Secret for Application Agent's Service Account: primaza-tkn-app-<cluster environment name>-<namespace>
  • RoleBinding between Application Agent Service Account and Application Agent Role: primaza:claimer-<cluster environment name>-<namespace>

Service Agent

Resources created in the Primaza Tenant namespace when a Service Namespace is configured:

  • Service Agent's Service Account: primaza-svc-<cluster environment name>-<namespace>
  • Access Token Secret for Service Agent's Service Account: primaza-tkn-svc-<cluster environment name>-<namespace>
  • RoleBinding between Service Agent Service Account and Service Agent Role: primaza:reporter-<cluster environment name>-<namespace>

Remote Cluster

Resources created in the Remote Cluster when it's joined to the Primaza Tenant:

  • ClusterEnvironment's Service Account in namespace kube-system: pmz-<tenant>-<cluster environment name>
  • Access Token Secret for ClusterEnvironment's Service Account in namespace kube-system: tkn-pmz-<tenant>-<cluster environment name>

Application Namespace

Resources created in the Remote Cluster when an Application Namespace is configured:

  • Service Account: primaza-app-agent
  • Agent's Deployment: primaza-app-agent
  • Kubeconfig Secret: primaza-app-kubeconfig
  • Leader Election Role: pmz:app:leader-election
  • Manager Role: pmz:app:manager
  • Primaza's Role: pmz:controlplane:app
  • Leader Election RoleBinding: pmz:app:leader-election
  • Manager RoleBinding: pmz:app:manager
  • Primaza's RoleBinding: pmz:controlplane:app

Service Namespaces

Resources created in the Remote Cluster when a Service Namespace is configured:

  • Service Account: primaza-svc-agent
  • Agent's Deployment: primaza-svc-agent
  • Kubeconfig Secret: primaza-svc-kubeconfig
  • Leader Election Role: pmz:svc:leader-election
  • Manager Role: pmz:svc:manager
  • Primaza's Role: pmz:controlplane:svc
  • Leader Election RoleBinding: pmz:svc:leader-election
  • Manager RoleBinding: pmz:svc:manager
  • Primaza's RoleBinding: pmz:controlplane:svc

Entities

  • Cluster Environment: represents an development environment on a Kubernetes Cluster.
  • Registered Service: represents running instance of a software service.
  • Service Binding: projects secrets referenced by ServiceBinding resources to application compute resources.
  • Service Class: defines how a registered service can be automatically generated from a service
  • Service Claim: represents a claim for Registered Service.
  • Service Catalog: represents group of Registered Services.

ClusterEnvironment

A ClusterEnvironment represents a development environment on a Kubernetes Cluster. Examples of environments are 'app1-prod','app1-dev', or 'app1-uat'.

ClusterEnvironments contain connection information and the namespaces in which Primaza should operate. ClusterEnvironments differentiate among Service and Application namespaces. Application namespaces are the ones in which Primaza pushes the Application Agent that in turn binds applications to services. Service namespaces are the ones in which Primaza pushes the Service Agent and that in turn performs service discovery. Please refer to the Architecture section for more information about Agents and Primaza's architecture.

Specification

The definition of ClusterEnvironments can be obtained directly from ClusterEnvironment CRD.

The ClusterEnvironment's specification contains the following required properties:

  • clusterContextSecret: contains the name of the secret that stores the kubeconfig that can be used to connect to the physical target cluster.
  • applicationNamespaces: contains a list of namespaces where claiming and binding will happen. Applications to be bound to services will be looked for in those namespaces.
  • serviceNamespaces: contains a list of namespaces where discovery will happen. Services that populate the Service Catalog will be looked for in those namespaces.

A ClusterEnvironment also defines the following optional properties:

  • contactInfo Cluster Admin contact information
  • description: Description of the ClusterEnvironment

Status

The ClusterEnvironment's status can have one of the following values:

  • Online
  • Partial
  • Offline

An Online ClusterEnvironment is reachable by Primaza, whereas an Offline one isn't reachable.

A Partial ClusterEnvironment is also reachable, but not configured correctly. This can happen if Primaza doesn't have the required permissions on this namespaces. More details can be found in the ClusterEnvironment's status conditions.

Use Cases

Creation

When a ClusterEnvironment is created, Primaza verifies the connection to the cluster. If it can not connect to the target cluster, it logs an error and retries later. Otherwise, it checks its permissions in application and service namespaces. For each service and application namespace on which permissions are granted, Primaza pushes respectively the service or application agent.

ClusterEnvironment's State and Conditions are updated according to tests and agents' deployment results.

When a ClusterEnvironment is created, Primaza ensures a Service Catalog exists for its environment. The Service Catalog thereby created are also pushed to the ClusterEnvironment application namespace where permissions are granted.

Deletion

When a ClusterEnvironment is deleted, the permissions granted in Primaza's namespace to Service Accounts associated to namespace agents and agent deployments on target cluster's namespaces are removed.

Update

As on creation, Primaza verifies the connection to and its permissions into the target cluster. Finally, it pushes agents in cluster's application and service namespaces. As on deletion, if application or service namespaces are removed, Primaza deletes agent deployments and agents-granted permissions.

RegisteredService

A RegisteredService represents an instance of a running service.

This resource can be created by both a human operator or a piece of software (agent, bot, script). This flexibility allows for the service provisioning and service discovery operations to be decoupled from representation of that service in a Service Catalog.

A RegisteredService can be claimed to be used by a specific application, and once claimed, the RegisteredService can't be claimed by other application. If the application doesn't require the service any longer, it can remove the claim and the registered service is put back in the pool of available services.

Specification

The definition of RegisteredServices can be obtained directly from RegisteredService CRD.

The RegisteredService's specification contains the following required properties:

  • serviceClassIdentity: A set of key/value pairs that identify the service class. Examples of service class identity keys include type of service, and provider of service. This property is required.
  • serviceEndpointDefinition: A set of key/value pairs that provides two pieces of information: the service connectivity and the service authentication. Values can be either a string or a reference to a secret field. This property is required.

A RegisteredService also has the following optional properties, which gives the user more control over the resource:

  • constraints: Restrictions of the registered service. A constraint could be a specific environment where the service can't be claimed from. This property is optional, when it's absent, it means there isn't constraints. For more details, look at the Constraints section.
  • healthcheck: A mechanism to be able to verify the service is online and ready to use. One way this can be accomplished is by providing an image containing a client that can be run to test connectivity and authentication. This property is optional, when it's absent, it means the service will be considered available as soon as it's registered.
  • sla: Provides multiple levels of resiliency, scalability, fault tolerance and security. This allows claims to consider the robustness of service. This property is optional, when it's absent, it means that there is no distinctions between services given the SLA.

Constraints

The constraints section of a RegisteredService defines a list of environments. The environment list can contain explicit environments that are to include the service, or it can also contain a list of environments that are to exclude the service.

The way to depict exclusion is by adding the ! prefix to the environment, i.e !prod. Please note that exclusions have precedence over inclusions.

For example, if the list contains !prod but also includes dev, then dev is considered to be in the !prod set of environments and therefore redundant. If there is a third environment stage, then !prod would include both stage and dev even if they're not defined in the list explicitly.

Metadata

A Primaza's discovered RegisteredService has the following annotations:

  • primaza.io/cluster-environment: the name of the ClusterEnvironemnt it's part of
  • primaza.io/service-apiversion: the APIVersion of the resource represented by the RegisteredService
  • primaza.io/service-kind: the kind of the resource represented by the RegisteredService
  • primaza.io/service-name: the name of the resource represented by the RegisteredService
  • primaza.io/service-namespace: the namespace of the resource represented by the RegisteredService
  • primaza.io/service-uid: the UID of the resource represented by the RegisteredService

Status

The state of a RegisteredService could be one of the following:

  • Registered
  • Available
  • Claimed
  • Unreachable
  • Undetermined

As of now, health-check are still not implemented. The only states that are used are Available and Claimed.

The RegisteredService state is Registered right after creation. Usually, this will change immediately after the controller gets notified of existence of new resource, unless controller fails even before it tries to run health-check. If the health check passes or isn't defined, the state will change to Available.

Once a RegisteredService is claimed, its state moves to Claimed. On the other hand, if the health-check comes back as a failure, the state would change to Unreachable.

Sometimes, the health check could fail before it can determine whether the service is healthy or not. In those cases, the state of the RegisteredService will go to Undetermined indicating that there is an issue with the health-check and not with the service itself.

There are some situations when the health-check will fail while the RegisteredService is in Claimed state. In those cases the RegisteredService will move to Unreachable. If, at a later time, the health-check passes then the controller will check if there is still a claim matching the RegisteredService and move the state back to Claimed. However, if there isn't claim matching the RegisteredService the state will move to Available.

Use Cases

Creation

When a RegisteredService is created, Primaza will first check if the health check can be run and will modify the state accordingly. The ServiceCatalog will be updated to include the new RegisteredService only if the state can be moved to Available.

Deletion

When a RegisteredService resource is deleted, the ServiceCatalog entry for the service should be deleted. Also, if a RegisteredService is Claimed, the ServiceClaim resource state will be changed to Pending by the ServiceClaim controller since the matched RegisteredService doesn't exist any longer. Additionally, when a RegisteredService resource state changes to Claimed the corresponding entry in the ServiceCatalog resource is removed.

Update

When a RegisteredService is updated, a few things can happen:

  • Existing ServiceClaim resources may not be able to match the registered service any longer. In those case the ServiceClaim controller will try to find another match and, if unsuccessful, the ServiceClaim resource state will change to Pending.
  • Existing service endpoint definition has changed and should trigger a binding secret generation.

ServiceBinding

A ServiceBinding represents the secrets along with the Applications which are to bound with it. ServiceBindings may explicitly request an Application by Name or by LabelSelector. LabelSelector can match more than one resource.

ServiceBinding resource is being created by the ServiceClaim controller.

ServiceBinding is being performed by Primaza Application Agent, and the agent should be deployed in the Application namespace where the workloads are to be bound. When a ServiceBinding is created (or updated) into an Application namespace, the Application Agent gets the data from the secrets and project them into applications specified in the ServiceBinding instance.

Currently the secret data is being projected as volume mounts. SERVICE_BINDING_ROOT points to the environment variable in the container which is used as the volume mount path. In the absence of this environment variable, /bindings is used as the volume mount path.

Please refer to https://github.com/servicebinding/spec#reconciler-implementation for more information.

Specification

The definition of ServiceBindings can be obtained directly from ServiceBinding CRD.

The ServiceBinding's specification contains the following required properties:

  • serviceEndpointDefinitionSecret: ServiceEndpointDefinitionSecret is the name of the secret to project into the application. This property is required.
  • application: Application resource to inject the binding info. It could be any process running within a container. A ServiceBinding MAY define the application reference by name or by label selector. Name and label selector are mutually exclusive.

The ServiceBinding's specification also contains the following optional property:

  • envs: Envs declares environment variables based on the ServiceEndpointDefinitionSecret to be projected into the application

Metadata

Each ServiceBinding takes note of its RegisteredService in the following annotations:

  • primaza.io/registered-service-name: The RegisteredService Name
  • primaza.io/registered-service-uid: The RegisteredService UID

Status

The ServiceBinding's status contains the properties state and conditions.

The state of the service binding can be Malformed or Ready. The default value of service binding state is Malformed.

The conditions list of the service binding contains the following properties:

  • Type: The service binding condition type is Bound or NotBound.
    • Bound means that the secret is projected into the application.
    • NotBound denotes that the secret isn't projected into the application. This can only occur if the secret isn't found in the application namespace.
  • Message: This contains the error logs for the service binding resources. This value will be an empty string if successful.
  • Status: Status of service binding can be True or False.
  • Reason: The reason has values defined as NoMatchingWorkloads, ErrorFetchSecret, Successful and Binding Failure
  • Connections: The list of workloads the service is bound to

Use Cases

Creation

When a ServiceBinding is created, the secret referenced by the ServiceBinding itself will be projected into all the matching applications. The EnvironmentVariables envs declared in the specification will also be projected to the matching application pods. Matching applications are calculated as defined at in the section Specification

Deletion

If the ServiceBinding is deleted the secret projection from the workloads is removed too. In case the secret referenced in the ServiceBinding resource is deleted, the projection is removed from the workloads and the ServiceBinding status is updated to Malformed.

Update

When a ServiceBinding is updated, Primaza Application Agent will update the workload resources with the secret details. If the secret is updated the projection in the workloads will be updated accordingly.

ServiceClass

A Service Class is intended to define how a registered service can be automatically generated from a service. Currently, this service takes the shape of a resource already existing within a cluster: the Service Class controller will recognize these services and create a corresponding registered service.

This resource exists on both primaza and worker environments. The Primaza environment will push these resources to worker environments, where the service agent will capture the state of services within its namespace.

Specification

The definition of ServiceClasses can be obtained directly from ServiceClass CRD.

Within a Service Class, there are two required properties:

  • resource defines the APIVersion and Kind of the resources to look for. It also contains a list of Service Endpoint Definition Mappings, which define how binding information for a particular service may be obtained. For more details, have a look at the resource field section.
  • serviceClassIdentity defines a set of attributes that are sufficient to identify a Service Class. This field is copied to the generated registered services.

A Service Class also contains two optional properties, constraints and healthCheck. Both of these fields correspond exactly to their identically named properties within the Registered Service resource. For more information on how to use these properties, refer to the Registered Service documentation

resource field

The resource's ServiceClass field contains all the information needed for identifying the resources it refers to, that's apiVersion and kind. It also contains the rules for extracting the Service Endpoint Definition secret data, that's serviceEndpointDefinitionMappings.

Mapping rules apply to the resource specification (resourceFields) or to a secret (secretRefFields).

To extract data from the resource, add an entry to the resourceFields list. Each entry contains the following properties:

  • name: is used as the key in the Registered Service's Service Endpoint Definition
  • jsonPath: a JSONPath rule to extract the value for the current Service Endpoint Definition key
  • secret: declares whether this field should be stored in a secret of if it can be embedded in the Registered Service specification

To extract data from a secret, add an entry to the secretRefFields list. Data extracted from a secret is always stored in a new secret. Each entry contains the following properties:

  • name: is used as the key in the Registered Service's Service Endpoint Definition
  • secretName: represents the name of the secret to look for. It also contains two mutually exclusive sub-properties:
    • jsonPath: a JSONPath rule to extract the name of the secret from the resource specification
    • constant: a constant value for the secret name
  • secretKey: represents the secret's key to use. It contains two mutually exclusive sub-properties:
    • jsonPath: a JSONPath rule to extract the key of the secret from the resource specification
    • constant: a constant value for the secret name

Status

Whenever a Service Class is created or updated, a connection test from the service environment to Primaza is performed. The status of the Service Class will be updated to contain the results of this test underneath the condition type Connection.

Use Cases

Creation

When a Service Class is created, Primaza pushes it to Service Namespaces whose Cluster Environment satisfies its constraints. Once created, the service agent will collect all the services corresponding to that Service Class and will push the Registered Services it generates back to Primaza.

Deletion

When a Service Class is deleted, Primaza removes it from Service Namespaces whose Cluster Environment satisfies its constraints. The Service Agent will identify the Registered Services that correspond to that Service Class and will delete them.

Update

When a Service Class is updated, Primaza pushes it to Service Namespaces whose Cluster Environment satisfies its constraints. The Service Agent will then collect the services corresponding to that Service Class and will create or update the Registered Services in Primaza.

ServiceClaim

A ServiceClaim represents a claim for a RegisteredService.

ServiceBindings may explicitly request an Application by Name or by LabelSelector. LabelSelector can match more than one resource.

Specification

The definition of a ServiceClaim can be obtained directly from ServiceClaim CRD.

The specification contains the following properties:

  • serviceClassIdentity: A set of key/value pairs that identify the service class. Examples of service class identity keys include type of service, and provider of service. This property is required.
  • serviceEndpointDefinitionKeys: An array of keys that's required for connectivity. The values corresponding to each of these keys will be extracted from the service. This property is required.
  • application: identifies application resources through kind, apiVersion, and label selector or name.
  • target: Field that identifies the ServiceClaim target, may be an application deployed in a specific cluster or an entire environment
    • environmentTag: A string representing one of the environment.
    • applicationClusterContext: A combination of ClusterEnvironment resource name and namespace.
  • envs: allows projecting Service Endpoint Definition's data as Environment Variables in the Pod

The environmentTag and applicationClusterContext are mutually exclusive.

application field values are passed to the ServiceBinding resource. The application's label selector and application name are mutually exclusive.

Status

The Status of the ServiceClaim is also defined under the ServiceClaimCRD. It contains a mandatory property to track the state.

The state could be either Pending or Resolved or Invalid. If the state is Resolved, the RegisteredService claimed is tracked in the ServiceClaim status. Indeed, the status field registeredService takes track of the name and UID of the claimed RegisteredService.

The spec of a ServiceClaim isn't meant to be updated. If a user updates the spec of a ServiceClaim then the status of ServiceClaim is updated as Invalid when Primaza Application Agent attempts to update the ServiceClaim on Primaza Control Plane.

There is an optional claimID field with a unique ID for the claim.

Use Cases

Creation

When a ServiceClaim is created, Primaza should find a RegisteredService based on ServiceClassIdentity and ServiceEndpointDefinitionKeys and create Secret and ServiceBinding resources. The Environment Variables envs declared in the ServiceClaim are also added to the ServiceBinding specification. The ServiceBinding resource will be marked as the owner for the secret. Then it will update the state of ServiceClaim to Resolved. The state of RegisteredService will be changed to Claimed.

If no match for RegisteredService is found, the state of ServiceClaim will be set to Pending.

Deletion

When a ServiceClaim is deleted, Primaza will delete the Service Endpoint Definition Secret and the ServiceBinding. As ServiceBinding is the owner of the Service Endpoint Definition Secret, deleting it ensures deletion of the secret too. It also change the state of RegisteredService to Available.

Update

When a ServiceClaim is updated, Primaza will update the Service Endpoint Definition Secret, the ServiceBinding and the ServiceClaim's state accordingly. The state changes will happen similar to that of creation time.

ServiceCatalog

A ServiceCatalog represents a group of RegisteredServices which are available in a Environment.

The ServiceCatalog can be consumed by Kubernetes applications that are unaware of the lifecycle of the service. Events on RegisteredServices signal an update to the ServiceCatalog. The ServiceCatalog can then be advertised to the application developers.

Specification

The definition of a ServiceCatalog can be obtained directly from ServiceCatalog CRD. The specification contains a list of services and claimedByLabels.

The services contains the following fields:

  • name: Name defines the name of the known service
  • serviceClassIdentity: A set of key/value pairs that identify the service class. Examples of service class identity keys include type of service, and provider of service. This property is required.
  • serviceEndpointDefinitionKeys: An array of keys that's required for connectivity. The values corresponding to each of these keys will be extracted from the service. This property is required.

The claimedByLabels contains the following fields:

  • name: Name defines the name of the known service
  • serviceClassIdentity: A set of key/value pairs that identify the service class. Examples of service class identity keys include type of service, and provider of service. This property is required.
  • serviceEndpointDefinitionKeys: An array of keys that's required for connectivity. The values corresponding to each of these keys will be extracted from the service. This property is required.
  • labels: Labels used to claim the service.

Use Cases

Creation

When a ClusterEnvironment is created, Primaza ensures a ServiceCatalog exists for its environment. The ServiceCatalog thus created is then been pushed to all the Application Namespaces of matching ClusterEnvironment where permission is granted.

Deletion

ServiceCatalog gets deleted when the ClusterEnvironment is being deleted. The Catalog will also be removed from the matching Application Namespaces.

Update

When a RegisteredService is updated, the appropriate ServiceCatalog will get updated as well. The updated ServiceCatalog will be made available to all the Application Namespaces of matching ClusterEnvironments by Primaza.

The ServiceCatalog for each ClusterEnvironment will get updated with a service change if either the service has no constraints or the service has a constraint that matches the environment tag of the cluster environment. Matching means the environment is either included explicitly or it's not excluded. For more details look at the Constraints section in the RegisteredService page.

Monitoring

Primaza's resources are enriched with metadata that can be used to monitor the connections between workloads and services.

  • Discovered RegisteredServices takes track of the Service they represent in their annotations.
  • ServiceBindings take note of the RegisteredService they're related in their annotations and the workloads it tampered in the status.
  • ServiceClaims stores info about the claimed RegisteredService in its status

Releases

Primaza is released via GitHub Releases and GitHub Container Registry (ghcr).

To create a new release you need to push a Tag respecting the Semantic Versioning (SemVer) specification.

Once a SemVer tag is pushed, a GitHub Action will run. The Action builds and push the Primaza docker images, bakes the manifests, and creates a draft release.

The pushed images are the following:

The manifests baked are the following:

  • application_namespace_config_<TAG>.yaml: manifests for configuring an Application Namespace
  • service_namespace_config_<TAG>.yaml: manifests for configuring a Service Namespace
  • crds_config_<TAG>.yaml: manifests for installing Primaza's CRDs
  • control_plane_config_<TAG>.yaml: manifests for installing the Control Plane

The manifests are published as part of the GitHub Release artifacts.

Tutorials

In this section you will find a set of tutorials for running Primaza, use it for discovering services, and for binding a service to an application.

Tenant Setup

Primaza has requirements only at namespace level. The namespaces needed for a working Primaza setup can be spread across different clusters.

image

In the following Multi Cluster, Single Cluster, and Single Namespace Primaza configuration are detailed.

Multi-Cluster Setup

In this tutorial you will create the following Multi-Cluster environment using primazactl

image

Prerequisites

Tutorial

  1. Create the Main Cluster

    kind create cluster --name main
    
  2. Install the Cert-Manager on Main Cluster

    kubectl apply \
        -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml \
        --kubeconfig <(kind get kubeconfig --name main)
    kubectl rollout status -n cert-manager deploy/cert-manager-webhook -w --timeout=120s \
        --kubeconfig <(kind get kubeconfig --name main)
    
  3. Create the Worker Cluster

    kind create cluster --name worker
    
  4. Install the Cert-Manager on Worker Cluster

    kubectl apply \
        -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml \
        --kubeconfig <(kind get kubeconfig --name worker)
    kubectl rollout status -n cert-manager deploy/cert-manager-webhook -w --timeout=120s \
        --kubeconfig <(kind get kubeconfig --name worker)
    
  5. Create a Primaza Tenant

    primazactl create tenant primaza-mytenant \
        --version latest \
        --context kind-main
    
  6. Join the Worker cluster

    primazactl join cluster \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment worker \
        --environment demo \
        --context kind-worker \
        --tenant-context kind-main
    
  7. Create an Application Namespace named "applications" in the Worker Cluster

    primazactl create application-namespace applications \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment worker \
        --context kind-worker \
        --tenant-context kind-main
    
  8. Create a Service Namespace named "services" in the Worker Cluster

    primazactl create service-namespace services \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment worker \
        --context kind-worker \
        --tenant-context kind-main
    

Single Cluster Setup

In this tutorial you will create the following Single Cluster Primaza setup using primazactl

image

Prerequisites

Tutorial

  1. Create the Main Cluster

    kind create cluster --name main
    
  2. Install the Cert-Manager on Main Cluster

    kubectl apply \
        -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml \
        --kubeconfig <(kind get kubeconfig --name main)
    kubectl rollout status -n cert-manager deploy/cert-manager-webhook -w --timeout=120s \
        --kubeconfig <(kind get kubeconfig --name main)
    
  3. Create a Primaza Tenant

    primazactl create tenant primaza-mytenant \
        --version latest \
        --context kind-main
    
  4. Join the Main cluster

    ip=$(docker container inspect main-control-plane --format '{{.NetworkSettings.Networks.kind.IPAddress}}')
    kind get kubeconfig --name main | \
        sed 's/server: .*$/server: https:\/\/'"$ip"':6443/g' > /tmp/kc-primaza-single-setup
    primazactl join cluster \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment main \
        --environment demo \
        --context kind-main \
        --tenant-context kind-main \
        --kubeconfig /tmp/kc-primaza-single-setup
    
  5. Create an Application Namespace named "applications" in the Main Cluster

    primazactl create application-namespace applications \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment main \
        --context kind-main \
        --tenant-context kind-main
    
  6. Create a Service Namespace named "services" in the Main Cluster

    primazactl create service-namespace services \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment main \
        --context kind-main \
        --tenant-context kind-main
    

Single-Namespace

In this tutorial you will create the following Single-Namespace Primaza setup using primazactl

image

Tutorial

  1. Create the Main Cluster

    kind create cluster --name main
    
  2. Install the Cert-Manager on Main Cluster

    kubectl apply \
        -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml \
        --kubeconfig <(kind get kubeconfig --name main)
    kubectl rollout status -n cert-manager deploy/cert-manager-webhook -w --timeout=120s \
        --kubeconfig <(kind get kubeconfig --name main)
    
  3. Create a Primaza Tenant

    primazactl create tenant primaza-mytenant \
        --version latest \
        --context kind-main
    
  4. Join the Main cluster

    ip=$(docker container inspect main-control-plane --format '{{.NetworkSettings.Networks.kind.IPAddress}}')
    kind get kubeconfig --name main | \
        sed 's/server: .*$/server: https:\/\/'"$ip"':6443/g' > /tmp/kc-primaza-single-setup
    primazactl join cluster \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment main \
        --environment demo \
        --context kind-main \
        --tenant-context kind-main \
        --kubeconfig /tmp/kc-primaza-single-setup
    
  5. Configure the "primaza-mytenant" namespace as an Application Namespace

    primazactl create application-namespace primaza-mytenant \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment main \
        --context kind-main \
        --tenant-context kind-main
    
  6. Configure the "primaza-mytenant" namespace as a Service Namespace

    primazactl create service-namespace primaza-mytenant \
        --version latest \
        --tenant primaza-mytenant \
        --cluster-environment main \
        --context kind-main \
        --tenant-context kind-main
    

Discovery

In this section you can find tutorials for discovering services and registering them in Primaza.

The following approaches can be used for registering services in Primaza:

  • Manually Register a service
  • Use the Primaza's Service Discovery feature for automatic Service discovery
  • Configure a third party mechanism that creates registered services

Manual Registration of a Registered Service

To manually register a service you need to create the RegisteredService and, if needed, a Secret containing service's secret data.

Tutorial

The tutorial is thought to be executed in Multi-Cluster setup. Tune-it as needed if running in a different Primaza setup.

  1. Configure the shell

    TENANT="primaza-mytenant"
    TENANT_CLUSTER_CONTEXT="kind-main"
    SERVICE_NAMESPACE="services" # would be primaza-mytenant in Single-Namespace scenario
    
  2. Create a PostgreSQL Service

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$SERVICE_NAMESPACE"
    apiVersion: v1
    kind: Secret
    metadata:
      name: postgresql
    stringData:
      database: postgresql-db
      username: postgresql-user
      password: postgresql-passwd
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: postgresql
      labels:
        app: postgresql
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: postgresql
      template:
        metadata:
          labels:
            app: postgresql
        spec:
          containers:
            - name: postgresql
              image: postgres:13
              imagePullPolicy: IfNotPresent
              env:
                - name: POSTGRES_DB_FILE
                  value: /secrets/database
                - name: POSTGRES_PASSWORD_FILE
                  value: /secrets/password
                - name: POSTGRES_USER_FILE
                  value: /secrets/username
                - name: PGDATA
                  value: /tmp/data
              volumeMounts:
                - name: postgresql
                  mountPath: "/secrets"
                  readOnly: true
              ports:
                - name: postgresql
                  containerPort: 5432
          volumes:
            - name: postgresql
              secret:
                secretName: postgresql
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: postgresql
      name: postgresql
    spec:
      ports:
        - port: 5432
          protocol: TCP
          targetPort: 5432
      selector:
        app: postgresql
    EOF
    
  3. Create the RegisteredService and the Service Endpoint Definition Secret for the PostgreSQL Service in Primaza's Control Plane

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: v1
    kind: Secret
    metadata:
      name: postgresql
    stringData:
      username: postgresql-user
      password: postgresql-passwd
    ---
    apiVersion: primaza.io/v1alpha1
    kind: RegisteredService
    metadata:
      name: postgres
    spec:
      serviceClassIdentity:
      - name: type
        value: database
      - name: engine
        value: postgres
      - name: version
        value: "13"
      serviceEndpointDefinition:
      - name: database
        value: postgres
      - name: host
        value: postgresql.$SERVICE_NAMESPACE.svc.cluster.local
      - name: port
        value: "5432"
      - name: password
        valueFromSecret:
          name: postgresql
          key: password
      - name: username
        valueFromSecret:
          name: postgresql
          key: username
    EOF
    

In-cluster Service Discovery

In this tutorial you will configure the Primaza's Service Discovery mechanism to automatically discover services for you.

Tutorial

The tutorial is thought to be executed in Multi-Cluster setup. Tune-it as needed if running in a different Primaza setup.

  1. Configure the shell

    TENANT="primaza-mytenant"
    TENANT_CLUSTER_CONTEXT="kind-main"
    TARGET_CLUSTER_CONTEXT="kind-worker" # would be main in Single-Cluster and Single-Namespace scenarios
    SERVICE_NAMESPACE="services"         # would be primaza-mytenant in Single-Namespace scenario
    
  2. Install the MondoDB Operator

    helm repo add mongodb https://mongodb.github.io/helm-charts
    helm repo update
    helm install community-operator mongodb/community-operator \
        --namespace "$SERVICE_NAMESPACE" \
        --kube-context "$TARGET_CLUSTER_CONTEXT" \
        --create-namespace
    
  3. Create the MongoDB Database

    cat << EOF | kubectl apply -f - \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$SERVICE_NAMESPACE"
    apiVersion: mongodbcommunity.mongodb.com/v1
    kind: MongoDBCommunity
    metadata:
      name: mongodb
    spec:
      members: 3
      type: ReplicaSet
      version: "6.0.5"
      security:
        authentication:
          modes: ["SCRAM"]
      users:
        - name: my-user
          db: admin
          passwordSecretRef: # a reference to the secret that will be used to generate the user's password
            name: mongodb-my-user-password
          roles:
            - name: clusterAdmin
              db: admin
            - name: userAdminAnyDatabase
              db: admin
          scramCredentialsSecretName: my-scram
      additionalMongodConfig:
        storage.wiredTiger.engineConfig.journalCompressor: zlib
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: mongodb-my-user-password
    type: Opaque
    stringData:
      password: $(tr -cd '[:alnum:]' < /dev/urandom | fold -w16 | head -n1)
    EOF
    
  4. Grant Primaza's Service Agent the right to look for MongoDB instances

    cat << EOF | kubectl apply -f - \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$SERVICE_NAMESPACE"
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: primaza-svc-community-mongodb
    rules:
    - apiGroups:
      - mongodbcommunity.mongodb.com
      resources:
      - mongodbcommunity
      verbs:
      - get
      - list
      - watch
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: primaza-svc-community-mongodb
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: primaza-svc-community-mongodb
    subjects:
    - kind: ServiceAccount
      name: primaza-svc-agent
      namespace: $SERVICE_NAMESPACE
    EOF
    
  5. Deploy the ServiceClass

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: ServiceClass
    metadata:
      name: mongodb
    spec:
      serviceClassIdentity:
      - name: type
        value: database
      - name: operator
        value: community
      - name: engine
        value: mongodb
      resource:
        apiVersion: mongodbcommunity.mongodb.com/v1
        kind: MongoDBCommunity
        serviceEndpointDefinitionMappings:
          secretRefFields:
          - name: password
            secretName:
              jsonPath: .spec.users[0].passwordSecretRef.name
            secretKey:
              constant: password
          resourceFields:
          - name: username
            jsonPath: .spec.users[0].name
            secret: true
          - name: database
            jsonPath: .spec.users[0].db
            secret: false
          - name: host
            jsonPath: .metadata.name
            secret: false
    EOF
    
  6. Wait for the RegisteredService to be created

    until kubectl get registeredservices mongodb \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT" \
        --output yaml; \
            do sleep 10; done
    

Amazon Web Services Controllers for Kubernetes Service Discovery

In this tutorial you will use the AWS Controllers for Kubernetes to create an RDS Database and you will configure the Primaza's Service Discovery mechanism to automatically discover the service for you.

Tutorial

The tutorial is thought to be executed in Multi-Cluster setup. Tune-it as needed if running in a different Primaza setup.

  1. Configure the shell

    TENANT="primaza-mytenant"
    TENANT_CLUSTER_CONTEXT="kind-main"
    TARGET_CLUSTER_CONTEXT="kind-worker" # would be kind-main in Single-Cluster and Single-Namespace scenarios
    SERVICE_NAMESPACE="services"         # would be primaza-mytenant in Single-Namespace scenario
    AWS_REGION=$( aws configure get region )
    AWS_RDS_RELEASE_VERSION=$( \
        curl -sL https://api.github.com/repos/aws-controllers-k8s/rds-controller/releases/latest | \
             grep '"tag_name":' | cut -d'"' -f4 | tr -d "v" )
    
  2. Install the ACK RDS operator

    aws ecr-public get-login-password --region "us-east-1" | \
        helm registry login --username "AWS" --password-stdin "public.ecr.aws"
    helm upgrade --install "ack-rds-controller" \
        "oci://public.ecr.aws/aws-controllers-k8s/rds-chart" \
        --namespace "$SERVICE_NAMESPACE" \
        --create-namespace \
        --kube-context "$TARGET_CLUSTER_CONTEXT" \
        --version="$AWS_RDS_RELEASE_VERSION" \
        --set=aws.region="$AWS_REGION" \
        --set=installScope=namespace
    kubectl set env "deployment/ack-rds-controller-rds-chart" \
        AWS_ACCESS_KEY_ID="$( aws configure get aws_access_key_id )" \
        AWS_SECRET_ACCESS_KEY="$( aws configure get aws_secret_access_key )" \
        --namespace "$SERVICE_NAMESPACE" \
        --context "$TARGET_CLUSTER_CONTEXT"
    
  3. Create the MongoDB Database

    cat << EOF | kubectl apply -f - \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$SERVICE_NAMESPACE"
    apiVersion: rds.services.k8s.aws/v1alpha1
    kind: DBInstance
    metadata:
      name: rds
    spec:
      allocatedStorage: 20
      dbInstanceClass: db.t3.micro
      dbInstanceIdentifier: primaza-ack-tutorial
      engine: postgres
      engineVersion: "14"
      masterUsername: "postgres"
      masterUserPassword:
        namespace: "$SERVICE_NAMESPACE"
        name: rds-password
        key: password
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: rds-password
    stringData:
      password: $(tr -cd '[:alnum:]' < /dev/urandom | fold -w16 | head -n1)
    EOF
    
  4. Grant Primaza's Service Agent the right to look for ACK DBInstances

    cat << EOF | kubectl apply -f - \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$SERVICE_NAMESPACE"
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: primaza-svc-ack-rds
    rules:
    - apiGroups:
      - rds.services.k8s.aws
      resources:
      - dbinstances
      verbs:
      - get
      - list
      - watch
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: primaza-svc-ack-rds
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: primaza-svc-ack-rds
    subjects:
    - kind: ServiceAccount
      name: primaza-svc-agent
      namespace: $SERVICE_NAMESPACE
    EOF
    
  5. Deploy the ServiceClass

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: ServiceClass
    metadata:
      name: rds
    spec:
      serviceClassIdentity:
      - name: type
        value: database
      - name: operator
        value: ack
      - name: provider
        value: aws
      resource:
        apiVersion: rds.services.k8s.aws/v1alpha1
        kind: DBInstance
        serviceEndpointDefinitionMappings:
          secretRefFields:
          - name: password
            secretName:
              jsonPath: .spec.masterUserPassword.name
            secretKey:
              jsonPath: .spec.masterUserPassword.key
          resourceFields:
          - name: username
            jsonPath: .spec.masterUsername
            secret: true
          - name: region
            jsonPath: .status.ackResourceMetadata.region
            secret: false
          - name: port
            jsonPath: .status.endpoint.port
            secret: false
          - name: host
            jsonPath: .status.endpoint.address
    EOF
    
  6. Wait for the RegisteredService to be created

    until kubectl get registeredservices rds \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT" \
        --output yaml
        do sleep 10; done
    

Claiming

Once you have a RegisteredService, you can claim it for one or more workloads. The following examples uses the service from the Manual Registration tutorial.

A ServiceClaim can select the workload to use filtering by name or labels.

Finally, you can claim from the Control Plane or from an Application Namespace.

WorkflowSingle NamespaceAll NamespacesBy LabelBy Name
Control Plane
Application Namespace

Control Plane Workflow

If you create the ServiceClaim in the Control Plane you can define whether it applies to all the Application Namespaces in the Environment or it's just for one specific namespace. This filter is implemented by the mean of the Claim's property applicationClusterContext.

For examples refer to Filtering by Name or Filtering by Labels tutorials for Environment wide ServiceClaim examples, and to Application Namespace Scoped tutorial.

Application Namespace Workflow

When a Service Claim is created in an Application Namespace, Primaza pushes the ServiceBinding and Secret just to this namespace.

In this case, indeed, the Application Agent tampers and send the ServiceClaim to the Control Plane. The Control Plane, in turn, pushes the ServiceBinding and Secret only to the ApplicationNamespace in which the ServiceClaim was created.

By Application Name

When claiming a service, you can explicitly declare the target workload for the binding. Primaza's Application Agent will project binding data just into that workload.

To obtain this result, you need to declare in the ServiceClaim how to identify the workload using the application property.

Tutorial

  1. Configure the shell

    TENANT="primaza-mytenant"
    TENANT_CLUSTER_CONTEXT="kind-main"
    TARGET_CLUSTER_CONTEXT="kind-worker" # it would be main in Single-Cluster and Single-Namespace scenarios
    APPLICATION_NAMESPACE="applications" # it would be primaza-mytenant in Single-Namespace scenario
    
  2. Create a RegisteredService for the service to bind to our application

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: RegisteredService
    metadata:
      name: claim-by-name
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-by-name
      serviceEndpointDefinition:
      - name: url
        value: https://my-app-by-name-service.dev
      - name: password
        value: SomeoneThinksImAPassword
    EOF
    
  3. Create our target application

    kubectl create deployment my-app-name  --image bash:latest \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- sleep infinity
    
  4. Create the ServiceClaim in Primaza's Control Plane

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: ServiceClaim
    metadata:
      name: my-app-dummy-by-name
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-by-name
      serviceEndpointDefinitionKeys:
      - url
      - password
      target:
        environmentTag: demo
      application:
        kind: Deployment
        apiVersion: apps/v1
        name: my-app-name
    EOF
    
  5. Wait for Service Binding to be created and Resolved

    until kubectl get servicebindings my-app-dummy-by-name \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    do sleep 2; done
    kubectl wait --for=condition=Bound servicebindings my-app-dummy-by-name \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    
  6. Read data injected in the workload

    until kubectl exec "pod/$(kubectl get pod \
            -l app=my-app-name \
            -o jsonpath="{.items[0].metadata.name}" \
            --context "$TARGET_CLUSTER_CONTEXT" \
            --namespace "$APPLICATION_NAMESPACE" )" \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- bash -c '[ -d "/bindings/my-app-dummy-by-name" ] && ( ls /bindings/my-app-dummy-by-name | xargs -I@ bash -c '"'"'echo "$1: $(cat /bindings/my-app-dummy-by-name/$1)"'"'"' -- @ )'
        do sleep 5; done
    

By Application Name

When claiming a service, you can identify the target workloads for the binding by declaring the labels they should have. Primaza's Application Agent will project binding data just into all matching workloads.

To obtain this result, you need to declare in the ServiceClaim how to identify the workload using the application property.

Tutorial

  1. Configure the shell

    TENANT="primaza-mytenant"
    TENANT_CLUSTER_CONTEXT="kind-main"
    TARGET_CLUSTER_CONTEXT="kind-worker" # it would be main in Single-Cluster and Single-Namespace scenarios
    APPLICATION_NAMESPACE="applications" # it would be primaza-mytenant in Single-Namespace scenario
    
  2. Create a RegisteredService for the service to bind to our application

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: RegisteredService
    metadata:
      name: claim-by-labels
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-by-labels
      serviceEndpointDefinition:
      - name: url
        value: https://my-app-by-labels-service.dev
      - name: password
        value: SomeoneThinksImAPassword
    EOF
    
  3. Create our target application

    kubectl create deployment my-app-labels --image bash:latest \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- sleep infinity
    
  4. Create the ServiceClaim in Primaza's Control Plane

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: ServiceClaim
    metadata:
      name: my-app-dummy-by-labels
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-by-labels
      serviceEndpointDefinitionKeys:
      - url
      - password
      target:
        environmentTag: demo
      application:
        kind: Deployment
        apiVersion: apps/v1
        selector:
          matchLabels:
            app: my-app-labels
    EOF
    
  5. Wait for Service Binding to be created and Resolved

    until kubectl get servicebindings my-app-dummy-by-labels \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    do sleep 2; done
    kubectl wait --for=condition=Bound servicebindings my-app-dummy-by-labels \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    
  6. Read data injected in the workload

    until kubectl exec "pod/$(kubectl get pod \
            -l app=my-app-labels \
            -o jsonpath="{.items[0].metadata.name}" \
            --context "$TARGET_CLUSTER_CONTEXT" \
            --namespace "$APPLICATION_NAMESPACE" )" \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- bash -c '[ -d "/bindings/my-app-dummy-by-labels" ] && ( ls /bindings/my-app-dummy-by-labels | xargs -I@ bash -c '"'"'echo "$1: $(cat /bindings/my-app-dummy-by-labels/$1)"'"'"' -- @ )'
        do sleep 5; done
    

Application Namespace Scoped

You can create a ServiceClaim that applies to a single Application Namespace in the Environment. To obtain this result, you need to declare in the application namespace using the applicationClusterContext property.

This has no implication on workload filtering, so you can identify workloads in the Application Namespace either by Name or by Labels.

Tutorial

  1. Configure the shell

    TENANT="primaza-mytenant"
    TENANT_CLUSTER_CONTEXT="kind-main"
    TARGET_CLUSTER_CONTEXT="kind-worker" # it would be main in Single-Cluster and Single-Namespace scenarios
    APPLICATION_NAMESPACE="applications" # it would be primaza-mytenant in Single-Namespace scenario
    
  2. Create a RegisteredService for the service to bind to our application

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: RegisteredService
    metadata:
      name: claim-for-appns
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-for-appns
      serviceEndpointDefinition:
      - name: url
        value: https://my-app-for-appns-service.dev
      - name: password
        value: SomeoneThinksImAPassword
    EOF
    
  3. Create our target application

    kubectl create deployment my-app-appns --image bash:latest \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- sleep infinity
    
  4. Create the ServiceClaim in Primaza's Control Plane

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: ServiceClaim
    metadata:
      name: my-app-dummy-for-appns
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-for-appns
      serviceEndpointDefinitionKeys:
      - url
      - password
      target:
        applicationClusterContext:
          clusterEnvironmentName: worker
          namespace: applications
      application:
        kind: Deployment
        apiVersion: apps/v1
        selector:
          matchLabels:
            app: my-app-appns
    EOF
    
  5. Wait for Service Binding to be created and Resolved

    until kubectl get servicebindings my-app-dummy-for-appns \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    do sleep 2; done
    kubectl wait --for=condition=Bound servicebindings my-app-dummy-for-appns \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    
  6. Read data injected in the workload

    until kubectl exec "pod/$(kubectl get pod \
            -l app=my-app-appns \
            -o jsonpath="{.items[0].metadata.name}" \
            --context "$TARGET_CLUSTER_CONTEXT" \
            --namespace "$APPLICATION_NAMESPACE" )" \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- bash -c '[ -d "/bindings/my-app-dummy-for-appns" ] && ( ls /bindings/my-app-dummy-for-appns | xargs -I@ bash -c '"'"'echo "$1: $(cat /bindings/my-app-dummy-for-appns/$1)"'"'"' -- @ )'
        do sleep 5; done
    

From Application Namespace

You can create a ServiceClaim directly from an Application Namespace. When a Service Claim is created in an Application Namespace, Primaza pushes the ServiceBinding and Secret just to this namespace.

In this case, indeed, the Application Agent tampers and send the ServiceClaim to the Control Plane. The Control Plane, in turn, pushes the ServiceBinding and Secret only to the ApplicationNamespace in which the ServiceClaim was created.

This has no implication on workload filtering, so you can identify workloads in the Application Namespace either by Name or by Labels.

Tutorial

  1. Configure the shell

    TENANT="primaza-mytenant"
    TENANT_CLUSTER_CONTEXT="kind-main"
    TARGET_CLUSTER_CONTEXT="kind-worker" # it would be main in Single-Cluster and Single-Namespace scenarios
    APPLICATION_NAMESPACE="applications" # it would be primaza-mytenant in Single-Namespace scenario
    
  2. Create a RegisteredService for the service to bind to our application

    cat << EOF | kubectl apply -f - \
        --context "$TENANT_CLUSTER_CONTEXT" \
        --namespace "$TENANT"
    apiVersion: primaza.io/v1alpha1
    kind: RegisteredService
    metadata:
      name: claim-from-appns
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-from-appns
      serviceEndpointDefinition:
      - name: url
        value: https://my-app-from-appns-service.dev
      - name: password
        value: SomeoneThinksImAPassword
    EOF
    
  3. Create our target application

    kubectl create deployment my-app-appns --image bash:latest \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- sleep infinity
    
  4. Create the ServiceClaim in Primaza's Control Plane

    cat << EOF | kubectl apply -f - \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    apiVersion: primaza.io/v1alpha1
    kind: ServiceClaim
    metadata:
      name: my-app-dummy-from-appns
    spec:
      serviceClassIdentity:
      - name: type
        value: dummy
      - name: scope
        value: claim-from-appns
      serviceEndpointDefinitionKeys:
      - url
      - password
      application:
        kind: Deployment
        apiVersion: apps/v1
        selector:
          matchLabels:
            app: my-app-appns
    EOF
    
  5. Wait for Service Binding to be created and Resolved

    until kubectl get servicebindings my-app-dummy-from-appns \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    do sleep 2; done
    kubectl wait --for=condition=Bound servicebindings my-app-dummy-from-appns \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE"
    
  6. Read data injected in the workload.

    until kubectl exec "pod/$(kubectl get pod \
            -l app=my-app-appns \
            -o jsonpath="{.items[0].metadata.name}" \
            --context "$TARGET_CLUSTER_CONTEXT" \
            --namespace "$APPLICATION_NAMESPACE" )" \
        --context "$TARGET_CLUSTER_CONTEXT" \
        --namespace "$APPLICATION_NAMESPACE" \
        -- bash -c '[ -d "/bindings/my-app-dummy-from-appns" ] && ( ls /bindings/my-app-dummy-from-appns | xargs -I@ bash -c '"'"'echo "$1: $(cat /bindings/my-app-dummy-from-appns/$1)"'"'"' -- @ )'
        do sleep 5; done