P5: Mark devpi migration complete

- Add P5_devpi.complete.md with implementation notes
- Document key learnings (registry mirrors, memory, env vars)
- Remove incomplete P5_devpi.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-01-20 14:54:36 -08:00
commit 765117bb9e
2 changed files with 102 additions and 190 deletions

View file

@ -0,0 +1,102 @@
# Phase 5: devpi Migration to Kubernetes
**Goal**: Migrate devpi PyPI caching proxy from indri to k8s
**Status**: Complete (2026-01-20)
**Prerequisites**: [Phase 4](P4_miniflux.complete.md) complete
---
## Summary
Successfully migrated devpi from mcquack LaunchAgent on indri to Kubernetes:
- Custom container image with devpi-server + devpi-web + auto-init startup script
- StatefulSet with 50Gi PVC for data persistence
- Tailscale Ingress at `pypi.tail8d86e.ts.net`
- Root password from 1Password secret, auto-initialized on first run
- Verified pip caching proxy and mcquack package upload
---
## Key Learnings
### Registry Mirror Configuration
- Minikube's CRI-O can't resolve Tailscale hostnames directly
- Added registry mirror config to redirect `registry.tail8d86e.ts.net``host.containers.internal:5050`
- Also added direct insecure registry entry for `host.containers.internal:5050`
- Config in `ansible/roles/minikube/files/zot-mirror.conf`
### Memory Requirements
- devpi-web's Whoosh search indexer needs significant memory during PyPI index build
- Initial 512Mi limit caused OOMKills
- Solution: High limit (2Gi) with low request (256Mi) - memory reclaimed after indexing
### Environment Variable Conflicts
- Kubernetes auto-sets `DEVPI_PORT` for service discovery
- Conflicted with our port config - renamed to `DEVPI_LISTEN_PORT`
### Tailscale Serve Cleanup
- Use `tailscale serve status --json` to see entries (non-JSON output can be empty)
- Use `tailscale serve clear svc:<name>` to remove entries
### ArgoCD Workflow
- Changed `apps` to manual sync (was auto-sync with prune)
- Workflow: sync apps → set revision to feature branch → sync service → test → reset to main after merge
---
## Verification Checklist
- [x] devpi pod healthy in k8s
- [x] https://pypi.tail8d86e.ts.net accessible
- [x] Web interface shows root/pypi index
- [x] `pip install <package>` works through proxy
- [x] mcquack v1.0.0 uploaded to eblume/dev
- [x] `pip install --index-url https://pypi.tail8d86e.ts.net/eblume/dev/+simple/ mcquack` works
- [x] Old devpi service removed from indri
- [ ] zk documentation updated (deferred - no existing devpi card)
---
## Files Changed
### New Files
| Path | Purpose |
|------|---------|
| `argocd/apps/devpi.yaml` | ArgoCD Application definition |
| `argocd/manifests/devpi/Dockerfile` | Container image with startup script |
| `argocd/manifests/devpi/start.sh` | Auto-init startup script |
| `argocd/manifests/devpi/statefulset.yaml` | StatefulSet with PVC |
| `argocd/manifests/devpi/service.yaml` | ClusterIP Service |
| `argocd/manifests/devpi/ingress-tailscale.yaml` | Tailscale Ingress |
| `argocd/manifests/devpi/kustomization.yaml` | Kustomize configuration |
| `argocd/manifests/devpi/secret-root.yaml.tpl` | 1Password secret template |
| `argocd/manifests/devpi/README.md` | Setup documentation |
### Modified Files
| Path | Change |
|------|--------|
| `CLAUDE.md` | Added k8s/ArgoCD workflow documentation |
| `ansible/playbooks/indri.yml` | Removed devpi and devpi_metrics roles |
| `ansible/roles/tailscale_serve/defaults/main.yml` | Removed svc:pypi |
| `ansible/roles/alloy/defaults/main.yml` | Removed devpi log collection |
| `ansible/roles/borgmatic/defaults/main.yml` | Removed devpi backup paths |
| `ansible/roles/minikube/files/zot-mirror.conf` | Added registry mirror for Tailscale hostname |
| `argocd/apps/apps.yaml` | Changed to manual sync policy |
### Roles Kept (not deleted)
- `ansible/roles/devpi/` - Kept for reference
- `ansible/roles/devpi_metrics/` - Kept for reference
---
## Post-Merge Cleanup
After PR merge, reset ArgoCD apps to main:
```fish
argocd app set apps --revision main
argocd app sync apps
argocd app set devpi --revision main
argocd app sync devpi
```

View file

