Skip to content

aws-samples/sample-kro-serverless-app-stack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Kro Serverless Application Stack

License Kubernetes Kro ACK Crossplane

Deploy complete serverless applications on AWS with a single kubectl apply using the Hybrid Kro Compositions pattern — combining Kro, ACK, and Crossplane on Kubernetes.

Table of Contents

  1. Overview
  2. The Sample Application
  3. Architecture
  4. What Gets Deployed
  5. Prerequisites
  6. Getting Started
  7. Repository Structure
  8. Documentation
  9. Related Resources

Overview

This repository implements a three-layer hybrid architecture for deploying serverless applications on AWS using Kubernetes-native orchestration:

Layer What How Lifecycle
Layer 1 — Shared Resources CloudFront, Cognito, IAM roles, S3 buckets Standalone ACK/Crossplane manifests (kubectl apply) One-time setup, long-lived
Layer 2 — ResourceGraphDefinition Kro RGD orchestrating 15 resources (ACK + Crossplane + K8s) kubectl apply the RGD definition One-time per cluster
Layer 3 — Application Instances Complete serverless app (API + Lambda + DynamoDB + UI) kubectl apply a 5-field instance YAML Per-application, dynamic

Kro is the central orchestrator. It takes a simple instance spec (team, developer, app, environment, region) and provisions 15 AWS resources with automatic dependency management — in 5-8 minutes.

💡 Why the separation? Shared resources (Layer 1) are intentionally kept as individual manifests so developers can explore each ACK/Crossplane resource type independently. The Kro RGD (Layer 2) demonstrates the real power: orchestrating complex, multi-resource applications while referencing pre-existing platform infrastructure.

The Sample Application

The application deployed by this stack is Smart Inventory Hub — a product inventory management system with a REST API and a web UI. It's intentionally simple so the focus stays on the infrastructure orchestration pattern, not the business logic.

Smart Inventory Hub - Sample Application

What it does:

  • CRUD operations for products (create, read, update, delete) via a REST API
  • Dashboard with inventory metrics: total products, inventory value, low stock alerts, categories
  • Search and filter products by name, SKU, category
  • Interactive charts for stock status and category distribution

Why these AWS services:

Service Why it's needed What it demonstrates
DynamoDB Stores product data (NoSQL, serverless, on-demand) ACK managing a data store
Lambda Runs the Python API handler — no servers to manage Crossplane managing compute
API Gateway Exposes the Lambda as HTTP endpoints (GET, POST, PUT, DELETE) ACK managing API routing with multiple routes
CloudFront Serves the static UI via CDN with path-based routing per instance ACK managing a shared CDN
Route 53 Maps yourdomain.com to CloudFront (single record for all instances) Prerequisite DNS configuration
Cognito JWT authentication for the API with centralized callback Crossplane managing auth resources
S3 Stores Lambda code and UI static files ACK managing object storage
IAM Least-privilege roles for Lambda and S3 sync jobs ACK managing security

The combination of these services creates a realistic serverless stack that exercises cross-controller dependencies — DynamoDB → Lambda → API Gateway — which is exactly what Kro is designed to orchestrate.

Architecture

Three-Layer Overview

Three-Layer Overview

Cross-Controller Integration

Kro orchestrates resources across ACK, Crossplane, and native Kubernetes seamlessly:

cross-controller dependencies

Deployment Flow

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Dev as Developer
    participant K8s as Kubernetes
    participant Kro
    participant Controllers as ACK/Crossplane
    participant AWS
    
    Dev->>K8s: kubectl apply instance.yaml
    K8s->>Kro: Create SmartInventoryHub
    Kro->>Kro: Analyze dependencies
    Kro->>Controllers: Create resources in order
    Controllers->>AWS: Provision AWS resources
    AWS-->>Controllers: Resources ready
    Kro->>K8s: Create Job for UI sync
    K8s->>AWS: Sync UI assets to S3
    Kro-->>Dev: Instance ready (5-8 min)
Loading

What Gets Deployed

A single RGD instance provisions 15 resources:

