@malamute/ai-rules 1.0.0 → 1.2.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 +270 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
- package/configs/_shared/.claude/rules/conventions/git.md +265 -0
- package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
- package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
- package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/.claude/rules/devops/docker.md +275 -0
- package/configs/_shared/.claude/rules/devops/nx.md +194 -0
- package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
- package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
- package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
- package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
- package/configs/_shared/.claude/rules/quality/logging.md +45 -0
- package/configs/_shared/.claude/rules/quality/observability.md +240 -0
- package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
- package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
- package/configs/angular/.claude/rules/core/resource.md +285 -0
- package/configs/angular/.claude/rules/core/signals.md +323 -0
- package/configs/angular/.claude/rules/http.md +338 -0
- package/configs/angular/.claude/rules/routing.md +291 -0
- package/configs/angular/.claude/rules/ssr.md +312 -0
- package/configs/angular/.claude/rules/state/signal-store.md +408 -0
- package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
- package/configs/angular/.claude/rules/testing.md +7 -7
- package/configs/angular/.claude/rules/ui/aria.md +422 -0
- package/configs/angular/.claude/rules/ui/forms.md +424 -0
- package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/.claude/settings.json +1 -0
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
- package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/dotnet/.claude/rules/background-services.md +552 -0
- package/configs/dotnet/.claude/rules/configuration.md +426 -0
- package/configs/dotnet/.claude/rules/ddd.md +447 -0
- package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
- package/configs/dotnet/.claude/rules/mediatr.md +320 -0
- package/configs/dotnet/.claude/rules/middleware.md +489 -0
- package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
- package/configs/dotnet/.claude/rules/validation.md +388 -0
- package/configs/dotnet/.claude/settings.json +21 -3
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
- package/configs/fastapi/.claude/rules/dependencies.md +170 -0
- package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
- package/configs/fastapi/.claude/rules/lifespan.md +274 -0
- package/configs/fastapi/.claude/rules/middleware.md +229 -0
- package/configs/fastapi/.claude/rules/pydantic.md +433 -0
- package/configs/fastapi/.claude/rules/responses.md +251 -0
- package/configs/fastapi/.claude/rules/routers.md +202 -0
- package/configs/fastapi/.claude/rules/security.md +222 -0
- package/configs/fastapi/.claude/rules/testing.md +251 -0
- package/configs/fastapi/.claude/rules/websockets.md +298 -0
- package/configs/fastapi/.claude/settings.json +33 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/flask/.claude/rules/blueprints.md +208 -0
- package/configs/flask/.claude/rules/cli.md +285 -0
- package/configs/flask/.claude/rules/configuration.md +281 -0
- package/configs/flask/.claude/rules/context.md +238 -0
- package/configs/flask/.claude/rules/error-handlers.md +278 -0
- package/configs/flask/.claude/rules/extensions.md +278 -0
- package/configs/flask/.claude/rules/flask.md +171 -0
- package/configs/flask/.claude/rules/marshmallow.md +206 -0
- package/configs/flask/.claude/rules/security.md +267 -0
- package/configs/flask/.claude/rules/testing.md +284 -0
- package/configs/flask/.claude/settings.json +33 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
- package/configs/nestjs/.claude/rules/filters.md +376 -0
- package/configs/nestjs/.claude/rules/interceptors.md +317 -0
- package/configs/nestjs/.claude/rules/middleware.md +321 -0
- package/configs/nestjs/.claude/rules/modules.md +26 -0
- package/configs/nestjs/.claude/rules/pipes.md +351 -0
- package/configs/nestjs/.claude/rules/websockets.md +451 -0
- package/configs/nestjs/.claude/settings.json +16 -2
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nextjs/.claude/rules/api-routes.md +358 -0
- package/configs/nextjs/.claude/rules/authentication.md +355 -0
- package/configs/nextjs/.claude/rules/components.md +52 -0
- package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
- package/configs/nextjs/.claude/rules/database.md +400 -0
- package/configs/nextjs/.claude/rules/middleware.md +303 -0
- package/configs/nextjs/.claude/rules/routing.md +324 -0
- package/configs/nextjs/.claude/rules/seo.md +350 -0
- package/configs/nextjs/.claude/rules/server-actions.md +353 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
- package/configs/nextjs/.claude/settings.json +5 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/package.json +23 -9
- package/src/cli.js +220 -0
- package/src/config.js +29 -0
- package/src/index.js +13 -0
- package/src/installer.js +361 -0
- package/src/merge.js +116 -0
- package/src/tech-config.json +29 -0
- package/src/utils.js +96 -0
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
- /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
- /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "Dockerfile"
|
|
4
|
+
- "docker-compose*.yml"
|
|
5
|
+
- "gunicorn.conf.py"
|
|
6
|
+
- "uvicorn_config.py"
|
|
7
|
+
- "**/deploy/**"
|
|
8
|
+
- ".github/workflows/*.yml"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Python Deployment
|
|
12
|
+
|
|
13
|
+
## Dockerfile (FastAPI)
|
|
14
|
+
|
|
15
|
+
```dockerfile
|
|
16
|
+
# Dockerfile
|
|
17
|
+
FROM python:3.12-slim AS base
|
|
18
|
+
|
|
19
|
+
# Set environment variables
|
|
20
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
21
|
+
PYTHONUNBUFFERED=1 \
|
|
22
|
+
PYTHONFAULTHANDLER=1 \
|
|
23
|
+
PIP_NO_CACHE_DIR=1 \
|
|
24
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
25
|
+
|
|
26
|
+
WORKDIR /app
|
|
27
|
+
|
|
28
|
+
# Install system dependencies
|
|
29
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
30
|
+
curl \
|
|
31
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
32
|
+
|
|
33
|
+
# Builder stage
|
|
34
|
+
FROM base AS builder
|
|
35
|
+
|
|
36
|
+
# Install build dependencies
|
|
37
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
38
|
+
build-essential \
|
|
39
|
+
libpq-dev \
|
|
40
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
41
|
+
|
|
42
|
+
# Install Python dependencies
|
|
43
|
+
COPY requirements.txt .
|
|
44
|
+
RUN pip install --user -r requirements.txt
|
|
45
|
+
|
|
46
|
+
# Production stage
|
|
47
|
+
FROM base AS production
|
|
48
|
+
|
|
49
|
+
# Copy installed packages from builder
|
|
50
|
+
COPY --from=builder /root/.local /root/.local
|
|
51
|
+
ENV PATH=/root/.local/bin:$PATH
|
|
52
|
+
|
|
53
|
+
# Copy application code
|
|
54
|
+
COPY ./app ./app
|
|
55
|
+
COPY ./alembic ./alembic
|
|
56
|
+
COPY alembic.ini .
|
|
57
|
+
|
|
58
|
+
# Create non-root user
|
|
59
|
+
RUN useradd --create-home --shell /bin/bash appuser
|
|
60
|
+
USER appuser
|
|
61
|
+
|
|
62
|
+
# Health check
|
|
63
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
64
|
+
CMD curl -f http://localhost:8000/health || exit 1
|
|
65
|
+
|
|
66
|
+
EXPOSE 8000
|
|
67
|
+
|
|
68
|
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Gunicorn Configuration
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# gunicorn.conf.py
|
|
75
|
+
import multiprocessing
|
|
76
|
+
import os
|
|
77
|
+
|
|
78
|
+
# Server socket
|
|
79
|
+
bind = os.getenv("BIND", "0.0.0.0:8000")
|
|
80
|
+
backlog = 2048
|
|
81
|
+
|
|
82
|
+
# Worker processes
|
|
83
|
+
workers = int(os.getenv("WORKERS", multiprocessing.cpu_count() * 2 + 1))
|
|
84
|
+
worker_class = "uvicorn.workers.UvicornWorker"
|
|
85
|
+
worker_connections = 1000
|
|
86
|
+
max_requests = 1000
|
|
87
|
+
max_requests_jitter = 100
|
|
88
|
+
timeout = 120
|
|
89
|
+
graceful_timeout = 30
|
|
90
|
+
keepalive = 5
|
|
91
|
+
|
|
92
|
+
# Preload app for memory efficiency
|
|
93
|
+
preload_app = True
|
|
94
|
+
|
|
95
|
+
# Logging
|
|
96
|
+
accesslog = "-"
|
|
97
|
+
errorlog = "-"
|
|
98
|
+
loglevel = os.getenv("LOG_LEVEL", "info")
|
|
99
|
+
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
|
|
100
|
+
|
|
101
|
+
# Process naming
|
|
102
|
+
proc_name = "myapp"
|
|
103
|
+
|
|
104
|
+
# Server mechanics
|
|
105
|
+
daemon = False
|
|
106
|
+
pidfile = None
|
|
107
|
+
umask = 0
|
|
108
|
+
user = None
|
|
109
|
+
group = None
|
|
110
|
+
tmp_upload_dir = None
|
|
111
|
+
|
|
112
|
+
# Hooks
|
|
113
|
+
def on_starting(server):
|
|
114
|
+
"""Called before the master process is initialized."""
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def on_reload(server):
|
|
119
|
+
"""Called to recycle workers during a reload via SIGHUP."""
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def worker_int(worker):
|
|
124
|
+
"""Called when a worker receives SIGINT or SIGQUIT."""
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def worker_abort(worker):
|
|
129
|
+
"""Called when a worker receives SIGABRT."""
|
|
130
|
+
pass
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Docker Compose
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
# docker-compose.yml
|
|
137
|
+
services:
|
|
138
|
+
app:
|
|
139
|
+
build:
|
|
140
|
+
context: .
|
|
141
|
+
target: production
|
|
142
|
+
ports:
|
|
143
|
+
- "8000:8000"
|
|
144
|
+
environment:
|
|
145
|
+
- DATABASE_URL=postgresql://user:pass@db:5432/app
|
|
146
|
+
- REDIS_URL=redis://redis:6379/0
|
|
147
|
+
- SECRET_KEY=${SECRET_KEY}
|
|
148
|
+
depends_on:
|
|
149
|
+
db:
|
|
150
|
+
condition: service_healthy
|
|
151
|
+
redis:
|
|
152
|
+
condition: service_started
|
|
153
|
+
healthcheck:
|
|
154
|
+
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
|
155
|
+
interval: 30s
|
|
156
|
+
timeout: 10s
|
|
157
|
+
retries: 3
|
|
158
|
+
deploy:
|
|
159
|
+
replicas: 2
|
|
160
|
+
resources:
|
|
161
|
+
limits:
|
|
162
|
+
cpus: "1"
|
|
163
|
+
memory: 512M
|
|
164
|
+
reservations:
|
|
165
|
+
cpus: "0.25"
|
|
166
|
+
memory: 256M
|
|
167
|
+
|
|
168
|
+
worker:
|
|
169
|
+
build:
|
|
170
|
+
context: .
|
|
171
|
+
target: production
|
|
172
|
+
command: celery -A app.celery worker --loglevel=info
|
|
173
|
+
environment:
|
|
174
|
+
- DATABASE_URL=postgresql://user:pass@db:5432/app
|
|
175
|
+
- REDIS_URL=redis://redis:6379/0
|
|
176
|
+
depends_on:
|
|
177
|
+
- db
|
|
178
|
+
- redis
|
|
179
|
+
deploy:
|
|
180
|
+
replicas: 2
|
|
181
|
+
|
|
182
|
+
beat:
|
|
183
|
+
build:
|
|
184
|
+
context: .
|
|
185
|
+
target: production
|
|
186
|
+
command: celery -A app.celery beat --loglevel=info
|
|
187
|
+
environment:
|
|
188
|
+
- DATABASE_URL=postgresql://user:pass@db:5432/app
|
|
189
|
+
- REDIS_URL=redis://redis:6379/0
|
|
190
|
+
depends_on:
|
|
191
|
+
- db
|
|
192
|
+
- redis
|
|
193
|
+
|
|
194
|
+
db:
|
|
195
|
+
image: postgres:16-alpine
|
|
196
|
+
volumes:
|
|
197
|
+
- postgres_data:/var/lib/postgresql/data
|
|
198
|
+
environment:
|
|
199
|
+
- POSTGRES_USER=user
|
|
200
|
+
- POSTGRES_PASSWORD=pass
|
|
201
|
+
- POSTGRES_DB=app
|
|
202
|
+
healthcheck:
|
|
203
|
+
test: ["CMD-SHELL", "pg_isready -U user -d app"]
|
|
204
|
+
interval: 10s
|
|
205
|
+
timeout: 5s
|
|
206
|
+
retries: 5
|
|
207
|
+
|
|
208
|
+
redis:
|
|
209
|
+
image: redis:7-alpine
|
|
210
|
+
volumes:
|
|
211
|
+
- redis_data:/data
|
|
212
|
+
|
|
213
|
+
volumes:
|
|
214
|
+
postgres_data:
|
|
215
|
+
redis_data:
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## GitHub Actions CI/CD
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
# .github/workflows/deploy.yml
|
|
222
|
+
name: Deploy
|
|
223
|
+
|
|
224
|
+
on:
|
|
225
|
+
push:
|
|
226
|
+
branches: [main]
|
|
227
|
+
|
|
228
|
+
env:
|
|
229
|
+
REGISTRY: ghcr.io
|
|
230
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
231
|
+
|
|
232
|
+
jobs:
|
|
233
|
+
test:
|
|
234
|
+
runs-on: ubuntu-latest
|
|
235
|
+
services:
|
|
236
|
+
postgres:
|
|
237
|
+
image: postgres:16
|
|
238
|
+
env:
|
|
239
|
+
POSTGRES_PASSWORD: test
|
|
240
|
+
options: >-
|
|
241
|
+
--health-cmd pg_isready
|
|
242
|
+
--health-interval 10s
|
|
243
|
+
--health-timeout 5s
|
|
244
|
+
--health-retries 5
|
|
245
|
+
ports:
|
|
246
|
+
- 5432:5432
|
|
247
|
+
|
|
248
|
+
steps:
|
|
249
|
+
- uses: actions/checkout@v4
|
|
250
|
+
|
|
251
|
+
- name: Set up Python
|
|
252
|
+
uses: actions/setup-python@v5
|
|
253
|
+
with:
|
|
254
|
+
python-version: "3.12"
|
|
255
|
+
cache: "pip"
|
|
256
|
+
|
|
257
|
+
- name: Install dependencies
|
|
258
|
+
run: |
|
|
259
|
+
pip install -r requirements.txt
|
|
260
|
+
pip install -r requirements-dev.txt
|
|
261
|
+
|
|
262
|
+
- name: Run linting
|
|
263
|
+
run: |
|
|
264
|
+
ruff check .
|
|
265
|
+
ruff format --check .
|
|
266
|
+
|
|
267
|
+
- name: Run type checking
|
|
268
|
+
run: mypy app
|
|
269
|
+
|
|
270
|
+
- name: Run tests
|
|
271
|
+
run: pytest --cov=app --cov-report=xml
|
|
272
|
+
env:
|
|
273
|
+
DATABASE_URL: postgresql://postgres:test@localhost:5432/test
|
|
274
|
+
|
|
275
|
+
- name: Upload coverage
|
|
276
|
+
uses: codecov/codecov-action@v4
|
|
277
|
+
with:
|
|
278
|
+
file: ./coverage.xml
|
|
279
|
+
|
|
280
|
+
build:
|
|
281
|
+
needs: test
|
|
282
|
+
runs-on: ubuntu-latest
|
|
283
|
+
permissions:
|
|
284
|
+
contents: read
|
|
285
|
+
packages: write
|
|
286
|
+
|
|
287
|
+
steps:
|
|
288
|
+
- uses: actions/checkout@v4
|
|
289
|
+
|
|
290
|
+
- name: Set up Docker Buildx
|
|
291
|
+
uses: docker/setup-buildx-action@v3
|
|
292
|
+
|
|
293
|
+
- name: Log in to Container Registry
|
|
294
|
+
uses: docker/login-action@v3
|
|
295
|
+
with:
|
|
296
|
+
registry: ${{ env.REGISTRY }}
|
|
297
|
+
username: ${{ github.actor }}
|
|
298
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
299
|
+
|
|
300
|
+
- name: Extract metadata
|
|
301
|
+
id: meta
|
|
302
|
+
uses: docker/metadata-action@v5
|
|
303
|
+
with:
|
|
304
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
305
|
+
tags: |
|
|
306
|
+
type=sha
|
|
307
|
+
type=raw,value=latest,enable={{is_default_branch}}
|
|
308
|
+
|
|
309
|
+
- name: Build and push
|
|
310
|
+
uses: docker/build-push-action@v5
|
|
311
|
+
with:
|
|
312
|
+
context: .
|
|
313
|
+
push: true
|
|
314
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
315
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
316
|
+
cache-from: type=gha
|
|
317
|
+
cache-to: type=gha,mode=max
|
|
318
|
+
|
|
319
|
+
deploy:
|
|
320
|
+
needs: build
|
|
321
|
+
runs-on: ubuntu-latest
|
|
322
|
+
environment: production
|
|
323
|
+
|
|
324
|
+
steps:
|
|
325
|
+
- name: Deploy to production
|
|
326
|
+
run: |
|
|
327
|
+
# Trigger deployment (e.g., ArgoCD, kubectl, etc.)
|
|
328
|
+
curl -X POST ${{ secrets.DEPLOY_WEBHOOK_URL }}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Kubernetes Deployment
|
|
332
|
+
|
|
333
|
+
```yaml
|
|
334
|
+
# k8s/deployment.yaml
|
|
335
|
+
apiVersion: apps/v1
|
|
336
|
+
kind: Deployment
|
|
337
|
+
metadata:
|
|
338
|
+
name: myapp
|
|
339
|
+
labels:
|
|
340
|
+
app: myapp
|
|
341
|
+
spec:
|
|
342
|
+
replicas: 3
|
|
343
|
+
selector:
|
|
344
|
+
matchLabels:
|
|
345
|
+
app: myapp
|
|
346
|
+
template:
|
|
347
|
+
metadata:
|
|
348
|
+
labels:
|
|
349
|
+
app: myapp
|
|
350
|
+
spec:
|
|
351
|
+
containers:
|
|
352
|
+
- name: app
|
|
353
|
+
image: ghcr.io/org/myapp:latest
|
|
354
|
+
ports:
|
|
355
|
+
- containerPort: 8000
|
|
356
|
+
env:
|
|
357
|
+
- name: DATABASE_URL
|
|
358
|
+
valueFrom:
|
|
359
|
+
secretKeyRef:
|
|
360
|
+
name: myapp-secrets
|
|
361
|
+
key: database-url
|
|
362
|
+
resources:
|
|
363
|
+
requests:
|
|
364
|
+
memory: "256Mi"
|
|
365
|
+
cpu: "250m"
|
|
366
|
+
limits:
|
|
367
|
+
memory: "512Mi"
|
|
368
|
+
cpu: "1000m"
|
|
369
|
+
readinessProbe:
|
|
370
|
+
httpGet:
|
|
371
|
+
path: /health
|
|
372
|
+
port: 8000
|
|
373
|
+
initialDelaySeconds: 5
|
|
374
|
+
periodSeconds: 10
|
|
375
|
+
livenessProbe:
|
|
376
|
+
httpGet:
|
|
377
|
+
path: /health
|
|
378
|
+
port: 8000
|
|
379
|
+
initialDelaySeconds: 15
|
|
380
|
+
periodSeconds: 20
|
|
381
|
+
---
|
|
382
|
+
apiVersion: v1
|
|
383
|
+
kind: Service
|
|
384
|
+
metadata:
|
|
385
|
+
name: myapp
|
|
386
|
+
spec:
|
|
387
|
+
selector:
|
|
388
|
+
app: myapp
|
|
389
|
+
ports:
|
|
390
|
+
- port: 80
|
|
391
|
+
targetPort: 8000
|
|
392
|
+
---
|
|
393
|
+
apiVersion: networking.k8s.io/v1
|
|
394
|
+
kind: Ingress
|
|
395
|
+
metadata:
|
|
396
|
+
name: myapp
|
|
397
|
+
annotations:
|
|
398
|
+
kubernetes.io/ingress.class: nginx
|
|
399
|
+
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
400
|
+
spec:
|
|
401
|
+
tls:
|
|
402
|
+
- hosts:
|
|
403
|
+
- api.example.com
|
|
404
|
+
secretName: myapp-tls
|
|
405
|
+
rules:
|
|
406
|
+
- host: api.example.com
|
|
407
|
+
http:
|
|
408
|
+
paths:
|
|
409
|
+
- path: /
|
|
410
|
+
pathType: Prefix
|
|
411
|
+
backend:
|
|
412
|
+
service:
|
|
413
|
+
name: myapp
|
|
414
|
+
port:
|
|
415
|
+
number: 80
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Health Check Endpoint
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
# app/api/health.py
|
|
422
|
+
from fastapi import APIRouter, Depends
|
|
423
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
424
|
+
from sqlalchemy import text
|
|
425
|
+
from redis import Redis
|
|
426
|
+
import time
|
|
427
|
+
|
|
428
|
+
router = APIRouter(tags=["health"])
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@router.get("/health")
|
|
432
|
+
async def health_check(
|
|
433
|
+
db: AsyncSession = Depends(get_db),
|
|
434
|
+
redis: Redis = Depends(get_redis),
|
|
435
|
+
) -> dict:
|
|
436
|
+
"""Health check endpoint for load balancers."""
|
|
437
|
+
checks = {}
|
|
438
|
+
|
|
439
|
+
# Database check
|
|
440
|
+
try:
|
|
441
|
+
start = time.time()
|
|
442
|
+
await db.execute(text("SELECT 1"))
|
|
443
|
+
checks["database"] = {
|
|
444
|
+
"status": "healthy",
|
|
445
|
+
"latency_ms": round((time.time() - start) * 1000, 2),
|
|
446
|
+
}
|
|
447
|
+
except Exception as e:
|
|
448
|
+
checks["database"] = {"status": "unhealthy", "error": str(e)}
|
|
449
|
+
|
|
450
|
+
# Redis check
|
|
451
|
+
try:
|
|
452
|
+
start = time.time()
|
|
453
|
+
redis.ping()
|
|
454
|
+
checks["redis"] = {
|
|
455
|
+
"status": "healthy",
|
|
456
|
+
"latency_ms": round((time.time() - start) * 1000, 2),
|
|
457
|
+
}
|
|
458
|
+
except Exception as e:
|
|
459
|
+
checks["redis"] = {"status": "unhealthy", "error": str(e)}
|
|
460
|
+
|
|
461
|
+
# Overall status
|
|
462
|
+
all_healthy = all(c.get("status") == "healthy" for c in checks.values())
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
"status": "healthy" if all_healthy else "unhealthy",
|
|
466
|
+
"checks": checks,
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
@router.get("/ready")
|
|
471
|
+
async def readiness_check() -> dict:
|
|
472
|
+
"""Readiness probe - is the app ready to receive traffic?"""
|
|
473
|
+
return {"status": "ready"}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
@router.get("/live")
|
|
477
|
+
async def liveness_check() -> dict:
|
|
478
|
+
"""Liveness probe - is the app alive?"""
|
|
479
|
+
return {"status": "alive"}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Anti-patterns
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
# BAD: Running as root
|
|
486
|
+
FROM python:3.12
|
|
487
|
+
WORKDIR /app
|
|
488
|
+
COPY . .
|
|
489
|
+
CMD ["python", "main.py"] # Runs as root!
|
|
490
|
+
|
|
491
|
+
# GOOD: Non-root user
|
|
492
|
+
RUN useradd --create-home appuser
|
|
493
|
+
USER appuser
|
|
494
|
+
|
|
495
|
+
# BAD: Installing dev dependencies in production
|
|
496
|
+
COPY requirements.txt requirements-dev.txt ./
|
|
497
|
+
RUN pip install -r requirements.txt -r requirements-dev.txt
|
|
498
|
+
|
|
499
|
+
# GOOD: Only production dependencies
|
|
500
|
+
COPY requirements.txt ./
|
|
501
|
+
RUN pip install -r requirements.txt
|
|
502
|
+
|
|
503
|
+
# BAD: No health checks
|
|
504
|
+
# Container appears healthy even if app is broken
|
|
505
|
+
|
|
506
|
+
# GOOD: Proper health checks
|
|
507
|
+
HEALTHCHECK CMD curl -f http://localhost:8000/health || exit 1
|
|
508
|
+
|
|
509
|
+
# BAD: Hardcoded secrets
|
|
510
|
+
ENV DATABASE_URL=postgresql://user:password@db/app
|
|
511
|
+
|
|
512
|
+
# GOOD: Use secrets or environment variables at runtime
|
|
513
|
+
ENV DATABASE_URL=${DATABASE_URL}
|
|
514
|
+
|
|
515
|
+
# BAD: No resource limits
|
|
516
|
+
# Worker can consume all memory
|
|
517
|
+
|
|
518
|
+
# GOOD: Set resource limits in docker-compose/k8s
|
|
519
|
+
deploy:
|
|
520
|
+
resources:
|
|
521
|
+
limits:
|
|
522
|
+
memory: 512M
|
|
523
|
+
```
|