diff --git a/.agent/skills/deployment-kubernetes/SKILL.md b/.agent/skills/deployment-kubernetes/SKILL.md
new file mode 100644
index 00000000..3cf136ac
--- /dev/null
+++ b/.agent/skills/deployment-kubernetes/SKILL.md
@@ -0,0 +1,441 @@
+---
+name: deployment-kubernetes
+description: Kubernetes deployment patterns. Use for Pods, Services, Ingress, Helm Charts, ConfigMaps, Secrets, và health probes.
+compatibility: "Kubernetes 1.28+, Helm 3+"
+metadata:
+ author: Velik Ho
+ version: "1.0"
+---
+
+# Kubernetes Deployment Patterns / Mẫu Triển Khai Kubernetes
+
+Kubernetes deployment patterns cho GoodGo microservices production.
+
+## When to Use This Skill / Khi Nào Sử Dụng
+
+Use this skill when:
+- Deploying services to Kubernetes / Triển khai services lên Kubernetes
+- Creating Helm charts / Tạo Helm charts
+- Configuring Ingress routing / Cấu hình Ingress routing
+- Managing secrets and configs / Quản lý secrets và configs
+- Setting up health probes / Cài đặt health probes
+- Scaling applications / Scale ứng dụng
+
+## Core Concepts / Khái Niệm Cốt Lõi
+
+### Kubernetes Architecture / Kiến Trúc Kubernetes
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ KUBERNETES CLUSTER │
+├─────────────────────────────────────────────────────────────┤
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ INGRESS │ │
+│ │ (NGINX / Traefik Controller) │ │
+│ └──────────────────────┬──────────────────────────────┘ │
+│ │ │
+│ ┌──────────────────────▼──────────────────────────────┐ │
+│ │ SERVICES │ │
+│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
+│ │ │ iam-svc │ │ order-svc │ │storage-svc│ │ │
+│ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │
+│ └──────────┼──────────────┼──────────────┼────────────┘ │
+│ │ │ │ │
+│ ┌──────────▼──────────────▼──────────────▼────────────┐ │
+│ │ PODS │ │
+│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
+│ │ │ Pod 1 │ │ Pod 2 │ │ Pod 3 │ │ │
+│ │ │ replica │ │ replica │ │ replica │ │ │
+│ │ └─────────┘ └─────────┘ └─────────┘ │ │
+│ └─────────────────────────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Key Resources / Các Tài Nguyên Chính
+
+| Resource | Purpose | Example |
+|----------|---------|---------|
+| **Pod** | Smallest deployable unit | Container(s) + volumes |
+| **Deployment** | Manages ReplicaSets | Rolling updates |
+| **Service** | Stable network endpoint | Load balancing |
+| **Ingress** | HTTP routing | Host/path rules |
+| **ConfigMap** | Non-sensitive config | App settings |
+| **Secret** | Sensitive data | Passwords, keys |
+
+### Health Probes / Các Loại Probe
+
+| Probe | Purpose | Failure Action |
+|-------|---------|----------------|
+| **Liveness** | Is container alive? | Restart container |
+| **Readiness** | Can accept traffic? | Remove from LB |
+| **Startup** | Has started? | Block other probes |
+
+## Key Patterns / Mẫu Chính
+
+### Deployment Manifest
+
+```yaml
+# k8s/base/iam-service/deployment.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: iam-service
+ labels:
+ app: iam-service
+ tier: backend
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: iam-service
+ template:
+ metadata:
+ labels:
+ app: iam-service
+ spec:
+ containers:
+ - name: iam-service
+ image: goodgo/iam-service:latest
+ ports:
+ - containerPort: 8080
+ env:
+ - name: ASPNETCORE_ENVIRONMENT
+ value: "Production"
+ - name: ConnectionStrings__DefaultConnection
+ valueFrom:
+ secretKeyRef:
+ name: iam-secrets
+ key: database-url
+ resources:
+ requests:
+ memory: "256Mi"
+ cpu: "100m"
+ limits:
+ memory: "512Mi"
+ cpu: "500m"
+ livenessProbe:
+ httpGet:
+ path: /health/live
+ port: 8080
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ failureThreshold: 3
+ readinessProbe:
+ httpGet:
+ path: /health/ready
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ failureThreshold: 3
+ startupProbe:
+ httpGet:
+ path: /health/startup
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ failureThreshold: 30
+```
+
+### Service Manifest
+
+```yaml
+# k8s/base/iam-service/service.yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: iam-service
+ labels:
+ app: iam-service
+spec:
+ type: ClusterIP
+ ports:
+ - port: 80
+ targetPort: 8080
+ protocol: TCP
+ name: http
+ selector:
+ app: iam-service
+```
+
+### Ingress Configuration
+
+```yaml
+# k8s/base/ingress.yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: goodgo-ingress
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+ nginx.ingress.kubernetes.io/ssl-redirect: "true"
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+spec:
+ ingressClassName: nginx
+ tls:
+ - hosts:
+ - api.goodgo.vn
+ secretName: goodgo-tls
+ rules:
+ - host: api.goodgo.vn
+ http:
+ paths:
+ - path: /api/v1/iam
+ pathType: Prefix
+ backend:
+ service:
+ name: iam-service
+ port:
+ number: 80
+ - path: /api/v1/orders
+ pathType: Prefix
+ backend:
+ service:
+ name: order-service
+ port:
+ number: 80
+ - path: /api/v1/storage
+ pathType: Prefix
+ backend:
+ service:
+ name: storage-service
+ port:
+ number: 80
+```
+
+### ConfigMap & Secret
+
+```yaml
+# k8s/base/configmap.yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: app-config
+data:
+ ASPNETCORE_ENVIRONMENT: "Production"
+ Logging__LogLevel__Default: "Information"
+ Redis__InstanceName: "GoodGo:"
+
+---
+# k8s/base/secret.yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: iam-secrets
+type: Opaque
+stringData:
+ database-url: "Host=postgres;Database=iam_db;Username=postgres;Password=secret"
+ jwt-secret: "your-super-secret-key-here"
+```
+
+### Helm Chart Structure
+
+```
+charts/
+└── goodgo-service/
+ ├── Chart.yaml
+ ├── values.yaml
+ ├── templates/
+ │ ├── deployment.yaml
+ │ ├── service.yaml
+ │ ├── ingress.yaml
+ │ ├── configmap.yaml
+ │ ├── secret.yaml
+ │ ├── hpa.yaml
+ │ └── _helpers.tpl
+ └── values/
+ ├── development.yaml
+ ├── staging.yaml
+ └── production.yaml
+```
+
+### Helm Values File
+
+```yaml
+# charts/goodgo-service/values.yaml
+replicaCount: 3
+
+image:
+ repository: goodgo/iam-service
+ tag: "latest"
+ pullPolicy: IfNotPresent
+
+service:
+ type: ClusterIP
+ port: 80
+ targetPort: 8080
+
+ingress:
+ enabled: true
+ className: nginx
+ annotations:
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+ hosts:
+ - host: api.goodgo.vn
+ paths:
+ - path: /api/v1/iam
+ pathType: Prefix
+ tls:
+ - secretName: goodgo-tls
+ hosts:
+ - api.goodgo.vn
+
+resources:
+ requests:
+ memory: "256Mi"
+ cpu: "100m"
+ limits:
+ memory: "512Mi"
+ cpu: "500m"
+
+autoscaling:
+ enabled: true
+ minReplicas: 2
+ maxReplicas: 10
+ targetCPUUtilizationPercentage: 70
+
+env:
+ - name: ASPNETCORE_ENVIRONMENT
+ value: "Production"
+
+envFromSecret:
+ - name: ConnectionStrings__DefaultConnection
+ secretName: iam-secrets
+ secretKey: database-url
+```
+
+### HorizontalPodAutoscaler
+
+```yaml
+# k8s/base/hpa.yaml
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: iam-service-hpa
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: iam-service
+ minReplicas: 2
+ maxReplicas: 10
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 70
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: 80
+```
+
+## Common Mistakes / Lỗi Thường Gặp
+
+### 1. No Resource Limits
+
+```yaml
+# ❌ BAD: No limits
+containers:
+ - name: app
+ image: myapp
+
+# ✅ GOOD: With limits
+containers:
+ - name: app
+ image: myapp
+ resources:
+ requests:
+ memory: "256Mi"
+ cpu: "100m"
+ limits:
+ memory: "512Mi"
+ cpu: "500m"
+```
+
+### 2. Missing Health Probes
+
+```yaml
+# ❌ BAD: No probes
+containers:
+ - name: app
+
+# ✅ GOOD: All probes configured
+containers:
+ - name: app
+ livenessProbe:
+ httpGet:
+ path: /health/live
+ port: 8080
+ readinessProbe:
+ httpGet:
+ path: /health/ready
+ port: 8080
+```
+
+### 3. Hardcoded Secrets
+
+```yaml
+# ❌ BAD: Secret in env
+env:
+ - name: DB_PASSWORD
+ value: "mysecretpassword"
+
+# ✅ GOOD: From Secret
+env:
+ - name: DB_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: db-secrets
+ key: password
+```
+
+## Quick Reference / Tham Chiếu Nhanh
+
+### kubectl Commands
+
+```bash
+# EN: Apply manifests / VI: Áp dụng manifests
+kubectl apply -f k8s/base/
+
+# EN: Check pod status / VI: Kiểm tra trạng thái pods
+kubectl get pods -l app=iam-service
+
+# EN: View logs / VI: Xem logs
+kubectl logs -f deployment/iam-service
+
+# EN: Scale deployment / VI: Scale deployment
+kubectl scale deployment iam-service --replicas=5
+
+# EN: Rollout status / VI: Trạng thái rollout
+kubectl rollout status deployment/iam-service
+
+# EN: Rollback / VI: Rollback
+kubectl rollout undo deployment/iam-service
+```
+
+### Helm Commands
+
+```bash
+# EN: Install chart / VI: Cài đặt chart
+helm install iam-service ./charts/goodgo-service -f values/production.yaml
+
+# EN: Upgrade / VI: Nâng cấp
+helm upgrade iam-service ./charts/goodgo-service -f values/production.yaml
+
+# EN: Rollback / VI: Rollback
+helm rollback iam-service 1
+
+# EN: List releases / VI: Liệt kê releases
+helm list -A
+```
+
+## Resources / Tài Nguyên
+
+- [Detailed Examples](./references/REFERENCE.md) - Full configurations
+- [Docker Traefik](../docker-traefik/SKILL.md) - Container basics
+- [Observability](../observability/SKILL.md) - Health checks
+- [Error Handling](../error-handling-patterns/SKILL.md) - Probes setup
diff --git a/.agent/skills/deployment-kubernetes/references/REFERENCE.md b/.agent/skills/deployment-kubernetes/references/REFERENCE.md
new file mode 100644
index 00000000..c8d35515
--- /dev/null
+++ b/.agent/skills/deployment-kubernetes/references/REFERENCE.md
@@ -0,0 +1,446 @@
+# Kubernetes Deployment - Detailed Reference
+
+Detailed configurations và examples cho Kubernetes deployment trong GoodGo.
+
+## Table of Contents
+
+1. [Complete Deployment Example](#complete-deployment-example)
+2. [Helm Chart Templates](#helm-chart-templates)
+3. [Kustomize Configuration](#kustomize-configuration)
+4. [CI/CD Integration](#cicd-integration)
+5. [Production Configurations](#production-configurations)
+
+---
+
+## Complete Deployment Example
+
+### Namespace
+
+```yaml
+# k8s/base/namespace.yaml
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: goodgo
+ labels:
+ name: goodgo
+ environment: production
+```
+
+### Complete Service Deployment
+
+```yaml
+# k8s/base/iam-service/deployment.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: iam-service
+ namespace: goodgo
+ labels:
+ app.kubernetes.io/name: iam-service
+ app.kubernetes.io/part-of: goodgo
+ app.kubernetes.io/version: "1.0.0"
+spec:
+ replicas: 3
+ revisionHistoryLimit: 5
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 0
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: iam-service
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: iam-service
+ annotations:
+ prometheus.io/scrape: "true"
+ prometheus.io/port: "8080"
+ prometheus.io/path: "/metrics"
+ spec:
+ serviceAccountName: iam-service
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 1000
+ fsGroup: 1000
+ containers:
+ - name: iam-service
+ image: goodgo/iam-service:1.0.0
+ imagePullPolicy: IfNotPresent
+ securityContext:
+ allowPrivilegeEscalation: false
+ readOnlyRootFilesystem: true
+ capabilities:
+ drop:
+ - ALL
+ ports:
+ - name: http
+ containerPort: 8080
+ protocol: TCP
+ envFrom:
+ - configMapRef:
+ name: iam-config
+ env:
+ - name: ConnectionStrings__DefaultConnection
+ valueFrom:
+ secretKeyRef:
+ name: iam-secrets
+ key: database-url
+ - name: Jwt__SecretKey
+ valueFrom:
+ secretKeyRef:
+ name: iam-secrets
+ key: jwt-secret
+ resources:
+ requests:
+ memory: "256Mi"
+ cpu: "100m"
+ limits:
+ memory: "512Mi"
+ cpu: "500m"
+ livenessProbe:
+ httpGet:
+ path: /health/live
+ port: http
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ timeoutSeconds: 5
+ failureThreshold: 3
+ readinessProbe:
+ httpGet:
+ path: /health/ready
+ port: http
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ timeoutSeconds: 3
+ failureThreshold: 3
+ startupProbe:
+ httpGet:
+ path: /health/startup
+ port: http
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ failureThreshold: 30
+ volumeMounts:
+ - name: tmp
+ mountPath: /tmp
+ volumes:
+ - name: tmp
+ emptyDir: {}
+ affinity:
+ podAntiAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ podAffinityTerm:
+ labelSelector:
+ matchLabels:
+ app.kubernetes.io/name: iam-service
+ topologyKey: kubernetes.io/hostname
+ topologySpreadConstraints:
+ - maxSkew: 1
+ topologyKey: topology.kubernetes.io/zone
+ whenUnsatisfiable: ScheduleAnyway
+ labelSelector:
+ matchLabels:
+ app.kubernetes.io/name: iam-service
+```
+
+### Service Account
+
+```yaml
+# k8s/base/iam-service/serviceaccount.yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: iam-service
+ namespace: goodgo
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: iam-service-role
+ namespace: goodgo
+rules:
+ - apiGroups: [""]
+ resources: ["configmaps", "secrets"]
+ verbs: ["get", "list", "watch"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: iam-service-rolebinding
+ namespace: goodgo
+subjects:
+ - kind: ServiceAccount
+ name: iam-service
+roleRef:
+ kind: Role
+ name: iam-service-role
+ apiGroup: rbac.authorization.k8s.io
+```
+
+### PodDisruptionBudget
+
+```yaml
+# k8s/base/iam-service/pdb.yaml
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+ name: iam-service-pdb
+ namespace: goodgo
+spec:
+ minAvailable: 2
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: iam-service
+```
+
+---
+
+## Helm Chart Templates
+
+### Chart.yaml
+
+```yaml
+# charts/goodgo-service/Chart.yaml
+apiVersion: v2
+name: goodgo-service
+description: A Helm chart for GoodGo microservices
+type: application
+version: 1.0.0
+appVersion: "1.0.0"
+maintainers:
+ - name: GoodGo Team
+ email: team@goodgo.vn
+```
+
+### Deployment Template
+
+```yaml
+# charts/goodgo-service/templates/deployment.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "goodgo-service.fullname" . }}
+ labels:
+ {{- include "goodgo-service.labels" . | nindent 4 }}
+spec:
+ {{- if not .Values.autoscaling.enabled }}
+ replicas: {{ .Values.replicaCount }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "goodgo-service.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ annotations:
+ checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
+ {{- with .Values.podAnnotations }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "goodgo-service.selectorLabels" . | nindent 8 }}
+ spec:
+ serviceAccountName: {{ include "goodgo-service.serviceAccountName" . }}
+ securityContext:
+ {{- toYaml .Values.podSecurityContext | nindent 8 }}
+ containers:
+ - name: {{ .Chart.Name }}
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ ports:
+ - name: http
+ containerPort: {{ .Values.service.targetPort }}
+ protocol: TCP
+ {{- if .Values.env }}
+ env:
+ {{- toYaml .Values.env | nindent 12 }}
+ {{- end }}
+ {{- if .Values.envFromSecret }}
+ envFrom:
+ - secretRef:
+ name: {{ include "goodgo-service.fullname" . }}-secrets
+ {{- end }}
+ livenessProbe:
+ httpGet:
+ path: {{ .Values.probes.liveness.path }}
+ port: http
+ initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
+ periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
+ readinessProbe:
+ httpGet:
+ path: {{ .Values.probes.readiness.path }}
+ port: http
+ initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
+ periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
+```
+
+### Helpers Template
+
+```yaml
+# charts/goodgo-service/templates/_helpers.tpl
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "goodgo-service.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+*/}}
+{{- define "goodgo-service.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "goodgo-service.labels" -}}
+helm.sh/chart: {{ include "goodgo-service.chart" . }}
+{{ include "goodgo-service.selectorLabels" . }}
+app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "goodgo-service.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "goodgo-service.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+```
+
+### Production Values
+
+```yaml
+# charts/goodgo-service/values/production.yaml
+replicaCount: 3
+
+image:
+ repository: gcr.io/goodgo/iam-service
+ tag: "1.0.0"
+ pullPolicy: IfNotPresent
+
+resources:
+ requests:
+ memory: "512Mi"
+ cpu: "250m"
+ limits:
+ memory: "1Gi"
+ cpu: "1000m"
+
+autoscaling:
+ enabled: true
+ minReplicas: 3
+ maxReplicas: 20
+ targetCPUUtilizationPercentage: 70
+ targetMemoryUtilizationPercentage: 80
+
+ingress:
+ enabled: true
+ className: nginx
+ annotations:
+ nginx.ingress.kubernetes.io/rate-limit: "100"
+ nginx.ingress.kubernetes.io/rate-limit-window: "1m"
+ hosts:
+ - host: api.goodgo.vn
+ paths:
+ - path: /api/v1/iam
+ pathType: Prefix
+ tls:
+ - secretName: goodgo-tls
+ hosts:
+ - api.goodgo.vn
+
+probes:
+ liveness:
+ path: /health/live
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ readiness:
+ path: /health/ready
+ initialDelaySeconds: 5
+ periodSeconds: 5
+```
+
+---
+
+## CI/CD Integration
+
+### GitHub Actions Deploy
+
+```yaml
+# .github/workflows/deploy.yml
+name: Deploy to Kubernetes
+
+on:
+ push:
+ branches: [main]
+ tags: ['v*']
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GCR
+ uses: docker/login-action@v3
+ with:
+ registry: gcr.io
+ username: _json_key
+ password: ${{ secrets.GCP_SA_KEY }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v5
+ with:
+ context: ./services/iam-service-net
+ push: true
+ tags: gcr.io/goodgo/iam-service:${{ github.sha }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Set up kubectl
+ uses: azure/setup-kubectl@v3
+
+ - name: Set up Helm
+ uses: azure/setup-helm@v3
+
+ - name: Deploy to Kubernetes
+ run: |
+ helm upgrade --install iam-service ./charts/goodgo-service \
+ --namespace goodgo \
+ --create-namespace \
+ --values ./charts/goodgo-service/values/production.yaml \
+ --set image.tag=${{ github.sha }} \
+ --wait
+```
+
+---
+
+## Resources / Tài Nguyên
+
+- [Kubernetes Documentation](https://kubernetes.io/docs/)
+- [Helm Documentation](https://helm.sh/docs/)
+- [Kustomize Documentation](https://kustomize.io/)
+- [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/)
diff --git a/.agent/skills/domain-driven-design/SKILL.md b/.agent/skills/domain-driven-design/SKILL.md
new file mode 100644
index 00000000..355d5534
--- /dev/null
+++ b/.agent/skills/domain-driven-design/SKILL.md
@@ -0,0 +1,480 @@
+---
+name: domain-driven-design
+description: DDD patterns cho complex business logic. Use for Aggregates, Value Objects, Entities, Domain Events, và Rich Domain Model.
+compatibility: ".NET 8+, EF Core 8+"
+metadata:
+ author: Velik Ho
+ version: "1.0"
+---
+
+# Domain-Driven Design Patterns / Mẫu DDD
+
+DDD patterns cho GoodGo microservices với complex business logic.
+
+## When to Use This Skill / Khi Nào Sử Dụng
+
+Use this skill when:
+- Modeling complex business domains / Mô hình hóa domain phức tạp
+- Designing aggregates and entities / Thiết kế aggregates và entities
+- Implementing business rules in domain / Triển khai business rules trong domain
+- Creating value objects / Tạo value objects
+- Raising domain events / Raise domain events
+
+## Core Concepts / Khái Niệm Cốt Lõi
+
+### DDD Building Blocks / Các Khối Xây Dựng DDD
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ DOMAIN MODEL LAYER │
+├─────────────────────────────────────────────────────────────┤
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ AGGREGATE ROOT │ │
+│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
+│ │ │ Entity │ │ Value Object │ │ │
+│ │ │ (Identity) │ │ (No Identity) │ │ │
+│ │ └─────────────────┘ └─────────────────┘ │ │
+│ │ │ │
+│ │ • Business Rules / Quy tắc nghiệp vụ │ │
+│ │ • Domain Events / Domain Events │ │
+│ │ • Invariants / Bất biến │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────┐ ┌─────────────────┐ │
+│ │ Domain Service │ │ Domain Events │ │
+│ └─────────────────┘ └─────────────────┘ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Entity vs Value Object / Entity vs Value Object
+
+| Aspect | Entity | Value Object |
+|--------|--------|--------------|
+| **Identity** | Has unique ID | No identity |
+| **Equality** | By ID | By all properties |
+| **Mutability** | Mutable (via methods) | Immutable |
+| **Lifecycle** | Independent | Belongs to Entity |
+| **Example** | Order, User | Address, Money |
+
+### Aggregate Rules / Quy Tắc Aggregate
+
+1. **One repository per aggregate root** / Một repository cho mỗi aggregate root
+2. **Reference only by ID** / Chỉ tham chiếu qua ID
+3. **Atomic transaction boundary** / Ranh giới transaction atomic
+4. **Consistency within aggregate** / Nhất quán trong aggregate
+
+## Key Patterns / Mẫu Chính
+
+### Entity Base Class
+
+```csharp
+///
+/// EN: Base class for all entities.
+/// VI: Base class cho tất cả entities.
+///
+public abstract class Entity
+{
+ private int? _requestedHashCode;
+ private List? _domainEvents;
+
+ public virtual Guid Id { get; protected set; }
+
+ public IReadOnlyCollection DomainEvents
+ => _domainEvents?.AsReadOnly() ?? Array.Empty().AsReadOnly();
+
+ public void AddDomainEvent(IDomainEvent eventItem)
+ {
+ _domainEvents ??= new List();
+ _domainEvents.Add(eventItem);
+ }
+
+ public void RemoveDomainEvent(IDomainEvent eventItem)
+ {
+ _domainEvents?.Remove(eventItem);
+ }
+
+ public void ClearDomainEvents()
+ {
+ _domainEvents?.Clear();
+ }
+
+ public bool IsTransient()
+ {
+ return Id == default;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is not Entity other)
+ return false;
+
+ if (ReferenceEquals(this, other))
+ return true;
+
+ if (GetType() != other.GetType())
+ return false;
+
+ if (IsTransient() || other.IsTransient())
+ return false;
+
+ return Id.Equals(other.Id);
+ }
+
+ public override int GetHashCode()
+ {
+ if (!IsTransient())
+ {
+ _requestedHashCode ??= Id.GetHashCode() ^ 31;
+ return _requestedHashCode.Value;
+ }
+ return base.GetHashCode();
+ }
+
+ public static bool operator ==(Entity? left, Entity? right)
+ {
+ return left?.Equals(right) ?? right is null;
+ }
+
+ public static bool operator !=(Entity? left, Entity? right)
+ {
+ return !(left == right);
+ }
+}
+```
+
+### Aggregate Root
+
+```csharp
+///
+/// EN: Marker interface for aggregate roots.
+/// VI: Interface đánh dấu aggregate roots.
+///
+public interface IAggregateRoot { }
+
+///
+/// EN: Order aggregate root with business rules.
+/// VI: Order aggregate root với business rules.
+///
+public class Order : Entity, IAggregateRoot
+{
+ private readonly List _orderItems = new();
+
+ public string UserId { get; private set; }
+ public Address ShippingAddress { get; private set; }
+ public OrderStatus Status { get; private set; }
+ public decimal TotalAmount { get; private set; }
+ public DateTime CreatedAt { get; private set; }
+ public DateTime? SubmittedAt { get; private set; }
+
+ public IReadOnlyCollection OrderItems => _orderItems.AsReadOnly();
+
+ // EN: Required by EF Core
+ private Order() { }
+
+ public Order(string userId, Address shippingAddress)
+ {
+ Id = Guid.NewGuid();
+ UserId = userId ?? throw new ArgumentNullException(nameof(userId));
+ ShippingAddress = shippingAddress ?? throw new ArgumentNullException(nameof(shippingAddress));
+ Status = OrderStatus.Draft;
+ CreatedAt = DateTime.UtcNow;
+ TotalAmount = 0;
+
+ // EN: Raise domain event
+ // VI: Raise domain event
+ AddDomainEvent(new OrderCreatedDomainEvent(Id, userId));
+ }
+
+ public void AddItem(Guid productId, int quantity, decimal unitPrice)
+ {
+ // EN: Business rule: Can only add items to draft orders
+ // VI: Quy tắc: Chỉ thêm items vào orders draft
+ if (Status != OrderStatus.Draft)
+ throw new DomainException("Cannot add items to non-draft order");
+
+ if (quantity <= 0)
+ throw new ArgumentException("Quantity must be positive", nameof(quantity));
+
+ if (unitPrice < 0)
+ throw new ArgumentException("Price cannot be negative", nameof(unitPrice));
+
+ var existingItem = _orderItems.FirstOrDefault(i => i.ProductId == productId);
+ if (existingItem != null)
+ {
+ existingItem.IncreaseQuantity(quantity);
+ }
+ else
+ {
+ _orderItems.Add(new OrderItem(productId, quantity, unitPrice));
+ }
+
+ RecalculateTotal();
+ }
+
+ public void RemoveItem(Guid productId)
+ {
+ if (Status != OrderStatus.Draft)
+ throw new DomainException("Cannot remove items from non-draft order");
+
+ var item = _orderItems.FirstOrDefault(i => i.ProductId == productId);
+ if (item != null)
+ {
+ _orderItems.Remove(item);
+ RecalculateTotal();
+ }
+ }
+
+ public void Submit()
+ {
+ // EN: Business rule: Cannot submit empty order
+ // VI: Quy tắc: Không thể submit order trống
+ if (!_orderItems.Any())
+ throw new DomainException("Cannot submit empty order");
+
+ if (Status != OrderStatus.Draft)
+ throw new DomainException($"Cannot submit order in {Status} status");
+
+ Status = OrderStatus.Submitted;
+ SubmittedAt = DateTime.UtcNow;
+
+ AddDomainEvent(new OrderSubmittedDomainEvent(Id, UserId, TotalAmount));
+ }
+
+ public void Cancel(string reason)
+ {
+ if (Status == OrderStatus.Shipped || Status == OrderStatus.Delivered)
+ throw new DomainException("Cannot cancel shipped or delivered order");
+
+ Status = OrderStatus.Cancelled;
+ AddDomainEvent(new OrderCancelledDomainEvent(Id, reason));
+ }
+
+ private void RecalculateTotal()
+ {
+ TotalAmount = _orderItems.Sum(i => i.Quantity * i.UnitPrice);
+ }
+}
+```
+
+### Value Object
+
+```csharp
+///
+/// EN: Base class for value objects.
+/// VI: Base class cho value objects.
+///
+public abstract class ValueObject
+{
+ protected abstract IEnumerable