Lesson 7.1: Packaging and Distribution
| Navigation: Module Overview | Next Lesson: RBAC and Security → |
Introduction
Before deploying operators to production, they need to be packaged and distributed. This lesson covers building container images, creating Helm charts, and packaging operators for distribution via OLM (Operator Lifecycle Manager).
Theory: Packaging and Distribution
Packaging operators enables reliable, repeatable deployments across environments.
Why Packaging Matters
Reproducibility:
- Same operator version everywhere
- Consistent deployments
- Version control
- Rollback capability
Distribution:
- Share operators with teams
- Deploy to multiple clusters
- Enable operator marketplace
- Simplify installation
Deployment:
- Standard deployment methods
- Helm charts for easy install
- OLM for operator marketplace
- Container images for portability
Packaging Strategies
Container Images:
- Standard format
- Works everywhere
- Versioned
- Portable
Helm Charts:
- Package operator + dependencies
- Parameterized configuration
- Easy upgrades
- Community standard
OLM Bundles:
- Operator marketplace format
- Metadata and manifests
- Version management
- Dependency resolution
Versioning
Semantic Versioning:
- Major: Breaking changes
- Minor: New features
- Patch: Bug fixes
Version Tags:
latest: Latest versionv1.2.3: Specific versionv1.2: Latest patch of minor versionstable: Stable release
Understanding packaging helps you distribute operators effectively.
Operator Packaging Flow
Here’s how operators are packaged and distributed:
graph TB
SOURCE[Source Code] --> BUILD[Build Image]
BUILD --> REGISTRY[Container Registry]
SOURCE --> HELM[Create Helm Chart]
HELM --> CHART[Helm Chart]
SOURCE --> OLM[Create OLM Bundle]
OLM --> BUNDLE[OLM Bundle]
REGISTRY --> DEPLOY[Deploy]
CHART --> DEPLOY
BUNDLE --> DEPLOY
style BUILD fill:#90EE90
style REGISTRY fill:#FFB6C1
Building Container Images
Kubebuilder generates a Dockerfile for you when you scaffold a project. It uses multi-stage builds for optimal image size and security.
Kubebuilder-Generated Dockerfile
When you run kubebuilder init, it creates a Dockerfile in your project root:
# Build stage
FROM golang:1.24 as builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# Cache deps before building and copying source
RUN go mod download
# Copy the go source
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY internal/ internal/
# Build
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} go build -a -o manager cmd/main.go
# Runtime stage
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532
ENTRYPOINT ["/manager"]
Note: The internal/ directory is copied entirely because it contains both:
internal/controller/- Your reconciliation logicinternal/webhook/- Webhook handlers (if you created webhooks in Module 5)
Building with Kubebuilder’s Makefile
Kubebuilder provides Makefile targets for building images:
# Build the container image
make docker-build IMG=<registry>/postgres-operator:v0.1.0
# Push to registry
make docker-push IMG=<registry>/postgres-operator:v0.1.0
# Build and push in one command
make docker-build docker-push IMG=<registry>/postgres-operator:v0.1.0
Image Build Process
sequenceDiagram
participant Dev
participant Make as Makefile
participant Docker
participant Registry
Dev->>Make: make docker-build IMG=...
Make->>Docker: docker build
Docker->>Docker: Build Go binary
Docker->>Docker: Create distroless image
Docker->>Docker: Tag image
Dev->>Make: make docker-push IMG=...
Make->>Docker: docker push
Docker->>Registry: Push image
Registry-->>Dev: Image available
Note over Docker: Multi-stage build<br/>for smaller images
Loading Images to kind
For local development with kind clusters:
# Build the image
make docker-build IMG=postgres-operator:latest
# Load into kind cluster
kind load docker-image postgres-operator:latest --name k8s-operators-course
Helm Charts for Operators
While kubebuilder uses Kustomize for deployment by default (config/ directory), you can create Helm charts for wider distribution. The manifests generated by kubebuilder can be used as a basis for Helm templates.
What an Operator Helm Chart Needs
A complete operator Helm chart must include all components from kubebuilder’s config/ directory:
| Component | Source Directory | Purpose |
|---|---|---|
| CRDs | config/crd/ |
Custom Resource Definitions |
| RBAC | config/rbac/ |
ServiceAccount, ClusterRole, ClusterRoleBinding |
| Deployment | config/manager/ |
Controller manager pod |
| Webhooks | config/webhook/ |
Validating/Mutating webhooks (if used) |
| Certificates | config/certmanager/ |
Webhook certificates (if using cert-manager) |
Important: A Helm chart with only the Deployment won’t work! The operator needs RBAC permissions to function and CRDs must be installed for the operator to manage custom resources.
Chart Structure
graph TB
CHART[Helm Chart]
CHART --> TEMPLATES[templates/]
CHART --> VALUES[values.yaml]
CHART --> CHARTS[Chart.yaml]
TEMPLATES --> CRD[crds.yaml]
TEMPLATES --> RBAC[rbac.yaml]
TEMPLATES --> DEPLOYMENT[deployment.yaml]
TEMPLATES --> WEBHOOK[webhook.yaml]
TEMPLATES --> HELPERS[_helpers.tpl]
style CHART fill:#90EE90
style CRD fill:#FFB6C1
style RBAC fill:#FFB6C1
Kubebuilder’s Kustomize vs Helm
Kubebuilder generates Kustomize manifests in config/:
config/
├── crd/ # CRD definitions
│ └── bases/
├── default/ # Default deployment configuration
├── manager/ # Controller deployment
├── rbac/ # RBAC rules
├── webhook/ # Webhook configuration
└── samples/ # Sample CR manifests
To deploy with Kustomize (recommended for development):
# Deploy the operator
make deploy IMG=<registry>/postgres-operator:v0.1.0
# This runs: kustomize build config/default | kubectl apply -f -
To create a Helm chart (for distribution):
# Create Helm chart directory
mkdir -p charts/postgres-operator/templates
# Export kustomize output as a starting point
kustomize build config/default > charts/postgres-operator/templates/all.yaml
# Then split into separate files and add templating
OLM Bundles
OLM Bundle Structure
graph TB
BUNDLE[OLM Bundle]
BUNDLE --> MANIFESTS[manifests/]
BUNDLE --> METADATA[metadata/]
MANIFESTS --> CRD[CRDs]
MANIFESTS --> CSV[ClusterServiceVersion]
MANIFESTS --> RBAC[RBAC]
METADATA --> ANNOTATIONS[annotations.yaml]
style BUNDLE fill:#FFB6C1
Bundle Creation
While kubebuilder focuses on controller development, you can use operator-sdk alongside kubebuilder for OLM bundle generation:
# Initialize operator-sdk integration (if not already done)
operator-sdk init --plugins=manifests
# Generate OLM bundle from kubebuilder manifests
operator-sdk generate bundle \
--version 0.1.0 \
--package postgres-operator \
--channels stable
# Creates:
# bundle/
# manifests/
# postgres-operator.clusterserviceversion.yaml
# database.example.com_databases.yaml
# metadata/
# annotations.yaml
Note: For most use cases, kubebuilder’s built-in make deploy with Kustomize is sufficient. OLM bundles are primarily needed when publishing to operator marketplaces like OperatorHub.
Versioning Strategy
Semantic Versioning
graph LR
VERSION[Version]
VERSION --> MAJOR[Major: Breaking]
VERSION --> MINOR[Minor: Features]
VERSION --> PATCH[Patch: Fixes]
MAJOR --> 1.0.0
MINOR --> 1.1.0
PATCH --> 1.1.1
style VERSION fill:#90EE90
Version format: v<major>.<minor>.<patch>
- Major: Breaking API changes
- Minor: New features, backward compatible
- Patch: Bug fixes, backward compatible
Distribution Strategies
Strategy 1: Container Registry
graph LR
BUILD[Build] --> TAG[Tag]
TAG --> PUSH[Push]
PUSH --> REGISTRY[Registry]
REGISTRY --> PULL[Pull]
PULL --> DEPLOY[Deploy]
style REGISTRY fill:#FFB6C1
Strategy 2: Helm Repository
graph LR
PACKAGE[Package Chart] --> REPO[Helm Repo]
REPO --> ADD[helm repo add]
ADD --> INSTALL[helm install]
style REPO fill:#90EE90
Strategy 3: OLM Catalog
graph LR
BUNDLE[Create Bundle] --> CATALOG[OLM Catalog]
CATALOG --> SUBSCRIBE[Subscribe]
SUBSCRIBE --> INSTALL[Install]
style CATALOG fill:#FFB6C1
Image Optimization
Multi-Stage Builds
# Stage 1: Build
FROM golang:1.24 AS builder
# ... build steps ...
# Stage 2: Runtime
FROM gcr.io/distroless/static:nonroot
# ... copy binary only ...
Benefits:
- Smaller final image
- No build tools in production
- Better security (distroless)
Image Size Comparison
graph LR
FULL[Full Image<br/>~800MB] --> OPTIMIZED[Optimized<br/>~50MB]
OPTIMIZED --> DISTROLESS[Distroless<br/>~20MB]
style FULL fill:#FFB6C1
style OPTIMIZED fill:#FFE4B5
style DISTROLESS fill:#90EE90
Automating with CI/CD
GitHub Actions for Releases
Automate releases with GitHub Actions:
# .github/workflows/release.yaml
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push image
run: make docker-build docker-push IMG=ghcr.io/${{ github.repository }}:${{ github.ref_name }}
- name: Generate and push Helm chart
run: |
make helm-chart helm-package
helm push dist/*.tgz oci://ghcr.io/${{ github.repository_owner }}/charts
Helm Chart Distribution
Modern approach: Push Helm charts to OCI registries (like GHCR):
# Push chart to OCI registry
helm push postgres-operator-0.1.0.tgz oci://ghcr.io/myorg/charts
# Install from OCI registry
helm install my-operator oci://ghcr.io/myorg/charts/postgres-operator --version 0.1.0
Key Takeaways
- Kubebuilder generates a production-ready Dockerfile
make docker-buildbuilds container images with proper tagging- Kustomize is the default deployment method in kubebuilder
make helm-chartcan generate Helm charts from Kustomize- OCI registries can host both images AND Helm charts
- GitHub Actions automate releases and chart publishing
- Semantic versioning tracks operator versions
- Multi-stage builds create smaller, secure images
Understanding for Building Operators
When packaging kubebuilder operators:
- Use
make docker-build IMG=...to build images - Use
make docker-push IMG=...to push to registry - Use
make deploy IMG=...for Kustomize-based deployment - Use
make helm-chartto generate Helm charts from Kustomize - Set up GitHub Actions for automated releases
- Push Helm charts to OCI registries for distribution
- Follow semantic versioning for your operator
- Use kind’s image loading for local development
Related Lab
- Lab 7.1: Packaging Your Operator - Hands-on exercises for this lesson
References
Official Documentation
Further Reading
- Kubernetes Operators by Jason Dobies and Joshua Wood - Chapter 12: Packaging
- Docker Deep Dive by Nigel Poulton - Container image best practices
- Helm Best Practices
Related Topics
Next Steps
Now that you understand packaging, let’s learn about RBAC and security.
| Navigation: ← Module Overview | Next: RBAC and Security → |