claude-agent-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -0
- package/bin/claude-framework +3 -0
- package/framework/agents/design-lead.md +240 -0
- package/framework/agents/product-owner.md +179 -0
- package/framework/agents/tech-lead.md +226 -0
- package/framework/commands/ayuda.md +127 -0
- package/framework/commands/a/303/261adir.md +98 -0
- package/framework/commands/backup.md +397 -0
- package/framework/commands/cambiar.md +110 -0
- package/framework/commands/cloud.md +457 -0
- package/framework/commands/code.md +142 -0
- package/framework/commands/debug.md +334 -0
- package/framework/commands/deploy.md +383 -0
- package/framework/commands/deshacer.md +120 -0
- package/framework/commands/estado.md +218 -0
- package/framework/commands/explica.md +227 -0
- package/framework/commands/feature.md +120 -0
- package/framework/commands/git.md +427 -0
- package/framework/commands/historial.md +202 -0
- package/framework/commands/learn.md +408 -0
- package/framework/commands/movil.md +245 -0
- package/framework/commands/nuevo.md +118 -0
- package/framework/commands/plan.md +134 -0
- package/framework/commands/prd.md +113 -0
- package/framework/commands/probar.md +148 -0
- package/framework/commands/revisar.md +208 -0
- package/framework/commands/seeds.md +230 -0
- package/framework/commands/seguridad.md +226 -0
- package/framework/commands/tasks.md +157 -0
- package/framework/skills/architecture/algorithms.md +970 -0
- package/framework/skills/architecture/clean-code.md +1080 -0
- package/framework/skills/architecture/design-patterns.md +1984 -0
- package/framework/skills/architecture/functional-programming.md +972 -0
- package/framework/skills/architecture/solid.md +991 -0
- package/framework/skills/cloud/cloud-aws.md +848 -0
- package/framework/skills/cloud/cloud-azure.md +931 -0
- package/framework/skills/cloud/cloud-gcp.md +848 -0
- package/framework/skills/cloud/message-queues.md +1229 -0
- package/framework/skills/core/accessibility.md +401 -0
- package/framework/skills/core/api.md +474 -0
- package/framework/skills/core/authentication.md +306 -0
- package/framework/skills/core/authorization.md +388 -0
- package/framework/skills/core/background-jobs.md +341 -0
- package/framework/skills/core/caching.md +473 -0
- package/framework/skills/core/code-review.md +341 -0
- package/framework/skills/core/controllers.md +290 -0
- package/framework/skills/core/cua.md +285 -0
- package/framework/skills/core/documentation.md +472 -0
- package/framework/skills/core/file-uploads.md +351 -0
- package/framework/skills/core/hotwire-native.md +296 -0
- package/framework/skills/core/hotwire.md +278 -0
- package/framework/skills/core/i18n.md +334 -0
- package/framework/skills/core/imports-exports.md +750 -0
- package/framework/skills/core/infrastructure.md +337 -0
- package/framework/skills/core/models.md +228 -0
- package/framework/skills/core/notifications.md +672 -0
- package/framework/skills/core/payments.md +581 -0
- package/framework/skills/core/performance.md +361 -0
- package/framework/skills/core/rails-scaffold.md +131 -0
- package/framework/skills/core/search.md +518 -0
- package/framework/skills/core/security.md +565 -0
- package/framework/skills/core/seeds.md +307 -0
- package/framework/skills/core/seo.md +542 -0
- package/framework/skills/core/testing.md +393 -0
- package/framework/skills/core/views.md +260 -0
- package/framework/skills/core/websockets.md +564 -0
- package/framework/skills/data/advanced-sql.md +1204 -0
- package/framework/skills/data/nosql.md +1141 -0
- package/framework/skills/devops/containers-advanced.md +1237 -0
- package/framework/skills/devops/debugging.md +834 -0
- package/framework/skills/devops/git-workflow.md +752 -0
- package/framework/skills/devops/networking.md +932 -0
- package/framework/skills/devops/shell-scripting.md +1132 -0
- package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
- package/framework/sub-agents/cloud-agent.md +677 -0
- package/framework/sub-agents/data.md +504 -0
- package/framework/sub-agents/debugging-agent.md +554 -0
- package/framework/sub-agents/devops.md +483 -0
- package/framework/sub-agents/docs.md +176 -0
- package/framework/sub-agents/frontend-dev.md +349 -0
- package/framework/sub-agents/git-workflow-agent.md +697 -0
- package/framework/sub-agents/integrations.md +630 -0
- package/framework/sub-agents/native-dev.md +434 -0
- package/framework/sub-agents/qa.md +138 -0
- package/framework/sub-agents/rails-dev.md +375 -0
- package/framework/sub-agents/security.md +526 -0
- package/framework/sub-agents/ui.md +437 -0
- package/framework/sub-agents/ux.md +284 -0
- package/framework/templates/api-spec.md +500 -0
- package/framework/templates/component-spec.md +248 -0
- package/framework/templates/feature.json +13 -0
- package/framework/templates/model-spec.md +318 -0
- package/framework/templates/prd-template.md +80 -0
- package/framework/templates/task-plan.md +122 -0
- package/framework/templates/task-user-story.md +52 -0
- package/framework/templates/technical-spec.md +260 -0
- package/framework/templates/user-story.md +95 -0
- package/package.json +42 -0
- package/project-templates/CLAUDE.md +42 -0
- package/project-templates/contexts/architecture.md +25 -0
- package/project-templates/contexts/conventions.md +46 -0
- package/project-templates/contexts/design-system.md +47 -0
- package/project-templates/contexts/requirements.md +38 -0
- package/project-templates/contexts/stack.md +30 -0
- package/project-templates/history/active/models.md +11 -0
- package/project-templates/history/changelog.md +15 -0
- package/project-templates/workspace/.gitkeep +0 -0
- package/src/cli.js +52 -0
- package/src/init.js +104 -0
- package/src/status.js +75 -0
- package/src/update.js +88 -0
|
@@ -0,0 +1,1237 @@
|
|
|
1
|
+
# Skill: Containers Advanced
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Orquestación de contenedores con Kubernetes para desplegar y escalar aplicaciones Rails en producción.
|
|
6
|
+
|
|
7
|
+
## Kubernetes Basics
|
|
8
|
+
|
|
9
|
+
### Arquitectura
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ Control Plane │
|
|
14
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
|
15
|
+
│ │ API │ │ etcd │ │Scheduler │ │ Controller │ │
|
|
16
|
+
│ │ Server │ │ │ │ │ │ Manager │ │
|
|
17
|
+
│ └──────────┘ └──────────┘ └──────────┘ └────────────┘ │
|
|
18
|
+
└─────────────────────────────────────────────────────────────┘
|
|
19
|
+
│
|
|
20
|
+
┌───────────────┼───────────────┐
|
|
21
|
+
│ │ │
|
|
22
|
+
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
|
|
23
|
+
│ Node 1 │ │ Node 2 │ │ Node 3 │
|
|
24
|
+
│ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │
|
|
25
|
+
│ │ kubelet│ │ │ │ kubelet│ │ │ │ kubelet│ │
|
|
26
|
+
│ └────────┘ │ │ └────────┘ │ │ └────────┘ │
|
|
27
|
+
│ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │
|
|
28
|
+
│ │ Pod │ │ │ │ Pod │ │ │ │ Pod │ │
|
|
29
|
+
│ │ Pod │ │ │ │ Pod │ │ │ │ Pod │ │
|
|
30
|
+
│ └────────┘ │ │ └────────┘ │ │ └────────┘ │
|
|
31
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Conceptos principales
|
|
35
|
+
|
|
36
|
+
| Concepto | Descripción |
|
|
37
|
+
|----------|-------------|
|
|
38
|
+
| **Pod** | Unidad mínima, uno o más contenedores |
|
|
39
|
+
| **Deployment** | Gestiona réplicas de Pods |
|
|
40
|
+
| **Service** | Expone Pods como servicio de red |
|
|
41
|
+
| **Ingress** | Gestiona acceso HTTP externo |
|
|
42
|
+
| **ConfigMap** | Configuración no sensible |
|
|
43
|
+
| **Secret** | Datos sensibles encriptados |
|
|
44
|
+
| **PersistentVolume** | Almacenamiento persistente |
|
|
45
|
+
| **Namespace** | Aislamiento lógico de recursos |
|
|
46
|
+
|
|
47
|
+
## kubectl Comandos Esenciales
|
|
48
|
+
|
|
49
|
+
### Información del cluster
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Ver info del cluster
|
|
53
|
+
kubectl cluster-info
|
|
54
|
+
kubectl version
|
|
55
|
+
|
|
56
|
+
# Ver nodos
|
|
57
|
+
kubectl get nodes
|
|
58
|
+
kubectl describe node <node-name>
|
|
59
|
+
|
|
60
|
+
# Ver namespaces
|
|
61
|
+
kubectl get namespaces
|
|
62
|
+
kubectl get ns
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Gestión de Pods
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Listar pods
|
|
69
|
+
kubectl get pods
|
|
70
|
+
kubectl get pods -n production
|
|
71
|
+
kubectl get pods --all-namespaces
|
|
72
|
+
kubectl get pods -o wide # Más info
|
|
73
|
+
|
|
74
|
+
# Describir pod
|
|
75
|
+
kubectl describe pod <pod-name>
|
|
76
|
+
|
|
77
|
+
# Logs
|
|
78
|
+
kubectl logs <pod-name>
|
|
79
|
+
kubectl logs <pod-name> -c <container-name> # Multi-container
|
|
80
|
+
kubectl logs -f <pod-name> # Follow
|
|
81
|
+
kubectl logs --tail=100 <pod-name> # Últimas 100 líneas
|
|
82
|
+
|
|
83
|
+
# Ejecutar comando en pod
|
|
84
|
+
kubectl exec -it <pod-name> -- /bin/bash
|
|
85
|
+
kubectl exec <pod-name> -- rails console
|
|
86
|
+
|
|
87
|
+
# Copiar archivos
|
|
88
|
+
kubectl cp <pod-name>:/app/log/production.log ./production.log
|
|
89
|
+
kubectl cp ./file.txt <pod-name>:/app/
|
|
90
|
+
|
|
91
|
+
# Port forward
|
|
92
|
+
kubectl port-forward <pod-name> 3000:3000
|
|
93
|
+
kubectl port-forward service/rails-app 3000:80
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Gestión de Deployments
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Listar deployments
|
|
100
|
+
kubectl get deployments
|
|
101
|
+
kubectl get deploy
|
|
102
|
+
|
|
103
|
+
# Crear deployment
|
|
104
|
+
kubectl create deployment rails-app --image=myapp:latest
|
|
105
|
+
|
|
106
|
+
# Escalar
|
|
107
|
+
kubectl scale deployment rails-app --replicas=5
|
|
108
|
+
|
|
109
|
+
# Actualizar imagen
|
|
110
|
+
kubectl set image deployment/rails-app rails=myapp:v2
|
|
111
|
+
|
|
112
|
+
# Ver historial
|
|
113
|
+
kubectl rollout history deployment/rails-app
|
|
114
|
+
|
|
115
|
+
# Rollback
|
|
116
|
+
kubectl rollout undo deployment/rails-app
|
|
117
|
+
kubectl rollout undo deployment/rails-app --to-revision=2
|
|
118
|
+
|
|
119
|
+
# Ver status del rollout
|
|
120
|
+
kubectl rollout status deployment/rails-app
|
|
121
|
+
|
|
122
|
+
# Pausar/reanudar rollout
|
|
123
|
+
kubectl rollout pause deployment/rails-app
|
|
124
|
+
kubectl rollout resume deployment/rails-app
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Gestión de Services
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Listar services
|
|
131
|
+
kubectl get services
|
|
132
|
+
kubectl get svc
|
|
133
|
+
|
|
134
|
+
# Exponer deployment
|
|
135
|
+
kubectl expose deployment rails-app --port=80 --target-port=3000
|
|
136
|
+
|
|
137
|
+
# Describir service
|
|
138
|
+
kubectl describe service rails-app
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Aplicar manifests
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Aplicar archivo
|
|
145
|
+
kubectl apply -f deployment.yaml
|
|
146
|
+
|
|
147
|
+
# Aplicar directorio
|
|
148
|
+
kubectl apply -f ./k8s/
|
|
149
|
+
|
|
150
|
+
# Aplicar con namespace
|
|
151
|
+
kubectl apply -f deployment.yaml -n production
|
|
152
|
+
|
|
153
|
+
# Eliminar
|
|
154
|
+
kubectl delete -f deployment.yaml
|
|
155
|
+
kubectl delete deployment rails-app
|
|
156
|
+
kubectl delete pod <pod-name>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Debug
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Ver eventos
|
|
163
|
+
kubectl get events
|
|
164
|
+
kubectl get events --sort-by='.lastTimestamp'
|
|
165
|
+
|
|
166
|
+
# Ver recursos
|
|
167
|
+
kubectl top nodes
|
|
168
|
+
kubectl top pods
|
|
169
|
+
|
|
170
|
+
# Dry run
|
|
171
|
+
kubectl apply -f deployment.yaml --dry-run=client
|
|
172
|
+
|
|
173
|
+
# Diff antes de aplicar
|
|
174
|
+
kubectl diff -f deployment.yaml
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Helm
|
|
178
|
+
|
|
179
|
+
### Conceptos
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
Chart = Paquete de manifests de Kubernetes
|
|
183
|
+
Release = Instancia de un Chart deployada
|
|
184
|
+
Values = Configuración personalizada
|
|
185
|
+
Repository = Colección de Charts
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Comandos básicos
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Añadir repositorio
|
|
192
|
+
helm repo add bitnami https://charts.bitnami.com/bitnami
|
|
193
|
+
helm repo update
|
|
194
|
+
|
|
195
|
+
# Buscar charts
|
|
196
|
+
helm search repo postgresql
|
|
197
|
+
helm search hub rails
|
|
198
|
+
|
|
199
|
+
# Instalar chart
|
|
200
|
+
helm install my-release bitnami/postgresql
|
|
201
|
+
helm install my-release bitnami/postgresql -f values.yaml
|
|
202
|
+
helm install my-release bitnami/postgresql --set postgresqlPassword=secret
|
|
203
|
+
|
|
204
|
+
# Listar releases
|
|
205
|
+
helm list
|
|
206
|
+
helm list -A # Todos los namespaces
|
|
207
|
+
|
|
208
|
+
# Ver valores
|
|
209
|
+
helm show values bitnami/postgresql
|
|
210
|
+
helm get values my-release
|
|
211
|
+
|
|
212
|
+
# Actualizar release
|
|
213
|
+
helm upgrade my-release bitnami/postgresql -f values.yaml
|
|
214
|
+
|
|
215
|
+
# Rollback
|
|
216
|
+
helm rollback my-release 1
|
|
217
|
+
|
|
218
|
+
# Desinstalar
|
|
219
|
+
helm uninstall my-release
|
|
220
|
+
|
|
221
|
+
# Ver historial
|
|
222
|
+
helm history my-release
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Crear Chart propio
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Crear estructura
|
|
229
|
+
helm create myapp-chart
|
|
230
|
+
|
|
231
|
+
# Estructura generada:
|
|
232
|
+
myapp-chart/
|
|
233
|
+
├── Chart.yaml # Metadata
|
|
234
|
+
├── values.yaml # Valores por defecto
|
|
235
|
+
├── charts/ # Dependencias
|
|
236
|
+
├── templates/ # Manifests con templates
|
|
237
|
+
│ ├── deployment.yaml
|
|
238
|
+
│ ├── service.yaml
|
|
239
|
+
│ ├── ingress.yaml
|
|
240
|
+
│ ├── configmap.yaml
|
|
241
|
+
│ ├── secret.yaml
|
|
242
|
+
│ ├── _helpers.tpl # Funciones helper
|
|
243
|
+
│ └── NOTES.txt # Instrucciones post-install
|
|
244
|
+
└── .helmignore
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Chart.yaml
|
|
248
|
+
|
|
249
|
+
```yaml
|
|
250
|
+
# Chart.yaml
|
|
251
|
+
apiVersion: v2
|
|
252
|
+
name: myapp
|
|
253
|
+
description: Rails application Helm chart
|
|
254
|
+
type: application
|
|
255
|
+
version: 0.1.0
|
|
256
|
+
appVersion: "1.0.0"
|
|
257
|
+
|
|
258
|
+
dependencies:
|
|
259
|
+
- name: postgresql
|
|
260
|
+
version: "12.x.x"
|
|
261
|
+
repository: "https://charts.bitnami.com/bitnami"
|
|
262
|
+
condition: postgresql.enabled
|
|
263
|
+
- name: redis
|
|
264
|
+
version: "17.x.x"
|
|
265
|
+
repository: "https://charts.bitnami.com/bitnami"
|
|
266
|
+
condition: redis.enabled
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### values.yaml
|
|
270
|
+
|
|
271
|
+
```yaml
|
|
272
|
+
# values.yaml
|
|
273
|
+
replicaCount: 3
|
|
274
|
+
|
|
275
|
+
image:
|
|
276
|
+
repository: myregistry.com/myapp
|
|
277
|
+
tag: "latest"
|
|
278
|
+
pullPolicy: IfNotPresent
|
|
279
|
+
|
|
280
|
+
service:
|
|
281
|
+
type: ClusterIP
|
|
282
|
+
port: 80
|
|
283
|
+
|
|
284
|
+
ingress:
|
|
285
|
+
enabled: true
|
|
286
|
+
className: nginx
|
|
287
|
+
hosts:
|
|
288
|
+
- host: myapp.example.com
|
|
289
|
+
paths:
|
|
290
|
+
- path: /
|
|
291
|
+
pathType: Prefix
|
|
292
|
+
tls:
|
|
293
|
+
- secretName: myapp-tls
|
|
294
|
+
hosts:
|
|
295
|
+
- myapp.example.com
|
|
296
|
+
|
|
297
|
+
resources:
|
|
298
|
+
limits:
|
|
299
|
+
cpu: 500m
|
|
300
|
+
memory: 512Mi
|
|
301
|
+
requests:
|
|
302
|
+
cpu: 250m
|
|
303
|
+
memory: 256Mi
|
|
304
|
+
|
|
305
|
+
rails:
|
|
306
|
+
env: production
|
|
307
|
+
masterKey: "" # Se provee en install
|
|
308
|
+
|
|
309
|
+
postgresql:
|
|
310
|
+
enabled: true
|
|
311
|
+
auth:
|
|
312
|
+
username: myapp
|
|
313
|
+
database: myapp_production
|
|
314
|
+
|
|
315
|
+
redis:
|
|
316
|
+
enabled: true
|
|
317
|
+
auth:
|
|
318
|
+
enabled: false
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Template de deployment
|
|
322
|
+
|
|
323
|
+
```yaml
|
|
324
|
+
# templates/deployment.yaml
|
|
325
|
+
apiVersion: apps/v1
|
|
326
|
+
kind: Deployment
|
|
327
|
+
metadata:
|
|
328
|
+
name: {{ include "myapp.fullname" . }}
|
|
329
|
+
labels:
|
|
330
|
+
{{- include "myapp.labels" . | nindent 4 }}
|
|
331
|
+
spec:
|
|
332
|
+
replicas: {{ .Values.replicaCount }}
|
|
333
|
+
selector:
|
|
334
|
+
matchLabels:
|
|
335
|
+
{{- include "myapp.selectorLabels" . | nindent 6 }}
|
|
336
|
+
template:
|
|
337
|
+
metadata:
|
|
338
|
+
labels:
|
|
339
|
+
{{- include "myapp.selectorLabels" . | nindent 8 }}
|
|
340
|
+
spec:
|
|
341
|
+
containers:
|
|
342
|
+
- name: {{ .Chart.Name }}
|
|
343
|
+
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
|
344
|
+
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
345
|
+
ports:
|
|
346
|
+
- name: http
|
|
347
|
+
containerPort: 3000
|
|
348
|
+
env:
|
|
349
|
+
- name: RAILS_ENV
|
|
350
|
+
value: {{ .Values.rails.env }}
|
|
351
|
+
- name: RAILS_MASTER_KEY
|
|
352
|
+
valueFrom:
|
|
353
|
+
secretKeyRef:
|
|
354
|
+
name: {{ include "myapp.fullname" . }}-secrets
|
|
355
|
+
key: rails-master-key
|
|
356
|
+
- name: DATABASE_URL
|
|
357
|
+
valueFrom:
|
|
358
|
+
secretKeyRef:
|
|
359
|
+
name: {{ include "myapp.fullname" . }}-secrets
|
|
360
|
+
key: database-url
|
|
361
|
+
resources:
|
|
362
|
+
{{- toYaml .Values.resources | nindent 12 }}
|
|
363
|
+
livenessProbe:
|
|
364
|
+
httpGet:
|
|
365
|
+
path: /up
|
|
366
|
+
port: http
|
|
367
|
+
initialDelaySeconds: 30
|
|
368
|
+
readinessProbe:
|
|
369
|
+
httpGet:
|
|
370
|
+
path: /up
|
|
371
|
+
port: http
|
|
372
|
+
initialDelaySeconds: 5
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## ConfigMaps y Secrets
|
|
376
|
+
|
|
377
|
+
### ConfigMap
|
|
378
|
+
|
|
379
|
+
```yaml
|
|
380
|
+
# configmap.yaml
|
|
381
|
+
apiVersion: v1
|
|
382
|
+
kind: ConfigMap
|
|
383
|
+
metadata:
|
|
384
|
+
name: rails-config
|
|
385
|
+
data:
|
|
386
|
+
RAILS_ENV: "production"
|
|
387
|
+
RAILS_LOG_TO_STDOUT: "true"
|
|
388
|
+
RAILS_SERVE_STATIC_FILES: "true"
|
|
389
|
+
WEB_CONCURRENCY: "2"
|
|
390
|
+
RAILS_MAX_THREADS: "5"
|
|
391
|
+
|
|
392
|
+
# Archivo de configuración
|
|
393
|
+
database.yml: |
|
|
394
|
+
production:
|
|
395
|
+
adapter: postgresql
|
|
396
|
+
encoding: unicode
|
|
397
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
398
|
+
url: <%= ENV['DATABASE_URL'] %>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
```yaml
|
|
402
|
+
# Usar en deployment
|
|
403
|
+
spec:
|
|
404
|
+
containers:
|
|
405
|
+
- name: rails
|
|
406
|
+
envFrom:
|
|
407
|
+
- configMapRef:
|
|
408
|
+
name: rails-config
|
|
409
|
+
volumeMounts:
|
|
410
|
+
- name: config-volume
|
|
411
|
+
mountPath: /app/config/database.yml
|
|
412
|
+
subPath: database.yml
|
|
413
|
+
volumes:
|
|
414
|
+
- name: config-volume
|
|
415
|
+
configMap:
|
|
416
|
+
name: rails-config
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Secrets
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# Crear secret desde literal
|
|
423
|
+
kubectl create secret generic rails-secrets \
|
|
424
|
+
--from-literal=rails-master-key=abc123 \
|
|
425
|
+
--from-literal=database-url=postgres://user:pass@host/db
|
|
426
|
+
|
|
427
|
+
# Crear desde archivo
|
|
428
|
+
kubectl create secret generic rails-secrets \
|
|
429
|
+
--from-file=./master.key
|
|
430
|
+
|
|
431
|
+
# Ver secret (base64)
|
|
432
|
+
kubectl get secret rails-secrets -o yaml
|
|
433
|
+
|
|
434
|
+
# Decodificar
|
|
435
|
+
kubectl get secret rails-secrets -o jsonpath='{.data.rails-master-key}' | base64 -d
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
```yaml
|
|
439
|
+
# secret.yaml
|
|
440
|
+
apiVersion: v1
|
|
441
|
+
kind: Secret
|
|
442
|
+
metadata:
|
|
443
|
+
name: rails-secrets
|
|
444
|
+
type: Opaque
|
|
445
|
+
stringData: # Se codifica automáticamente
|
|
446
|
+
rails-master-key: "abc123def456"
|
|
447
|
+
database-url: "postgres://user:pass@host:5432/myapp"
|
|
448
|
+
|
|
449
|
+
# O con data (ya codificado en base64)
|
|
450
|
+
# data:
|
|
451
|
+
# rails-master-key: YWJjMTIzZGVmNDU2
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```yaml
|
|
455
|
+
# Usar en deployment
|
|
456
|
+
spec:
|
|
457
|
+
containers:
|
|
458
|
+
- name: rails
|
|
459
|
+
env:
|
|
460
|
+
- name: RAILS_MASTER_KEY
|
|
461
|
+
valueFrom:
|
|
462
|
+
secretKeyRef:
|
|
463
|
+
name: rails-secrets
|
|
464
|
+
key: rails-master-key
|
|
465
|
+
- name: DATABASE_URL
|
|
466
|
+
valueFrom:
|
|
467
|
+
secretKeyRef:
|
|
468
|
+
name: rails-secrets
|
|
469
|
+
key: database-url
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Sealed Secrets (para Git)
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
# Instalar kubeseal
|
|
476
|
+
brew install kubeseal
|
|
477
|
+
|
|
478
|
+
# Crear sealed secret
|
|
479
|
+
kubectl create secret generic rails-secrets \
|
|
480
|
+
--from-literal=rails-master-key=abc123 \
|
|
481
|
+
--dry-run=client -o yaml | \
|
|
482
|
+
kubeseal --format yaml > sealed-secret.yaml
|
|
483
|
+
|
|
484
|
+
# El sealed secret se puede commitear
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Scaling
|
|
488
|
+
|
|
489
|
+
### Horizontal Pod Autoscaler (HPA)
|
|
490
|
+
|
|
491
|
+
```yaml
|
|
492
|
+
# hpa.yaml
|
|
493
|
+
apiVersion: autoscaling/v2
|
|
494
|
+
kind: HorizontalPodAutoscaler
|
|
495
|
+
metadata:
|
|
496
|
+
name: rails-app-hpa
|
|
497
|
+
spec:
|
|
498
|
+
scaleTargetRef:
|
|
499
|
+
apiVersion: apps/v1
|
|
500
|
+
kind: Deployment
|
|
501
|
+
name: rails-app
|
|
502
|
+
minReplicas: 2
|
|
503
|
+
maxReplicas: 10
|
|
504
|
+
metrics:
|
|
505
|
+
- type: Resource
|
|
506
|
+
resource:
|
|
507
|
+
name: cpu
|
|
508
|
+
target:
|
|
509
|
+
type: Utilization
|
|
510
|
+
averageUtilization: 70
|
|
511
|
+
- type: Resource
|
|
512
|
+
resource:
|
|
513
|
+
name: memory
|
|
514
|
+
target:
|
|
515
|
+
type: Utilization
|
|
516
|
+
averageUtilization: 80
|
|
517
|
+
behavior:
|
|
518
|
+
scaleDown:
|
|
519
|
+
stabilizationWindowSeconds: 300
|
|
520
|
+
policies:
|
|
521
|
+
- type: Percent
|
|
522
|
+
value: 10
|
|
523
|
+
periodSeconds: 60
|
|
524
|
+
scaleUp:
|
|
525
|
+
stabilizationWindowSeconds: 0
|
|
526
|
+
policies:
|
|
527
|
+
- type: Percent
|
|
528
|
+
value: 100
|
|
529
|
+
periodSeconds: 15
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
# Ver HPA
|
|
534
|
+
kubectl get hpa
|
|
535
|
+
kubectl describe hpa rails-app-hpa
|
|
536
|
+
|
|
537
|
+
# Crear HPA rápido
|
|
538
|
+
kubectl autoscale deployment rails-app --min=2 --max=10 --cpu-percent=70
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Vertical Pod Autoscaler (VPA)
|
|
542
|
+
|
|
543
|
+
```yaml
|
|
544
|
+
# vpa.yaml
|
|
545
|
+
apiVersion: autoscaling.k8s.io/v1
|
|
546
|
+
kind: VerticalPodAutoscaler
|
|
547
|
+
metadata:
|
|
548
|
+
name: rails-app-vpa
|
|
549
|
+
spec:
|
|
550
|
+
targetRef:
|
|
551
|
+
apiVersion: apps/v1
|
|
552
|
+
kind: Deployment
|
|
553
|
+
name: rails-app
|
|
554
|
+
updatePolicy:
|
|
555
|
+
updateMode: "Auto" # Off, Initial, Recreate, Auto
|
|
556
|
+
resourcePolicy:
|
|
557
|
+
containerPolicies:
|
|
558
|
+
- containerName: rails
|
|
559
|
+
minAllowed:
|
|
560
|
+
cpu: 100m
|
|
561
|
+
memory: 128Mi
|
|
562
|
+
maxAllowed:
|
|
563
|
+
cpu: 2
|
|
564
|
+
memory: 2Gi
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Cluster Autoscaler
|
|
568
|
+
|
|
569
|
+
```yaml
|
|
570
|
+
# Para EKS
|
|
571
|
+
apiVersion: eksctl.io/v1alpha5
|
|
572
|
+
kind: ClusterConfig
|
|
573
|
+
metadata:
|
|
574
|
+
name: my-cluster
|
|
575
|
+
region: us-west-2
|
|
576
|
+
|
|
577
|
+
nodeGroups:
|
|
578
|
+
- name: ng-1
|
|
579
|
+
instanceType: t3.medium
|
|
580
|
+
desiredCapacity: 3
|
|
581
|
+
minSize: 2
|
|
582
|
+
maxSize: 10
|
|
583
|
+
iam:
|
|
584
|
+
withAddonPolicies:
|
|
585
|
+
autoScaler: true
|
|
586
|
+
|
|
587
|
+
# El cluster autoscaler se instala como addon
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## Networking
|
|
591
|
+
|
|
592
|
+
### Service Types
|
|
593
|
+
|
|
594
|
+
```yaml
|
|
595
|
+
# ClusterIP (interno)
|
|
596
|
+
apiVersion: v1
|
|
597
|
+
kind: Service
|
|
598
|
+
metadata:
|
|
599
|
+
name: rails-app
|
|
600
|
+
spec:
|
|
601
|
+
type: ClusterIP # Default
|
|
602
|
+
selector:
|
|
603
|
+
app: rails-app
|
|
604
|
+
ports:
|
|
605
|
+
- port: 80
|
|
606
|
+
targetPort: 3000
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
# NodePort (expone en puerto del nodo)
|
|
610
|
+
apiVersion: v1
|
|
611
|
+
kind: Service
|
|
612
|
+
metadata:
|
|
613
|
+
name: rails-app-nodeport
|
|
614
|
+
spec:
|
|
615
|
+
type: NodePort
|
|
616
|
+
selector:
|
|
617
|
+
app: rails-app
|
|
618
|
+
ports:
|
|
619
|
+
- port: 80
|
|
620
|
+
targetPort: 3000
|
|
621
|
+
nodePort: 30080 # 30000-32767
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
# LoadBalancer (cloud provider)
|
|
625
|
+
apiVersion: v1
|
|
626
|
+
kind: Service
|
|
627
|
+
metadata:
|
|
628
|
+
name: rails-app-lb
|
|
629
|
+
spec:
|
|
630
|
+
type: LoadBalancer
|
|
631
|
+
selector:
|
|
632
|
+
app: rails-app
|
|
633
|
+
ports:
|
|
634
|
+
- port: 80
|
|
635
|
+
targetPort: 3000
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Ingress
|
|
639
|
+
|
|
640
|
+
```yaml
|
|
641
|
+
# ingress.yaml
|
|
642
|
+
apiVersion: networking.k8s.io/v1
|
|
643
|
+
kind: Ingress
|
|
644
|
+
metadata:
|
|
645
|
+
name: rails-app-ingress
|
|
646
|
+
annotations:
|
|
647
|
+
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
|
648
|
+
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
|
649
|
+
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
650
|
+
spec:
|
|
651
|
+
ingressClassName: nginx
|
|
652
|
+
tls:
|
|
653
|
+
- hosts:
|
|
654
|
+
- myapp.example.com
|
|
655
|
+
secretName: myapp-tls
|
|
656
|
+
rules:
|
|
657
|
+
- host: myapp.example.com
|
|
658
|
+
http:
|
|
659
|
+
paths:
|
|
660
|
+
- path: /
|
|
661
|
+
pathType: Prefix
|
|
662
|
+
backend:
|
|
663
|
+
service:
|
|
664
|
+
name: rails-app
|
|
665
|
+
port:
|
|
666
|
+
number: 80
|
|
667
|
+
- path: /api
|
|
668
|
+
pathType: Prefix
|
|
669
|
+
backend:
|
|
670
|
+
service:
|
|
671
|
+
name: rails-api
|
|
672
|
+
port:
|
|
673
|
+
number: 80
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### Network Policies
|
|
677
|
+
|
|
678
|
+
```yaml
|
|
679
|
+
# network-policy.yaml
|
|
680
|
+
apiVersion: networking.k8s.io/v1
|
|
681
|
+
kind: NetworkPolicy
|
|
682
|
+
metadata:
|
|
683
|
+
name: rails-app-policy
|
|
684
|
+
spec:
|
|
685
|
+
podSelector:
|
|
686
|
+
matchLabels:
|
|
687
|
+
app: rails-app
|
|
688
|
+
policyTypes:
|
|
689
|
+
- Ingress
|
|
690
|
+
- Egress
|
|
691
|
+
ingress:
|
|
692
|
+
- from:
|
|
693
|
+
- podSelector:
|
|
694
|
+
matchLabels:
|
|
695
|
+
app: nginx-ingress
|
|
696
|
+
ports:
|
|
697
|
+
- port: 3000
|
|
698
|
+
egress:
|
|
699
|
+
- to:
|
|
700
|
+
- podSelector:
|
|
701
|
+
matchLabels:
|
|
702
|
+
app: postgresql
|
|
703
|
+
ports:
|
|
704
|
+
- port: 5432
|
|
705
|
+
- to:
|
|
706
|
+
- podSelector:
|
|
707
|
+
matchLabels:
|
|
708
|
+
app: redis
|
|
709
|
+
ports:
|
|
710
|
+
- port: 6379
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
## Storage
|
|
714
|
+
|
|
715
|
+
### PersistentVolume y PersistentVolumeClaim
|
|
716
|
+
|
|
717
|
+
```yaml
|
|
718
|
+
# pv.yaml (admin crea)
|
|
719
|
+
apiVersion: v1
|
|
720
|
+
kind: PersistentVolume
|
|
721
|
+
metadata:
|
|
722
|
+
name: rails-uploads-pv
|
|
723
|
+
spec:
|
|
724
|
+
capacity:
|
|
725
|
+
storage: 10Gi
|
|
726
|
+
accessModes:
|
|
727
|
+
- ReadWriteMany
|
|
728
|
+
persistentVolumeReclaimPolicy: Retain
|
|
729
|
+
storageClassName: standard
|
|
730
|
+
# AWS EBS
|
|
731
|
+
awsElasticBlockStore:
|
|
732
|
+
volumeID: vol-xxx
|
|
733
|
+
fsType: ext4
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
# pvc.yaml (dev solicita)
|
|
737
|
+
apiVersion: v1
|
|
738
|
+
kind: PersistentVolumeClaim
|
|
739
|
+
metadata:
|
|
740
|
+
name: rails-uploads-pvc
|
|
741
|
+
spec:
|
|
742
|
+
accessModes:
|
|
743
|
+
- ReadWriteMany
|
|
744
|
+
resources:
|
|
745
|
+
requests:
|
|
746
|
+
storage: 10Gi
|
|
747
|
+
storageClassName: standard
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
```yaml
|
|
751
|
+
# Usar en deployment
|
|
752
|
+
spec:
|
|
753
|
+
containers:
|
|
754
|
+
- name: rails
|
|
755
|
+
volumeMounts:
|
|
756
|
+
- name: uploads
|
|
757
|
+
mountPath: /app/storage
|
|
758
|
+
volumes:
|
|
759
|
+
- name: uploads
|
|
760
|
+
persistentVolumeClaim:
|
|
761
|
+
claimName: rails-uploads-pvc
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### StorageClass
|
|
765
|
+
|
|
766
|
+
```yaml
|
|
767
|
+
# storageclass.yaml
|
|
768
|
+
apiVersion: storage.k8s.io/v1
|
|
769
|
+
kind: StorageClass
|
|
770
|
+
metadata:
|
|
771
|
+
name: fast-ssd
|
|
772
|
+
provisioner: kubernetes.io/aws-ebs
|
|
773
|
+
parameters:
|
|
774
|
+
type: gp3
|
|
775
|
+
iops: "3000"
|
|
776
|
+
throughput: "125"
|
|
777
|
+
reclaimPolicy: Retain
|
|
778
|
+
allowVolumeExpansion: true
|
|
779
|
+
volumeBindingMode: WaitForFirstConsumer
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
## Monitoring
|
|
783
|
+
|
|
784
|
+
### Prometheus + Grafana
|
|
785
|
+
|
|
786
|
+
```yaml
|
|
787
|
+
# prometheus-values.yaml para Helm
|
|
788
|
+
alertmanager:
|
|
789
|
+
enabled: true
|
|
790
|
+
|
|
791
|
+
server:
|
|
792
|
+
retention: "15d"
|
|
793
|
+
persistentVolume:
|
|
794
|
+
size: 50Gi
|
|
795
|
+
|
|
796
|
+
# Instalar
|
|
797
|
+
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
|
|
798
|
+
helm install prometheus prometheus-community/kube-prometheus-stack -f prometheus-values.yaml
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### Métricas de Rails
|
|
802
|
+
|
|
803
|
+
```ruby
|
|
804
|
+
# Gemfile
|
|
805
|
+
gem "prometheus_exporter"
|
|
806
|
+
|
|
807
|
+
# config/initializers/prometheus.rb
|
|
808
|
+
if Rails.env.production?
|
|
809
|
+
require "prometheus_exporter/middleware"
|
|
810
|
+
|
|
811
|
+
Rails.application.middleware.unshift PrometheusExporter::Middleware
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
# Procfile
|
|
815
|
+
worker: bundle exec prometheus_exporter -b 0.0.0.0
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### ServiceMonitor
|
|
819
|
+
|
|
820
|
+
```yaml
|
|
821
|
+
# servicemonitor.yaml
|
|
822
|
+
apiVersion: monitoring.coreos.com/v1
|
|
823
|
+
kind: ServiceMonitor
|
|
824
|
+
metadata:
|
|
825
|
+
name: rails-app
|
|
826
|
+
labels:
|
|
827
|
+
release: prometheus
|
|
828
|
+
spec:
|
|
829
|
+
selector:
|
|
830
|
+
matchLabels:
|
|
831
|
+
app: rails-app
|
|
832
|
+
endpoints:
|
|
833
|
+
- port: metrics
|
|
834
|
+
interval: 30s
|
|
835
|
+
path: /metrics
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
## Logging
|
|
839
|
+
|
|
840
|
+
### ELK Stack
|
|
841
|
+
|
|
842
|
+
```yaml
|
|
843
|
+
# Instalar con Helm
|
|
844
|
+
helm repo add elastic https://helm.elastic.co
|
|
845
|
+
helm install elasticsearch elastic/elasticsearch
|
|
846
|
+
helm install kibana elastic/kibana
|
|
847
|
+
helm install filebeat elastic/filebeat -f filebeat-values.yaml
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
```yaml
|
|
851
|
+
# filebeat-values.yaml
|
|
852
|
+
daemonset:
|
|
853
|
+
filebeatConfig:
|
|
854
|
+
filebeat.yml: |
|
|
855
|
+
filebeat.inputs:
|
|
856
|
+
- type: container
|
|
857
|
+
paths:
|
|
858
|
+
- /var/log/containers/*.log
|
|
859
|
+
processors:
|
|
860
|
+
- add_kubernetes_metadata:
|
|
861
|
+
host: ${NODE_NAME}
|
|
862
|
+
matchers:
|
|
863
|
+
- logs_path:
|
|
864
|
+
logs_path: "/var/log/containers/"
|
|
865
|
+
|
|
866
|
+
output.elasticsearch:
|
|
867
|
+
hosts: ['${ELASTICSEARCH_HOST:elasticsearch-master}:${ELASTICSEARCH_PORT:9200}']
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
### Fluentd
|
|
871
|
+
|
|
872
|
+
```yaml
|
|
873
|
+
# fluent.conf
|
|
874
|
+
<source>
|
|
875
|
+
@type tail
|
|
876
|
+
path /var/log/containers/*rails*.log
|
|
877
|
+
pos_file /var/log/fluentd-containers.log.pos
|
|
878
|
+
tag kubernetes.*
|
|
879
|
+
read_from_head true
|
|
880
|
+
<parse>
|
|
881
|
+
@type json
|
|
882
|
+
time_format %Y-%m-%dT%H:%M:%S.%NZ
|
|
883
|
+
</parse>
|
|
884
|
+
</source>
|
|
885
|
+
|
|
886
|
+
<match kubernetes.**>
|
|
887
|
+
@type elasticsearch
|
|
888
|
+
host elasticsearch
|
|
889
|
+
port 9200
|
|
890
|
+
logstash_format true
|
|
891
|
+
logstash_prefix k8s-rails
|
|
892
|
+
</match>
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
## CI/CD con Kubernetes
|
|
896
|
+
|
|
897
|
+
### ArgoCD
|
|
898
|
+
|
|
899
|
+
```yaml
|
|
900
|
+
# application.yaml
|
|
901
|
+
apiVersion: argoproj.io/v1alpha1
|
|
902
|
+
kind: Application
|
|
903
|
+
metadata:
|
|
904
|
+
name: rails-app
|
|
905
|
+
namespace: argocd
|
|
906
|
+
spec:
|
|
907
|
+
project: default
|
|
908
|
+
source:
|
|
909
|
+
repoURL: https://github.com/myorg/myapp.git
|
|
910
|
+
targetRevision: HEAD
|
|
911
|
+
path: k8s/production
|
|
912
|
+
destination:
|
|
913
|
+
server: https://kubernetes.default.svc
|
|
914
|
+
namespace: production
|
|
915
|
+
syncPolicy:
|
|
916
|
+
automated:
|
|
917
|
+
prune: true
|
|
918
|
+
selfHeal: true
|
|
919
|
+
syncOptions:
|
|
920
|
+
- CreateNamespace=true
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### GitHub Actions + K8s
|
|
924
|
+
|
|
925
|
+
```yaml
|
|
926
|
+
# .github/workflows/deploy.yml
|
|
927
|
+
name: Deploy to Kubernetes
|
|
928
|
+
|
|
929
|
+
on:
|
|
930
|
+
push:
|
|
931
|
+
branches: [main]
|
|
932
|
+
|
|
933
|
+
jobs:
|
|
934
|
+
deploy:
|
|
935
|
+
runs-on: ubuntu-latest
|
|
936
|
+
steps:
|
|
937
|
+
- uses: actions/checkout@v4
|
|
938
|
+
|
|
939
|
+
- name: Build and push Docker image
|
|
940
|
+
run: |
|
|
941
|
+
docker build -t myregistry.com/myapp:${{ github.sha }} .
|
|
942
|
+
docker push myregistry.com/myapp:${{ github.sha }}
|
|
943
|
+
|
|
944
|
+
- name: Configure kubectl
|
|
945
|
+
uses: azure/k8s-set-context@v3
|
|
946
|
+
with:
|
|
947
|
+
kubeconfig: ${{ secrets.KUBE_CONFIG }}
|
|
948
|
+
|
|
949
|
+
- name: Deploy to Kubernetes
|
|
950
|
+
run: |
|
|
951
|
+
kubectl set image deployment/rails-app \
|
|
952
|
+
rails=myregistry.com/myapp:${{ github.sha }} \
|
|
953
|
+
-n production
|
|
954
|
+
|
|
955
|
+
- name: Wait for rollout
|
|
956
|
+
run: kubectl rollout status deployment/rails-app -n production
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
## Rails en Kubernetes
|
|
960
|
+
|
|
961
|
+
### Dockerfile optimizado
|
|
962
|
+
|
|
963
|
+
```dockerfile
|
|
964
|
+
# Dockerfile
|
|
965
|
+
FROM ruby:3.3.0-slim AS builder
|
|
966
|
+
|
|
967
|
+
WORKDIR /app
|
|
968
|
+
|
|
969
|
+
# Install dependencies
|
|
970
|
+
RUN apt-get update && apt-get install -y \
|
|
971
|
+
build-essential \
|
|
972
|
+
libpq-dev \
|
|
973
|
+
nodejs \
|
|
974
|
+
npm \
|
|
975
|
+
git \
|
|
976
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
977
|
+
|
|
978
|
+
# Install bundler
|
|
979
|
+
RUN gem install bundler:2.5.0
|
|
980
|
+
|
|
981
|
+
# Copy Gemfile first for caching
|
|
982
|
+
COPY Gemfile Gemfile.lock ./
|
|
983
|
+
RUN bundle config set --local deployment 'true' && \
|
|
984
|
+
bundle config set --local without 'development test' && \
|
|
985
|
+
bundle install
|
|
986
|
+
|
|
987
|
+
# Copy app
|
|
988
|
+
COPY . .
|
|
989
|
+
|
|
990
|
+
# Precompile assets
|
|
991
|
+
RUN SECRET_KEY_BASE=dummy RAILS_ENV=production bundle exec rails assets:precompile
|
|
992
|
+
|
|
993
|
+
# Production image
|
|
994
|
+
FROM ruby:3.3.0-slim
|
|
995
|
+
|
|
996
|
+
WORKDIR /app
|
|
997
|
+
|
|
998
|
+
RUN apt-get update && apt-get install -y \
|
|
999
|
+
libpq-dev \
|
|
1000
|
+
curl \
|
|
1001
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
1002
|
+
|
|
1003
|
+
# Copy from builder
|
|
1004
|
+
COPY --from=builder /app /app
|
|
1005
|
+
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
|
1006
|
+
|
|
1007
|
+
# Create non-root user
|
|
1008
|
+
RUN useradd -m rails && chown -R rails:rails /app
|
|
1009
|
+
USER rails
|
|
1010
|
+
|
|
1011
|
+
EXPOSE 3000
|
|
1012
|
+
|
|
1013
|
+
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### Kubernetes manifests completos
|
|
1017
|
+
|
|
1018
|
+
```yaml
|
|
1019
|
+
# k8s/production/deployment.yaml
|
|
1020
|
+
apiVersion: apps/v1
|
|
1021
|
+
kind: Deployment
|
|
1022
|
+
metadata:
|
|
1023
|
+
name: rails-app
|
|
1024
|
+
namespace: production
|
|
1025
|
+
spec:
|
|
1026
|
+
replicas: 3
|
|
1027
|
+
selector:
|
|
1028
|
+
matchLabels:
|
|
1029
|
+
app: rails-app
|
|
1030
|
+
template:
|
|
1031
|
+
metadata:
|
|
1032
|
+
labels:
|
|
1033
|
+
app: rails-app
|
|
1034
|
+
spec:
|
|
1035
|
+
containers:
|
|
1036
|
+
- name: rails
|
|
1037
|
+
image: myregistry.com/myapp:latest
|
|
1038
|
+
ports:
|
|
1039
|
+
- containerPort: 3000
|
|
1040
|
+
env:
|
|
1041
|
+
- name: RAILS_ENV
|
|
1042
|
+
value: production
|
|
1043
|
+
- name: RAILS_LOG_TO_STDOUT
|
|
1044
|
+
value: "true"
|
|
1045
|
+
- name: RAILS_SERVE_STATIC_FILES
|
|
1046
|
+
value: "true"
|
|
1047
|
+
- name: RAILS_MASTER_KEY
|
|
1048
|
+
valueFrom:
|
|
1049
|
+
secretKeyRef:
|
|
1050
|
+
name: rails-secrets
|
|
1051
|
+
key: master-key
|
|
1052
|
+
- name: DATABASE_URL
|
|
1053
|
+
valueFrom:
|
|
1054
|
+
secretKeyRef:
|
|
1055
|
+
name: rails-secrets
|
|
1056
|
+
key: database-url
|
|
1057
|
+
resources:
|
|
1058
|
+
requests:
|
|
1059
|
+
cpu: 250m
|
|
1060
|
+
memory: 512Mi
|
|
1061
|
+
limits:
|
|
1062
|
+
cpu: 1000m
|
|
1063
|
+
memory: 1Gi
|
|
1064
|
+
livenessProbe:
|
|
1065
|
+
httpGet:
|
|
1066
|
+
path: /up
|
|
1067
|
+
port: 3000
|
|
1068
|
+
initialDelaySeconds: 30
|
|
1069
|
+
periodSeconds: 10
|
|
1070
|
+
readinessProbe:
|
|
1071
|
+
httpGet:
|
|
1072
|
+
path: /up
|
|
1073
|
+
port: 3000
|
|
1074
|
+
initialDelaySeconds: 5
|
|
1075
|
+
periodSeconds: 5
|
|
1076
|
+
volumeMounts:
|
|
1077
|
+
- name: uploads
|
|
1078
|
+
mountPath: /app/storage
|
|
1079
|
+
volumes:
|
|
1080
|
+
- name: uploads
|
|
1081
|
+
persistentVolumeClaim:
|
|
1082
|
+
claimName: rails-uploads
|
|
1083
|
+
imagePullSecrets:
|
|
1084
|
+
- name: registry-credentials
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
# k8s/production/service.yaml
|
|
1088
|
+
apiVersion: v1
|
|
1089
|
+
kind: Service
|
|
1090
|
+
metadata:
|
|
1091
|
+
name: rails-app
|
|
1092
|
+
namespace: production
|
|
1093
|
+
spec:
|
|
1094
|
+
selector:
|
|
1095
|
+
app: rails-app
|
|
1096
|
+
ports:
|
|
1097
|
+
- port: 80
|
|
1098
|
+
targetPort: 3000
|
|
1099
|
+
type: ClusterIP
|
|
1100
|
+
|
|
1101
|
+
---
|
|
1102
|
+
# k8s/production/ingress.yaml
|
|
1103
|
+
apiVersion: networking.k8s.io/v1
|
|
1104
|
+
kind: Ingress
|
|
1105
|
+
metadata:
|
|
1106
|
+
name: rails-app
|
|
1107
|
+
namespace: production
|
|
1108
|
+
annotations:
|
|
1109
|
+
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
1110
|
+
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
|
1111
|
+
spec:
|
|
1112
|
+
ingressClassName: nginx
|
|
1113
|
+
tls:
|
|
1114
|
+
- hosts:
|
|
1115
|
+
- myapp.com
|
|
1116
|
+
secretName: myapp-tls
|
|
1117
|
+
rules:
|
|
1118
|
+
- host: myapp.com
|
|
1119
|
+
http:
|
|
1120
|
+
paths:
|
|
1121
|
+
- path: /
|
|
1122
|
+
pathType: Prefix
|
|
1123
|
+
backend:
|
|
1124
|
+
service:
|
|
1125
|
+
name: rails-app
|
|
1126
|
+
port:
|
|
1127
|
+
number: 80
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
### Job para migraciones
|
|
1131
|
+
|
|
1132
|
+
```yaml
|
|
1133
|
+
# k8s/production/migration-job.yaml
|
|
1134
|
+
apiVersion: batch/v1
|
|
1135
|
+
kind: Job
|
|
1136
|
+
metadata:
|
|
1137
|
+
name: rails-migrate-{{ .Release.Revision }}
|
|
1138
|
+
namespace: production
|
|
1139
|
+
annotations:
|
|
1140
|
+
"helm.sh/hook": pre-upgrade
|
|
1141
|
+
"helm.sh/hook-weight": "-1"
|
|
1142
|
+
"helm.sh/hook-delete-policy": hook-succeeded
|
|
1143
|
+
spec:
|
|
1144
|
+
template:
|
|
1145
|
+
spec:
|
|
1146
|
+
containers:
|
|
1147
|
+
- name: migrate
|
|
1148
|
+
image: myregistry.com/myapp:{{ .Values.image.tag }}
|
|
1149
|
+
command: ["bundle", "exec", "rails", "db:migrate"]
|
|
1150
|
+
env:
|
|
1151
|
+
- name: RAILS_ENV
|
|
1152
|
+
value: production
|
|
1153
|
+
- name: RAILS_MASTER_KEY
|
|
1154
|
+
valueFrom:
|
|
1155
|
+
secretKeyRef:
|
|
1156
|
+
name: rails-secrets
|
|
1157
|
+
key: master-key
|
|
1158
|
+
- name: DATABASE_URL
|
|
1159
|
+
valueFrom:
|
|
1160
|
+
secretKeyRef:
|
|
1161
|
+
name: rails-secrets
|
|
1162
|
+
key: database-url
|
|
1163
|
+
restartPolicy: Never
|
|
1164
|
+
backoffLimit: 3
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
### Worker deployment (Solid Queue)
|
|
1168
|
+
|
|
1169
|
+
```yaml
|
|
1170
|
+
# k8s/production/worker-deployment.yaml
|
|
1171
|
+
apiVersion: apps/v1
|
|
1172
|
+
kind: Deployment
|
|
1173
|
+
metadata:
|
|
1174
|
+
name: rails-worker
|
|
1175
|
+
namespace: production
|
|
1176
|
+
spec:
|
|
1177
|
+
replicas: 2
|
|
1178
|
+
selector:
|
|
1179
|
+
matchLabels:
|
|
1180
|
+
app: rails-worker
|
|
1181
|
+
template:
|
|
1182
|
+
metadata:
|
|
1183
|
+
labels:
|
|
1184
|
+
app: rails-worker
|
|
1185
|
+
spec:
|
|
1186
|
+
containers:
|
|
1187
|
+
- name: worker
|
|
1188
|
+
image: myregistry.com/myapp:latest
|
|
1189
|
+
command: ["bundle", "exec", "rake", "solid_queue:start"]
|
|
1190
|
+
env:
|
|
1191
|
+
- name: RAILS_ENV
|
|
1192
|
+
value: production
|
|
1193
|
+
- name: RAILS_MASTER_KEY
|
|
1194
|
+
valueFrom:
|
|
1195
|
+
secretKeyRef:
|
|
1196
|
+
name: rails-secrets
|
|
1197
|
+
key: master-key
|
|
1198
|
+
- name: DATABASE_URL
|
|
1199
|
+
valueFrom:
|
|
1200
|
+
secretKeyRef:
|
|
1201
|
+
name: rails-secrets
|
|
1202
|
+
key: database-url
|
|
1203
|
+
resources:
|
|
1204
|
+
requests:
|
|
1205
|
+
cpu: 100m
|
|
1206
|
+
memory: 256Mi
|
|
1207
|
+
limits:
|
|
1208
|
+
cpu: 500m
|
|
1209
|
+
memory: 512Mi
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
## Checklist de Kubernetes
|
|
1213
|
+
|
|
1214
|
+
### Antes de deploy
|
|
1215
|
+
|
|
1216
|
+
- [ ] Dockerfile optimizado (multi-stage)
|
|
1217
|
+
- [ ] Health checks configurados
|
|
1218
|
+
- [ ] Resource limits definidos
|
|
1219
|
+
- [ ] Secrets en Secret/ConfigMap
|
|
1220
|
+
- [ ] Probes (liveness/readiness)
|
|
1221
|
+
- [ ] Network policies si es necesario
|
|
1222
|
+
|
|
1223
|
+
### Deploy
|
|
1224
|
+
|
|
1225
|
+
- [ ] Aplicar ConfigMaps y Secrets primero
|
|
1226
|
+
- [ ] Ejecutar migraciones (Job)
|
|
1227
|
+
- [ ] Deploy con rolling update
|
|
1228
|
+
- [ ] Verificar rollout status
|
|
1229
|
+
- [ ] Verificar logs
|
|
1230
|
+
|
|
1231
|
+
### Post-deploy
|
|
1232
|
+
|
|
1233
|
+
- [ ] Verificar pods healthy
|
|
1234
|
+
- [ ] Verificar ingress/service
|
|
1235
|
+
- [ ] Verificar métricas
|
|
1236
|
+
- [ ] Verificar logs centralizados
|
|
1237
|
+
- [ ] Test de carga si es necesario
|