README
ΒΆ
πͺ Orbital - AWS S3 Bucket Kubernetes Operator
A production-grade Kubernetes operator for managing AWS S3 buckets declaratively using Custom Resource Definitions (CRDs). Built with Kubebuilder, Orbital enables GitOps-friendly S3 bucket management with lifecycle policies, versioning, and secure credential management.
β¨ Features
- Declarative Management: Define S3 buckets as Kubernetes resources
- Lifecycle Policies: Automatic bucket deletion after specified days (1-3650 days)
- Versioning Support: Enable/disable S3 bucket versioning
- Secure Credentials: AWS credentials stored in Kubernetes Secrets
- Deletion Policies: Choose between
DeleteorRetainon CR deletion - Status Tracking: Real-time bucket status with conditions and timestamps
- Production Ready: Finalizers, leader election, metrics, and health checks
- GitOps Compatible: Fully compatible with ArgoCD, Flux, and other GitOps tools
π Table of Contents
- Architecture
- Quick Start
- Installation
- API Reference
- Usage Examples
- Production Use Cases
- Local Development
- Reconciliation Process
- Troubleshooting
- Contributing
- License
ποΈ Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Kubernetes Cluster β
β β
β ββββββββββββββββββββ β
β β S3Bucket CR β β
β β (Desired State) β β
β ββββββββββ¬ββββββββββ β
β β β
β β Watch & Reconcile β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββ β
β β Orbital Controller β β
β β β β
β β 1. Fetch CR β β
β β 2. Get AWS Credentials (Secret) β β
β β 3. Check Lifecycle Policy β β
β β 4. Reconcile with AWS β β
β β 5. Update Status β β
β ββββββββββ¬ββββββββββββββββββββββββββββββ β
β β β
β β AWS SDK Calls β
βββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β HTTPS
βΌ
ββββββββββββββββββββββ
β AWS S3 API β
β β
β - CreateBucket β
β - DeleteBucket β
β - PutVersioning β
β - HeadBucket β
ββββββββββββββββββββββ
Reconciliation Flow
graph TD
A[S3Bucket CR Applied] --> B{CR Deleted?}
B -->|Yes| C{Has Finalizer?}
C -->|Yes| D{DeletionPolicy?}
D -->|Delete| E[Delete S3 Bucket]
D -->|Retain| F[Skip Deletion]
E --> G[Remove Finalizer]
F --> G
G --> H[End]
B -->|No| I{Has Finalizer?}
I -->|No| J[Add Finalizer]
J --> K[Requeue]
I -->|Yes| L[Get AWS Credentials from Secret]
L --> M[Initialize S3 Client]
M --> N[Ensure Bucket Exists]
N --> O[Configure Versioning]
O --> P{Lifecycle Policy Set?}
P -->|Yes| Q{Creation Time Set?}
Q -->|No| R[Set Creation Time]
R --> S[Calculate Deletion Time]
S --> T{Time to Delete?}
T -->|Yes| U[Delete Bucket]
U --> V[Delete CR]
T -->|No| W[Update Status: Ready]
W --> X[Requeue Before Deletion]
P -->|No| Y[Update Status: Ready]
Q -->|Yes| T
Y --> H
X --> H
π Quick Start
Prerequisites
- Kubernetes cluster (v1.30+)
- kubectl configured
- AWS account with S3 permissions
- Helm 3.0+ (for Helm installation)
Install with Helm
# Add the Helm repository (if published)
helm repo add orbital https://raihankhan.github.io/orbital
helm repo update
# Install the operator
helm install orbital orbital/orbital \
-n orbital-system \
--create-namespace
# Or install from local chart
helm install orbital ./helm/orbital \
-n orbital-system \
--create-namespace
Create AWS Credentials Secret
kubectl create secret generic orbital-aws-creds \
--from-literal=AWS_ACCESS_KEY_ID=<your-access-key-id> \
--from-literal=AWS_SECRET_ACCESS_KEY=<your-secret-access-key> \
-n default
Create Your First S3 Bucket
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: my-app-bucket
spec:
bucketName: my-unique-app-bucket-12345
region: us-east-1
versioning: true
deletionPolicy: Delete
awsSecretRef:
name: orbital-aws-creds
namespace: default
lifecycle:
deleteAfterDays: 90 # Optional: auto-delete after 90 days
kubectl apply -f my-bucket.yaml
kubectl get s3buckets
kubectl describe s3bucket my-app-bucket
π¦ Installation
Option 1: Helm (Recommended)
See Quick Start above.
Option 2: Manual Installation with Kustomize
# Install CRDs
make install
# Deploy the controller
make deploy IMG=ghcr.io/raihankhan/orbital:v0.1.0
Option 3: Local Development
# Run locally (requires AWS credentials in environment or ~/.aws/credentials)
make run
π API Reference
S3BucketSpec
| Field | Type | Required | Description |
|---|---|---|---|
bucketName |
string | Yes | Unique S3 bucket name (3-63 chars, DNS-compliant) |
region |
string | Yes | AWS region (us-east-1, us-west-2, ap-south-1, eu-central-1) |
versioning |
bool | No | Enable S3 bucket versioning (default: false) |
deletionPolicy |
string | No | Delete or Retain (default: Delete) |
awsSecretRef |
SecretReference | No | Reference to Secret with AWS credentials |
lifecycle.deleteAfterDays |
int32 | No | Auto-delete bucket after N days (1-3650) |
S3BucketStatus
| Field | Type | Description |
|---|---|---|
phase |
string | Current phase: Pending, Ready, Error, Deleted |
observedGeneration |
int64 | Last observed generation of the spec |
conditions |
[]Condition | Standard Kubernetes conditions |
creationTime |
Time | Timestamp when bucket was created |
scheduledDeletionTime |
Time | When bucket is scheduled for lifecycle deletion |
Conditions
| Type | Status | Reason | Description |
|---|---|---|---|
Ready |
True |
Ready |
Bucket successfully synchronized |
Ready |
False |
Error |
Reconciliation failed |
π‘ Usage Examples
Example 1: Simple Bucket
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: simple-bucket
spec:
bucketName: my-simple-bucket
region: us-east-1
awsSecretRef:
name: orbital-aws-creds
namespace: default
Example 2: Versioned Bucket with Retention
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: versioned-bucket
spec:
bucketName: my-versioned-bucket
region: us-west-2
versioning: true
deletionPolicy: Retain # Keep bucket when CR is deleted
awsSecretRef:
name: orbital-aws-creds
namespace: default
Example 3: Temporary Bucket with Lifecycle
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: temp-bucket
spec:
bucketName: my-temp-bucket
region: ap-south-1
versioning: false
deletionPolicy: Delete
lifecycle:
deleteAfterDays: 7 # Auto-delete after 7 days
awsSecretRef:
name: orbital-aws-creds
namespace: default
π Production Use Cases
Use Case 1: Multi-Tenant Application Storage
Scenario: SaaS application needs isolated S3 buckets per tenant with automatic cleanup.
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: tenant-acme-storage
namespace: tenant-acme
labels:
tenant: acme
app: storage
spec:
bucketName: prod-tenant-acme-storage
region: us-east-1
versioning: true
deletionPolicy: Retain # Prevent accidental deletion
awsSecretRef:
name: s3-credentials
namespace: tenant-acme
Benefits:
- Declarative per-tenant bucket provisioning
- GitOps-friendly tenant onboarding
- Automatic bucket creation via CI/CD
- Namespace isolation for credentials
Use Case 2: Temporary Data Processing Buckets
Scenario: Data pipeline creates temporary buckets for batch jobs that should auto-cleanup.
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: batch-job-20260127
labels:
job-id: "20260127"
type: temporary
spec:
bucketName: batch-processing-20260127-abc123
region: us-west-2
versioning: false
deletionPolicy: Delete
lifecycle:
deleteAfterDays: 30 # Auto-cleanup after 30 days
awsSecretRef:
name: batch-job-credentials
namespace: data-pipeline
Benefits:
- Automatic cleanup prevents storage cost accumulation
- No manual intervention required
- Audit trail via Kubernetes events
- Cost optimization through lifecycle policies
Use Case 3: Disaster Recovery with Versioning
Scenario: Critical application data requires versioning and retention policies.
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: app-backup-primary
namespace: production
annotations:
backup.policy: "daily"
retention.days: "365"
spec:
bucketName: prod-app-backup-primary-us-east-1
region: us-east-1
versioning: true # Enable versioning for point-in-time recovery
deletionPolicy: Retain # Never delete on CR removal
awsSecretRef:
name: backup-s3-credentials
namespace: production
---
apiVersion: infra.orbital.dev/v1alpha1
kind: S3Bucket
metadata:
name: app-backup-dr
namespace: production
spec:
bucketName: prod-app-backup-dr-us-west-2
region: us-west-2 # Different region for DR
versioning: true
deletionPolicy: Retain
awsSecretRef:
name: backup-s3-credentials
namespace: production
Benefits:
- Infrastructure as Code for disaster recovery
- Consistent configuration across regions
- Version control for bucket definitions
- Automated DR bucket provisioning
π οΈ Local Development
Prerequisites
- Go 1.25+
- Docker
- kubectl
- Kubebuilder 4.11+
- AWS CLI configured (for testing)
Setup Development Environment
# Clone the repository
git clone https://github.com/raihankhan/orbital.git
cd orbital
# Install dependencies
go mod download
# Generate CRDs and code
make manifests generate
# Run tests
make test
# Run locally (connects to your current kubectl context)
make run
Project Structure
orbital/
βββ api/v1alpha1/ # CRD definitions
β βββ s3bucket_types.go
β βββ groupversion_info.go
βββ internal/
β βββ controller/ # Reconciliation logic
β β βββ s3bucket_controller.go
β β βββ s3bucket_controller_test.go
β βββ aws/ # AWS SDK wrapper
β βββ s3.go
β βββ types.go
βββ config/
β βββ crd/ # Generated CRDs
β βββ rbac/ # RBAC manifests
β βββ manager/ # Controller deployment
β βββ examples/ # Usage examples
βββ helm/orbital/ # Helm chart
βββ cmd/main.go # Entry point
βββ Makefile # Build targets
βββ README.md
Development Workflow
# 1. Make changes to API
vim api/v1alpha1/s3bucket_types.go
# 2. Regenerate manifests and code
make manifests generate
# 3. Run tests
make test
# 4. Test locally
make run
# 5. Build Docker image
make docker-build IMG=myregistry/orbital:dev
# 6. Deploy to test cluster
make deploy IMG=myregistry/orbital:dev
Running Tests
# Unit tests
make test
# Integration tests with envtest
make test-integration
# Coverage report
make test-coverage
Building from Source
# Build binary
make build
# Build and push Docker image
make docker-build docker-push IMG=ghcr.io/raihankhan/orbital:v0.1.0
Kubebuilder Scaffolding Commands
This project was scaffolded using Kubebuilder. Here are the exact commands used to create the project structure:
# 1. Initialize the project
kubebuilder init \
--domain orbital.dev \
--repo github.com/raihankhan/orbital \
--owner "Orbital Team"
# 2. Create the S3Bucket API and controller
kubebuilder create api \
--group infra \
--version v1alpha1 \
--kind S3Bucket \
--resource \
--controller \
--make
# 3. Generate manifests and code (after modifying types)
make manifests generate
# 4. Install CRDs into the cluster
make install
# 5. Run the controller locally for testing
make run
# 6. Build and deploy to cluster
make docker-build docker-push IMG=<your-registry>/orbital:tag
make deploy IMG=<your-registry>/orbital:tag
Key Files Modified After Scaffolding:
api/v1alpha1/s3bucket_types.go- Added CRD fields (bucketName, region, versioning, lifecycle, etc.)internal/controller/s3bucket_controller.go- Implemented reconciliation logicinternal/aws/- Created AWS SDK wrapper for S3 operationsconfig/rbac/- Added RBAC permissions for Secrets accesshelm/orbital/- Created Helm chart for deployment
Kubebuilder Markers Used:
// Validation markers
// +kubebuilder:validation:MinLength=3
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Pattern=`^[a-z0-9][a-z0-9.-]*[a-z0-9]$`
// +kubebuilder:validation:Enum=us-east-1;us-west-2;ap-south-1;eu-central-1
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=3650
// Default values
// +kubebuilder:default=Delete
// Resource configuration
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// RBAC markers
// +kubebuilder:rbac:groups=infra.orbital.dev,resources=s3buckets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
π Reconciliation Process
The Orbital controller follows a standard Kubernetes reconciliation pattern:
1. Fetch Phase
- Retrieve the S3Bucket CR from the API server
- Return early if not found (deleted)
2. Deletion Handling
- Check if CR has
deletionTimestampset - If
deletionPolicy: Delete, delete the S3 bucket via AWS SDK - If
deletionPolicy: Retain, skip AWS deletion - Remove finalizer to allow CR deletion
3. Finalizer Management
- Add
infra.orbital.dev/finalizerif not present - Ensures cleanup logic runs before CR deletion
4. AWS Authentication
- Fetch Secret referenced in
awsSecretRef - Extract
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY - Initialize AWS S3 client with credentials
5. Bucket Reconciliation
- Check if bucket exists in AWS (HeadBucket)
- Create bucket if missing (CreateBucket)
- Configure versioning (PutBucketVersioning)
- Idempotent operations ensure safe retries
6. Lifecycle Management
- Track
creationTimein status on first reconcile - Calculate
scheduledDeletionTimeiflifecycle.deleteAfterDaysis set - Check if current time >= scheduled deletion time
- Delete bucket and CR if lifecycle policy expired
- Use
RequeueAfterto check again before deletion time
7. Status Update
- Set
phase:Pending,Ready,Error, orDeleted - Update
observedGenerationto track spec changes - Set conditions with type
Ready - Record timestamps for creation and scheduled deletion
Error Handling
- All AWS errors are logged and reflected in status
- Transient errors trigger requeue with exponential backoff
- Permanent errors (e.g., invalid credentials) set status to
Error - Controller-runtime handles rate limiting automatically
π Troubleshooting
Bucket Not Created
Symptoms: CR shows phase: Error or phase: Pending
# Check controller logs
kubectl logs -n orbital-system deployment/orbital-controller-manager
# Check CR status
kubectl describe s3bucket <bucket-name>
# Common causes:
# 1. Invalid AWS credentials
kubectl get secret orbital-aws-creds -o yaml
# 2. Bucket name already exists globally
# 3. Insufficient IAM permissions
# 4. Invalid region
Lifecycle Deletion Not Working
Symptoms: Bucket not deleted after deleteAfterDays
# Check status timestamps
kubectl get s3bucket <bucket-name> -o jsonpath='{.status}'
# Verify scheduledDeletionTime is set
kubectl get s3bucket <bucket-name> -o jsonpath='{.status.scheduledDeletionTime}'
# Check controller is running
kubectl get pods -n orbital-system
Permission Denied Errors
Required IAM Permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:HeadBucket",
"s3:GetBucketLocation",
"s3:GetBucketVersioning",
"s3:PutBucketVersioning"
],
"Resource": "*"
}
]
}
Controller Crashlooping
# Check logs
kubectl logs -n orbital-system deployment/orbital-controller-manager
# Common issues:
# 1. CRDs not installed
kubectl get crd s3buckets.infra.orbital.dev
# 2. RBAC permissions missing
kubectl get clusterrole orbital-manager-role
# 3. Invalid image
kubectl describe pod -n orbital-system <pod-name>
π References
- Kubebuilder Book - Official Kubebuilder documentation
- Kubebuilder Quick Start - Getting started guide
- Controller Runtime - Controller runtime library docs
- AWS SDK for Go v2 - AWS SDK documentation
- Kubernetes API Conventions - API design guidelines
π€ Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Development Setup
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests (
make test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
π License
This project is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.0 - see the LICENSE file for details.
π Acknowledgments
- Built with Kubebuilder
- Inspired by AWS Controllers for Kubernetes (ACK)
- Thanks to the Kubernetes community