Provider quick start
This tutorial walks you through building your first service provider in Platform Mesh with api-syncagent. You will publish the HttpBin custom resource from a Kubernetes service cluster into kcp, then verify that a consumer workspace can create an HttpBin resource and receive status back.
By the end of this tutorial, you will have:
- a provider workspace in kcp with an APIExport for the HttpBin API
- the HttpBin operator running on the service cluster
- api-syncagent synchronizing desired state and status between kcp and the service cluster
- a PublishedResource that tells api-syncagent which CRD to expose
- a consumer workspace that can bind the API and create an HttpBin resource
The full source code is available in platform-mesh/provider-quickstart.
Development preview
The local setup is under active development. Commands and component versions may change.
Prerequisites
Before you begin, make sure you have:
- a local Platform Mesh setup without example data from Set up Platform Mesh locally
kubectlwith thekubectl-kcpplugin installed- Helm 3 installed
- basic familiarity with Kubernetes CRDs and operators
If you already ran the example-data setup, the HttpBin provider is deployed automatically. This tutorial is most useful from a clean setup where you build the provider flow yourself.
From the helm-charts/local-setup directory:
task local-setupWhat you will build
The HttpBin provider is a simple managed service provider. Consumers create HttpBin resources in their kcp workspace. api-syncagent synchronizes those resources into the service cluster, where the HttpBin operator reconciles them into running pods and writes status back.
The detailed synchronization flow looks like this:
The consumer never interacts with the service cluster directly. From their perspective, HttpBin is a Kubernetes resource type available in their workspace.
Set up kubeconfigs
In the local setup, one Kind cluster named platform-mesh hosts Platform Mesh and also acts as the service cluster for this tutorial. kcp runs inside that cluster, but exposes its own API server on https://localhost:8443.
You will use two kubeconfigs:
| Target | Kubeconfig | Purpose |
|---|---|---|
| kcp | .secret/kcp/admin.kubeconfig | Manage workspaces, APIExports, and APIBindings. |
| Kind cluster | default Kind kubeconfig | Manage operators, pods, CRDs, and api-syncagent. |
Run this from helm-charts/local-setup:
export KCP_KUBECONFIG=$(pwd)/.secret/kcp/admin.kubeconfig
kind export kubeconfig --name platform-mesh
export KIND_KUBECONFIG=$HOME/.kube/configVerify both connections:
KUBECONFIG=$KCP_KUBECONFIG kubectl kcp workspace use :root
kubectl --kubeconfig $KIND_KUBECONFIG get nodesCreate the provider workspace
Provider workspaces are organized under root:providers. Create the container workspace and the HttpBin provider workspace:
KUBECONFIG=$KCP_KUBECONFIG kubectl create-workspace providers \
--type=root:providers \
--ignore-existing \
--server="https://localhost:8443/clusters/root"
KUBECONFIG=$KCP_KUBECONFIG kubectl create-workspace httpbin-provider \
--type=root:provider \
--ignore-existing \
--server="https://localhost:8443/clusters/root:providers"Switch to the provider workspace:
KUBECONFIG=$KCP_KUBECONFIG kubectl kcp workspace use :root:providers:httpbin-providerExpected output:
Current workspace is "root:providers:httpbin-provider" (type root:provider).Create the APIExport
The APIExport makes the service API visible to consumers. Create it in the provider workspace:
KUBECONFIG=$KCP_KUBECONFIG kubectl apply -f - <<EOF
apiVersion: apis.kcp.io/v1alpha1
kind: APIExport
metadata:
name: orchestrate.platform-mesh.io
spec: {}
EOFGrant bind permission so a consumer workspace can create an APIBinding to this export:
KUBECONFIG=$KCP_KUBECONFIG kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: apiexport-bind
rules:
- apiGroups: ["apis.kcp.io"]
resources: ["apiexports"]
verbs: ["bind"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: anonymous-view
subjects:
- kind: User
name: system:anonymous
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: apiexport-bind
apiGroup: rbac.authorization.k8s.io
EOFVerify the APIExport:
KUBECONFIG=$KCP_KUBECONFIG kubectl get apiexportsExpected output:
NAME AGE
orchestrate.platform-mesh.io 5sDeploy the HttpBin operator
The HttpBin operator runs on the service cluster. api-syncagent handles the integration with kcp, so the operator itself does not need to know about Platform Mesh.
Run this from the helm-charts repository root:
cd ..
helm install example-httpbin-operator \
./charts/example-httpbin-operator \
--kubeconfig $KIND_KUBECONFIG \
-n example-httpbin-provider \
--create-namespaceVerify the operator:
kubectl --kubeconfig $KIND_KUBECONFIG \
get pods -n example-httpbin-providerVerify the CRD:
kubectl --kubeconfig $KIND_KUBECONFIG \
get crd httpbins.orchestrate.platform-mesh.ioThe CRD defines an HttpBin resource with a spec.region field and a status subresource:
apiVersion: orchestrate.platform-mesh.io/v1alpha1
kind: HttpBin
metadata:
name: my-httpbin
spec:
region: eu-west-1
status:
ready: false
url: ""
conditions: []The status subresource matters because api-syncagent uses it to synchronize provider status back to kcp.
Deploy api-syncagent
api-syncagent runs on the service cluster and connects to kcp. It needs a kubeconfig Secret for kcp access.
Create the Secret:
kubectl --kubeconfig $KIND_KUBECONFIG \
create secret generic httpbin-kubeconfig \
-n example-httpbin-provider \
--from-file=kubeconfig=$KCP_KUBECONFIGIn the local setup, kcp is exposed through Traefik on localhost:8443. Pods need a host alias so in-cluster traffic can resolve that address correctly.
TRAEFIK_IP=$(kubectl --kubeconfig $KIND_KUBECONFIG \
get svc traefik -n default -o jsonpath='{.spec.clusterIP}')
echo "Traefik ClusterIP: $TRAEFIK_IP"Install api-syncagent:
helm repo add kcp https://kcp-dev.github.io/helm-charts
helm repo update
helm install api-syncagent kcp/api-syncagent \
--kubeconfig $KIND_KUBECONFIG \
-n example-httpbin-provider \
--set apiExportEndpointSliceName=orchestrate.platform-mesh.io \
--set agentName=kcp-api-syncagent \
--set kcpKubeconfig=httpbin-kubeconfig \
--set hostAliases.enabled=true \
--set "hostAliases.values[0].ip=$TRAEFIK_IP" \
--set "hostAliases.values[0].hostnames[0]=localhost"Grant RBAC for api-syncagent on the service cluster:
kubectl --kubeconfig $KIND_KUBECONFIG apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: api-syncagent-httpbin
rules:
- apiGroups: ["orchestrate.platform-mesh.io"]
resources: ["httpbins", "httpbins/status"]
verbs: ["*"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: api-syncagent-httpbin
subjects:
- kind: ServiceAccount
name: api-syncagent
namespace: example-httpbin-provider
roleRef:
kind: ClusterRole
name: api-syncagent-httpbin
apiGroup: rbac.authorization.k8s.io
EOFVerify api-syncagent:
kubectl --kubeconfig $KIND_KUBECONFIG \
get pods -n example-httpbin-provider -l app.kubernetes.io/name=kcp-api-syncagentCreate a PublishedResource
PublishedResource tells api-syncagent which CRD to publish into kcp. Create it on the service cluster:
kubectl --kubeconfig $KIND_KUBECONFIG apply -f - <<EOF
apiVersion: syncagent.kcp.io/v1alpha1
kind: PublishedResource
metadata:
name: httpbin-local-provider
spec:
resource:
kind: HttpBin
apiGroup: orchestrate.platform-mesh.io
version: v1alpha1
EOFapi-syncagent will create an APIResourceSchema in kcp and update the APIExport.
Verify the schema:
KUBECONFIG=$KCP_KUBECONFIG kubectl kcp workspace use :root:providers:httpbin-provider
KUBECONFIG=$KCP_KUBECONFIG kubectl get apiresourceschemasVerify the APIExport references the schema:
KUBECONFIG=$KCP_KUBECONFIG kubectl get apiexport orchestrate.platform-mesh.io -o yamlLook for the published schemas in the spec block.
Check the agent logs:
kubectl --kubeconfig $KIND_KUBECONFIG \
logs -n example-httpbin-provider -l app.kubernetes.io/name=kcp-api-syncagent --tail=20Test the consumer flow
Create a consumer workspace:
KUBECONFIG=$KCP_KUBECONFIG kubectl create-workspace test-consumer \
--server="https://localhost:8443/clusters/root"
KUBECONFIG=$KCP_KUBECONFIG kubectl kcp workspace use :root:test-consumerCreate an APIBinding:
KUBECONFIG=$KCP_KUBECONFIG kubectl apply -f - <<EOF
apiVersion: apis.kcp.io/v1alpha1
kind: APIBinding
metadata:
name: orchestrate.platform-mesh.io
spec:
reference:
export:
name: orchestrate.platform-mesh.io
path: "root:providers:httpbin-provider"
permissionClaims:
- resource: namespaces
state: Accepted
all: true
- resource: events
state: Accepted
all: true
EOFVerify the binding:
KUBECONFIG=$KCP_KUBECONFIG kubectl get apibindingsVerify the API is available:
KUBECONFIG=$KCP_KUBECONFIG kubectl api-resources | grep httpbinCreate an HttpBin instance:
KUBECONFIG=$KCP_KUBECONFIG kubectl apply -f - <<EOF
apiVersion: orchestrate.platform-mesh.io/v1alpha1
kind: HttpBin
metadata:
name: my-httpbin
namespace: default
spec:
region: eu-west-1
EOFCheck the service cluster for the synchronized resource:
kubectl --kubeconfig $KIND_KUBECONFIG get httpbins --all-namespaces
kubectl --kubeconfig $KIND_KUBECONFIG get pods --all-namespaces | grep httpbinCheck status back in kcp:
KUBECONFIG=$KCP_KUBECONFIG kubectl kcp workspace use :root:test-consumer
KUBECONFIG=$KCP_KUBECONFIG kubectl get httpbin my-httpbin -n default -o yamlLook for a populated status section with readiness and URL information. If status is still empty, wait a few seconds and check the operator and api-syncagent logs.
Review the data flow
The HttpBin operator did not need Platform Mesh-specific code. api-syncagent handled the integration path by publishing the CRD to kcp and synchronizing resources between the consumer workspace and the service cluster.
Next
Continue with Consume a service from a controller to build the consumer side of the loop you just opened.
Optional branches:
- api-syncagent for the architecture behind what you just built.
- Build a multi-cluster-runtime provider for the advanced provider track.
- Service provider persona for the role context.