This guide covers end-to-end usage of entity: installation, configuration, TLS/cert handling, COSI provisioning, S3 client access, scaling, operations, and troubleshooting.
entity provides:
- A Kubernetes operator (
ObjectServiceCRD) - A custom S3-compatible object service (no MinIO)
- COSI integration (
Bucket,BucketClaim,BucketAccess, classes) - Clustered operation (
replicas > 1) with leader-routed writes and quorum replication - TLS for S3/Admin and mTLS enforcement for internal replication endpoints
- Kubernetes cluster (tested with Kind in this repo)
kubectl,docker,go- A StorageClass for object data PVCs
- Optional: cert-manager if using
useCertManager: true
For Portworx, use your Portworx StorageClass (for example px-repl3) in ObjectService.spec.storageClassName.
Use this exact flow:
- Build and publish/load image.
- Apply CRDs (entity + COSI).
- Apply RBAC/service accounts.
- Deploy operator.
- Create
ObjectService. - Wait for StatefulSet and COSI deployment readiness.
- Create
BucketClassandBucketAccessClass. - Create
BucketClaimandBucketAccess. - Read generated credentials Secret.
- Use S3 API (PUT/GET/LIST).
This order avoids reconciliation races and missing-dependency errors.
From repo root:
make e2e-kindThis performs a full integration run:
- creates Kind cluster
- deploys entity
- provisions COSI bucket/access
- runs mTLS-negative check (replication call without client cert must fail
403) - runs S3 positive path (
put/get/list)
go build ./...
make docker-build IMAGE=ghcr.io/<your-org>/entity:<tag>entity publishes Helm chart artifacts to GHCR OCI:
oci://ghcr.io/mchenetz/charts/entity
Authenticate to GHCR:
echo <GITHUB_TOKEN> | helm registry login ghcr.io -u <GITHUB_USERNAME> --password-stdinInstall operator only:
helm upgrade --install entity oci://ghcr.io/mchenetz/charts/entity \
--version 0.1.0 \
--namespace entity-system --create-namespaceInstall operator + ObjectService + COSI classes:
helm upgrade --install entity oci://ghcr.io/mchenetz/charts/entity \
--version 0.1.0 \
--namespace entity-system --create-namespace \
--set image.repository=ghcr.io/mchenetz/entity \
--set image.tag=latest \
--set objectService.create=true \
--set objectService.storageClassName=px-repl3 \
--set cosi.createClasses=trueOptional: pull chart locally first:
helm pull oci://ghcr.io/mchenetz/charts/entity --version 0.1.0
tar -xzf entity-0.1.0.tgz
helm upgrade --install entity ./entity --namespace entity-system --create-namespaceOperator only (recommended first):
helm upgrade --install entity ./charts/entity \
--namespace entity-system --create-namespaceOperator + ObjectService + COSI classes:
helm upgrade --install entity ./charts/entity \
--namespace entity-system --create-namespace \
--set image.repository=ghcr.io/<your-org>/entity \
--set image.tag=<tag> \
--set objectService.create=true \
--set objectService.storageClassName=px-repl3 \
--set cosi.createClasses=trueEnable cert-manager mode via Helm:
helm upgrade --install entity ./charts/entity \
--namespace entity-system --create-namespace \
--set objectService.create=true \
--set objectService.storageClassName=px-repl3 \
--set objectService.useCertManager=true \
--set objectService.issuerRefName=entity-ca-issuerkubectl apply -f config/crd/bases/entity.io_objectservices.yaml
kubectl apply -f deploy/objectstorage.k8s.io_bucketclaims.yaml
kubectl apply -f deploy/objectstorage.k8s.io_buckets.yaml
kubectl apply -f deploy/objectstorage.k8s.io_bucketclasses.yaml
kubectl apply -f deploy/objectstorage.k8s.io_bucketaccesses.yaml
kubectl apply -f deploy/objectstorage.k8s.io_bucketaccessclasses.yaml
kubectl apply -f config/rbac/operator-rbac.yaml
kubectl apply -f deploy/operator.yamlIf needed, update image in deploy/operator.yaml and ENTITY_IMAGE env.
Example:
apiVersion: entity.io/v1alpha1
kind: ObjectService
metadata:
name: entity
namespace: entity-system
spec:
replicas: 3
storageClassName: px-repl3
volumeSize: 100Gi
serviceType: ClusterIP
port: 9000
dataPath: /dataApply:
kubectl apply -f config/samples/entity_v1alpha1_objectservice.yamlWait:
kubectl -n entity-system rollout status statefulset/entity
kubectl -n entity-system rollout status deploy/entity-cosikubectl apply -f config/samples/cosi-classes.yamlkubectl apply -f deploy/cosi-claim-example.yaml
kubectl wait --for=jsonpath='{.status.bucketReady}'=true bucketclaim/app-bucket -n default --timeout=300s
kubectl wait --for=jsonpath='{.status.accessGranted}'=true bucketaccess/app-bucket-access -n default --timeout=300sIf tlsSecretName is omitted, operator uses <name>-tls and manages cert material:
tls.crttls.keyca.crt
Rotation is automatic before expiry.
Use these ObjectService.spec fields:
useCertManager: trueissuerRefName: <issuer-or-clusterissuer-name>- optional
issuerRefKind(defaultIssuer) - optional
issuerRefGroup(defaultcert-manager.io) - optional
tlsSecretName
Example:
apiVersion: entity.io/v1alpha1
kind: ObjectService
metadata:
name: entity
namespace: entity-system
spec:
replicas: 3
storageClassName: px-repl3
volumeSize: 100Gi
useCertManager: true
issuerRefName: entity-ca-issuer
issuerRefKind: Issuer
issuerRefGroup: cert-manager.ioReplication endpoints (/_cluster/replicate/...) require:
- valid bearer token
- internal replication header
- verified client certificate
Without client cert, request is rejected with 403 and mTLS required.
kubectl -n default get secret app-bucket-credentials -o yamlFields include:
BUCKET_HOSTBUCKET_NAMEAWS_REGIONAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_CA_BUNDLE_PEMCOSI_BUCKET_INFO
# decode from secret first
AK=$(kubectl -n default get secret app-bucket-credentials -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d)
SK=$(kubectl -n default get secret app-bucket-credentials -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 -d)
HOST=$(kubectl -n default get secret app-bucket-credentials -o jsonpath='{.data.BUCKET_HOST}' | base64 -d)
BUCKET=$(kubectl -n default get secret app-bucket-credentials -o jsonpath='{.data.BUCKET_NAME}' | base64 -d)
REGION=$(kubectl -n default get secret app-bucket-credentials -o jsonpath='{.data.AWS_REGION}' | base64 -d)
CA=$(mktemp)
kubectl -n default get secret app-bucket-credentials -o jsonpath='{.data.AWS_CA_BUNDLE_PEM}' | base64 -d > "$CA"
export AWS_ACCESS_KEY_ID="$AK"
export AWS_SECRET_ACCESS_KEY="$SK"
export AWS_REGION="$REGION"
export AWS_CA_BUNDLE="$CA"
echo hello > hello.txt
aws --endpoint-url "https://$HOST" s3api put-object --bucket "$BUCKET" --key hello.txt --body hello.txt
aws --endpoint-url "https://$HOST" s3api get-object --bucket "$BUCKET" --key hello.txt out.txt
aws --endpoint-url "https://$HOST" s3api list-objects-v2 --bucket "$BUCKET"import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://<BUCKET_HOST>",
aws_access_key_id="<AWS_ACCESS_KEY_ID>",
aws_secret_access_key="<AWS_SECRET_ACCESS_KEY>",
region_name="<AWS_REGION>",
verify="/path/to/ca.pem",
)
s3.put_object(Bucket="<BUCKET_NAME>", Key="hello.txt", Body=b"hello")
print(s3.get_object(Bucket="<BUCKET_NAME>", Key="hello.txt")["Body"].read())Set spec.replicas to 3+ for quorum replication.
Example patch:
kubectl -n entity-system patch objectservice entity --type merge -p '{"spec":{"replicas":5}}'
kubectl -n entity-system rollout status statefulset/entityBehavior:
- Reads can be served by any pod.
- Mutating requests are routed to leader.
- Leader replicates to peers and requires quorum acknowledgement.
Order:
- Push new image.
- Update operator deployment image and
ENTITY_IMAGEenv. - Let operator roll.
- Operator reconciles StatefulSet/COSI deployment.
Commands:
kubectl -n entity-system set image deploy/entity-operator operator=ghcr.io/<org>/entity:<tag>
kubectl -n entity-system set env deploy/entity-operator ENTITY_IMAGE=ghcr.io/<org>/entity:<tag>
kubectl -n entity-system rollout status deploy/entity-operator
kubectl -n entity-system rollout status statefulset/entity
kubectl -n entity-system rollout status deploy/entity-cosi- Keep
serviceType: ClusterIPunless external access is required. - Restrict access with NetworkPolicies.
- Rotate
adminTokenperiodically. - Use cert-manager with enterprise PKI when available.
- Scope COSI access classes (
readonly: true) for read-only consumers.
kubectl -n entity-system logs deploy/entity-operator
kubectl -n entity-system get objectservice entity -o yamlkubectl get bucketclaim,bucket,bucketaccess -A -o wide
kubectl -n entity-system logs deploy/entity-cosiCheck secret contents:
kubectl -n entity-system get secret entity-tls -o yamlVerify CA is passed to client (AWS_CA_BUNDLE_PEM).
kubectl -n entity-system logs statefulset/entity -c objectd --tail=200Look for replication quorum errors or certificate verification failures.
kubectl delete -f deploy/cosi-claim-example.yaml --ignore-not-found
kubectl -n entity-system delete objectservice entity --ignore-not-found
kubectl delete -f config/samples/cosi-classes.yaml --ignore-not-found
kubectl delete -f deploy/operator.yaml --ignore-not-found
kubectl delete -f config/rbac/operator-rbac.yaml --ignore-not-found
kubectl delete -f deploy/objectstorage.k8s.io_bucketaccessclasses.yaml --ignore-not-found
kubectl delete -f deploy/objectstorage.k8s.io_bucketaccesses.yaml --ignore-not-found
kubectl delete -f deploy/objectstorage.k8s.io_bucketclasses.yaml --ignore-not-found
kubectl delete -f deploy/objectstorage.k8s.io_buckets.yaml --ignore-not-found
kubectl delete -f deploy/objectstorage.k8s.io_bucketclaims.yaml --ignore-not-found
kubectl delete -f config/crd/bases/entity.io_objectservices.yaml --ignore-not-foundReference files:
controllers/objectservice_controller.gocmd/objectd/main.gointernal/cluster/cluster.gointernal/cluster/replication_handler.gointernal/cosi/listeners.gohack/e2e-kind.sh