@ -1,190 +0,0 @@
# Phase 5: devpi Migration to Kubernetes
**Goal**: Migrate devpi PyPI caching proxy from indri to k8s
**Status**: In Progress (2026-01-20)
**Prerequisites**: [Phase 4](P4_miniflux.complete.md) complete
---
## Overview
This phase migrates devpi from mcquack LaunchAgent on indri to Kubernetes:
1. Stop devpi on indri and clear Tailscale service
2. Build custom devpi container image (devpi-server + devpi-web)
3. Deploy as StatefulSet with PVC for data persistence
4. Expose via Tailscale Ingress at `pypi.tail8d86e.ts.net`
5. Initialize fresh (no data migration - just cache and one private package)
6. Validate with pip proxy and mcquack package upload
**Decision: Fresh start** - The only private package is mcquack v1.0.0 which we'll re-upload. The PyPI cache is re-fetchable.
---
## Steps
### 1. Stop devpi on indri (FIRST)
```bash
# Stop and unload the LaunchAgent
ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.eblume.devpi.plist'
# Also stop the metrics collector
ssh indri 'launchctl unload ~/Library/LaunchAgents/mcquack.eblume.devpi-metrics.plist'
# Clear any tailscale serve entries (if any)
ssh indri 'tailscale serve off --service=svc:pypi' 2>/dev/null || true
```
**User action required**: Delete the `pypi` device/service from Tailscale admin console.
### 2. Build devpi container image
Create `argocd/manifests/devpi/Dockerfile`:
```dockerfile
FROM python:3.12-slim
# Install devpi-server and devpi-web
RUN pip install --no-cache-dir devpi-server devpi-web
# Create data directory
RUN mkdir -p /devpi
# Expose default port
EXPOSE 3141
# Use ENTRYPOINT for flexibility
ENTRYPOINT ["devpi-server"]
# Default args (can be overridden)
CMD ["--serverdir", "/devpi", "--host", "0.0.0.0", "--port", "3141"]
```
Build and push to Zot:
```bash
# From gilbert (has podman)
cd argocd/manifests/devpi
podman build -t registry.tail8d86e.ts.net/blumeops/devpi:latest .
podman push registry.tail8d86e.ts.net/blumeops/devpi:latest
```
### 3. Create k8s manifests
| File | Purpose |
|------|---------|
| `argocd/apps/devpi.yaml` | ArgoCD Application definition |
| `argocd/manifests/devpi/statefulset.yaml` | StatefulSet with PVC |
| `argocd/manifests/devpi/service.yaml` | ClusterIP Service |
| `argocd/manifests/devpi/ingress-tailscale.yaml` | Tailscale Ingress for `pypi.tail8d86e.ts.net` |
| `argocd/manifests/devpi/init-job.yaml` | One-time initialization job |
| `argocd/manifests/devpi/kustomization.yaml` | Kustomize configuration |
### 4. Deploy via ArgoCD
```bash
# Point app at feature branch for testing
argocd app create devpi \
--repo ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git \
--path argocd/manifests/devpi \
--dest-server https://kubernetes.default.svc \
--dest-namespace devpi \
--revision feature/p5-devpi
# Or add to apps/ and let app-of-apps sync
argocd app sync apps
argocd app sync devpi
```
### 5. Initialize devpi
After StatefulSet is running:
```bash
# Exec into pod to initialize
kubectl -n devpi exec -it devpi-0 -- devpi-init --serverdir /devpi --root-passwd <from-1password>
# Or use init job with secret
```
Create user and index:
```bash
# From workstation
uvx devpi use https://pypi.tail8d86e.ts.net
uvx devpi login root
uvx devpi user -c eblume email=blume.erich@gmail.com
uvx devpi index -c eblume/dev bases=root/pypi
```
### 6. Update pip.conf on gilbert
The existing `~/.config/pip/pip.conf` should work unchanged since we're keeping the same hostname `pypi.tail8d86e.ts.net`.
### 7. Remove devpi ansible role
- Remove `devpi` from `ansible/playbooks/indri.yml` roles list
- Remove `devpi_metrics` from alloy textfile collectors
- Keep devpi ansible role directory for reference (or delete)
### 8. Validate
```bash
# Test pip cache proxy
pip install --index-url https://pypi.tail8d86e.ts.net/root/pypi/+simple/ requests
# Upload mcquack
cd ~/code/personal/mcquack
uv build
uv publish --publish-url https://pypi.tail8d86e.ts.net/eblume/dev/
```
---
## New Files
| Path | Purpose |
|------|---------|
| `argocd/apps/devpi.yaml` | ArgoCD Application definition |
| `argocd/manifests/devpi/Dockerfile` | Container image build |
| `argocd/manifests/devpi/statefulset.yaml` | StatefulSet with PVC |
| `argocd/manifests/devpi/service.yaml` | ClusterIP Service |
| `argocd/manifests/devpi/ingress-tailscale.yaml` | Tailscale Ingress |
| `argocd/manifests/devpi/kustomization.yaml` | Kustomize configuration |
| `argocd/manifests/devpi/README.md` | Setup documentation |
## Modified Files
| Path | Change |
|------|--------|
| `ansible/playbooks/indri.yml` | Remove devpi role |
| `ansible/roles/alloy/defaults/main.yml` | Remove devpi textfile collector |
| `ansible/roles/borgmatic/defaults/main.yml` | Remove devpi backup (no longer on indri) |
## Deleted Files
| Path | Reason |
|------|--------|
| `ansible/roles/devpi/` | Role no longer needed |
| `ansible/roles/devpi_metrics/` | Metrics collected differently in k8s |
---
## Verification Checklist
- [ ] devpi pod healthy in k8s
- [ ] https://pypi.tail8d86e.ts.net accessible
- [ ] Web interface shows root/pypi index
- [ ] `pip install <package>` works through proxy
- [ ] mcquack v1.0.0 uploaded to eblume/dev
- [ ] `pip install --index-url https://pypi.tail8d86e.ts.net/eblume/dev/+simple/ mcquack` works
- [ ] Old devpi service removed from indri
- [ ] zk documentation updated
---
## Implementation Notes
*To be filled in during implementation*