[{"content":"Docker Compose 是单机多容器编排的标配工具，这里整理一些用得上的技巧。\n1. 多环境配置 用 docker-compose.override.yml 自动覆盖：\n# docker-compose.yml services: web: image: myapp ports: - \u0026#34;80:80\u0026#34; # docker-compose.override.yml（开发时用） services: web: ports: - \u0026#34;8080:80\u0026#34; volumes: - ./src:/app/src environment: DEBUG: \u0026#34;true\u0026#34; docker-compose up 会自动合并两个文件。生产环境用 docker-compose -f docker-compose.yml -f docker-compose.prod.yml up。\n2. 健康检查 services: db: image: postgres:16 healthcheck: test: [\u0026#34;CMD-SHELL\u0026#34;, \u0026#34;pg_isready -U postgres\u0026#34;] interval: 10s timeout: 5s retries: 5 start_period: 30s 其他服务可以依赖健康状态：\nservices: web: depends_on: db: condition: service_healthy 3. 资源限制 防止某个容器吃光内存：\nservices: web: image: myapp deploy: resources: limits: cpus: \u0026#34;1.0\u0026#34; memory: 512M reservations: memory: 128M 注意 deploy 在 compose 单机模式下也生效（v2 之后）。\n4. 日志管理 默认日志无限制会把磁盘撑爆：\nservices: web: image: myapp logging: driver: json-file options: max-size: \u0026#34;10m\u0026#34; max-file: \u0026#34;3\u0026#34; 5. 网络隔离 networks: frontend: backend: services: web: networks: - frontend - backend db: networks: - backend # 只在 backend 网络，外部访问不到 6. 环境变量 用 .env 文件管理：\n# .env POSTGRES_PASSWORD=secret APP_PORT=8080 # docker-compose.yml services: db: environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} web: ports: - \u0026#34;${APP_PORT}:80\u0026#34; .env 不要提交到 git，加到 .gitignore。\n7. Profile 按需启动 services: web: image: myapp debug-tools: image: busybox profiles: - debug 默认 docker-compose up 只启动 web，需要调试工具时：\ndocker-compose --profile debug up 8. 重启策略 services: web: restart: unless-stopped # 总是重启，除非手动停止 选项：\nno：默认，不重启 always：总是重启 on-failure：仅非零退出码重启 unless-stopped：总是重启，除非手动停止 9. 构建优化 services: web: build: context: . dockerfile: Dockerfile cache_from: - myapp:cache 配合 BuildKit：\nDOCKER_BUILDKIT=1 docker-compose build 10. 常用命令速查 docker-compose up -d # 后台启动 docker-compose down -v # 停止并删除卷 docker-compose logs -f web # 跟踪日志 docker-compose exec web bash # 进入容器 docker-compose restart web # 重启某服务 docker-compose ps # 查看状态 docker-compose config # 校验+展开配置 小结 Compose 看着简单，但配置项很多。把上面这些用熟了，单机部署基本不用折腾。\n","permalink":"https://vps.gongai.org/posts/2026-05-18-docker-compose-tips/","summary":"整理日常用 Docker Compose 的一些技巧：多环境配置、健康检查、资源限制、日志管理。","title":"Docker Compose 实用技巧合集"},{"content":"Rust 的 async/await 语法糖背后是怎么工作的？本文梳理一下 Tokio 运行时的核心设计。\n1. Future Trait Rust 异步的基础是 Future：\npub trait Future { type Output; fn poll(self: Pin\u0026lt;\u0026amp;mut Self\u0026gt;, cx: \u0026amp;mut Context\u0026lt;\u0026#39;_\u0026gt;) -\u0026gt; Poll\u0026lt;Self::Output\u0026gt;; } 关键点：Future 是惰性的，不调用 poll 就不会执行。这点和 JS 的 Promise 不同。\n2. Poll 的两种返回 pub enum Poll\u0026lt;T\u0026gt; { Ready(T), Pending, } Ready(value)：完成，返回值 Pending：还没好，等会儿再来 poll 3. Waker 当 Future 返回 Pending 时，它会注册一个 Waker。资源就绪时调用 waker.wake()，调度器把这个 Future 重新放回队列等待 poll。\nlet waker = cx.waker().clone(); // 资源就绪时 waker.wake(); 4. Tokio 的架构 Tokio 由三部分组成：\nReactor 负责 I/O 事件通知。基于 epoll（Linux）/kqueue（Mac）/IOCP（Windows）。当 socket 可读可写时，reactor 通知对应的 waker。\nExecutor 执行 task。Tokio 有两种 executor：\ncurrent_thread：单线程，task 都在当前线程跑 multi_thread：多线程 work-stealing，类似 Go 的 GMP Task async fn 包一层就成了 task：\ntokio::spawn(async { // ... }); 5. 异步 I/O 示例 use tokio::net::TcpListener; #[tokio::main] async fn main() { let listener = TcpListener::bind(\u0026#34;127.0.0.1:8080\u0026#34;).await.unwrap(); loop { let (mut socket, _) = listener.accept().await.unwrap(); tokio::spawn(async move { let mut buf = [0; 1024]; loop { match socket.read(\u0026amp;mut buf).await { Ok(0) =\u0026gt; return, Ok(n) =\u0026gt; { if socket.write_all(\u0026amp;buf[..n]).await.is_err() { return; } } Err(_) =\u0026gt; return, } } }); } } 6. 阻塞操作的陷阱 异步代码里不要直接调用阻塞操作（std:🧵:sleep、同步 I/O 等），会卡住整个 worker 线程。要用异步版本：\n// 错误：阻塞 std:🧵:sleep(Duration::from_secs(1)); // 正确：异步 tokio::time::sleep(Duration::from_secs(1)).await; 实在要跑阻塞代码，用 spawn_blocking 把它丢到专门的线程池：\nlet result = tokio::task::spawn_blocking(|| { // CPU 密集或阻塞操作 heavy_computation() }).await.unwrap(); 7. Select 多路复用 tokio::select! { val = receiver.recv() =\u0026gt; { println!(\u0026#34;received: {:?}\u0026#34;, val); } _ = tokio::time::sleep(Duration::from_secs(5)) =\u0026gt; { println!(\u0026#34;timeout\u0026#34;); } } 8. Channel Tokio 提供几种 channel：\nmpsc：多生产者单消费者 oneshot：一发一收 broadcast：广播 watch：只关心最新值 let (tx, mut rx) = tokio::sync::mpsc::channel(100); tokio::spawn(async move { tx.send(\u0026#34;hello\u0026#34;).await.unwrap(); }); while let Some(msg) = rx.recv().await { println!(\u0026#34;{}\u0026#34;, msg); } 小结 Rust 异步的精髓在于零成本抽象：Future 是状态机，编译器把 async/await 转成 enum + poll。Tokio 在这之上提供了完整的运行时。理解 poll、waker、reactor、executor 这几个概念，写异步代码就不会懵了。\n","permalink":"https://vps.gongai.org/posts/2026-04-03-rust-async-runtime-notes/","summary":"深入理解 Rust 异步运行时 Tokio 的工作原理，包括 reactor、executor、task 调度。","title":"Rust 异步运行时笔记：Tokio 的设计"},{"content":"之前用 WordPress 嫌太重，Hexo 又觉得 node 依赖烦人，最后选了 Hugo。Go 写的单二进制，没有任何依赖，构建速度极快（几百篇文章秒级出）。\n1. 安装 Hugo Debian 12 直接 apt 装就行：\napt install -y hugo hugo version 如果要最新版可以去 GitHub Release 下 deb 包。\n2. 创建站点 hugo new site myblog cd myblog 3. 安装主题 我选了 PaperMod，简洁干净：\ngit init git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod 在 hugo.toml 里加：\ntheme = \u0026#34;PaperMod\u0026#34; 4. 配置 baseURL = \u0026#34;https://example.com/\u0026#34; languageCode = \u0026#34;zh-cn\u0026#34; title = \u0026#34;我的博客\u0026#34; [params] author = \u0026#34;作者\u0026#34; ShowReadingTime = true ShowCodeCopyButtons = true [[menu.main]] identifier = \u0026#34;archives\u0026#34; name = \u0026#34;归档\u0026#34; url = \u0026#34;/archives/\u0026#34; weight = 5 5. 写文章 hugo new posts/first-post.md 文章头部是 front matter：\n--- title: \u0026#34;第一篇文章\u0026#34; date: 2026-02-20T19:45:00+08:00 draft: false categories: [\u0026#34;随笔\u0026#34;] tags: [\u0026#34;test\u0026#34;] --- 正文内容... 6. 本地预览 hugo server -D # -D 包含 draft 浏览器打开 http://localhost:1313 就能看。\n7. 构建 hugo --minify 生成的静态文件在 public/ 目录。\n8. 部署到 VPS 我用的方案：本地构建，rsync 推送到 VPS 的 /var/www/html/：\nhugo --minify rsync -avz --delete public/ root@vps:/var/www/html/ 也可以在 VPS 上直接放源码然后 cron 定时构建，或者用 GitHub Actions 推。\n9. Nginx 配置 server { listen 443 ssl http2; server_name example.com; root /var/www/html; index index.html; location / { try_files $uri $uri/ =404; } } 10. 自动化 写个简单的部署脚本：\n#!/bin/bash cd ~/myblog hugo --minify rsync -avz --delete public/ root@vps:/var/www/html/ echo \u0026#34;Deployed at $(date)\u0026#34; 放到 ~/bin/deploy-blog.sh，chmod +x 后就能 deploy-blog.sh 一键部署了。\n小结 Hugo 的优势是简单：单二进制、零依赖、构建快。配合 Nginx 静态托管，性能极好，VPS 几乎不吃资源。\n","permalink":"https://vps.gongai.org/posts/2026-02-20-hugo-blog-deploy/","summary":"记录用 Hugo 搭建静态博客并部署到 VPS 的完整流程，包括主题选择、内容编写、自动构建。","title":"用 Hugo 部署静态博客到 VPS"},{"content":"给网站配置 HTTPS 已经是标配了，但很多人配完能用就算，没有充分考虑到安全性和性能。本文记录一些 Nginx TLS 配置的最佳实践。\n1. 证书申请 推荐用 Let\u0026rsquo;s Encrypt，免费、自动化、受信任。\napt install -y certbot certbot certonly --standalone -d example.com 证书路径通常在 /etc/letsencrypt/live/example.com/。\n2. 最小化 TLS 协议 2026 年了，TLS 1.0 和 1.1 应该彻底淘汰：\nssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; 3. 会话缓存 启用会话缓存和票证能减少握手开销：\nssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; 4. OCSP Stapling 启用 OCSP Stapling 让客户端不需要单独去查询证书状态，加快握手：\nssl_stapling on; ssl_stapling_verify on; resolver 1.1.1.1 8.8.8.8 valid=300s; resolver_timeout 5s; 5. HTTP 强制跳转 HTTPS server { listen 80; server_name example.com; return 301 https://$server_name$request_uri; } 6. HSTS 头 add_header Strict-Transport-Security \u0026#34;max-age=63072000; includeSubDomains\u0026#34; always; 注意：HSTS 一旦设置，浏览器会强制走 HTTPS，如果你证书过期了网站就完全访问不了。先用短 max-age 测试。\n7. 安全头 add_header X-Frame-Options \u0026#34;SAMEORIGIN\u0026#34; always; add_header X-Content-Type-Options \u0026#34;nosniff\u0026#34; always; add_header X-XSS-Protection \u0026#34;1; mode=block\u0026#34; always; add_header Referrer-Policy \u0026#34;strict-origin-when-cross-origin\u0026#34; always; 8. 隐藏版本号 server_tokens off; 9. HTTP/2 listen 443 ssl http2; HTTP/2 多路复用能显著提升页面加载速度。\n10. 证书自动续期 Let\u0026rsquo;s Encrypt 证书 90 天过期，certbot 装好后会自带 systemd timer：\nsystemctl status certbot.timer 测试续期：\ncertbot renew --dry-run 完整示例 server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_stapling on; ssl_stapling_verify on; add_header Strict-Transport-Security \u0026#34;max-age=63072000; includeSubDomains\u0026#34; always; add_header X-Frame-Options \u0026#34;SAMEORIGIN\u0026#34; always; add_header X-Content-Type-Options \u0026#34;nosniff\u0026#34; always; } 把这些配置项都加上，网站的 TLS 评分上 SSL Labs 应该能拿到 A+。\n","permalink":"https://vps.gongai.org/posts/2026-01-08-nginx-tls-best-practice/","summary":"Nginx 配置 HTTPS 的一些最佳实践，包括证书申请、协议选择、安全头设置等。","title":"Nginx TLS 配置最佳实践"},{"content":"最近入手了一台新的 VPS，系统是 Debian 12 (bookworm)，记录一下初始化配置过程，方便以后参考。\n1. 系统更新 apt update \u0026amp;\u0026amp; apt upgrade -y apt install -y curl wget git vim ufw fail2ban 2. 创建普通用户 adduser gong usermod -aG sudo gong 3. 配置 SSH 密钥登录 本地生成密钥对：\nssh-keygen -t ed25519 -C \u0026#34;gong@vps\u0026#34; 把公钥上传到服务器：\nssh-copy-id -i ~/.ssh/id_ed25519.pub gong@server_ip 修改 /etc/ssh/sshd_config：\nPermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes Port 22222 重启 SSH 服务：\nsystemctl restart sshd 4. 配置防火墙 ufw default deny incoming ufw default allow outgoing ufw allow 22222/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable 5. 配置 fail2ban 编辑 /etc/fail2ban/jail.local：\n[sshd] enabled = true port = 22222 maxretry = 3 bantime = 86400 systemctl enable fail2ban systemctl restart fail2ban 6. 时区与时间同步 timedatectl set-timezone Asia/Shanghai apt install -y chrony systemctl enable chrony 小结 服务器初始化的关键是减少攻击面：禁用 root 登录、强制密钥认证、关闭不必要的端口、配置自动封禁。下次配置新服务器时照这个流程走一遍就行。\n","permalink":"https://vps.gongai.org/posts/2025-11-15-debian12-server-initial-setup/","summary":"记录一次 Debian 12 服务器的初始化过程，包括用户创建、SSH 配置、防火墙设置等。","title":"Debian 12 服务器初始化配置笔记"}]