%%{init: {'theme':'dark'}}%%
graph TB
    subgraph Instance["SmartInventoryHub Instance"]
        YAML["instance.yaml<br/>5 fields"]
    end
    
    subgraph DataLayer["Data Layer"]
        DDB["DynamoDB Table<br/>Encrypted, On-Demand"]
    end
    
    subgraph ComputeLayer["Compute Layer"]
        Lambda["Lambda Function<br/>Python 3.12"]
        Perm["Lambda Permission"]
    end
    
    subgraph APILayer["API Layer"]
        API["API Gateway HTTP"]
        INT["Lambda Integration"]
        Routes["5 Routes<br/>GET/POST/PUT/DELETE/OPTIONS"]
        Stage["API Stage"]
    end
    
    subgraph AutomationLayer["Automation Layer"]
        SA["ServiceAccount"]
        Jobs["Job<br/>UI Sync"]
        CM["ConfigMap"]
    end
    
    YAML --> DDB
    DDB --> Lambda
    Lambda --> INT
    API --> INT
    INT --> Routes
    Routes --> Stage
    Stage --> Perm
    Lambda --> CM
    API --> CM
    SA --> Jobs
    
    style Instance fill:#1a1a2e,stroke:#4A90E2,stroke-width:2px
    style DataLayer fill:#1a1a2e,stroke:#FF9F43,stroke-width:2px
    style ComputeLayer fill:#1a1a2e,stroke:#4A90E2,stroke-width:2px
    style APILayer fill:#1a1a2e,stroke:#FF9F43,stroke-width:2px
    style AutomationLayer fill:#1a1a2e,stroke:#26DE81,stroke-width:2px
Loading
# Resource Controller Purpose
1 DynamoDB Table ACK Data storage
2 Lambda Function Crossplane Business logic
3 API Gateway ACK HTTP API
4 API Integration ACK Lambda proxy
5 JWT Authorizer ACK Cognito JWT validation
6-10 API Routes (5) ACK CRUD endpoints
11 API Stage ACK Environment stage
12 Lambda Permission Crossplane API Gateway invoke
13 ConfigMap K8s App info
14 S3 Uploader SA K8s IRSA for S3
15 UI Sync Job K8s Sync UI assets
Feature Description
🚀 One-Command Deployment Deploy complete serverless stacks with a single kubectl apply
🔄 GitOps Ready Declarative YAML manifests for version control and CI/CD integration
🔐 Security First Pre-configured IAM roles with least-privilege access and Cognito JWT authentication
📊 Multi-Environment Support for dev, staging, and production environments
🌐 CDN Integration Shared CloudFront distribution with per-app path routing
🔑 Authentication Integrated Cognito authentication with JWT-protected API endpoints
💰 Cost Optimized 90%+ cost reduction through shared platform resources

Prerequisites

Before starting, ensure you have the following infrastructure and tools ready.

Component Summary

CLI Tools

Tool Version Install
AWS CLI 2.x Install Guide
kubectl 1.28+ Install Guide
Helm 3.x Install Guide
envsubst any brew install gettext (macOS)

Cluster & Controllers

Component Version Install
EKS 1.28+ EKS User Guide
Kro v0.8.4+ EKS Capability · Helm
ACK Latest EKS Capability · Helm
Crossplane 2.x Helm Install

Crossplane Providers

Provider Version Install
Lambda v1.16.0 Upbound Marketplace
Cognito v2.4.0 Upbound Marketplace

AWS Resources

Resource Required Details
ACM Certificate for yourdomain.com in us-east-1 — required for CloudFront · ACM Guide
Route53 DNS management · Route 53 Guide

🌐 About the domain: This stack uses a single domain with path-based routing to give each application instance its own URL (e.g., yourdomain.com/john-inventory-dev/). You need a domain registered in Route 53 with an ACM certificate in us-east-1. If you don't have a domain, you can register one through Route 53 or use an existing domain by delegating a subdomain to a Route 53 hosted zone. A single DNS record and a single Cognito callback URL cover all application instances — no per-instance changes needed.

💡 EKS Capabilities vs Helm: EKS Capabilities are AWS-managed — no IRSA needed for controllers, automatic updates, single command install. Helm gives you more control but requires IRSA configuration per controller. This repo includes trust policy files in prerequisites/ for the EKS Capability approach.

⚠️ IRSA Note: ACK/Kro controllers as EKS Capabilities do not need IRSA. However, the shared resource IAM roles (s3-uploader) and Crossplane providers still require IRSA because they are assumed by Kubernetes Pods via ServiceAccount annotations.

IAM OIDC Provider

IRSA requires an IAM OIDC identity provider associated with your EKS cluster. This is a one-time setup:

# Check if already associated
aws iam list-open-id-connect-providers --no-cli-pager | grep $(aws eks describe-cluster \
  --name <cluster-name> --region <region> \
  --query "cluster.identity.oidc.issuer" --output text --no-cli-pager | sed 's|https://||' | cut -d'/' -f4)

