Kustomize Overlay กับ Hexagonal Architecture —
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
- Base + Overlay: แยก Base Configuration กลาง Overlay เฉพาะ Environment
- Components: ใช้ Kustomize Components สำหรับ Feature ที่ใช้ซ้ำ
- Ports Interface: กำหนด Port เป็น Abstract Class ใน Domain Layer
- Adapters แยก: แต่ละ Adapter เป็น Package แยก เปลี่ยนได้อิสระ
- DI Container: ใช้ Dependency Injection เชื่อม Port กับ Adapter
- GitOps: ใช้ Kustomize กับ Flux CD หรือ ArgoCD GitOps Workflow
Kustomize คืออะไร
จัดการ Kubernetes YAML ไม่ใช้ Template Base Overlay แยก Environment มาพร้อม kubectl kustomization.yaml Resources Patches Components