Sync blog source tree

This commit is contained in:
2026-05-10 14:29:49 +08:00
parent 7a1787a5c6
commit 2005dfda53
22 changed files with 3189 additions and 11446 deletions

240
docs/deploy-k3s.md Normal file
View File

@@ -0,0 +1,240 @@
# blog_astro K3s Deployment
Last updated: 2026-04-06
## Overview
This project is deployed to the `notion-stack` namespace in the existing K3s cluster.
Current traffic topology:
- Public entry: Traefik `IngressRoute` for `nvme0n1p.dev`
- Public service target: `notion-blog-api:3000`
- Frontend upstream inside cluster: `http://blog-astro:3000`
- Frontend runtime: Astro SSR in a container listening on `3000`
- Preferred build host: `root@10.66.66.3`
- Fallback build host: `root@156.239.238.24`
Important: the public Ingress does **not** point directly to `blog-astro`. The site is exposed through `notion-blog-api`, which proxies the frontend via `FRONTEND_UPSTREAM=http://blog-astro:3000`.
## Why Build Remotely
Local development may happen on a non-amd64 machine, while the K3s node running this workload is amd64. To avoid cross-architecture image issues, build the image on the amd64 Ubuntu host, then import it into K3s containerd on that same host.
The frontend workload is pinned to the `ubuntu` node.
## Current Kubernetes Layout
- Namespace: `notion-stack`
- Deployment: `blog-astro`
- Service: `blog-astro`
- Container port: `3000`
- Image pull mode: `Never`
- Node selector: `kubernetes.io/hostname=ubuntu`
Useful checks:
```bash
ssh root@10.66.66.3 'kubectl -n notion-stack get deploy,svc,pods -l app=blog-astro -o wide'
ssh root@10.66.66.3 'kubectl -n notion-stack get ingressroute notion-stack -o yaml'
ssh root@10.66.66.3 'kubectl -n notion-stack get deploy notion-blog-api -o yaml'
```
## Deployment Procedure
### 1. Sync the current working tree to the remote build host
This pushes the current local state, including uncommitted changes.
```bash
rsync -az --delete \
--exclude '.git' \
--exclude 'node_modules' \
--exclude 'dist' \
--exclude '.astro' \
--exclude '.vercel' \
--exclude '.DS_Store' \
./ root@10.66.66.3:/root/deploy/blog_astro/
```
If `10.66.66.3` is unavailable, use:
```bash
rsync -az --delete \
--exclude '.git' \
--exclude 'node_modules' \
--exclude 'dist' \
--exclude '.astro' \
--exclude '.vercel' \
--exclude '.DS_Store' \
./ root@156.239.238.24:/root/deploy/blog_astro/
```
### 2. Build a new uniquely tagged image on the remote host
Do not reuse `blog-astro:deploy` for the build itself. Use a timestamped tag first so the rollout target is explicit.
Example:
```bash
ssh root@10.66.66.3 '
cd /root/deploy/blog_astro &&
docker build -t blog-astro:deploy-20260406-1251 .
'
```
If the SSH session is unstable, run the build in the background and log to a file:
```bash
ssh root@10.66.66.3 '
cd /root/deploy/blog_astro &&
rm -f build.log build.ok build.fail &&
nohup sh -lc '\''docker build -t blog-astro:deploy-20260406-1251 . > build.log 2>&1 && echo ok > build.ok || echo fail > build.fail'\'' >/dev/null 2>&1 &
'
ssh root@10.66.66.3 '
cd /root/deploy/blog_astro &&
if [ -f build.ok ]; then
echo BUILD_OK
elif [ -f build.fail ]; then
echo BUILD_FAIL
else
echo BUILD_RUNNING
fi &&
tail -n 40 build.log
'
```
### 3. Import the built image into K3s containerd
The Docker daemon image is not automatically visible to K3s. Import it explicitly:
```bash
ssh root@10.66.66.3 '
docker save blog-astro:deploy-20260406-1251 | ctr -n k8s.io images import -
'
```
Verify the imported image exists:
```bash
ssh root@10.66.66.3 '
ctr -n k8s.io images ls | grep "blog-astro.*deploy-20260406-1251"
'
```
### 4. Update the Deployment to the new image
```bash
ssh root@10.66.66.3 '
kubectl -n notion-stack set image deployment/blog-astro \
blog-astro=blog-astro:deploy-20260406-1251 &&
kubectl -n notion-stack rollout status deployment/blog-astro --timeout=180s
'
```
Inspect the result:
```bash
ssh root@10.66.66.3 '
kubectl -n notion-stack get deploy blog-astro -o wide &&
kubectl -n notion-stack get pods -l app=blog-astro -o wide
'
```
## Verification
### Verify the frontend service directly
```bash
ssh root@10.66.66.3 '
curl -sS -o /tmp/blog_home.html -w "%{http_code}\n" http://10.43.98.176:3000/ &&
grep -o "<title>[^<]*</title>" /tmp/blog_home.html | head -n 1
'
```
### Verify through the public entrypoint
This confirms Traefik -> `notion-blog-api` -> `blog-astro` is still correct.
```bash
ssh root@10.66.66.3 '
curl -sS -H "Host: nvme0n1p.dev" \
-o /tmp/ext_home.html \
-w "%{http_code}\n" \
http://127.0.0.1:31349/ &&
grep -o "<title>[^<]*</title>" /tmp/ext_home.html | head -n 1
'
```
### 2026-04-06 verification example
- New image: `blog-astro:deploy-20260406-1251`
- New pod: `blog-astro-58f9c8cf4c-bj9zv`
- Frontend service `/`: `200`, title `Home | 溴化锂的笔记本`
- Frontend service `/blog/apfs-sparsebundle-linux`: `200`
- Public entry `/`: `200`, title `Home | 溴化锂的笔记本`
- Public entry `/blog/apfs-sparsebundle-linux`: `200`
## Rollback
List old ReplicaSets and images:
```bash
ssh root@10.66.66.3 '
kubectl -n notion-stack get rs -l app=blog-astro &&
ctr -n k8s.io images ls | grep blog-astro
'
```
Rollback by switching the Deployment image back to the previous known-good tag:
```bash
ssh root@10.66.66.3 '
kubectl -n notion-stack set image deployment/blog-astro \
blog-astro=blog-astro:deploy
'
```
If the previous version also used a unique tag, set that exact tag instead.
## Troubleshooting
### SSH connects on port 22 but does not return a banner
Symptom:
- `nc -vz <host> 22` succeeds
- `ssh` hangs or fails during banner exchange
Likely cause:
- `sshd` is overloaded or limiting new unauthenticated sessions
Mitigation:
- Avoid many parallel SSH probes
- Prefer a single session
- Run long builds with `nohup` and inspect `build.log`
### New Docker image is built but Pods still use the old one
Cause:
- K3s uses containerd, and `imagePullPolicy: Never` means Kubernetes only sees images already imported into containerd
Fix:
```bash
ssh root@10.66.66.3 '
docker save blog-astro:<tag> | ctr -n k8s.io images import -
'
```
### Public site still works even though Ingress does not point to `blog-astro`
This is expected. Public traffic goes:
`Traefik -> notion-blog-api -> blog-astro`
So frontend deployments normally do not require Ingress changes.