# If not found, associate it
eksctl utils associate-iam-oidc-provider \
  --cluster <cluster-name> --region <region> --approve

Crossplane Provider Setup

The RGD uses Crossplane for Lambda and Cognito resources. Provider manifests are in prerequisites/:

# 1. Apply runtime config (IRSA annotation for provider pods)
kubectl apply -f prerequisites/crossplane-runtime-config.yaml

# 2. Install providers
kubectl apply -f prerequisites/crossplane-providers.yaml

# 3. Wait for HEALTHY
kubectl get providers -w

# 4. Apply ProviderConfig
kubectl apply -f prerequisites/crossplane-provider-config.yaml
Provider Version CRDs Used Layer
Lambda v1.16.0 Function, Permission Layer 2 (RGD)
Cognito v2.4.0 UserPool, UserPoolClient, UserPoolDomain Layer 1 (Shared)
Family v2.4.0 Base provider (auto-installed)

⚠️ Version Compatibility: The cognitoidp and lambda providers must use compatible provider-family-aws versions. If you see incompatible dependencies errors, ensure both providers target the same family version (currently v2.4.0).

ACK CRD Differences: Helm vs EKS Capability

ACK installed via EKS Capability uses newer CRD schemas than Helm-based installations. The manifests in this repo are tested with EKS Capability. If you use Helm-installed ACK controllers, you may need to adjust field names:

Service Field EKS Capability (this repo) Helm (older versions)
S3 Public access block blockPublicACLs, ignorePublicACLs blockPublicAcls, ignorePublicAcls
IAM Inline policies inlinePolicies: { Name: "json" } (map) policies: [{ policyName, policyDocument }] (array)
CloudFront Collection fields quantity not needed (inferred from items) quantity: N required alongside items
CloudFront Error response code responseCode: "200" (string) responseCode: 200 (integer)
CloudFront Origin fields All fields required (originPath, customHeaders, connectionAttempts) Partial fields accepted
Route 53 Record name Relative to hosted zone (subdomain only, e.g., hkc) FQDN (e.g., hkc.example.com)

💡 Tip: To check the exact field names on your cluster, use kubectl explain:

kubectl explain bucket.s3.services.k8s.aws.spec.publicAccessBlock
kubectl explain role.iam.services.k8s.aws.spec.inlinePolicies
kubectl explain distribution.cloudfront.services.k8s.aws.spec.distributionConfig.origins

Getting Started

The deployment follows the three-layer architecture: first set up shared platform resources (Layer 1), then apply the RGD definition (Layer 2), and finally create application instances (Layer 3).

Step 1: Clone and Configure

git clone https://github.com/aws-samples/kro-serverless-app-stack.git
cd kro-serverless-app-stack

# Copy and customize the environment template
cp scripts/env.template scripts/env.sh
vim scripts/env.sh    # Edit Sections 1-2 only (see below)

# Source the environment
source scripts/env.sh

Fill in Sections 1-2 of env.sh with values you already know:

Section Variables How to find them
1 — Core AWS Config AWS_ACCOUNT_ID, AWS_REGION, OIDC_PROVIDER aws sts get-caller-identity, aws eks describe-cluster
2 — DNS & Certificates ROUTE53_HOSTED_ZONE_ID, DOMAIN_NAME, DNS_SUBDOMAIN, ACM_GLOBAL_CERT_ARN aws route53 list-hosted-zones, aws acm list-certificates --region us-east-1

💡 Note: Sections 3-5 (shared resources, IAM roles, Cognito) are auto-populated by the deployment script in Step 2. Leave them as placeholders for now.

Step 2: Deploy Shared Platform Resources

Layer 1 — Deploy the shared platform infrastructure that all application instances will reference. These are standalone ACK/Crossplane manifests applied individually.

# Verify prerequisites are met
./scripts/0-verify-prerequisites.sh

# Deploy all shared resources (Cognito, CloudFront, S3, IAM)
./scripts/1-deploy-shared-resources.sh

This creates:

  • Cognito — User Pool, App Client, User Groups (Crossplane)
  • S3 — Platform Assets bucket, Shared UI bucket (ACK)
  • IAM — Lambda Execution role, S3 Uploader role (ACK)
  • CloudFront — Distribution with OAC and URL rewrite Function (ACK)

Each resource is a standalone Kubernetes manifest you can inspect and customize:

Resource Controller Manifest
User Pool Crossplane shared-resources/cognito/user-pool.yaml
User Pool Domain Crossplane shared-resources/cognito/user-pool-domain.yaml
User Groups Crossplane shared-resources/cognito/user-groups.yaml
App Client Crossplane shared-resources/cognito/app-client.yaml
Platform Assets Bucket ACK S3 shared-resources/s3/platform-assets-bucket.yaml
Shared UI Bucket ACK S3 shared-resources/s3/shared-ui-bucket.yaml
Lambda Execution Role ACK IAM shared-resources/iam/lambda-execution-role.yaml
S3 Uploader Role ACK IAM shared-resources/iam/s3-uploader-role.yaml
Origin Access Control ACK CloudFront shared-resources/cloudfront/origin-access-control.yaml
URL Rewrite Function ACK CloudFront shared-resources/cloudfront/cloudfront-function-resource.yaml
CDN Distribution ACK CloudFront shared-resources/cloudfront/distribution.yaml
DNS Record ACK Route 53 shared-resources/route53/dns-record.yaml

⏱️ Note: CloudFront distribution takes ~15-20 minutes to deploy. Other resources complete in 1-2 minutes.

The script automatically updates env.sh with the created resource values (S3 bucket names, IAM role ARNs, CloudFront distribution ID, Cognito IDs). Re-source it before continuing:

source scripts/env.sh

Verify all shared resources are deployed:

kubectl get userpool.cognitoidp.aws.upbound.io,userpoolclient.cognitoidp.aws.upbound.io,userpoolDomain.cognitoidp.aws.upbound.io,usergroup.cognitoidp.aws.upbound.io,bucket.s3.services.k8s.aws,role.iam.services.k8s.aws,originaccesscontrol.cloudfront.services.k8s.aws,function.cloudfront.services.k8s.aws,distribution.cloudfront.services.k8s.aws,recordset.route53.services.k8s.aws -l app.kubernetes.io/part-of=shared-platform

Step 3: Upload Application Assets

Upload the Lambda function code and UI static files to the shared S3 assets bucket. These are the application artifacts that every instance will reference — the Lambda handler (lambda-code/index.py) and the frontend UI (ui-src/).

./scripts/2-upload-assets.sh

Step 4: Apply the ResourceGraphDefinition

Layer 2 — This is the core of the pattern. The RGD (rgd/smart-inventory-hub-v5.yaml) defines how Kro orchestrates 15 resources across ACK, Crossplane, and native Kubernetes from a single instance spec. Applying the RGD registers the SmartInventoryHubV5 custom resource type on your cluster.

Once applied, the RGD acts as a template: each developer or team can create their own isolated Smart Inventory Hub instance by specifying just 5 fields (team, developer, app, environment, region). Kro uses those fields to generate unique resource names, connect dependencies, and provision a complete serverless stack — each with its own DynamoDB table, Lambda function, API Gateway, and UI path in the shared S3 bucket. Multiple instances coexist on the same cluster without conflicts.

./scripts/3-apply-rgd.sh

# Verify the RGD is active
kubectl get rgd smart-inventory-hub-v5

# Inspect the generated CRD schema
kubectl get crd smartinventoryhubv5s.kro.run -o yaml | head -80

Step 5: Deploy Your Application

Layer 3 — This is the payoff. Each actor (developer, team, environment) creates their own instance by providing just 5 fields. Kro provisions a complete, isolated serverless stack — DynamoDB table, Lambda function, API Gateway with 5 routes, and a UI synced to the shared S3 bucket — all wired together with correct dependencies.

The naming convention ensures isolation: each instance generates unique resource names based on the combination of team, developer, app, and environment. Multiple developers can deploy simultaneously on the same cluster without conflicts.

# Developer "john" deploys a dev instance
./scripts/4-create-instance.sh --team team-backend --app inventory --env dev --developer john

# Developer "carol" deploys her own dev instance (same cluster, no conflicts)
./scripts/4-create-instance.sh --team team-backend --app inventory --env dev --developer carol

# Team deploys a staging instance (no developer name — team-level resource)
kubectl apply -f instances/staging-instance.yaml

The instance YAML is minimal — just 5 fields:

apiVersion: kro.run/v1alpha1
kind: SmartInventoryHubV5
metadata:
  name: team-backend-john-inventory-dev
spec:
  teamName: team-backend
  developerName: john
  appName: inventory
  environment: dev
  region: us-east-2

