Files
riotpiao.com/app/homelab/article/page.tsx
T
Story Crater Bot 17ef6d8a8a
CI / CI (pull_request) Failing after 1m46s
feat: add architecture diagrams page with archify visualizations
- Create /diagrams page with interactive homelab architecture visualizations
- Add deepDive link to homelab project pointing to diagrams
- Include 4 archify diagrams:
  * Talos cluster topology (control planes, GPU worker, storage)
  * GitOps workflow (ArgoCD + Terraform)
  * LLM inference stack (vLLM, Ollama, TEI routing)
  * API request flow sequence diagram
- Support full-screen viewer and embedded grid layout
- Dark/light theme support with responsive design
2026-09-09 14:22:42 +09:00

302 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import Link from 'next/link'
import { ArrowLeft, Clock, Zap, BookOpen } from 'lucide-react'
import { motion } from 'framer-motion'
const sections = [
{
chapter: 'Chapter 1',
title: 'AWS Step Functions: The Foundation',
period: '20222024',
icon: '🏗️',
highlights: [
'Owned Distributed-Map end-to-end: design → production across 57+ regions',
'Sub-100ms P99 latency, 20x burst handling',
'Learned deployment alignment: frontend spec updates must sync with service deployments',
'Built Checkpoint recovery: customers resume mid-workflow without re-runs',
'Solved distributed edge cases: race conditions, concurrent updates, dedup',
'Oncall mastery: CloudWatch dashboards, runbooks, production debugging'
],
keyLearning: 'Backward compatibility is invisible until it breaks silently. A missed spec change cascades across regions.'
},
{
chapter: 'Chapter 2',
title: 'RBC: Infrastructure as Code & Observability',
period: 'Nov 2024 May 2025',
icon: '⚡',
highlights: [
'Problem: Unstable deployments with 500+ resource state files, API rate-limits, race conditions',
'Solution: JFrog Artifactory backend + workspace prefixes, plan artifacts, -parallelism=5 throttle',
'Result: Zero state corruption, zero pipeline blockage',
'Problem: Configuration drift invisible for weeks, massive unreadable diffs',
'Solution: Nightly terraform plan -refresh-only -detailed-exitcode → Slack webhook',
'Result: Drift visibility from 3 weeks → <24 hours'
],
keyLearning: 'Terraform wins for things that barely change. For K8s resources that churn (pods, configmaps), you need a different tool—GitOps.'
},
{
chapter: 'Chapter 3',
title: 'Homelab: Building AWS from Scratch',
period: 'May 2025 Present',
icon: '🚀',
highlights: [
'Question: How does LLM serving work at scale? Build it at home.',
'4 bare-metal machines: 1 GPU node, 1 Dell PowerEdge, 2 mini-desktops',
'Discovery: Powerline adapters killed etcd consensus. Ran ethernet to garage.',
'Wildcard DNS via Cloudflare, HTTPS for all subdomains (cert-manager + Let\'s Encrypt)',
'GitOps split: Terraform for Talos machine config, ArgoCD for K8s resources',
'99.2% uptime with Talos Linux, Cilium CNI, Longhorn, PostgreSQL HA, Prometheus+Grafana+Loki'
],
keyLearning: 'Design infrastructure so AI can modify it easily. GitOps + immutable OS = no surprises.'
},
{
chapter: 'Chapter 4',
title: 'Poimen: AI Meets Workflows',
period: 'Building',
icon: '🤖',
highlights: [
'Insight: AI is powerful because of context, tool-calling, and memory.',
'poimen-memory: Graph-RAG with wiki-link indexing, pgvector + OpenSearch hybrid search',
'poimen-workflows: Natural language → executable Temporal workflows via reasoning model',
'Activity Knowledge Base informs LLM about timeouts, retries, dependencies',
'Generic state machine: JSON workflow spec + JSONPath parameter chaining',
'60% latency reduction for LLM serving (vLLM INT4 quantization, custom Go gateway)'
],
keyLearning: 'Skill factory: each Temporal activity is independently refined. General-purpose workflow orchestration emerges.'
}
]
const technologies = {
'Infrastructure': ['Talos Linux', 'Kubernetes', 'Cilium CNI', 'Terraform', 'kustomize'],
'Data': ['PostgreSQL + HA', 'pgvector', 'Kafka/Redpanda', 'MinIO', 'Longhorn'],
'GitOps': ['ArgoCD', 'Forgejo', 'Docker-in-Docker'],
'Identity': ['Authentik OIDC', 'RBAC', 'SOPS encrypted secrets', 'cert-manager'],
'Observability': ['Prometheus', 'Grafana', 'Loki', 'Tempo', 'OpenTelemetry'],
'AI/ML': ['vLLM', 'Ollama', 'TEI', 'KServe', 'Temporal', 'Qwen3-32B'],
}
const keyInsights = [
{
title: 'Deployment Alignment',
description: 'When frontend consumes the latest API, you need backward-compat checks. A missed spec change breaks customers silently across all regions.',
},
{
title: 'Infrastructure as Code Split',
description: 'Terraform for things that rarely change (machine config). GitOps for things that churn (pods, configmaps). Different tools, different philosophies.',
},
{
title: 'Network is Critical',
description: 'Powerline adapters killed etcd consensus at 200ms latency. Ethernet cable to garage solved it. Hardware matters.',
},
{
title: 'AI-Friendly Infra',
description: 'Design systems so AI can modify them. Declarative configs + clear abstractions = easier for models to reason about.',
},
{
title: 'Observability First',
description: 'Drift detection <24hr. Prometheus + Grafana dashboards before you have problems. Not post-mortem tools.',
},
]
export default function HomelabArticle() {
return (
<main className="min-h-screen bg-white dark:bg-gray-950">
{/* Hero */}
<section className="bg-gradient-to-br from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-900 border-b border-gray-200 dark:border-gray-800">
<div className="max-w-3xl mx-auto px-6 py-16">
<Link
href="/homelab"
className="inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:underline mb-8"
>
<ArrowLeft size={16} />
Back to Homelab
</Link>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<h1 className="text-5xl font-bold text-gray-900 dark:text-white mb-4 leading-tight">
Building AWS at Home:<br />A 3-Year Journey
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 mb-6">
From Step Functions to LLM inferencehow distributed systems knowledge compounds.
</p>
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1">
<Clock size={16} />
<span>12 min read</span>
</div>
<span></span>
<span>May 2025</span>
</div>
</motion.div>
</div>
</section>
{/* Main Content */}
<article className="max-w-3xl mx-auto px-6 py-20">
{/* Intro */}
<motion.section
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="mb-16 prose prose-lg dark:prose-invert max-w-none"
>
<p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed mb-4">
There's a difference between knowing how systems work in theory and building them in production. I've spent the last 3 years learning this difference the hard wayfirst at AWS, then at RBC, and now at home.
</p>
<p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed">
This is the story of how I learned distributed systems by owning every layer: from workflow orchestration to hardware networking, from GitOps to AI agents.
</p>
</motion.section>
{/* Chapters */}
<div className="space-y-24">
{sections.map((section, idx) => (
<motion.section
key={section.chapter}
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1 }}
className="border-l-4 border-blue-500 pl-8"
>
<div className="flex items-baseline gap-3 mb-4">
<span className="text-4xl">{section.icon}</span>
<div>
<div className="text-sm font-semibold text-blue-600 dark:text-blue-400 uppercase tracking-wide">
{section.chapter}
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white">
{section.title}
</h2>
</div>
</div>
<div className="text-sm text-gray-600 dark:text-gray-400 mb-6 font-medium">
{section.period}
</div>
{/* Highlights */}
<ul className="space-y-3 mb-8">
{section.highlights.map((highlight, i) => (
<li key={i} className="flex gap-3 text-gray-700 dark:text-gray-300">
<span className="text-blue-500 font-bold mt-0.5 flex-shrink-0"></span>
<span>{highlight}</span>
</li>
))}
</ul>
{/* Key Learning */}
<div className="bg-blue-50 dark:bg-blue-900/30 border-l-4 border-blue-500 p-4 rounded-r">
<div className="font-semibold text-blue-900 dark:text-blue-200 mb-1">Key Learning</div>
<p className="text-blue-800 dark:text-blue-100 text-sm">
{section.keyLearning}
</p>
</div>
</motion.section>
))}
</div>
{/* Key Insights */}
<motion.section
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="mt-24 pt-16 border-t border-gray-200 dark:border-gray-800"
>
<div className="flex items-center gap-2 mb-8">
<Zap size={24} className="text-yellow-500" />
<h3 className="text-2xl font-bold text-gray-900 dark:text-white">
Core Insights
</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{keyInsights.map((insight, idx) => (
<motion.div
key={idx}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.05 }}
className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800"
>
<h4 className="font-bold text-gray-900 dark:text-white mb-2">
{insight.title}
</h4>
<p className="text-sm text-gray-600 dark:text-gray-400">
{insight.description}
</p>
</motion.div>
))}
</div>
</motion.section>
{/* Tech Stack */}
<motion.section
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="mt-24 pt-16 border-t border-gray-200 dark:border-gray-800"
>
<div className="flex items-center gap-2 mb-8">
<BookOpen size={24} className="text-purple-500" />
<h3 className="text-2xl font-bold text-gray-900 dark:text-white">
Technologies Mastered
</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{Object.entries(technologies).map(([category, techs]) => (
<motion.div key={category}
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
>
<h4 className="font-bold text-gray-900 dark:text-white mb-3">
{category}
</h4>
<div className="flex flex-wrap gap-2">
{techs.map((tech) => (
<span
key={tech}
className="text-xs px-3 py-1.5 rounded-full bg-gradient-to-r from-blue-100 to-purple-100 dark:from-blue-900 dark:to-purple-900 text-gray-800 dark:text-gray-200 font-medium"
>
{tech}
</span>
))}
</div>
</motion.div>
))}
</div>
</motion.section>
{/* CTA */}
<motion.section
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="mt-24 pt-16 border-t border-gray-200 dark:border-gray-800 text-center"
>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Want the full details?
</h3>
<p className="text-gray-600 dark:text-gray-400 mb-6">
Ask Poimen any technical question about the architecture, deployment strategies, or lessons learned.
</p>
<Link
href="/#project-homelab"
className="inline-block bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg font-semibold transition"
>
Ask Poimen
</Link>
</motion.section>
</article>
</main>
)
}