feat: Claude Code Monitor — lanes, pipelines and a merged workspace

Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
This commit is contained in:
2026-07-29 17:07:45 +07:00
commit 62425b2f58
781 changed files with 220565 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# Kubernetes Manifests
Production-ready Kubernetes resources using Kustomize for environment management, with optional blue-green and canary deployment strategies.
## Structure
```
kubernetes/
├── base/ # Shared base (all environments inherit from this)
│ ├── kustomization.yaml
│ ├── namespace.yaml # agent-monitor namespace with Pod Security Standards
│ ├── configmap.yaml # Environment configuration
│ ├── serviceaccount.yaml # Minimal-privilege service account
│ ├── deployment.yaml # Main deployment (2 replicas, 3 health probes)
│ ├── service.yaml # ClusterIP with WebSocket sticky sessions
│ ├── ingress.yaml # NGINX ingress with TLS + WebSocket headers
│ ├── pvc.yaml # 10Gi persistent volume for SQLite
│ ├── hpa.yaml # Horizontal Pod Autoscaler (210 pods)
│ ├── pdb.yaml # Pod Disruption Budget (minAvailable: 1)
│ └── networkpolicy.yaml # Ingress restricted to NGINX controller
├── overlays/
│ ├── dev/ # 1 replica, no HPA, minimal resources
│ ├── staging/ # 2 replicas, standard resources
│ └── production/ # 3 replicas, HPA 320, strict anti-affinity
├── strategies/
│ ├── blue-green/ # Dual-slot deployment with service switching
│ └── canary/ # Progressive rollout with Argo Rollouts analysis
└── components/
├── mcp-sidecar/ # Adds MCP server container to pods
└── monitoring/ # Adds Prometheus ServiceMonitor
```
## Usage
```bash
# Apply an environment
kubectl apply -k overlays/dev/
kubectl apply -k overlays/staging/
kubectl apply -k overlays/production/
# Add MCP sidecar (edit overlay kustomization.yaml):
# components:
# - ../../components/mcp-sidecar
# Blue-green switch
kubectl patch svc agent-monitor -n agent-monitor \
-p '{"spec":{"selector":{"slot":"green"}}}'
```
## Security
All manifests enforce:
- `runAsNonRoot: true`
- `readOnlyRootFilesystem: true`
- `drop: [ALL]` capabilities
- `seccompProfile: RuntimeDefault`
- No service account token auto-mount
- NetworkPolicy restricting ingress sources
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: agent-monitor-config
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: config
app.kubernetes.io/managed-by: kustomize
data:
NODE_ENV: "production"
DASHBOARD_PORT: "4820"
LOG_LEVEL: "info"
+147
View File
@@ -0,0 +1,147 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
spec:
replicas: 2
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
template:
metadata:
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
annotations:
# Force rollout on configmap changes via kustomize hash
checksum/config: "placeholder"
spec:
serviceAccountName: agent-monitor
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- agent-monitor
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: agent-monitor
containers:
- name: agent-monitor
image: ${IMAGE_REGISTRY}/agent-monitor:${IMAGE_TAG}
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 4820
protocol: TCP
envFrom:
- configMapRef:
name: agent-monitor-config
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
startupProbe:
httpGet:
path: /api/health
port: http
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /app/data
- name: tmp
mountPath: /tmp
lifecycle:
preStop:
exec:
# Allow in-flight requests to drain before SIGTERM
command: ["sh", "-c", "sleep 5"]
volumes:
- name: data
persistentVolumeClaim:
claimName: agent-monitor-data
- name: tmp
emptyDir:
sizeLimit: 100Mi
+49
View File
@@ -0,0 +1,49 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: autoscaling
app.kubernetes.io/managed-by: kustomize
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: agent-monitor
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
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 60
selectPolicy: Min
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Pods
value: 2
periodSeconds: 60
- type: Percent
value: 50
periodSeconds: 60
selectPolicy: Max
+51
View File
@@ -0,0 +1,51 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: ingress
app.kubernetes.io/managed-by: kustomize
annotations:
# NGINX Ingress Controller annotations
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# WebSocket upgrade support
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/upstream-hash-by: "$remote_addr"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Sticky sessions for WebSocket
nginx.ingress.kubernetes.io/affinity: cookie
nginx.ingress.kubernetes.io/affinity-mode: persistent
nginx.ingress.kubernetes.io/session-cookie-name: agent-monitor-affinity
nginx.ingress.kubernetes.io/session-cookie-max-age: "10800"
nginx.ingress.kubernetes.io/session-cookie-samesite: Strict
nginx.ingress.kubernetes.io/session-cookie-secure: "true"
# Security headers
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/hsts: "true"
nginx.ingress.kubernetes.io/hsts-max-age: "31536000"
nginx.ingress.kubernetes.io/hsts-include-subdomains: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- agent-monitor.example.com
secretName: agent-monitor-tls
rules:
- host: agent-monitor.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: agent-monitor
port:
name: http
@@ -0,0 +1,23 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: agent-monitor-base
commonLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/managed-by: kustomize
resources:
- namespace.yaml
- configmap.yaml
- serviceaccount.yaml
- pvc.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
- hpa.yaml
- pdb.yaml
- networkpolicy.yaml
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Namespace
metadata:
name: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: namespace
app.kubernetes.io/managed-by: kustomize
# Enable Pod Security Standards (restricted)
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
@@ -0,0 +1,53 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: network
app.kubernetes.io/managed-by: kustomize
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
policyTypes:
- Ingress
- Egress
ingress:
# Allow traffic from ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
- podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 4820
# Allow intra-namespace traffic (pod-to-pod)
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: agent-monitor
ports:
- protocol: TCP
port: 4820
egress:
# Allow DNS resolution (required for service discovery)
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
# Allow outbound HTTPS (for external API calls if needed)
- ports:
- port: 443
protocol: TCP
# Allow internal communication within the namespace (pod-to-pod)
- to:
- podSelector: {}
+17
View File
@@ -0,0 +1,17 @@
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: availability
app.kubernetes.io/managed-by: kustomize
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: agent-monitor-data
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: storage
app.kubernetes.io/managed-by: kustomize
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
+29
View File
@@ -0,0 +1,29 @@
apiVersion: v1
kind: Service
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
annotations:
# Document the WebSocket requirement
service.kubernetes.io/topology-mode: Auto
spec:
type: ClusterIP
# Sticky sessions for WebSocket support
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
selector:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
@@ -0,0 +1,12 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: serviceaccount
app.kubernetes.io/managed-by: kustomize
automountServiceAccountToken: false
@@ -0,0 +1,69 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor
namespace: agent-monitor
spec:
template:
spec:
containers:
- name: mcp-sidecar
image: ${IMAGE_REGISTRY}/agent-monitor-mcp:${IMAGE_TAG}
imagePullPolicy: IfNotPresent
ports:
- name: mcp
containerPort: 8819
protocol: TCP
env:
- name: MCP_TRANSPORT
value: "http"
- name: MCP_DASHBOARD_BASE_URL
value: "http://localhost:4820"
- name: MCP_PORT
value: "8819"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "250m"
startupProbe:
tcpSocket:
port: mcp
failureThreshold: 30
periodSeconds: 2
readinessProbe:
tcpSocket:
port: mcp
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
tcpSocket:
port: mcp
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
metadata:
name: mcp-sidecar
patches:
- path: deployment-patch.yaml
target:
kind: Deployment
name: agent-monitor
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
metadata:
name: monitoring
resources:
- servicemonitor.yaml
@@ -0,0 +1,31 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: monitoring
app.kubernetes.io/managed-by: kustomize
# Common label for Prometheus Operator discovery
release: prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
namespaceSelector:
matchNames:
- agent-monitor
endpoints:
- port: http
path: /api/health
interval: 30s
scrapeTimeout: 10s
honorLabels: true
metricRelabelings:
- sourceLabels: [__name__]
regex: "(http_requests_total|http_request_duration_.*|nodejs_.*|process_.*)"
action: keep
@@ -0,0 +1,36 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: agent-monitor-dev
namespace: agent-monitor
resources:
- ../../base
images:
- name: ${IMAGE_REGISTRY}/agent-monitor
newName: agent-monitor
newTag: dev
configMapGenerator:
- name: agent-monitor-config
behavior: merge
literals:
- NODE_ENV=development
- LOG_LEVEL=debug
patches:
# Disable HPA in dev (single replica, no autoscaling needed)
- target:
kind: HorizontalPodAutoscaler
name: agent-monitor
patch: |
$patch: delete
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: agent-monitor
# Deployment overrides for dev
- path: patches/deployment-patch.yaml
@@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor
namespace: agent-monitor
spec:
# Single replica for dev
replicas: 1
template:
spec:
# Remove anti-affinity in dev (single node is fine)
affinity: null
topologySpreadConstraints: []
containers:
- name: agent-monitor
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "250m"
@@ -0,0 +1,26 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: agent-monitor-production
namespace: agent-monitor
resources:
- ../../base
patches:
- path: patches/deployment-patch.yaml
- path: patches/hpa-patch.yaml
images:
- name: ${IMAGE_REGISTRY}/agent-monitor
newName: agent-monitor
newTag: latest
configMapGenerator:
- name: agent-monitor-config
behavior: merge
literals:
- NODE_ENV=production
- LOG_LEVEL=warn
@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor
namespace: agent-monitor
spec:
replicas: 3
template:
spec:
# Production: require spreading across nodes
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- agent-monitor
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: agent-monitor
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: agent-monitor
containers:
- name: agent-monitor
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
@@ -0,0 +1,26 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: agent-monitor
namespace: agent-monitor
spec:
minReplicas: 3
maxReplicas: 20
behavior:
scaleDown:
stabilizationWindowSeconds: 600
policies:
- type: Pods
value: 1
periodSeconds: 120
selectPolicy: Min
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 4
periodSeconds: 60
- type: Percent
value: 100
periodSeconds: 60
selectPolicy: Max
@@ -0,0 +1,25 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: agent-monitor-staging
namespace: agent-monitor
resources:
- ../../base
patches:
- path: patches/deployment-patch.yaml
images:
- name: ${IMAGE_REGISTRY}/agent-monitor
newName: agent-monitor
newTag: staging
configMapGenerator:
- name: agent-monitor-config
behavior: merge
literals:
- NODE_ENV=staging
- LOG_LEVEL=info
@@ -0,0 +1,18 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor
namespace: agent-monitor
spec:
replicas: 2
template:
spec:
containers:
- name: agent-monitor
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
@@ -0,0 +1,136 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor-blue
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
slot: blue
spec:
replicas: 3
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
slot: blue
template:
metadata:
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
slot: blue
spec:
serviceAccountName: agent-monitor
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- agent-monitor
topologyKey: kubernetes.io/hostname
containers:
- name: agent-monitor
image: ${IMAGE_REGISTRY}/agent-monitor:blue
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 4820
protocol: TCP
envFrom:
- configMapRef:
name: agent-monitor-config
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
startupProbe:
httpGet:
path: /api/health
port: http
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /app/data
- name: tmp
mountPath: /tmp
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
volumes:
- name: data
persistentVolumeClaim:
claimName: agent-monitor-data
- name: tmp
emptyDir:
sizeLimit: 100Mi
@@ -0,0 +1,136 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor-green
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
slot: green
spec:
replicas: 3
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
slot: green
template:
metadata:
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
slot: green
spec:
serviceAccountName: agent-monitor
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- agent-monitor
topologyKey: kubernetes.io/hostname
containers:
- name: agent-monitor
image: ${IMAGE_REGISTRY}/agent-monitor:green
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 4820
protocol: TCP
envFrom:
- configMapRef:
name: agent-monitor-config
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
startupProbe:
httpGet:
path: /api/health
port: http
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /app/data
- name: tmp
mountPath: /tmp
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
volumes:
- name: data
persistentVolumeClaim:
claimName: agent-monitor-data
- name: tmp
emptyDir:
sizeLimit: 100Mi
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Service
metadata:
name: agent-monitor
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
annotations:
# Document which slot is currently active
# To switch traffic: kubectl patch svc agent-monitor -n agent-monitor \
# -p '{"spec":{"selector":{"slot":"green"}}}'
agent-monitor.io/active-slot: blue
spec:
type: ClusterIP
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
selector:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
# Toggle this value between "blue" and "green" to switch traffic
slot: blue
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
@@ -0,0 +1,97 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: agent-monitor-canary-analysis
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: canary-analysis
app.kubernetes.io/managed-by: kustomize
spec:
args:
- name: service-name
value: agent-monitor-canary
- name: namespace
value: agent-monitor
metrics:
# Success rate must be above 99%
- name: success-rate
interval: 60s
count: 5
successCondition: result[0] >= 0.99
failureLimit: 2
provider:
prometheus:
address: http://prometheus.monitoring.svc.cluster.local:9090
query: |
sum(
rate(
http_requests_total{
namespace="{{args.namespace}}",
service="{{args.service-name}}",
code!~"5.."
}[2m]
)
)
/
sum(
rate(
http_requests_total{
namespace="{{args.namespace}}",
service="{{args.service-name}}"
}[2m]
)
)
# P99 latency must be under 500ms
- name: p99-latency
interval: 60s
count: 5
successCondition: result[0] < 500
failureLimit: 2
provider:
prometheus:
address: http://prometheus.monitoring.svc.cluster.local:9090
query: |
histogram_quantile(
0.99,
sum(
rate(
http_request_duration_milliseconds_bucket{
namespace="{{args.namespace}}",
service="{{args.service-name}}"
}[2m]
)
) by (le)
)
# Error rate must stay below 1%
- name: error-rate
interval: 60s
count: 5
successCondition: result[0] <= 0.01
failureLimit: 2
provider:
prometheus:
address: http://prometheus.monitoring.svc.cluster.local:9090
query: |
sum(
rate(
http_requests_total{
namespace="{{args.namespace}}",
service="{{args.service-name}}",
code=~"5.."
}[2m]
)
)
/
sum(
rate(
http_requests_total{
namespace="{{args.namespace}}",
service="{{args.service-name}}"
}[2m]
)
)
@@ -0,0 +1,124 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-monitor-canary
namespace: agent-monitor
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor-canary
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
track: canary
spec:
replicas: 1
revisionHistoryLimit: 5
selector:
matchLabels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor-canary
track: canary
template:
metadata:
labels:
app.kubernetes.io/name: agent-monitor
app.kubernetes.io/instance: agent-monitor-canary
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: server
app.kubernetes.io/managed-by: kustomize
track: canary
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "4820"
prometheus.io/path: "/api/health"
spec:
serviceAccountName: agent-monitor
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: agent-monitor
image: ${IMAGE_REGISTRY}/agent-monitor:canary
imagePullPolicy: Always
ports:
- name: http
containerPort: 4820
protocol: TCP
envFrom:
- configMapRef:
name: agent-monitor-config
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
startupProbe:
httpGet:
path: /api/health
port: http
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /app/data
- name: tmp
mountPath: /tmp
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
volumes:
- name: data
persistentVolumeClaim:
claimName: agent-monitor-data
- name: tmp
emptyDir:
sizeLimit: 100Mi