Each instance gets its own:

  • DynamoDB table: team-backend-john-inventory-dev-inventory
  • Lambda function: team-backend-john-inventory-dev-function
  • API Gateway: team-backend-john-inventory-dev-api
  • UI path: s3://shared-bucket/john-inventory-dev/
  • URL: https://yourdomain.com/john-inventory-dev/

Kro automatically provisions all 15 resources with correct dependencies in 5-8 minutes.

Step 6: Verify Deployment

Confirm all 15 resources were provisioned correctly. The verification script checks each resource type (DynamoDB, Lambda, API Gateway, Jobs) and tests the API endpoint. You can also access the UI via the path prefix on the shared CloudFront domain.

./scripts/5-verify-deployment.sh

# Get application URLs
kubectl get configmap john-inventory-dev-info -o jsonpath='{.data.api-url}'
kubectl get configmap john-inventory-dev-info -o jsonpath='{.data.ui-url}'

# Test the API
API_URL=$(kubectl get configmap john-inventory-dev-info -o jsonpath='{.data.api-endpoint}')
curl -s "$API_URL/products" | jq .

Cleanup

To remove resources when you're done:

# Delete a specific instance (removes all 15 instance resources)
./scripts/cleanup.sh --instance team-backend-john-inventory-dev

# Delete all shared resources (Cognito, S3, IAM, CloudFront)
./scripts/cleanup.sh --shared-only

# Delete everything (all instances + shared resources)
./scripts/cleanup.sh --all

⚠️ Warning: Deleting shared resources affects all application instances. Delete instances first, then shared resources. CloudFront distribution deletion takes ~15-20 minutes.

Repository Structure

kro-serverless-app-stack/
├── README.md                    # This file
├── LICENSE                      # MIT-0 (No Attribution) license
├── CONTRIBUTING.md              # Contribution guidelines
├── prerequisites/               # IAM roles for EKS Capabilities
│   ├── kro-capability-trust-policy.json
│   └── ack-capability-trust-policy.json
├── docs/                        # Documentation
│   ├── ARCHITECTURE.md          # Detailed architecture
│   ├── DEPLOYMENT-GUIDE.md      # Step-by-step deployment
│   ├── PREREQUISITES.md         # Requirements and installation
│   ├── TROUBLESHOOTING.md       # Common issues
│   ├── COST-ESTIMATION.md       # Cost breakdown
│   └── SECURITY.md              # Security best practices
├── shared-resources/            # Layer 1: Shared resources
│   ├── cloudfront/              # CloudFront distribution, OAC, Function
│   ├── s3/                      # S3 buckets
│   ├── iam/                     # IAM roles
│   └── cognito/                 # Cognito User Pool + Client
├── rgd/                         # Layer 2: ResourceGraphDefinition
│   ├── smart-inventory-hub-v5.yaml # The RGD definition
│   └── rgd-env.sh              # Environment variables for RGD
├── instances/                   # Layer 3: Instance templates
│   ├── dev-instance.yaml        # Development template
│   ├── staging-instance.yaml    # Staging template
│   └── prod-instance.yaml       # Production template
├── lambda-code/                 # Lambda function source
│   ├── index.py                 # Python handler
│   └── package.sh               # Packaging script
├── ui-src/                      # UI source files
│   ├── index.html               # Main HTML
│   ├── app.js                   # JavaScript
│   ├── styles.css               # CSS styles
│   └── auth/callback/index.html # OAuth callback handler
└── scripts/                     # Deployment automation
    ├── env.template             # Environment variables template
    ├── 0-verify-prerequisites.sh
    ├── 1-deploy-shared-resources.sh
    ├── 2-upload-assets.sh
    ├── 3-apply-rgd.sh
    ├── 4-create-instance.sh
    ├── 5-verify-deployment.sh
    └── cleanup.sh

Documentation

Document Description
ARCHITECTURE.md Detailed architecture explanation
DEPLOYMENT-GUIDE.md Step-by-step deployment instructions
PREREQUISITES.md Requirements and installation
TROUBLESHOOTING.md Common issues and solutions
SECURITY.md Security best practices

Related Resources

Security

See CONTRIBUTING for security issue reporting.

License

This project is licensed under the MIT-0 (MIT No Attribution) License - see the LICENSE file for details.

Authors

  • AWS WWSO LATAM Team

About

KRO Deploy complete serverless applications on AWS with a single kubectl apply — using Kro, ACK, EKS Capabilities, and Crossplane on Kubernetes (Hybrid Kro Compositions pattern)

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors