SiamCafe.net Blog
Technology

Kustomize Overlay Hexagonal Architecture

kustomize overlay hexagonal architecture
Kustomize Overlay Hexagonal Architecture | SiamCafe Blog
2025-12-28· อ. บอม — SiamCafe.net· 9,741 คำ

Kustomize Overlay

Kustomize จัดการ Kubernetes YAML ไม่ใช้ Template Base + Overlay แยก Configuration ตาม Environment มาพร้อม kubectl kustomization.yaml Resources Patches

Hexagonal Architecture Ports and Adapters แยก Business Logic Infrastructure Ports Interface Adapters Implementation เปลี่ยน Infrastructure ไม่กระทบ Domain

Kustomize Structure

# === Kustomize Project Structure ===

# k8s/
# ├── base/                          # Base Configuration
# │   ├── kustomization.yaml
# │   ├── namespace.yaml
# │   ├── deployment.yaml
# │   ├── service.yaml
# │   ├── configmap.yaml
# │   └── hpa.yaml
# ├── overlays/
# │   ├── dev/                       # Development
# │   │   ├── kustomization.yaml
# │   │   ├── deployment-patch.yaml
# │   │   └── configmap-patch.yaml
# │   ├── staging/                   # Staging
# │   │   ├── kustomization.yaml
# │   │   ├── deployment-patch.yaml
# │   │   └── ingress.yaml
# │   └── production/                # Production
# │       ├── kustomization.yaml
# │       ├── deployment-patch.yaml
# │       ├── hpa-patch.yaml
# │       ├── ingress.yaml
# │       └── pdb.yaml
# └── components/                    # Reusable Components
#     ├── monitoring/
#     │   ├── kustomization.yaml
#     │   └── service-monitor.yaml
#     └── istio/
#         ├── kustomization.yaml
#         └── virtual-service.yaml

# base/kustomization.yaml
# apiVersion: kustomize.config.k8s.io/v1beta1
# kind: Kustomization
# namespace: my-app
# resources:
#   - namespace.yaml
#   - deployment.yaml
#   - service.yaml
#   - configmap.yaml
#   - hpa.yaml
# commonLabels:
#   app: my-app
#   managed-by: kustomize

# base/deployment.yaml
# apiVersion: apps/v1
# kind: Deployment
# metadata:
#   name: api-server
# spec:
#   replicas: 1
#   selector:
#     matchLabels:
#       app: api-server
#   template:
#     spec:
#       containers:
#       - name: api-server
#         image: my-app:latest
#         ports:
#         - containerPort: 8080
#         resources:
#           requests:
#             cpu: 100m
#             memory: 128Mi
#           limits:
#             cpu: 500m
#             memory: 512Mi

# overlays/production/kustomization.yaml
# apiVersion: kustomize.config.k8s.io/v1beta1
# kind: Kustomization
# namespace: my-app-prod
# bases:
#   - ../../base
# components:
#   - ../../components/monitoring
#   - ../../components/istio
# patches:
#   - path: deployment-patch.yaml
#   - path: hpa-patch.yaml
# images:
#   - name: my-app
#     newName: registry.example.com/my-app
#     newTag: v1.2.3
# resources:
#   - ingress.yaml
#   - pdb.yaml

# overlays/production/deployment-patch.yaml
# apiVersion: apps/v1
# kind: Deployment
# metadata:
#   name: api-server
# spec:
#   replicas: 3
#   template:
#     spec:
#       containers:
#       - name: api-server
#         resources:
#           requests:
#             cpu: 500m
#             memory: 512Mi
#           limits:
#             cpu: 2000m
#             memory: 2Gi

kustomize_commands = {
    "kubectl kustomize overlays/dev": "Preview dev configuration",
    "kubectl apply -k overlays/dev": "Apply dev configuration",
    "kubectl apply -k overlays/production": "Apply production",
    "kustomize build overlays/staging": "Build staging YAML",
    "kubectl diff -k overlays/production": "Diff before apply",
    "kustomize edit set image my-app=v1.3.0": "Update image tag",
}

print("Kustomize Commands:")
for cmd, desc in kustomize_commands.items():
    print(f"  {cmd}")
    print(f"    -> {desc}")

Hexagonal Architecture Pattern

# hexagonal.py — Hexagonal Architecture (Ports and Adapters)
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List, Optional

# === Domain Layer (Core Business Logic) ===
@dataclass
class Order:
    id: str
    customer_id: str
    items: List[dict]
    total: float
    status: str = "pending"

# === Ports (Interfaces) ===
class OrderRepository(ABC):
    """Port: Outbound — Database Access"""
    @abstractmethod
    def save(self, order: Order) -> bool: ...

    @abstractmethod
    def find_by_id(self, order_id: str) -> Optional[Order]: ...

    @abstractmethod
    def find_by_customer(self, customer_id: str) -> List[Order]: ...

class PaymentGateway(ABC):
    """Port: Outbound — Payment Processing"""
    @abstractmethod
    def charge(self, amount: float, customer_id: str) -> dict: ...

class NotificationService(ABC):
    """Port: Outbound — Notifications"""
    @abstractmethod
    def send(self, to: str, message: str) -> bool: ...

class OrderUseCase(ABC):
    """Port: Inbound — Application API"""
    @abstractmethod
    def create_order(self, customer_id: str, items: List[dict]) -> Order: ...

    @abstractmethod
    def get_order(self, order_id: str) -> Optional[Order]: ...

# === Application Service (Orchestration) ===
class OrderService(OrderUseCase):
    """Application Service — Orchestrates Ports"""

    def __init__(self, repo: OrderRepository,
                 payment: PaymentGateway,
                 notifier: NotificationService):
        self.repo = repo
        self.payment = payment
        self.notifier = notifier

    def create_order(self, customer_id: str, items: List[dict]) -> Order:
        total = sum(item["price"] * item["qty"] for item in items)
        order = Order(
            id=f"ORD-{customer_id[:4]}-001",
            customer_id=customer_id,
            items=items,
            total=total,
        )

        # Charge payment
        result = self.payment.charge(total, customer_id)
        if result["status"] == "success":
            order.status = "paid"
            self.repo.save(order)
            self.notifier.send(customer_id, f"Order {order.id} confirmed")
        else:
            order.status = "failed"

        return order

    def get_order(self, order_id: str) -> Optional[Order]:
        return self.repo.find_by_id(order_id)

# === Adapters (Implementations) ===
class PostgresOrderRepository(OrderRepository):
    """Adapter: PostgreSQL Database"""
    def save(self, order): print(f"  [PostgreSQL] Saved {order.id}"); return True
    def find_by_id(self, order_id): return None
    def find_by_customer(self, customer_id): return []

class StripePaymentGateway(PaymentGateway):
    """Adapter: Stripe Payment"""
    def charge(self, amount, customer_id):
        print(f"  [Stripe] Charged {amount} to {customer_id}")
        return {"status": "success", "transaction_id": "txn_123"}

class EmailNotificationService(NotificationService):
    """Adapter: Email Notification"""
    def send(self, to, message):
        print(f"  [Email] Sent to {to}: {message}")
        return True

# === Dependency Injection ===
repo = PostgresOrderRepository()
payment = StripePaymentGateway()
notifier = EmailNotificationService()

service = OrderService(repo, payment, notifier)

print("Hexagonal Architecture Demo:")
order = service.create_order("CUST-001", [
    {"name": "Widget", "price": 29.99, "qty": 2},
    {"name": "Gadget", "price": 49.99, "qty": 1},
])
print(f"  Order: {order.id} | Status: {order.status} | Total: {order.total}")

# แสดง Architecture Layers
layers = {
    "Domain": ["Order (Entity)", "Business Rules"],
    "Ports (Inbound)": ["OrderUseCase (API Interface)"],
    "Ports (Outbound)": ["OrderRepository", "PaymentGateway", "NotificationService"],
    "Adapters (Inbound)": ["REST API Controller", "gRPC Handler", "CLI"],
    "Adapters (Outbound)": ["PostgreSQL", "Stripe", "Email/SMS"],
}

print(f"\n\nHexagonal Architecture Layers:")
for layer, items in layers.items():
    print(f"  [{layer}]")
    for item in items:
        print(f"    - {item}")

Kustomize + Hexagonal Mapping

# kustomize_hexagonal.py — Mapping Hexagonal to K8s
mapping = {
    "Inbound Adapter: REST API": {
        "k8s_resource": "Deployment + Service + Ingress",
        "kustomize": "base/deployment.yaml + overlays/*/ingress.yaml",
        "port": "8080 (HTTP)",
    },
    "Inbound Adapter: gRPC": {
        "k8s_resource": "Deployment + Service (ClusterIP)",
        "kustomize": "base/grpc-service.yaml",
        "port": "50051 (gRPC)",
    },
    "Outbound Adapter: PostgreSQL": {
        "k8s_resource": "StatefulSet + PVC + Secret",
        "kustomize": "overlays/*/database-patch.yaml",
        "port": "5432",
    },
    "Outbound Adapter: Redis": {
        "k8s_resource": "Deployment + Service + ConfigMap",
        "kustomize": "components/redis/",
        "port": "6379",
    },
    "Outbound Adapter: Kafka": {
        "k8s_resource": "StatefulSet + Service + ConfigMap",
        "kustomize": "components/kafka/",
        "port": "9092",
    },
    "Cross-cutting: Monitoring": {
        "k8s_resource": "ServiceMonitor + PrometheusRule",
        "kustomize": "components/monitoring/",
        "port": "9090 (metrics)",
    },
}

print("Hexagonal -> Kubernetes Mapping:")
for adapter, info in mapping.items():
    print(f"\n  [{adapter}]")
    for key, value in info.items():
        print(f"    {key}: {value}")

# Environment Differences
env_diff = {
    "Development": {
        "replicas": 1,
        "resources": "100m CPU, 128Mi RAM",
        "database": "SQLite / Docker PostgreSQL",
        "cache": "ไม่มี หรือ In-memory",
        "ingress": "localhost:8080",
    },
    "Staging": {
        "replicas": 2,
        "resources": "250m CPU, 256Mi RAM",
        "database": "PostgreSQL (shared)",
        "cache": "Redis (shared)",
        "ingress": "staging.example.com",
    },
    "Production": {
        "replicas": "3-10 (HPA)",
        "resources": "500m-2000m CPU, 512Mi-2Gi RAM",
        "database": "PostgreSQL HA (Patroni)",
        "cache": "Redis Cluster",
        "ingress": "api.example.com + CDN",
    },
}

print(f"\n\nEnvironment Configuration:")
for env, config in env_diff.items():
    print(f"\n  [{env}]")
    for key, value in config.items():
        print(f"    {key}: {value}")

Best Practices

Kustomize คืออะไร

จัดการ Kubernetes YAML ไม่ใช้ Template Base Overlay แยก Environment มาพร้อม kubectl kustomization.yaml Resources Patches Components

Hexagonal Architecture คืออะไร

Ports and Adapters แยก Business Logic Infrastructure Ports Interface Adapters Implementation เปลี่ยน Infrastructure ไม่กระทบ Domain

Kustomize Overlay ทำงานอย่างไร

Base Configuration พื้นฐาน Overlays เฉพาะ Environment Patch ทับ Base เปลี่ยน Replicas Image ConfigMap Resource Limits Strategic Merge Patch JSON Patch

Hexagonal Architecture ใช้กับ Kubernetes อย่างไร

Port map K8s Service Inbound Adapters Ingress Service Outbound Adapters External Services Kustomize แยก Adapter Configuration ตาม Environment

สรุป

Kustomize Base Overlay จัดการ K8s YAML แยก Environment Hexagonal Architecture Ports Adapters แยก Domain Infrastructure Dependency Injection เปลี่ยน Adapter ได้อิสระ GitOps Flux CD ArgoCD

📖 บทความที่เกี่ยวข้อง

Kustomize Overlay Kubernetes Deploymentอ่านบทความ → BGP Routing Advanced Hexagonal Architectureอ่านบทความ → Kustomize Overlay Home Lab Setupอ่านบทความ → Kustomize Overlay Serverless Architectureอ่านบทความ → Kustomize Overlay AR VR Developmentอ่านบทความ →

📚 ดูบทความทั้งหมด →