[{"content":"前言 2023 年 3 月 5 日，我在这台服务器上敲下第一篇笔记《Linux 文件权限与特殊权限位详解》。今天翻到归档页，正好第 73 篇。借这个节点，做一次三年复盘——不是流水账，是提炼给同样在路上的人。\n一、回看这三年写了什么 2023（20篇）: 打地基 Linux 基础/三剑客/进程/磁盘 → Bash 脚本 第一次装 Docker, 第一次读 K8s 概念 2024（24篇）: 上强度 Kubernetes 深入（部署/网络/存储） Prometheus 监控、Ansible、CI/CD Go 的并发/测试/Web 2025（17篇）: 补深度 ELK、性能案例复盘、MySQL 优化 容器底层原理——终于把 namespace/cgroup 玩明白了 2026（11篇）: 拔高度 OpenStack、Istio、网关选型、安全基线 视角从\u0026#34;会用\u0026#34;转向\u0026#34;选型与设计\u0026#34; 一张技能雷达图的变化：Linux 从\u0026quot;能干活\u0026quot;到\u0026quot;懂原理\u0026quot;；云原生从\u0026quot;概念\u0026quot;到\u0026quot;生产实战\u0026quot;；编程从\u0026quot;脚本\u0026quot;到\u0026quot;工程\u0026quot;。\n二、学习方法的三个转折 转折一：从\u0026quot;收藏\u0026quot;到\u0026quot;输出\u0026quot; 早期我也囤教程、收藏夹吃灰。直到逼自己每学一块就写一篇——写作是最残酷的检验：你写不清楚的，就是你不明白的。哪怕没人看，最大的受益者也是自己（这博客三年访问量约等于零，但我不亏 😄）。\n转折二：从\u0026quot;背命令\u0026quot;到\u0026quot;建模型\u0026quot; 记 ss -tan 的参数毫无意义，理解\u0026quot;连接是有状态的、状态是可以数的\u0026quot;才有意义。后来我学任何新东西都先问三个问题：\n它解决什么问题（没有它之前人们怎么办）？ 它的核心抽象是什么（Docker=进程隔离、K8s=声明式调谐）？ 它把复杂度藏到了哪里（藏掉的迟早要还）？ 有了模型，命令自然长在模型上。\n转折三：从\u0026quot;学技术\u0026quot;到\u0026quot;钻事故\u0026quot; 平时按兴趣学，事故按根因学。2026 年那篇 CPU 排查案例，一次告警让我把 top→jstack→perf 全链路打通——一次深度事故的价值超过十篇教程。所以现在我巴不得多值班（适度）。\n三、踩过的最大的三个坑 坑一：在不理解原理时上生产 早期把不懂的东西直接部署（当时连 iptables 的 NAT 都没搞清就上了网关），出问题两眼一抹黑。教训：上线一个东西前，至少要能说清它的流量路径和故障形态。\n坑二：过度追求新潮 中间有段时间啥都想上：刚会 Docker 就要服务网格，结果一个五人团队的项目背上了 Istio 的复杂度，最后还是拆掉了。教训：技术选型看团队消化能力，不看 GitHub star。网关选型那篇的决策树，就是这坑换来的。\n坑三：只建设不演练 备份做了三年，第一次恢复演练是今年——然后发现备份脚本有个路径 bug，根本恢复不了。从此信奉：没演练过的预案等于没有预案。\n四、对\u0026quot;运维\u0026quot;的重新理解 三年前以为运维是\u0026quot;装机器、改配置、背锅\u0026quot;。现在的理解：\n运维的本质是: 管理复杂度与不确定性 - 监控/日志 = 对不确定性的感知 - 高可用/冗余 = 对不确定性的对冲 - 自动化 = 把复杂度从人脑挪进系统 - 文档/复盘 = 把不确定性沉淀为组织的确定性 岗位在变（SRE/平台工程/DevOps），这个内核没变。AI 时代同理：它会写命令，但\u0026quot;该信什么、该查什么、出了事先看哪\u0026quot;的判断力，依然来自这些基础原理的积累。\n五、感谢与数据 73 篇文章 / 约 12 万字 覆盖: Linux×21 云原生×18 云计算×5 Python×9 Golang×9 工具×2 数据库×1 安全×2 随笔×1 最爱的三篇（自己评的）: 《容器底层原理》—— 写完真正\u0026#34;懂\u0026#34;了容器 《CPU 飙高排查案例》—— 一次完整破案的快感 《三剑客》—— 一切的起点味道 感谢每一篇被我参考过的官方文档与博客作者；感谢 antidebug.cn 这台小机器三年没挂（UPTIME 除外）。\n六、下一阶段目标 数据方向：ClickHouse / 时序数据进阶 把博客的构建发布做成 GitOps（正好复习 CI/CD 系列） 每季度一次恢复演练（说到做到） 读完《SRE：Google 运维解密》英文原版 系列文章补齐：网络协议专题（TCP/IP 从抓包学起） 七、写给同样在路上的你 如果你正在从\u0026quot;会用工具\u0026quot;往\u0026quot;理解原理\u0026quot;的路上爬，我的经验浓缩成三句：\n写下来——输出倒逼输入； 拆开看——每个黑盒都值得打开一次； 别独自学——博客、社区、复盘会给你复利。 练级之路没有满级，但每升一级，地图就大一圈。\n下一篇见。🎮\n—— 写于 2026 年 8 月，antidebug.cn 上线三年。\n","permalink":"https://antidebug.cn/posts/2026/08/%E5%8D%9A%E5%AE%A2%E4%B8%89%E5%B9%B4%E6%88%91%E7%9A%84%E8%BF%90%E7%BB%B4%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF%E5%B0%8F%E7%BB%93/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e2023 年 3 月 5 日，我在这台服务器上敲下第一篇笔记《Linux 文件权限与特殊权限位详解》。今天翻到归档页，正好第 73 篇。借这个节点，做一次三年复盘——不是流水账，是提炼给同样在路上的人。\u003c/p\u003e","title":"博客三年：我的运维学习之路小结"},{"content":"前言 把三年里写过的安全内容（SSH 加固、用户管理、防火墙……）整理成一份上线前必过、每季复查的清单。每项都给出检查命令和整改动作——直接对着服务器打勾即可。\n一、账号与认证（10 项） # 1. 无空密码账号（期望: 无输出） awk -F: \u0026#39;($2==\u0026#34;\u0026#34;){print $1}\u0026#39; /etc/shadow # 2. UID=0 只有 root awk -F: \u0026#39;$3==0 {print $1}\u0026#39; /etc/passwd # 3. 禁止 root 直接 SSH（PermitRootLogin no） sshd -T | grep permitrootlogin # 4. SSH 禁用密码（密钥登录） sshd -T | grep passwordauthentication # 5. 密码策略: 90天/复杂度 grep PASS_MAX_DAYS /etc/login.defs cat /etc/security/pwquality.conf | grep -E \u0026#34;minlen|minclass\u0026#34; # 6. 登录失败锁定（fail2ban 运行中） systemctl is-active fail2ban # 7. 无遗留僵尸账号（90天未登录清单） lastlog -b 90 | awk \u0026#39;NR\u0026gt;1 \u0026amp;\u0026amp; $2!=\u0026#34;\u0026#34; {print $1}\u0026#39; | head # 8. sudo 白名单化（NOPASSWD: ALL 全量盘点） grep -rn \u0026#34;NOPASSWD\u0026#34; /etc/sudoers /etc/sudoers.d/ 2\u0026gt;/dev/null # 9. 离职流程有记录（制度项: 删号/锁号/清crontab/清sudoers） # 10. 默认 umask 022（不为 000） grep -i umask /etc/profile /etc/bashrc | head -3 二、攻击面收敛（8 项） # 1. 监听端口清单化（每个端口都能说清是谁） ss -tlnpu # 2. 无 0.0.0.0 的调试/管理端口（mysql/redis es 等） ss -tln | awk \u0026#39;$4 ~ /0.0.0.0|:::/ {print $4}\u0026#39; # 3. Redis 不裸奔: bind 内网 + requirepass + 禁高危命令 redis-cli CONFIG GET bind redis-cli CONFIG GET requirepass # 4. MySQL 不开放公网: bind-address 内网/localhost mysql -e \u0026#34;SHOW VARIABLES LIKE \u0026#39;bind_address\u0026#39;;\u0026#34; # 5. 防火墙默认拒绝（firewalld zone 明确/安全组最小放行） firewall-cmd --list-all # 6. 关闭不需要的服务（bluetooth/cups/avahi...） systemctl list-unit-files --state=enabled | wc -l # 7. 内网服务不出公网（安全组层面确认） # 8. 不用的老服务下线（nfs/rsh/telnet 检查） rpm -qa | grep -E \u0026#34;^(rsh|telnet|ypbind)\u0026#34; 三、系统与内核（7 项） # 1. 及时打安全补丁 dnf updateinfo list sec --available | head dnf needs-restarting -r # 2. 内核安全参数（呼应 sysctl 篇） sysctl net.ipv4.conf.all.rp_filter net.ipv4.tcp_syncookies # 3. 时间同步（chrony, 证书/审计的前提） timedatectl | grep -E \u0026#34;synchronized|NTP\u0026#34; # 4. 磁盘水位与日志清理策略（logrotate/journald 限额） journalctl --disk-usage # 5. 内核不随意外加载模块（对高危模块黑名单） # 6. SELinux 状态有明确决策（启用或\u0026#34;评估后豁免\u0026#34;） getenforce # 7. reboot 后内核参数/服务自启均持久化（sysctl --system 无报错） 四、数据与凭据（6 项） # 1. 私钥权限 600 / .ssh 目录 700 find /root /home -name \u0026#34;id_*\u0026#34; -exec ls -l {} \\; # 2. 代码/配置仓库无明文密码 grep -rnE \u0026#34;(password|secret|token)\\s*=\u0026#34; /opt/app/config/ | head # 3. 敏感文件权限收紧（ssh config、备份脚本里的凭据） stat -c \u0026#34;%a %n\u0026#34; /etc/shadow # 4. 备份策略验证（恢复演练过!） ls -lh /backup/$(hostname)/ | tail -3 # 5. 数据库账号最小权限（无 % 通配 root） mysql -e \u0026#34;SELECT user,host FROM mysql.user;\u0026#34; # 6. 云上 AK/SK 不落盘（用角色/实例元数据） find / -maxdepth 4 -name \u0026#34;credentials\u0026#34; -path \u0026#34;*aws*\u0026#34; 2\u0026gt;/dev/null 五、可观测与响应（5 项） # 1. 登录审计有人看 grep \u0026#34;Accepted\u0026#34; /var/log/secure | tail -5 # 2. 告警通道畅通（钉钉/邮件测试消息） # 3. 高危操作留痕（sudo 日志纳入采集） journalctl -t sudo | tail -3 # 4. 指标监控覆盖（CPU/内存/磁盘/进程/端口） curl -s http://localhost:9100/metrics | head -1 # 5. 应急预案文档化（断网/隔离/取证流程谁会做?） 六、容器与 K8s 专项（6 项） # 1. 无 privileged 容器 docker ps --format \u0026#39;{{.Names}}\u0026#39; | while read c; do docker inspect -f \u0026#39;{{.HostConfig.Privileged}}\u0026#39; \u0026#34;$c\u0026#34;; done # 2. 容器非 root 运行 docker inspect -f \u0026#39;{{.Config.User}}\u0026#39; $(docker ps -q) | sort -u # 3. 不挂载 docker.sock docker inspect $(docker ps -q) | grep -c docker.sock # 4. 镜像来源受控（私有仓库 + Trivy 扫描） trivy image --severity HIGH,CRITICAL --exit-code 0 myapp:latest | tail -3 # 5. K8s: RBAC 最小权限（can-i \u0026#39;*\u0026#39; \u0026#39;*\u0026#39; 应为 no） kubectl auth can-i \u0026#39;*\u0026#39; \u0026#39;*\u0026#39; kubectl get clusterrolebindings -o name | wc -l # 6. Pod 安全基线（不共享 hostNetwork/hostPath 滥用） kubectl get pods -A -o wide | grep -vE \u0026#34;calico|flannel\u0026#34; | wc -l 七、一键自查脚本（节选） #!/usr/bin/env bash # security-baseline.sh —— 输出: [FAIL]/[PASS] + 整改建议 check() { # $1=描述 $2=检查命令 $3=期望输出包含 result=$(eval \u0026#34;$2\u0026#34; 2\u0026gt;/dev/null) if echo \u0026#34;$result\u0026#34; | grep -q \u0026#34;$3\u0026#34;; then printf \u0026#34;[PASS] %s\\n\u0026#34; \u0026#34;$1\u0026#34; else printf \u0026#34;[FAIL] %s → %s\\n\u0026#34; \u0026#34;$1\u0026#34; \u0026#34;$4\u0026#34; fi } check \u0026#34;root SSH 已禁用\u0026#34; \u0026#34;sshd -T | grep -i permitrootlogin\u0026#34; \u0026#34;no\u0026#34; \u0026#34;改 /etc/ssh/sshd_config\u0026#34; check \u0026#34;SSH 密码已禁用\u0026#34; \u0026#34;sshd -T | grep -i passwordauth\u0026#34; \u0026#34;no\u0026#34; \u0026#34;改 sshd_config 后 -t + reload\u0026#34; check \u0026#34;fail2ban 运行中\u0026#34; \u0026#34;systemctl is-active fail2ban\u0026#34; \u0026#34;active\u0026#34; \u0026#34;dnf install fail2ban 并启动\u0026#34; check \u0026#34;无空密码账号\u0026#34; \u0026#34;awk -F: \u0026#39;(\\$2==\\\u0026#34;\\\u0026#34;)\u0026#39; /etc/shadow | wc -l\u0026#34; \u0026#34;0\u0026#34; \u0026#34;passwd 锁定相关账号\u0026#34; check \u0026#34;私钥权限正确\u0026#34; \u0026#34;stat -c %a /root/.ssh/id_* | grep -vc 600\u0026#34; \u0026#34;0\u0026#34; \u0026#34;chmod 600\u0026#34; check \u0026#34;时间已同步\u0026#34; \u0026#34;timedatectl | grep -c synchronized\u0026#34; \u0026#34;1\u0026#34; \u0026#34;启用 chronyd\u0026#34; # 接入 cron 每周一 9 点跑, FAIL 项发告警 0 9 * * 1 root /opt/scripts/security-baseline.sh | grep FAIL | mail -s \u0026#34;安全基线告警\u0026#34; ops@corp 八、加固的元原则 分层防御：单点失守不等于全线失守（账号层+网络层+应用层+数据层） 最小权限：默认全关，按需打开，定期回收 可审计：没有日志的加固等于没加固 自动化：人盯清单必遗漏，脚本+告警才是正道 演练：预案没演练过 = 没有预案 小结 这份清单不是\u0026quot;做完就安全\u0026quot;的许愿池，而是持续运营的仪表盘：季度复查、新机器过基线、FAIL 项进工单。安全没有毕业，只有坚持。\n本文是「安全」系列第 2 篇。\n","permalink":"https://antidebug.cn/posts/2026/08/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%89%E5%85%A8%E5%8A%A0%E5%9B%BA%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e把三年里写过的安全内容（SSH 加固、用户管理、防火墙……）整理成一份\u003cstrong\u003e上线前必过、每季复查\u003c/strong\u003e的清单。每项都给出检查命令和整改动作——直接对着服务器打勾即可。\u003c/p\u003e","title":"服务器安全加固检查清单"},{"content":"前言 所有流量从北向南进入集群的那个\u0026quot;大门\u0026quot;叫网关。老朋友 Nginx 能打，但 K8s 时代有了新选手：Traefik（云原生原生）、APISIX（插件平台）。这篇给一个可落地的选型框架。\n一、网关要解决什么 ┌─── 路由: 域名/路径/Header → 后端 流量 ───►├─── TLS 终结: 证书统一管理 ├─── 认证: JWT/API Key/OIDC ├─── 限流熔断: 保护后端 ├─── 可观测: 访问日志/指标/trace └─── 灰度: 按 header/比例分流 演进主线：静态配置 → 动态配置（不 reload） → API 驱动（可编程）。\n二、三位选手画像 Nginx（+ OpenResty/Lua） # 经典配置: 一切靠文件 + reload server { listen 443 ssl http2; server_name api.example.com; location / { proxy_pass http://upstream; limit_req zone=api burst=100; } } 优点 缺点 性能天花板、极稳 改配置要 reload（毫秒级断连风险） 生态/文档/运维经验最厚 动态能力靠 Lua（OpenResty）开发门槛 K8s 有 ingress-nginx 传承 原生没有控制台/API Traefik（云原生亲儿子） # K8s IngressRoute: 自动发现后端, 改动秒级生效无 reload apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: api spec: entryPoints: [websecure] routes: - match: Host(`api.example.com`) \u0026amp;\u0026amp; PathPrefix(`/v2`) kind: Rule services: [{name: api-v2, port: 80}] middlewares: [{name: ratelimit}] --- apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: ratelimit spec: rateLimit: {average: 100, burst: 50} 优点 缺点 K8s CRD 声明式、自动服务发现 极端性能弱于 Nginx/APISIX 内置 Dashboard（拓扑一目了然） 复杂插件生态较薄 Let\u0026rsquo;s Encrypt 自动证书！ 超大集群性能调优要花心思 APISIX（插件平台） // 一切皆 API/etcd: 动态路由 + 100+ 现成插件 curl http://127.0.0.1:9180/apisix/admin/routes/1 -X PUT \\ -H \u0026#34;X-API-KEY: $ADMIN_KEY\u0026#34; -d \u0026#39;{ \u0026#34;uri\u0026#34;: \u0026#34;/api/*\u0026#34;, \u0026#34;upstream\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;roundrobin\u0026#34;, \u0026#34;nodes\u0026#34;: {\u0026#34;api-svc:80\u0026#34;: 1}}, \u0026#34;plugins\u0026#34;: { \u0026#34;limit-count\u0026#34;: {\u0026#34;count\u0026#34;: 100, \u0026#34;time_window\u0026#34;: 60}, \u0026#34;jwt-auth\u0026#34;: {}, \u0026#34;prometheus\u0026#34;: {} } }\u0026#39; 优点 缺点 全动态（etcd 存储, 热更新） 组件多（etcd+dashboard）运维面大 插件生态炸裂（认证/限流/日志/transform） 学习成本中上 国产开源（Apache 顶级项目, 文档中文友好） 三、硬核对比表 维度 Nginx Traefik APISIX 配置方式 文件+reload CRD/标签 REST API/etcd/CRD 热更新 ✗（Lua 可补） ✓ ✓✓ K8s 集成 ingress-nginx 原生最佳 Ingress Controller 控制台 ✗ ✓ 内置 ✓ 强大 认证插件 手写/Lua 中间件若干 100+ 插件 性能 ★★★★★ ★★★★ ★★★★☆ 团队上手 谁都会 容易 中等 适合 传统架构/极致性能 云原生中小团队 平台化/API 门户 四、选型决策树 你的场景是? ├─ 传统 VM 部署 / 静态转发为主 │ └─► Nginx（别折腾, 它就是为此而生） ├─ K8s 集群入口, 团队 \u0026lt; 50 人, 要省心 │ └─► Traefik（IngressRoute + 自动证书真香） ├─ 需要 API 门户: 大量域名/租户/鉴权/限流/计费 │ └─► APISIX（插件化平台能力） ├─ 已有 Nginx 运维体系, 上 K8s │ └─► ingress-nginx（平滑迁移, 思想不变） └─ 超大规模多团队共享网关 └─► APISIX / Higress + 平台工程化 一句忠告：选型先看团队会什么——Nginx 用得好好的团队迁移 APISIX 的收益，往往抵不过重新踩坑的成本。\n五、各自的典型落地 Nginx：加动态健康检查的模板（ingress-nginx 思想） upstream api { server 10.0.1.11:8080 max_fails=2 fail_timeout=10s; server 10.0.1.12:8080 max_fails=2 fail_timeout=10s; keepalive 64; # 长连接池, 大幅提升吞吐 } Traefik：全链路一条龙 helm install traefik traefik/traefix -n traefik --create-namespace \\ --set ingressClass.enabled=true # cert-resolver 自动 HTTPS（最杀手锏） # IngressRoute 加: tls: {certResolver: letsencrypt} # 域名解析生效后, 证书自动签发+续期, 零手工 APISIX： observable 限流路由 # Helm 安装（含 etcd + dashboard） helm repo add apisix https://charts.apiseven.com helm install apisix apisix/apisix -n ingress --create-namespace # 或 K8s 声明式（ApisixRoute CRD） # 插件即插即用: prometheus → Grafana 大盘开箱 六、无论选谁都要做的 事项 说明 健康检查 被动+主动，摘除坏节点 访问日志 带上游耗时（呼应 Nginx 篇的 log_format） 指标接入 QPS/延迟/状态码 → Prometheus 限流兜底 保护后端的最后一道闸 证书管理 自动续期（别再人肉换证书） 变更管理 网关配置纳入 Git + review（网关是单点中的单点） 七、常见坑 现象 选型相关原因 Traefik 路由不生效 entryPoints/match 表达式写错；看 dashboard 排查 APISIX 节点同步异常 etcd 集群健康；admin API 版本 Nginx reload 抖动 长连接被重置；考虑 OpenResty balancer_by_lua 网关成了单点 至少 2 副本 + 前置 LB/VIP 配置漂移 手改配置未入库 → GitOps 化 八、小结 需求关键词 推荐 稳、快、传统 Nginx K8s、省心、自动证书 Traefik API 门户、插件、平台化 APISIX 迁移平滑 ingress-nginx 网关的尽头不是选型，而是把配置变更做成可审计的工程——那一层做好后，具体是哪个网关反而没那么重要了。\n本文是「云原生」系列第 18 篇。\n","permalink":"https://antidebug.cn/posts/2026/07/%E4%BA%91%E5%8E%9F%E7%94%9F%E7%BD%91%E5%85%B3%E9%80%89%E5%9E%8Bnginxtraefik-%E4%B8%8E-apisix/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e所有流量从北向南进入集群的那个\u0026quot;大门\u0026quot;叫网关。老朋友 Nginx 能打，但 K8s 时代有了新选手：Traefik（云原生原生）、APISIX（插件平台）。这篇给一个可落地的选型框架。\u003c/p\u003e","title":"云原生网关选型：Nginx、Traefik 与 APISIX"},{"content":"前言 集群一旦多人使用，\u0026ldquo;谁都能改一切\u0026quot;就是定时炸弹。K8s 的 RBAC（基于角色的访问控制）决定谁能对什么资源做什么操作——这篇把模型讲透并落地一套多团队权限方案。\n一、RBAC 四对象 ┌──────────────┐ RoleBinding ──┤ Role ├── 定义\u0026#34;能做什么\u0026#34;（一组规则） （谁↔角色） │ (namespace级) │ └──────────────┘ ┌──────────────┐ ClusterRoleBinding─┤ ClusterRole ├── 同上, 但集群级 └──────────────┘ 绑定关系: User/Group/ServiceAccount ──binding──► Role ──► [资源+动词] 对象 作用域 说明 Role 单 namespace 规则（能做什么） ClusterRole 全集群 集群级资源/跨 ns 权限 RoleBinding 单 namespace 把角色绑给\u0026quot;人\u0026rdquo;（限本 ns） ClusterRoleBinding 全集群 全局生效（慎用） 精妙之处：ClusterRole 可以通过 RoleBinding 绑定——规则定义一次，在某 ns 内生效（常用套路）。\n二、规则：资源与动词 apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: dev name: dev-deployer rules: - apiGroups: [\u0026#34;apps\u0026#34;] # \u0026#34;\u0026#34; = core组(pod/svc/configmap...) resources: [\u0026#34;deployments\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods\u0026#34;, \u0026#34;pods/log\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] # 开发只读 Pod 与日志 - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/exec\u0026#34;] verbs: [\u0026#34;create\u0026#34;] # 允许 exec 进容器调试 verbs 速查：\n动词 对应操作 get / list / watch 查看单个/列出/监听 create / update / patch 创建/替换/部分更新 delete / deletecollection 删除 * 全部（Danger!） 特殊资源名（resourceNames 收窄到具体对象）：\n- apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] resourceNames: [\u0026#34;app-conf\u0026#34;] # 只能操作这一个 ConfigMap verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] 三、主体：谁能被授权 User 外部认证的用户（证书/OIDC 对接公司账号体系） Group 用户组（证书 O 字段/OIDC group） ServiceAccount 集群内的\u0026#34;服务账号\u0026#34;（应用/CI 用） 四、实战一：给开发团队开权限 需求：dev 团队只能用 dev namespace，能部署应用、看日志、进容器，不能碰 secrets 和其他 ns。\napiVersion: v1 kind: Namespace metadata: name: dev --- apiVersion: v1 kind: ServiceAccount metadata: namespace: dev name: dev-team --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: dev name: dev-role rules: - apiGroups: [\u0026#34;apps\u0026#34;] resources: [\u0026#34;deployments\u0026#34;, \u0026#34;replicasets\u0026#34;] verbs: [\u0026#34;*\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;services\u0026#34;, \u0026#34;configmaps\u0026#34;, \u0026#34;pods\u0026#34;, \u0026#34;pods/log\u0026#34;, \u0026#34;pods/exec\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;create\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;, \u0026#34;delete\u0026#34;] # 注意: 刻意不含 secrets! --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: dev name: dev-team-binding subjects: - kind: ServiceAccount name: dev-team namespace: dev roleRef: kind: Role name: dev-role apiGroup: rbac.authorization.k8s.io 验证权限：\nkubectl auth can-i get pods -n dev --as=system:serviceaccount:dev:dev-team # yes kubectl auth can-i get secrets -n dev --as=system:serviceaccount:dev:dev-team # no ✅ 符合预期 kubectl auth can-i get pods -n prod --as=system:serviceaccount:dev:dev-team # no ✅ 越不过 namespace # 一键看全部权限 kubectl auth can-i --list -n dev --as=system:serviceaccount:dev:dev-team 五、实战二：给 CI 机器人只读+部署权 apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole # 集群级定义 metadata: name: deployer rules: - apiGroups: [\u0026#34;apps\u0026#34;] resources: [\u0026#34;deployments\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;patch\u0026#34;] # set image 需要 patch --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding # 但只绑到 ci 用的 ns → 权限被限制住 metadata: namespace: staging name: ci-deployer subjects: - kind: ServiceAccount name: gitlab-ci namespace: staging roleRef: kind: ClusterRole name: deployer apiGroup: rbac.authorization.k8s.io ClusterRole + RoleBinding = 复用规则又限制范围——权限设计的精髓。\n六、常见内置 ClusterRole 角色 内容 view 全集群只读 edit 读写常规资源（不能改权限/namespace） admin ns 内几乎全部（含 quota） cluster-admin 超级管理员（等于 root，只有平台组该有） # 给值班同学 ns 级 admin（而非集群级!） kubectl create rolebinding oncall-admin -n prod \\ --clusterrole=admin --user=zy@corp 七、Pod 使用 ServiceAccount spec: serviceAccountName: app-sa # 默认是 default（能读本ns部分信息） automountServiceAccountToken: false # 不需要调 API 的应用关掉挂载（收缩面） # Pod 内拿 token 调 API（应用自发现的场景） kubectl exec -it pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token 安全提醒：default SA 在老版本默认可读 ns 内全部 Secret 的能力面（实际 API 探测）；业务 Pod 一律显式 SA + 关闭 automount。\n八、排查与审计 # 我能干嘛 kubectl auth can-i delete pods -n prod kubectl auth can-i \u0026#39;*\u0026#39; \u0026#39;*\u0026#39; # 是不是管理员 # 为什么他有这个权限（绑定溯源） kubectl get rolebindings,clusterrolebindings -A -o wide | grep \u0026lt;user\u0026gt; # 看规则明细 kubectl describe clusterrole view | head -30 # 开审计日志（API Server 参数 或 /etc/kubernetes/audit-policy.yaml） audit.k8s.io 提到: - level: Metadata # 记录谁在何时对什么对象做了什么 # 谁读了 secrets → 排查泄密的关键证据 九、设计原则 最小权限：从只读开始，按报错逐步加（can-i 随手验证） namespace 即边界：多团队 → 多 ns + 各自 Role 宁可 RoleBinding 也不 ClusterRoleBinding：全局权限是奢侈品 禁用通配：resources: [\u0026quot;*\u0026quot;] / verbs: [\u0026quot;*\u0026quot;] 出现就该被 review SA 一应用一个，别共用 default 定期审计：clusterrolebindings 全量 review（每季度） 十、常见坑 现象 原因 403 but 我明明绑了 binding 的 ns 不对；SA 引用要带 ns RoleBinding 绑 ClusterRole 不生效 检查 rule 的资源本身是否集群级（nodes 等必须 ClusterRoleBinding） can-i 有但 kubectl 报错 细分到子资源（pods/log vs pods） 升级后 token 失效 bound token 有过期时间；重新拿 exec 被拒 pods/exec 需要单独授权且动词是 create 小结 组件 一句话 Role/ClusterRole 规则：资源 × 动词 Binding 把规则绑给用户/组/SA can-i 权限自测第一命令 套路 ClusterRole 定义 + RoleBinding 限定范围 红线 最小权限、拒绝 *、SA 专号专用 本文是「云原生」系列第 17 篇。\n","permalink":"https://antidebug.cn/posts/2026/06/k8s-%E5%AE%89%E5%85%A8rbac-%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6%E5%85%A5%E9%97%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e集群一旦多人使用，\u0026ldquo;谁都能改一切\u0026quot;就是定时炸弹。K8s 的 RBAC（基于角色的访问控制）决定\u003cstrong\u003e谁能对什么资源做什么操作\u003c/strong\u003e——这篇把模型讲透并落地一套多团队权限方案。\u003c/p\u003e","title":"K8s 安全：RBAC 权限控制入门"},{"content":"前言 数据要在两台机器间保持同步——备份、分发、迁移——十几年过去了，答案依然是 rsync：增量传输、断点续传、保持属性、走 SSH。配合 inotify 触发，还能做到准实时同步。\n一、rsync 核心概念 增量算法：两端比对块校验和，只传差异部分——同步 100GB 的目录，改动 10MB 就只传 10MB。\n源 ──►（差异计算）──► 目标 滚动校验和比对, 只传变化的块 三种工作模式：\n# 1. 本地同步 rsync -av /data/src/ /data/backup/ # 2. 远程（走 SSH, 最常用） rsync -av /data/src/ user@backup:/data/backup/ # 3. 守护进程模式（rsyncd, 大量数据/免交互） rsync -av /data/src/ rsync://backupHost/mod-backup/ 二、必懂参数与斜杠陷阱 rsync -avz --delete /data/src/ backup:/data/backup/ 参数 含义 -a 归档模式（递归+保持权限/时间/属主/软链） -v 显示过程 -z 传输时压缩（文本目录很值; 已压缩文件别开） --delete 删除目标端多余文件（镜像同步; 生产慎用!） -P 显示进度+断点续传（= \u0026ndash;partial \u0026ndash;progress） --exclude 排除（可多次） -n / --dry-run 演练模式（只说要做啥, 不真做） --bwlimit=10240 限速 10MB/s（避开业务高峰） 斜杠决定语义（新手第一大坑）：\nrsync -av /data/src /backup/ # → /backup/src/... 同步\u0026#34;src目录本身\u0026#34; rsync -av /data/src/ /backup/ # → /backup/... 同步\u0026#34;src里的内容\u0026#34; 排错利器 dry-run：\n# 永远先演练, 尤其带 --delete 时! rsync -avn --delete /data/src/ backup:/data/backup/ | head 三、实战：每日增量备份 #!/usr/bin/env bash # /opt/scripts/backup-data.sh set -euo pipefail SRC=\u0026#34;/data/app/\u0026#34; DEST=\u0026#34;backup@10.0.2.50:/backup/app-$(hostname)/\u0026#34; LOG=\u0026#34;/var/log/rsync-backup.log\u0026#34; exec \u0026gt;\u0026gt;\u0026#34;$LOG\u0026#34; 2\u0026gt;\u0026amp;1 echo \u0026#34;=== $(date \u0026#39;+%F %T\u0026#39;) 备份开始 ===\u0026#34; rsync -az --delete \\ --exclude=\u0026#34;*.tmp\u0026#34; \\ --exclude=\u0026#34;cache/\u0026#34; \\ --bwlimit=51200 \\ -e \u0026#34;ssh -p 22 -i /home/backup/.ssh/id_ed25519\u0026#34; \\ \u0026#34;$SRC\u0026#34; \u0026#34;$DEST\u0026#34; echo \u0026#34;=== 备份完成, 同步 $(du -sh $SRC | cut -f1) ===\u0026#34; 配合 cron（每日 2 点）：\n# /etc/cron.d/data-backup 0 2 * * * backup /opt/scripts/backup-data.sh 镜像型备份的缺陷：源被误删/中勒索，--delete 会把备份也同步删掉！必须配合快照或版本化：\n# 版本化: 每天一个硬链接快照目录（rsync --link-dest 神器） TODAY=/backup/app/$(date +%F) YESTERDAY=/backup/app/$(date -d yesterday +%F) rsync -a --delete --link-dest=\u0026#34;$YESTERDAY\u0026#34; /data/app/ \u0026#34;$TODAY/\u0026#34; # 未变化的文件 = 硬链接（0额外空间）, 变化的文件 = 新副本 # 效果: 每天完整视图 + 增量磁盘占用 四、inotify：实时触发同步 rsync 本身是\u0026quot;轮询全量比对\u0026quot;，inotify 让文件一变化就触发：\ndnf install -y inotify-tools # 监控目录事件 inotifywait -mrq /data/web \\ -e create,modify,delete,move,close_write \\ --format \u0026#39;%w%f %e\u0026#39; | while read -r file event; do echo \u0026#34;[$event] $file\u0026#34; done 组合成实时同步服务：\n#!/usr/bin/env bash # /opt/scripts/realtime-sync.sh（用 systemd 常驻） set -euo pipefail SRC=/data/web/ DEST=web@10.0.2.60:/data/web/ LOCK=/var/run/realtime-sync.lock # 防重入 exec 9\u0026gt;\u0026#34;$LOCK\u0026#34;; flock -n 9 || exit 0 # 先做一次全量基线 rsync -a --delete \u0026#34;$SRC\u0026#34; \u0026#34;$DEST\u0026#34; # 监听变化, 攒 2 秒批量同步（避免高频触发） inotifywait -mrq \u0026#34;$SRC\u0026#34; -e create,modify,delete,move,close_write --format \u0026#39;%w%f\u0026#39; | while read -r _; do sleep 2 rsync -a --delete \u0026#34;$SRC\u0026#34; \u0026#34;$DEST\u0026#34; done # /etc/systemd/system/realtime-sync.service [Unit] Description=Realtime sync /data/web After=network-online.target [Service] ExecStart=/opt/scripts/realtime-sync.sh Restart=always RestartSec=5 [Install] WantedBy=multi-user.target inotify 的坑：\n问题 处理 大目录报队列溢出 sysctl fs.inotify.max_user_watches=524288 极高频写入刷爆 rsync 批量节流（如上 sleep）；或改 lsyncd 只适合单向 双向同步不要双机互 rsync（会脑裂），要双向用 syncthing/unison lsyncd：lua 封装的 inotify+rsync，配置化更优雅：\n-- /etc/lsyncd.conf.lua settings { logfile = \u0026#34;/var/log/lsyncd.log\u0026#34;, statusFile = \u0026#34;/var/log/lsyncd.status\u0026#34; } sync { default.rsync, source = \u0026#34;/data/web\u0026#34;, target = \u0026#34;web@10.0.2.60:/data/web\u0026#34;, delay = 3, rsync = { archive = true, delete = true } } 五、rsync 守护进程模式（免 SSH 大带宽场景） # /etc/rsyncd.conf（服务端, 备份机） uid = backup gid = backup use chroot = yes max connections = 10 [mod-backup] path = /backup/app comment = app backup read only = false auth users = syncer secrets file = /etc/rsyncd.secrets # 格式: syncer:密码, 权限600 systemctl enable --now rsyncd # 客户端 rsync -az --password-file=/etc/rsync.pass /data/app/ \\ rsync://syncer@10.0.2.50/mod-backup/ 六、同步方案选型表 需求 方案 每日备份 cron + rsync -a + \u0026ndash;link-dest 版本化 准实时单向 inotify+rsync / lsyncd 双向同步 syncthing / unison 海量小文件 rsync 排除临时 + --whole-file 局域网加速 跨机房/公网 rsync over SSH 或对象存储同步工具 数据库 别直接 rsync 活库！逻辑备份(xtrabackup/mysqldump)后同步 七、排障速查 # 权限错误 rsync: chown ... failed: Operation not permitted → 目标端无 root; 去掉属主保留(-a 里去掉 -o -g: 用 -rlptD) # 速度慢 → 看是否走了压缩大文件; 局域网加 --whole-file; 公网加 -z → iostat 看磁盘是否瓶颈; --bwlimit 反向确认没被限速 # @ERROR: auth failed → secrets 文件权限600; 用户名密码对齐服务端 # inotify 无输出 → 内核 watches 上限; 监控路径是否挂载点（跨文件系统要分层监控） 小结 场景 命令要点 日常同步 rsync -avz --exclude 注意结尾斜杠 镜像同步 --delete 必先 dry-run 备份保护 --link-dest 版本化快照 实时单向 inotifywait + flock + 批量节流 常驻服务 systemd Restart=always 本文是「Linux 运维」系列第 15 篇。\n","permalink":"https://antidebug.cn/posts/2026/05/rsync-%E4%B8%8E-inotify-%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e数据要在两台机器间保持同步——备份、分发、迁移——十几年过去了，答案依然是 \u003cstrong\u003ersync\u003c/strong\u003e：增量传输、断点续传、保持属性、走 SSH。配合 inotify 触发，还能做到准实时同步。\u003c/p\u003e","title":"Rsync 与 inotify 数据同步方案"},{"content":"前言\u0026quot; 容器原理篇说过 cgroup 是容器的三大支柱之一，这篇把它单独拿出来亲手玩透——不用 Docker，直接用内核的 cgroup 文件系统给任意进程上\u0026quot;资源紧箍咒\u0026quot;。\n一、先看懂版本 stat -fc %T /sys/fs/cgroup/ # cgroup2fs → cgroup v2（统一层级, RHEL9/新系统默认） # tmpfs → cgroup v1（多层级, 老系统） 版本 特点 v1 每种资源一个树（cpu/ memory/ \u0026hellip;），各管各的 v2 统一一棵树，接口更简洁，进程归属唯一 本文以 v2 为主（趋势），v1 差异处标注。\n二、v2 目录结构与核心文件 ls /sys/fs/cgroup/ # cgroup.controllers 本级可用的控制器 # cgroup.subtree_control 启用哪些控制器下放给子组 # cgroup.procs 组里的进程 PID 列表 # cpu.max / memory.max / io.max ...各控制器文件 # 一层要往下传控制器才可用（父子约定） cat /sys/fs/cgroup/cgroup.controllers # cpuset cpu io memory hugetlb pids rdma misc 三、实战一：CPU 限制（0.5 核） # 1. 建组（mkdir 即创建） mkdir /sys/fs/cgroup/lab # 若提示 no internal process: 先确保父级没有进程直接挂在组上 # 2. 配额: 每 100ms 调度周期内最多用 50ms CPU = 0.5 核 echo \u0026#34;50000 100000\u0026#34; \u0026gt; /sys/fs/cgroup/lab/cpu.max # 3. 把一个死循环进程圈进去 sha1sum /dev/zero \u0026amp; PID=$! echo $PID \u0026gt; /sys/fs/cgroup/lab/cgroup.procs # 4. 验证（另开终端） top -p $PID # %CPU ≈ 50.0 ← 被摁在半核, 稳如老狗 # v1 等价操作: # echo 50000 \u0026gt; /sys/fs/cgroup/cpu/lab/cpu.cfs_quota_us # echo 100000 \u0026gt; /sys/fs/cgroup/cpu/lab/cpu.cfs_period_us # echo $PID \u0026gt; /sys/fs/cgroup/cpu/lab/tasks 原理：CFS 带宽控制——周期内用完配额，该组进程**被节流（throttle）**直到下个周期。观察节流：\ncat /sys/fs/cgroup/lab/cpu.stat # usage_usec 5123000 # nr_throttled 45 ← 被节流 45 次 # throttled_usec 3200000 ← 累计被冻结的时间 其他 CPU 手段：\n# cpuset: 直接限定只能用哪几个核（绑核） echo \u0026#34;2-3\u0026#34; \u0026gt; /sys/fs/cgroup/lab/cpuset.cpus # weight: 相对份额（默认100, 类似 nice） echo \u0026#34;50\u0026#34; \u0026gt; /sys/fs/cgroup/lab/cpu.weight 四、实战二：内存上限与 OOM # 限额 100MB echo \u0026#34;104857600\u0026#34; \u0026gt; /sys/fs/cgroup/lab/memory.max # 圈进一个吃内存进程 python3 -c \u0026#34; import time buf = [] while True: buf.append(b\u0026#39;x\u0026#39; * 1024 * 1024) # 每秒吃1MB time.sleep(0.1) \u0026#34; \u0026amp; echo $! \u0026gt; /sys/fs/cgroup/lab/cgroup.procs # 观察 watch -n1 cat /sys/fs/cgroup/lab/memory.current # 涨到 104857600 时 → 进程被 OOM Kill dmesg | tail -2 # Memory cgroup out of memory: Killed process 23831 (python3) 内存的软硬两级：\necho \u0026#34;52428800\u0026#34; \u0026gt; /sys/fs/cgroup/lab/memory.high # 软限: 超过就回收/减速 echo \u0026#34;104857600\u0026#34; \u0026gt; /sys/fs/cgroup/lab/memory.max # 硬限: 超过就杀 监听 OOM 事件（编排系统的预警手段）：\n# memory.events 里是计数 cat /sys/fs/cgroup/lab/memory.events # oom 1 # oom_kill 1 ← 真杀了 # 事件通知: 监听 memory.events 文件变化（inotify） 五、实战三：IO 限制（防止刷盘怪兽） # 限写 /dev/vdb 到 10MB/s（先查设备号） lsblk -o NAME,MAJ:MIN # vdb 253:16 echo \u0026#34;253:16 rbps=10485760\u0026#34; \u0026gt; /sys/fs/cgroup/lab/io.max # 圈进 dd 测试 dd if=/dev/zero of=/data/test.img bs=1M count=500 oflag=direct \u0026amp; echo $! \u0026gt; /sys/fs/cgroup/lab/cgroup.procs # dd: 10.4 MB/s ← 被限速 六、pids：防 fork 炸弹 # 该组最多 100 个进程 echo 100 \u0026gt; /sys/fs/cgroup/lab/pids.max # 验证 bash -c \u0026#39;for i in $(seq 200); do sleep 100 \u0026amp; done\u0026#39; 2\u0026gt;\u0026amp;1 | tail -1 # bash: fork: 资源暂时不可用 ← 到顶了, 系统其他部分安然无恙 七、systemd 与 cgroup：日常的正确入口 生产环境不建议手搓 cgroup 文件——systemd 已经是标准管理入口：\n# slice = cgroup 组的封装 systemd-cgls # 查看层级树 systemd-cgtop # 各组资源排行 # 给服务限额 = 写 unit（服务管理篇的老朋友） [Service] CPUQuota=50% # → cpu.max MemoryMax=200M # → memory.max TasksMax=100 # → pids.max IOWriteBandwidth=/data 10M # → io.max systemctl daemon-reload \u0026amp;\u0026amp; systemctl restart myapp # 验证 unit 落到了哪个 cgroup cat /proc/$(systemctl show -p MainPID --value myapp)/cgroup # 0::/system.slice/myapp.service ls /sys/fs/cgroup/system.slice/myapp.service/ 临时跑个受限进程（一行搞定）：\nsystemd-run --scope -p CPUQuota=30% -p MemoryMax=100M \\ --uid=app ./heavy-job 八、回看容器/K8s 的限额 docker run --cpus=0.5 --memory=256m busybox sleep 99 \u0026amp; # 它做的事（v2 机器上）: ls /sys/fs/cgroup/system.slice/docker-\u0026lt;id\u0026gt;.scope/ cat /sys/fs/cgroup/system.slice/docker-\u0026lt;id\u0026gt;.scope/cpu.max # 50000 100000 cat /sys/fs/cgroup/system.slice/docker-\u0026lt;id\u0026gt;.scope/memory.max # 268435456 # K8s 的 requests/limits: # limits.cpu: 500m → 本质就是这个 cpu.max # limits.memory: 256Mi → memory.max, 超了= Pod OOMKilled(exit 137) # requests 内存 → 影响调度, 不设硬限 Docker/K8s 没有魔法，只是 cgroup 的编排器——你现在有了直接看穿和调试它们的能力：\n# K8s 节点上找 Pod 的 cgroup kubectl get pods -o wide # 找节点 crictl pods | grep \u0026lt;pod\u0026gt; # 拿 cgroup 路径 cat /sys/fs/cgroup/kubepods.slice/.../cpu.max 九、常见坑 现象 原因 mkdir 报 no internal process v2 要求中间组不能直接挂进程，移走 cgroup.procs echo 报 no such file 控制器没在父级 subtree_control 里启用 限额不生效 进程 PID 写错组；子进程继承但组要确认 cpu.max 设 1000 100000 千分之一的核——配额/周期别写反 删不掉组 组里还有进程或子组，先迁移再 rmdir 小结 需求 文件（v2） systemd 等价 CPU 硬限 cpu.max CPUQuota=50% 绑核 cpuset.cpus AllowedCPUs= 内存硬限 memory.max MemoryMax= 内存软限 memory.high MemoryHigh= IO 限速 io.max IOWriteBandwidth= 防炸 pids.max TasksMax= 观测 cpu.stat / memory.events systemd-cgtop 本文是「Linux 基础」系列第 10 篇。\n","permalink":"https://antidebug.cn/posts/2026/04/linux-cgroups-%E8%B5%84%E6%BA%90%E9%99%90%E5%88%B6%E5%AE%9E%E6%88%98/","summary":"\u003ch2 id=\"前言\"\u003e前言\u0026quot;\u003c/h2\u003e\n\u003cp\u003e容器原理篇说过 cgroup 是容器的三大支柱之一，这篇把它单独拿出来\u003cstrong\u003e亲手玩透\u003c/strong\u003e——不用 Docker，直接用内核的 cgroup 文件系统给任意进程上\u0026quot;资源紧箍咒\u0026quot;。\u003c/p\u003e","title":"Linux cgroups 资源限制实战"},{"content":"前言 微服务多了，治理需求爆炸：灰度、熔断、重试、加密、链路追踪……把这些能力从业务代码里抽出来下沉到基础设施，就是服务网格（Service Mesh）。Istio 是这个领域的事实标准。\n一、为什么需要服务网格 没有网格: 有网格: ┌────────────┐ ┌────────────┐ │ 业务代码 │ │ 业务代码 │ ← 只关心业务 │ + 重试逻辑 │ ├────────────┤ │ + 熔断逻辑 │ │ Envoy代理 │ ← 重试/熔断/加密 │ + 加密代码 │ │ (sidecar) │ 全在这里 │ + 追踪埋点 │ └────────────┘ └────────────┘ 框架SDK耦合: 换语言=重写治理 多语言天然统一, 业务零侵入 核心机制：Sidecar 模式——每个 Pod 注入一个 Envoy 代理容器，进出 Pod 的所有流量都被它劫持：\n# 注入后看 Pod kubectl get pods # api-7d9f6c-x2v4x 2/2 Running ← 2 个容器 = 业务 + istio-proxy kubectl exec -it api-xxx -c istio-proxy -- pilot-agent version 二、架构与安装 ┌─ 控制面 istiod ──────────────┐ │ 配置下发(xDS) + 证书签发(CA) │──► 所有 Envoy └──────────────────────────────┘ 数据面: 每个 Pod 里的 Envoy ↔ 组成服务间通信的实际通路 # 安装（国内镜像） curl -L https://istio.io/installIstio | ISTIO_VERSION=1.23.0 sh - export PATH=$PATH:$(pwd)/istio-1.23.0/bin istioctl install --set profile=demo -y # 给 namespace 打标签, 新 Pod 自动注入 sidecar kubectl label namespace prod istio-injection=enabled kubectl rollout restart deploy/api -n prod # 重启生效 # 验证 istioctl proxy-status 三、流量管理：网格的看家本领 VirtualService：路由规则 apiVersion: networking.istio.io/v1 kind: VirtualService metadata: name: api spec: hosts: [api] http: - match: - headers: {x-canary: {exact: \u0026#34;true\u0026#34;}} # 按请求头路由 route: - destination: {host: api, subset: v2} - route: - destination: {host: api, subset: v1} weight: 95 # 默认流量 95% 走 v1 - destination: {host: api, subset: v2} weight: 5 # 5% 灰度 v2 DestinationRule：定义版本与策略 apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: api spec: host: api subsets: - name: v1 labels: {version: v1} - name: v2 labels: {version: v2} trafficPolicy: connectionPool: tcp: {maxConnections: 100} outlierDetection: # 熔断: 连续5次5xx则踢出30秒 consecutive5xxErrors: 5 interval: 10s baseEjectionTime: 30s 金丝雀发布全流程（对应 CI/CD 篇的策略）：\nkubectl apply -f dr.yaml # v1/v2 subset 就位 kubectl apply -f vs-5.yaml # 5% → v2 # 观察错误率/延迟 → OK kubectl apply -f vs-50.yaml # 50% kubectl apply -f vs-100.yaml # 全量 # 出问题? vs-0.yaml 一键回 0（秒级, 不用动 Deployment） 四、弹性能力：重试与超时 http: - route: [{destination: {host: api}}] timeout: 2s # 请求级超时 retries: attempts: 3 perTryTimeout: 500ms retryOn: \u0026#34;5xx,reset,connect-failure\u0026#34; # 只重试幂等可重试错误 治理能力完全 YAML 化——改重试策略不用改代码发版，这是网格相对 SDK 的本质优势。\n五、安全：mTLS 零改造加密 apiVersion: security.istio.io/v1 kind: PeerAuthentication metadata: name: default namespace: istio-system # 全网格生效 spec: mtls: {mode: STRICT} # 服务间通信强制双向TLS # 验证: 抓包看到的全是 TLS istioctl x authn check api.xxx # 控制谁能访问谁（L7 授权） apiVersion: security.istio.io/v1 kind: AuthorizationPolicy metadata: name: api-only-from-gateway namespace: prod spec: selector: {matchLabels: {app: api}} action: ALLOW rules: - from: [{source: {principals: [\u0026#34;cluster.local/ns/istio-system/sa/istio-ingressgateway\u0026#34;]}}] 六、可观测性：开箱即用的黄金指标 装上 Kiali（拓扑图）+ 内置的指标上报（Prometheus 格式）：\nkubectl apply -f samples/addons/kiali.yaml kubectl apply -f samples/addons/prometheus.yaml # 访问 Kiali: 服务拓扑、流量动画、错误率一目了然 # istioctl 也能直接查 istioctl x describe pod api-xxx # 这个Pod的配置/证书状态 Envoy 自动上报请求级指标（成功率/延迟/p200\u0026hellip;）——微服务监控从\u0026quot;进程级\u0026quot;升到\u0026quot;服务调用级\u0026quot;。\n七、代价：不是免费的午餐 代价 说明 延迟 每跳多经过两层代理（客户端+服务端 sidecar），P99 增加约 1~3ms 资源 每个 Pod 多一个容器（内存 ~100MB 起），百 Pod 规模=一台节点的开销 复杂度 排障链路多了一层（istioctl proxy-status/log 是新必修课） 什么时候值得上：\n✅ 微服务 \u0026gt; 20 个、多语言并存、有专职平台团队 ✅ 强合规要求（全链路 mTLS、审计） ⚠️ 几个服务的中小团队：K8s 原生 + 网关层治理先够用，别为简历上网格 八、排障速查 istioctl proxy-status # 所有 sidecar 同步状态 istioctl proxy-config routes api-xxx # 查看生效的路由（vs没生效先看这!） istioctl proxy-config cluster api-xxx kubectl logs api-xxx -c istio-proxy --tail=50 # 常见坑 sidecar 没注入 → namespace label + 重启 Pod 503 no healthy upstream → subset label 不匹配 / 探针失败 路由规则不生效 → hosts 写错(短名/FQDN)、DR/VS 顺序 UDP/特殊协议不通 → 网格默认代理 TCP, 例外要显式配置 小结 概念 作用 Sidecar (Envoy) 劫持进出流量, 承载全部治理 istiod 控制面: 配置下发 + 证书 VirtualService 怎么路由（灰度/分流/匹配） DestinationRule 到哪里去（版本/连接池/熔断） PeerAuthentication mTLS 加密 AuthorizationPolicy 服务级授权 本文是「云原生」系列第 16 篇。\n","permalink":"https://antidebug.cn/posts/2026/04/%E6%9C%8D%E5%8A%A1%E7%BD%91%E6%A0%BC%E5%85%A5%E9%97%A8istio-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e微服务多了，治理需求爆炸：灰度、熔断、重试、加密、链路追踪……把这些能力\u003cstrong\u003e从业务代码里抽出来下沉到基础设施\u003c/strong\u003e，就是服务网格（Service Mesh）。Istio 是这个领域的事实标准。\u003c/p\u003e","title":"服务网格入门：Istio 核心概念"},{"content":"前言 性能工具篇讲了\u0026quot;怎么用\u0026quot;，这篇讲\u0026quot;怎么破案\u0026quot;——一个 CPU 告警的完整排查复盘，还原每一步的推理过程。\n一、案发 22:14 告警: prod-api-03 CPU \u0026gt; 90%, 持续 5 分钟 影响: 该节点上的服务 P99 延迟 300ms → 2.8s 二、第一现场：top 全景 top # 按 P（CPU排序）按 1（展开核） load average: 14.2, 12.8, 9.5 ← 8核机器, load 14 明显过载 %Cpu(s): 92.3 us, 5.1 sy, 0.0 wa, 0.0 id, 0.0 st ▲ └ 92% 是 us（用户态）! ← 关键线索①: 应用在烧CPU, 不是内核/IO PID USER %CPU %MEM S COMMAND 8473 app 780.0 8.2 R java ← 元凶进程（多线程吃满约8核） 9102 app 2.1 3.1 S sidecar 推理：\nus 高 → 应用代码问题（死循环/正则回溯/GC 风暴），而不是内核态（系统调用/锁）或 IO wa=0, st=0 → 排除磁盘慢与宿主机超卖 进程 8473 一家吃掉 780% → 线程级下钻 三、线程级下钻 # 看该进程内哪个线程在烧 top -H -p 8473 PID USER %CPU S COMMAND 8501 app 198.0 R java ← 线程 8501 烧了约2核 8502 app 198.0 R java 8503 app 190.0 R java 8473 app 5.2 S java 三个线程几乎打满各 2 核。把线程号转成 16 进制（Java 的线程栈用 nid=0x… 标识）：\nprintf \u0026#34;%x\\n\u0026#34; 8501 # 2135 jstack 8473 | grep -A 20 \u0026#34;nid=0x2135\u0026#34; \u0026#34;worker-3\u0026#34; #123 daemon prio=5 os_prio=0 tid=0x... nid=0x2135 runnable at com.demo.parser.RegexParser.match(RegexParser.java:87) at com.demo.parser.RegexParser.parse(RegexParser.java:45) ... 线索指向：正则匹配模块。看代码：\n// RegexParser.java:87 private static final Pattern BAD = Pattern.compile(\u0026#34;(a+)+b\u0026#34;); // 典型的灾难性回溯（ReDoS 模式）! // 输入 \u0026#34;aaaaaaaaaaaaaaaaaaaaaaaaaaaaX\u0026#34;（不匹配b）时, 回溯组合爆炸 初步结论：一段恶意/异常输入触发正则回溯风暴，三个 worker 线程全陷进去 → CPU 100% → 处理队列堆积 → 延迟飙升。\n四、用 perf 佐证（不是 Java 也能用这招） # 对进程采样 10 秒 perf top -p 8473 # 或采样落盘 perf record -F 99 -p 8473 -g -- sleep 10 perf report --stdio | head -30 62.41% libjava.so [.] re_match 18.20% libjava.so [.] re_compile ... re_match RegexParser.match ... re_match（正则引擎）占 62% ——与 jstack 结论互相印证。\n火焰图（一图看穿）：\ngit clone https://ghfast.top/https://github.com/brendangregg/FlameGraph perf record -F 99 -p 8473 -g -- sleep 15 perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl \u0026gt; cpu.svg # 浏览器打开: 一片\u0026#34;高原\u0026#34;正对着 re_match → 热点一目了然 五、应急与根治 应急（5 分钟内恢复） # 1. 隔离: 从负载均衡摘除该节点（别急着重启, 保留现场） # 2. 抓证据 jstack 8473 \u0026gt; /tmp/jstack-$(date +%F-%H%M).txt # 已抓 perf record ... # 已采样 # 3. 临时止血: 限流该接口 / kill 卡死线程 curl -X POST http://localhost:9090/actuator/throttle -d \u0026#39;regex_api=off\u0026#39; 根治 // 1. 修正则: 消除嵌套量词 Pattern.compile(\u0026#34;a+b\u0026#34;); // 等价且线性时间 // 或用占有优先量词断掉回溯 Pattern.compile(\u0026#34;(?\u0026gt;(a+))b\u0026#34;); // 2. 输入长度限制 + 匹配超时兜底 if (input.length() \u0026gt; 1024) throw new BizError(\u0026#34;input too long\u0026#34;); // 3. 加监控: 该接口处理耗时 P99 告警（这次是 CPU 告警先响, 属侥幸） 六、方法论沉淀：CPU 高的分流决策树 top 一眼看四数: ├─ us 高 → 应用代码问题 │ ├─ Java: top -H + jstack(16进制nid) / arthas thread -n 5 │ ├─ Go: top -H + pprof(http://x/debug/pprof/profile) │ └─ C/通用: perf top / 火焰图 ├─ sy 高 → 系统调用/上下文切换/锁 │ └─ vmstat 看 cs/in, strace -c -p PID 统计调用, 锁竞争查 /proc/PID/stack ├─ wa 高 → IO 瓶颈（CPU 在等盘）→ 转 iostat 路线（性能篇） ├─ st 高 → 宿主机超卖 → 找云厂商/迁移 └─ si 高 → 软中断（网络包）→ mpstat -P ALL 看, nethogs 找流量源 load 高但 CPU 空闲？ → D 状态进程排队（IO/NFS 卡死），vmstat 看 b 列，ps -eo pcpu,stat,wchan -p 查等待点。\n七、工具对照卡 工具 一句话 top -H -p PID 线程级 CPU 定位第一步 jstack / arthas Java 栈快照（nid 十六进制对线程） perf top / record 采样热点函数（内核用户态都行） 火焰图 宽 = 热，一眼找宽路 strace -c 统计系统调用开销（sy 高时用） pprof Go 程序的 profile 标配 八、复盘清单 告警先摘流量、保现场，再动手查（这次顺序对了） us/sy/wa/st 四象限分流，避免瞎忙 线程 → 栈 → 代码行，证据链完整 perf 与 jstack 交叉验证 根治后加\u0026quot;耗时维度\u0026quot;监控（CPU 只是症状放大器） 正则这类\u0026quot;埋雷代码\u0026quot;进 code review 检查项 小结 CPU 排查的完整闭环：top 定方向（us/sy/wa/st）→ 线程级下钻（top -H）→ 栈/采样定位（jstack/perf/pprof）→ 代码级根因 → 应急+根治+监控补位。工具不难，难的是每一步都带着假设去验证。\n本文是「Linux 运维」系列第 14 篇。\n","permalink":"https://antidebug.cn/posts/2026/03/linux-%E6%80%A7%E8%83%BD%E6%8E%92%E6%9F%A5%E6%A1%88%E4%BE%8Bcpu-%E9%A3%99%E9%AB%98%E5%88%86%E6%9E%90/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e性能工具篇讲了\u0026quot;怎么用\u0026quot;，这篇讲\u0026quot;怎么破案\u0026quot;——一个 CPU 告警的完整排查复盘，还原每一步的推理过程。\u003c/p\u003e\n\u003ch2 id=\"一案发\"\u003e一、案发\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e22:14  告警: prod-api-03 CPU \u0026gt; 90%, 持续 5 分钟\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e影响:  该节点上的服务 P99 延迟 300ms → 2.8s\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"二第一现场top-全景\"\u003e二、第一现场：top 全景\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etop                              \u003cspan class=\"c1\"\u003e# 按 P（CPU排序）按 1（展开核）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eload average: 14.2, 12.8, 9.5        ← 8核机器, load 14 明显过载\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e%Cpu(s): 92.3 us, 5.1 sy, 0.0 wa, 0.0 id, 0.0 st\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        ▲\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        └ 92% 是 us（用户态）!        ← 关键线索①: 应用在烧CPU, 不是内核/IO\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  PID USER    %CPU %MEM  S  COMMAND\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e 8473 app     780.0  8.2  R  java            ← 元凶进程（多线程吃满约8核）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e 9102 app       2.1  3.1  S  sidecar\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e推理\u003c/strong\u003e：\u003c/p\u003e","title":"Linux 性能排查案例：CPU 飙高分析"},{"content":"前言 Flask 时代写 API 要自己造的轮子——参数校验、文档、类型提示——FastAPI 一次全给：写函数签名，送你文档和校验。这篇从零到部署走一遍。\n一、五分钟起步 pip install \u0026#34;fastapi[standard]\u0026#34; -i https://mirrors.aliyun.com/pypi/simple/ # main.py from fastapi import FastAPI app = FastAPI(title=\u0026#34;运维平台 API\u0026#34;, version=\u0026#34;1.0.0\u0026#34;) @app.get(\u0026#34;/healthz\u0026#34;) def healthz(): return {\u0026#34;status\u0026#34;: \u0026#34;ok\u0026#34;} @app.get(\u0026#34;/hello/{name}\u0026#34;) def hello(name: str): return {\u0026#34;message\u0026#34;: f\u0026#34;hello, {name}\u0026#34;} fastapi dev main.py # 开发模式, 改代码热重载 # 打开 http://localhost:8000/docs ← 自动交互式文档（Swagger UI） # 打开 http://localhost:8000/redoc ← 另一套文档样式 类型注解就是功能：name: str 声明后，框架自动解析路径参数、校验类型、生成文档。\n二、Pydantic：请求与响应模型 from pydantic import BaseModel, Field, EmailStr from typing import Optional class HostCreate(BaseModel): name: str = Field(..., min_length=2, max_length=30, examples=[\u0026#34;web01\u0026#34;]) ip: str = Field(..., pattern=r\u0026#34;^(\\d{1,3}\\.){3}\\d{1,3}$\u0026#34;) port: int = Field(22, ge=1, le=65535) tags: list[str] = [] owner: Optional[EmailStr] = None class HostOut(HostCreate): id: int status: str @app.post(\u0026#34;/api/hosts\u0026#34;, response_model=HostOut, status_code=201) def create_host(host: HostCreate): # 走到这里, 数据一定合法（不合法根本进不来） saved = save_to_db(host) # 假装入库 return saved 非法请求的自动处理：\ncurl -X POST http://localhost:8000/api/hosts -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;w\u0026#34;,\u0026#34;ip\u0026#34;:\u0026#34;999.1.1.1\u0026#34;,\u0026#34;port\u0026#34;:70000}\u0026#39; # 422 Unprocessable Entity # {\u0026#34;detail\u0026#34;:[ # {\u0026#34;loc\u0026#34;:[\u0026#34;body\u0026#34;,\u0026#34;name\u0026#34;],\u0026#34;msg\u0026#34;:\u0026#34;String should have at least 2 characters\u0026#34;}, # {\u0026#34;loc\u0026#34;:[\u0026#34;body\u0026#34;,\u0026#34;ip\u0026#34;],\u0026#34;msg\u0026#34;:\u0026#34;String should match pattern...\u0026#34;}, # {\u0026#34;loc\u0026#34;:[\u0026#34;body\u0026#34;,\u0026#34;port\u0026#34;],\u0026#34;msg\u0026#34;:\u0026#34;Input should be at most 65535\u0026#34;}]} response_model 的妙处：输出自动过滤（比如过滤密码字段）、自动转成声明的形状——文档永远和代码一致。\n三、查询参数、过滤与分页 from fastapi import Query @app.get(\u0026#34;/api/hosts\u0026#34;) def list_hosts( page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100), tag: Optional[str] = None, ): return {\u0026#34;page\u0026#34;: page, \u0026#34;size\u0026#34;: size, \u0026#34;tag\u0026#34;: tag} # /api/hosts?page=2\u0026amp;size=50\u0026amp;tag=prod 四、依赖注入：中间逻辑的优雅归宿 鉴权、取 DB 会话、分页参数——不用每个函数复制粘贴：\nfrom fastapi import Depends, Header, HTTPException API_TOKEN = \u0026#34;secret-token\u0026#34; def verify_token(authorization: str = Header(...)): if authorization != f\u0026#34;Bearer {API_TOKEN}\u0026#34;: raise HTTPException(status_code=401, detail=\u0026#34;invalid token\u0026#34;) def get_db(): db = SessionLocal() try: yield db # 用完自动走 finally 关闭 finally: db.close() @app.get(\u0026#34;/api/hosts\u0026#34;, dependencies=[Depends(verify_token)]) def list_hosts(db=Depends(get_db)): return db.query(Host).all() 依赖可以嵌套依赖，测试时用 app.dependency_overrides 替换假实现——解耦利器。\n五、异步端点 import httpx @app.get(\u0026#34;/api/check\u0026#34;) async def check(url: str): async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=5) return {\u0026#34;url\u0026#34;: url, \u0026#34;status\u0026#34;: resp.status_code} # 注意: async 端点里绝不能有阻塞调用（time.sleep/同步IO） # 要么用 await 异步库, 要么用普通 def（FastAPI 自动丢线程池） 六、后台任务与定时活 from fastapi import BackgroundTasks def write_audit(action: str): with open(\u0026#34;audit.log\u0026#34;, \u0026#34;a\u0026#34;) as f: f.write(f\u0026#34;{action} at {time.time()}\\n\u0026#34;) @app.post(\u0026#34;/api/hosts/{hid}/restart\u0026#34;) def restart_host(hid: int, bg: BackgroundTasks): bg.add_task(write_audit, f\u0026#34;restart host {hid}\u0026#34;) # 响应后异步执行 return {\u0026#34;result\u0026#34;: \u0026#34;restarting\u0026#34;} # 更重的任务用 Celery/ARQ 队列, BackgroundTasks 适合轻量收尾 七、错误处理与中间件 from fastapi import Request from fastapi.responses import JSONResponse class BizError(Exception): def __init__(self, code: str, msg: str): self.code, self.msg = code, msg @app.exception_handler(BizError) async def biz_handler(request: Request, exc: BizError): return JSONResponse(status_code=400, content={\u0026#34;code\u0026#34;: exc.code, \u0026#34;message\u0026#34;: exc.msg}) @app.middleware(\u0026#34;http\u0026#34;) async def add_timing(request: Request, call_next): start = time.time() response = await call_next(request) response.headers[\u0026#34;X-Process-Time\u0026#34;] = f\u0026#34;{time.time()-start:.3f}\u0026#34; return response 八、项目结构与部署 app/ ├── main.py # FastAPI 实例与路由注册 ├── deps.py # 公共依赖 ├── models.py # Pydantic 模型 └── routers/ ├── hosts.py └── users.py # routers/hosts.py —— APIRouter 拆分路由 from fastapi import APIRouter router = APIRouter(prefix=\u0026#34;/api/hosts\u0026#34;, tags=[\u0026#34;hosts\u0026#34;]) @router.get(\u0026#34;/{hid}\u0026#34;) def get_host(hid: int): ... 生产部署：\n# 多 worker 跑生产 gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 # 或纯 uvicorn uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 前置 Nginx（反向代理篇的配置直接复用）+ systemd 拉起：\n[Service] ExecStart=/opt/app/.venv/bin/gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 127.0.0.1:8000 九、测试 from fastapi.testclient import TestClient client = TestClient(app) def test_create_host(): resp = client.post(\u0026#34;/api/hosts\u0026#34;, json={\u0026#34;name\u0026#34;: \u0026#34;web01\u0026#34;, \u0026#34;ip\u0026#34;: \u0026#34;10.0.0.1\u0026#34;}) assert resp.status_code == 201 assert resp.json()[\u0026#34;name\u0026#34;] == \u0026#34;web01\u0026#34; def test_invalid_ip(): resp = client.post(\u0026#34;/api/hosts\u0026#34;, json={\u0026#34;name\u0026#34;: \u0026#34;web01\u0026#34;, \u0026#34;ip\u0026#34;: \u0026#34;bad\u0026#34;}) assert resp.status_code == 422 同步 TestClient + pytest，无起服务的开销，CI 里跑飞快。\n十、Flask 还是 FastAPI？ 维度 FastAPI Flask 类型/校验 原生 Pydantic ✅ 手写或插件 异步 原生 ✅ 需要 async 视图改造 文档 自动 ✅ flask-restx 等 生态/历史 较新 极其成熟 适合 新 API 项目 传统 Web 页面/老项目 我的结论：2026 年起新 API 项目默认 FastAPI，除非团队有厚重 Flask 资产。\n小结 能力 写法 校验 Pydantic 模型 + Field 约束 文档 自动 /docs，response_model 保持一致 复用逻辑 Depends 依赖注入 异步 async def + await 收尾任务 BackgroundTasks 部署 gunicorn -k UvicornWorker 本文是「Python」系列第 9 篇。\n","permalink":"https://antidebug.cn/posts/2026/02/python-web-%E6%A1%86%E6%9E%B6-fastapi-%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eFlask 时代写 API 要自己造的轮子——参数校验、文档、类型提示——FastAPI 一次全给：\u003cstrong\u003e写函数签名，送你文档和校验\u003c/strong\u003e。这篇从零到部署走一遍。\u003c/p\u003e\n\u003ch2 id=\"一五分钟起步\"\u003e一、五分钟起步\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epip install \u003cspan class=\"s2\"\u003e\u0026#34;fastapi[standard]\u0026#34;\u003c/span\u003e -i https://mirrors.aliyun.com/pypi/simple/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# main.py\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003efastapi\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eFastAPI\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eFastAPI\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;运维平台 API\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eversion\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1.0.0\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@app.get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/healthz\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehealthz\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;status\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ok\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@app.get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/hello/\u003c/span\u003e\u003cspan class=\"si\"\u003e{name}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehello\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;hello, \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efastapi dev main.py              \u003cspan class=\"c1\"\u003e# 开发模式, 改代码热重载\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 打开 http://localhost:8000/docs        ← 自动交互式文档（Swagger UI）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 打开 http://localhost:8000/redoc        ← 另一套文档样式\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e类型注解就是功能\u003c/strong\u003e：\u003ccode\u003ename: str\u003c/code\u003e 声明后，框架自动解析路径参数、校验类型、生成文档。\u003c/p\u003e","title":"Python Web 框架 FastAPI 快速上手"},{"content":"前言 在 K8s 上部署一个应用要写 Deployment+Service+ConfigMap+Ingress 一大摞 YAML；部署十个微服务？复制粘贴地狱。Helm 就是 K8s 的包管理器——模板化 + 参数化 + 版本化，一个命令装好整套应用。\n一、三大概念 Chart = 应用包（模板 + 默认值的集合, 一个目录/压缩包） Release = Chart 的一次安装实例（同名 Chart 可装多份, 各自独立） Repository = 存放 Chart 的仓库（官方/自建 Harbor） 类比：Chart ≈ rpm 包，values ≈ 配置文件，Release ≈ 安装好的软件实例，helm repo ≈ yum 源。\n二、安装与常用命令 # 安装 helm（国内加速） curl -LO https://ghfast.top/https://github.com/helm/helm/releases/download/v3.15.2/helm-v3.15.2-linux-amd64.tar.gz tar -xzf helm-v3.15.2-linux-amd64.tar.gz \u0026amp;\u0026amp; install linux-amd64/helm /usr/local/bin/ helm version # 仓库管理 helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update # 搜索与安装 helm search repo nginx helm install my-web bitnami/nginx \\ --namespace web --create-namespace \\ --set replicaCount=2 # 升级 / 回滚 / 卸载 helm upgrade my-web bitnami/nginx --set replicaCount=4 helm rollback my-web 1 helm uninstall my-web -n web # 查看 helm list -A helm status my-web -n web helm get values my-web -n web # 当前生效的覆盖值 helm history my-web -n web 三、Chart 结构 helm create myapp # 生成脚手架 myapp/ ├── Chart.yaml # 元信息: name/version/appVersion/依赖 ├── values.yaml # 默认值（被 --set/-f 覆盖） ├── charts/ # 子 Chart 依赖 └── templates/ ├── deployment.yaml # 模板（引用 values 的值） ├── service.yaml ├── ingress.yaml ├── _helpers.tpl # 公共命名函数 ├── NOTES.txt # 安装完成后的提示输出 └── tests/ # helm test 的 Pod 模板核心语法：\n# templates/deployment.yaml（节选） apiVersion: apps/v1 kind: Deployment metadata: name: {{ include \u0026#34;myapp.fullname\u0026#34; . }} spec: replicas: {{ .Values.replicaCount }} # 引用 values template: spec: containers: - name: {{ .Chart.Name }} image: \u0026#34;{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}\u0026#34; resources: requests: cpu: {{ .Values.resources.requests.cpu }} {{- if .Values.metrics.enabled }} # 条件块 ports: - containerPort: 9090 {{- end }} # values.yaml（可覆盖的旋钮） replicaCount: 2 image: repository: reg.local/myapp metrics: enabled: false resources: requests: {cpu: 100m} value 覆盖的三种方式（优先级从低到高）：values.yaml → -f custom.yaml → --set k=v。\n四、实战：部署一个完整业务 # 用自定义 values 部署（比改模板优雅） cat \u0026gt; prod-values.yaml \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; replicaCount: 5 image: repository: reg.local/team/api tag: v1.8.0 ingress: enabled: true hosts: - host: api.antidebug.cn paths: [{path: /, pathType: Prefix}] tls: - secretName: api-tls hosts: [api.antidebug.cn] resources: limits: {cpu: 500m, memory: 512Mi} autoscaling: enabled: true minReplicas: 5 maxReplicas: 20 EOF helm upgrade --install api ./myapp \\ -n prod --create-namespace \\ -f prod-values.yaml # --install: 没装过就装, 装过就升级（幂等, CI 首选写法） 多环境管理：\nmyapp/ ├── values.yaml # 公共默认 └── envs/ ├── dev.yaml # 1副本/debug ├── staging.yaml # 2副本 └── prod.yaml # 5副本/HPA/正式域名 五、发布与版本管理 # 打包 helm package myapp # → myapp-0.1.0.tgz # 推到私有仓库（Harbor 开启 chartmuseum） helm plugin install https://ghfast.top/https://github.com/chartmuseum/helm-push helm repo add harbor https://harbor.local/chartrepo/team helm push myapp-0.1.0.tgz harbor # 团队成员 helm repo update helm install team-api team/myapp -f my-overrides.yaml 版本语义：Chart.yaml 的 version 是 Chart 打包版本，appVersion 是应用版本——升应用改 image.tag 即可，改模板结构才动 version。\n六、Helm 的版本机制本质 helm install/upgrade 时: 1. 渲染全部模板 → 生成最终 YAML 2. 写入 Secret 保存这份 YAML（就是 Release 历史） 3. 应用到集群 helm rollback 时: 直接取出历史 Secret 里的旧 YAML 重新 apply → 所以\u0026#34;回滚秒级且可靠\u0026#34; # 只渲染不安装（调试模板神器） helm template myapp -f prod-values.yaml | less helm template myapp --debug | kubectl apply --dry-run=server -f - # 模板渲染错误排查 helm lint myapp helm install --dry-run --debug myapp ./myapp 七、依赖与公共 Chart 复用 # Chart.yaml: 应用依赖一个数据库 dependencies: - name: redis version: \u0026#34;19.x.x\u0026#34; repository: \u0026#34;oci://registry-1.docker.io/bitnamicharts\u0026#34; helm dependency update myapp # 拉取到 charts/ 目录 helm install myapp ./myapp 子 Chart 的值用 redis.auth.password=xxx 前缀覆盖——复杂中间件直接复用公共 Chart，别手写。\n八、常见坑 现象 原因 --set 值没生效 key 路径写错；helm get values 看实际值 模板改了但没变化 忘了 upgrade；看 helm list 的 chart 版本 uninstall 后资源还在 资源带了 helm.sh/resource-policy: keep 注解 rollback 报丢失 Release 被 purge 过（--keep-history 没加） 数字/布尔被当字符串 --set replicaCount=2 是数字, --set name=\u0026quot;2\u0026quot; 要引号 升级卡 pending-upgrade 上次操作超时中断 → helm rollback 或删 lock 九、Helm vs Kustomize 维度 Helm Kustomize 思路 模板 + 变量渲染 原始 YAML + overlay 打补丁 适合 一个产品装 N 家、参数多 同一套 YAML 多环境微调 依赖/仓库 ✅ 完整生态 无 学习 模板语法有成本 几乎零语法 实践：中间件/公共组件用 Helm，自家应用多环境用 Kustomize 或 Helm 皆可。\n小结 任务 命令 装 helm install xx chart -f values.yaml 升级 helm upgrade --install（幂等） 回滚 helm rollback xx 1 调试 helm template / --dry-run --debug 发布 helm package + helm push 到 Harbor 本文是「云原生」系列第 15 篇。\n","permalink":"https://antidebug.cn/posts/2026/02/%E4%BA%91%E5%8E%9F%E7%94%9Fhelm-%E5%BA%94%E7%94%A8%E5%8C%85%E7%AE%A1%E7%90%86%E5%85%A5%E9%97%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e在 K8s 上部署一个应用要写 Deployment+Service+ConfigMap+Ingress 一大摞 YAML；部署十个微服务？复制粘贴地狱。\u003cstrong\u003eHelm 就是 K8s 的包管理器\u003c/strong\u003e——模板化 + 参数化 + 版本化，一个命令装好整套应用。\u003c/p\u003e","title":"云原生：Helm 应用包管理入门"},{"content":"前言 公有云用得再熟，也总有一天会遇到\u0026quot;数据必须放自己机房\u0026quot;的需求——那时绕不开 OpenStack：开源界的\u0026quot;云操作系统\u0026quot;，用于自建 IaaS。这篇把它的核心组件和运作机制讲清楚。\n一、OpenStack 是什么 一句话：把一堆物理服务器、交换机、存储，变成一套可 API 调用的云平台。\n用户/运维 │ Dashboard(Horizon) / CLI / REST API ▼ ┌──────────────── OpenStack 控制面 ────────────────┐ │ Keystone(认证) → Nova(计算) / Neutron(网络) │ │ / Cinder(块存储) / Glance(镜像) / Swift(对象存储) │ └──────────────────────┬───────────────────────────┘ │ 驱动调度 ┌──────────────┼──────────────┐ ▼ ▼ ▼ 计算节点×N 网络节点×N 存储节点×N (KVM/libvirt) (OVS/Linux网桥) (Ceph/LVM) 它和 KVM 的关系：KVM 是发动机，OpenStack 是整车厂——负责把虚拟化能力包装成多租户、可配额、有网络的完整云。\n二、六大核心组件 1. Keystone：门卫与户口本 认证（Authentication）+ 授权（Authorization）+ 服务目录。\n- 用户/项目/角色模型: user → project(租户) → role - 所有组件的 API 都要先过 Keystone 拿 token - 服务目录: 记录\u0026#34;Nova 的 API 在哪、Cinder 的 API 在哪\u0026#34; openstack token issue # 拿 token openstack user list openstack role add --user zy --project devops member 2. Nova：计算的大脑 虚拟机的生命周期管理（不自己跑虚拟机，调度给计算节点上的 libvirt/KVM）：\nnova-api 接收请求 nova-scheduler 决定落在哪台计算节点（CPU/内存/磁盘过滤+权衡） nova-conductor 中介（帮计算节点写库, 解耦） nova-compute 每台计算节点上的工人（调 libvirt 开机） openstack server create --flavor m1.medium --image rocky9 \\ --network private --key-name mykey vm-lab01 openstack server list 3. Neutron：网络的灵魂 提供租户自服务网络（这是 OpenStack 最复杂也最有价值的部分）：\n概念 作用 network / subnet 虚拟网络与网段（VXLAN 隧道隔离租户） router 虚拟路由器（SNAT 出网 / floating IP 入站） port 虚拟机网卡的\u0026quot;插座\u0026quot;（绑 IP/MAC） security group 实例级防火墙规则 openstack network create net-app openstack subnet create --subnet-range 10.10.0.0/24 --network net-app subnet-app openstack router add subnet router-app subnet-app 4. Glance：镜像仓库 openstack image create --disk-format qcow2 --container-format bare \\ --file rocky-9.qcow2 --public rocky9 openstack image list # Nova 建虚拟机时从这里拉镜像 → 增量拷贝到计算节点 5. Cinder：块存储（云硬盘） openstack volume create --size 50 vol-data openstack server add volume vm-lab01 vol-data # 底层由后端驱动实现: Ceph RBD / LVM / 商业存储 # 支持快照、在线扩容、跨机迁移（云厂商 CBS 的原型） 6. Swift：对象存储 # REST API 存取海量对象（对标公有云 OSS/S3） curl -H \u0026#34;X-Auth-Token: $TOKEN\u0026#34; -T backup.tar.gz \\ http://swift:8080/v1/AUTH_xxx/container/backup.tar.gz rings + 副本策略保证持久性，适合归档/镜像备份场景。\n三、一个虚拟机的诞生之旅 把组件串起来的完整链路（也是排障的思路图）：\n1. 用户: openstack server create（带 token） 2. Keystone 验 token → 放行到 nova-api 3. nova-api 写库(状态 BUILD) → 消息队列 → scheduler 4. scheduler 过滤计算节点（剩多少内存/镜像在哪/可用区） → 选定 compute-03 5. compute-03: a. 从 Glance 下载镜像（或命中本地缓存） b. 向 Neutron 申请 port（分配 IP/MAC） c. 从 Cinder 挂载卷（若有） d. 调 libvirt → KVM 虚拟机启动 e. cloud-init 注入密钥/主机名 6. 状态 → ACTIVE, 用户拿到 IP 任何一步失败：openstack server show vm -f value fault + 各组件日志（/var/log/nova/*.log）定位。\n四、部署形态与现实建议 形态 说明 DevStack all-in-one 快速体验（仅实验） Kolla-Ansible 容器化生产部署（推荐） Puppet/Terraform 传统部署 老牌，定制强 厂商发行版 红帽 OSP / 国内超融合方案 谁该上 OpenStack：\n✅ 中大型企业私有云、运营商、科研机构：多租户 + 自服务 + 规模化 ✅ 需要云 API 但数据不能出机房 ⚠️ 三五台机器的小机房：别上，KVM + virsh + Ansible 足够（虚拟化实战篇那套） ⚠️ 想省钱：OpenStack 本身免费但人力成本高（需要专职团队） 五、与公有云/K8s 的关系 很多公司的真实架构: 公有云（弹性业务） + OpenStack 私有云（敏感数据/稳定负载） + K8s 跑在两者之上（OpenStack Magnum/公有云 ACK/TKE） 层级关系: OpenStack 管\u0026#34;机器\u0026#34;（IaaS, 虚拟机的云） K8s 管\u0026#34;容器\u0026#34;（PaaS, 应用的云） → 不是替代关系, 是上下层 六、排障速查 openstack server show vm01 -f value fault # 直接错误信息 openstack service list # 组件健康 nova service-list / openstack compute service list openstack network agent list # 网络代理状态 # 经典故障对照 虚拟机 ERROR \u0026#34;No valid host\u0026#34; → 资源不够/调度器过滤全挂 ping 不通 floating IP → 检查 router 网关/security group/外网桥 卷 attach 卡住 → cinder 后端状态/多路径 镜像下载超时 → Glance 存储/计算节点到 Glance 网络 小结 组件 一句话 Keystone 认证与服务目录（一切的入口） Nova 虚拟机生命周期与调度 Neutron 租户虚拟网络 Glance 镜像仓库 Cinder 云硬盘 Swift 对象存储 本质 KVM 的\u0026quot;整车厂\u0026quot;，私有云的操作系统 本文是「云计算」系列第 5 篇。\n","permalink":"https://antidebug.cn/posts/2026/01/%E4%BA%91%E8%AE%A1%E7%AE%97%E8%BF%9B%E9%98%B6openstack-%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6%E6%A6%82%E8%A7%88/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e公有云用得再熟，也总有一天会遇到\u0026quot;数据必须放自己机房\u0026quot;的需求——那时绕不开 \u003cstrong\u003eOpenStack\u003c/strong\u003e：开源界的\u0026quot;云操作系统\u0026quot;，用于自建 IaaS。这篇把它的核心组件和运作机制讲清楚。\u003c/p\u003e","title":"云计算进阶：OpenStack 核心组件概览"},{"content":"前言 一个人写代码，Git 怎么用都对；一个团队写代码，分支模型就是交通规则。这篇讲主流模型的取舍、PR 工作流和冲突处理的正确姿势。\n一、分支模型选型 GitHub Flow（现代主流，推荐） main ──●────●────●────●──► （随时可部署的稳定线） \\ \\ \\ feature ●──┘ ●──┘ （短命特性分支, 合完即删） hotfix ●───┘ 规则只有四条：\nmain 永远可部署 想改代码 → 从 main 拉分支 分支上提交 + Push，开 PR Review 通过 → 合并 → 立即部署 适合：持续部署的 Web/云原生团队（配合 CI/CD 篇的流水线）。分支生命周期短（小时~天），冲突自然少。\nGit Flow（重型传统） main ──●───────────●────●──► （只放生产版本 tag） develop ─●──●──●──●──●───────► （集成分支） \\ \\ \\ feature ●──● ● （功能开发） release ●──● （发布准备/测试修复） hotfix ●──────► main （紧急修复直上生产） 适合：版本发布周期固定（App/客户端/嵌入式）。Web 服务团队用它通常是被仪式感拖累——多数团队应选 GitHub Flow。\nTrunk-Based（极致简化） 所有人直接往 main 提交（未完成功能用 feature flag 隐藏），配合强大的 CI。Google 等大厂玩法，对小团队反而需要纪律。\n二、PR/MR 工作流 # 标准动作（以 GitHub 为例, GitLab 叫 MR） git switch main \u0026amp;\u0026amp; git pull # 1. 永远基于最新 main 开工 git switch -c feature/user-export # 2. 分支名规范: 类型/描述 # 3. 开发提交（小的、原子的提交, 别一坨） git add -p git commit -m \u0026#34;feat: 导出接口-支持CSV格式\u0026#34; git push -u origin feature/user-export # 4. 推上去 # 5. 平台开 PR: 填目的/测试方式/截图 PR 纪律：\n项 建议 体量 \u0026lt; 400 行改动为佳，大改拆多个 PR 存活期 当天~3天，僵尸 PR 果断关闭 标题 feat:/fix:/refactor: 前缀 描述 改了什么、为什么、怎么验证 Reviewer 至少 1 人；小 PR 换来快速 review 三、合并冲突的正确处理 # 场景: 你的分支落后 main, 远端要求先同步 git switch feature/x git fetch origin git rebase origin/main # CONFLICT (content): Merge conflict in src/api.py 打开冲突文件：\n\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt; HEAD def export_csv(rows): ======= def export_csv(rows, delimiter=\u0026#34;,\u0026#34;): # main 上的新版本 \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; origin/main 处理原则：\n\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt; 到 ======= 是你的，======= 到 \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; 是 main 的 手动融合成正确版本（不是二选一！两边改动可能都要） 删掉所有标记 vim src/api.py # 修好 git add src/api.py git rebase --continue # 继续处理下一个冲突 # 想反悔 git rebase --abort 预防冲突的日常习惯：\n# 每天开工先同步 git switch feature/x \u0026amp;\u0026amp; git fetch \u0026amp;\u0026amp; git rebase origin/main # 长命分支 = 冲突制造机: 分支尽早合并、尽早删除 四、保护规则（平台配置） GitHub/GitLab 仓库设置里配：\n✔ 禁止直接 push 到 main（走 PR） ✔ PR 至少 1 个 approve 才能合并 ✔ CI 必须绿（status check: test/lint）才能合并 ✔ 禁止 force push 到保护分支 ✔ 分支删除后自动删远端 这些规则是团队协作的护栏——写进制度不如配进平台。\n五、提交规范与历史整洁 feat: 新功能 fix: 修 bug docs: 文档 refactor: 重构（不改行为） test: 测试 chore: 构建/工具杂务 perf: 性能 ci: CI 配置 示例: feat(auth): 支持 LDAP 登录 fix(api): 修复分页 offset 为负时的 500 squash merge：PR 合并时压成一个提交，main 历史干净可读（平台按钮选 \u0026ldquo;Squash and merge\u0026rdquo;）。\n六、协作事故急救 事故一：把 WIP 推到了 main # main 上多了 2 个错误提交, 还没被别人拉取 git switch main git reset --hard HEAD~2 git push --force-with-lease origin main # 比 -f 安全: 有人先推了会拒绝 事故二：敏感信息已提交 # 1. 立即作废密钥（git 历史只是次要问题!） # 2. 从历史中清除 git filter-repo --replace-text \u0026lt;(echo \u0026#34;sk-12345==\u0026gt;REMOVED\u0026#34;) # 需要 git-filter-repo git push --force-with-lease # 3. 团队所有人重新 clone（历史已改写） 事故三：rebase 到了错误的分支 git reflog # 找到 rebase 前的 HEAD git reset --hard HEAD@{5} 事故四：feature 分支被误删 git switch -c recovered origin/feature/x # 远端还在 # 远端也删了: git log --all --oneline | grep \u0026lt;关键词\u0026gt; # reflog 里捞 七、团队模板落地 .github/PULL_REQUEST_TEMPLATE.md --- ## 改动说明 ## 影响范围 ## 自测方式 - [ ] 本地测试通过 - [ ] CI 绿 .gitmessage（git config commit.template .gitmessage） # \u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;subject\u0026gt; # \u0026lt;body\u0026gt; 八、协作 checklist 分支模型选定并在团队宣讲（默认 GitHub Flow） main 开保护：禁直推、必 review、CI 门禁 分支命名与提交前缀规范统一 每日 rebase main，分支存活 ≤ 3 天 PR 模板与 CHANGE-CAUSE 注解 定期清理已合并分支 小结 场景 做法 模型选型 默认 GitHub Flow，固定版本才 Git Flow 日常 main 拉分支 → 小提交 → PR → squash 合并 冲突 rebase main，手动融合双版本 护栏 保护分支 + CI 门禁 急救 reflog + force-with-lease 本文是「工具」系列第 2 篇。\n","permalink":"https://antidebug.cn/posts/2025/12/git-%E5%88%86%E6%94%AF%E6%A8%A1%E5%9E%8B%E4%B8%8E%E5%9B%A2%E9%98%9F%E5%8D%8F%E4%BD%9C/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e一个人写代码，Git 怎么用都对；一个团队写代码，分支模型就是交通规则。这篇讲主流模型的取舍、PR 工作流和冲突处理的正确姿势。\u003c/p\u003e\n\u003ch2 id=\"一分支模型选型\"\u003e一、分支模型选型\u003c/h2\u003e\n\u003ch3 id=\"github-flow现代主流推荐\"\u003eGitHub Flow（现代主流，推荐）\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emain ──●────●────●────●──►        （随时可部署的稳定线）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \\    \\      \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efeature  ●──┘     ●──┘           （短命特性分支, 合完即删）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehotfix         ●───┘\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e规则只有四条：\u003c/p\u003e","title":"Git 分支模型与团队协作"},{"content":"前言 kubectl set image 一敲，服务就完成了一次\u0026quot;无感升级\u0026quot;。这篇解剖滚动更新的完整机制——参数怎么调、探针怎么配合、出事怎么秒回滚，以及发布卡住的 N 种原因。\n一、滚动更新是怎么转的 初始: [v1][v1][v1] replicas=3, 假设 maxSurge=1, maxUnavailable=0 步骤1: [v1][v1][v1][v2↑] 起1个v2(总数4=3+maxSurge), 等它 ready 步骤2: [v1][v1]↓[v2][v2↑] v2 ready 后删1个v1, 再起1个v2... 步骤3: [v1]↓[v2][v2][v2↑] 终态: [v2][v2][v2] 全部替换完成 两个参数控制节奏（Deployment 篇出现过，这里是深水区）：\nstrategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 最多超出期望副本数几个（百分比也可: \u0026#34;25%\u0026#34;） maxUnavailable: 0 # 最多允许几个不可用 四种典型组合：\n组合 行为 适用 surge=1, unavailable=0 多起一个再删 → 容量不减 在线业务标配 ✅ surge=0, unavailable=1 先删再起 → 资源省 内部服务/资源紧张 surge=0, unavailable=0 ❌ 死锁（无法开始） 别这么写 surge=50%, unavailable=0 批量多起 副本数大时加速 二、探针是滚动的\u0026quot;裁判\u0026quot; 替换的时机完全由就绪探针裁决：\nreadinessProbe: httpGet: {path: /ready, port: 8080} periodSeconds: 5 failureThreshold: 3 新 Pod: Running ≠ Ready └─ ready 探针通过 → Endpoints 加入该 Pod → Deployment 才删下一个旧 Pod └─ 一直不 ready → 滚动停滞（卡住时先查它!） 启动慢的应用（JVM/Spring）加 startupProbe，避免 liveness 误杀：\nstartupProbe: httpGet: {path: /healthz, port: 8080} failureThreshold: 60 # 60 × 2s = 允许 2 分钟启动 periodSeconds: 2 三、发布实操全流程 # 1. 触发（三种等价方式） kubectl set image deploy/api api=reg.local/api:v1.5.0 kubectl edit deploy/api # 改 image 字段 kubectl apply -f api.yaml # 声明式（推荐, 走 CI） # 2. 盯进度 kubectl rollout status deploy/api # Waiting for deployment \u0026#34;api\u0026#34; rollout to finish: 2 of 3 updated... # 3. 实时看 Pod 更替 kubectl get pods -w # 4.（可选）金丝雀起手: 先只放一小部分 kubectl patch deploy api -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;replicas\u0026#34;:5}}\u0026#39; # 扩到5 kubectl set image deploy/api api=reg.local/api:v1.5.0 # 观察 v1.5.0 的 2/5 流量正常后再全量 四、回滚：历史与时光机 # 历史版本（revisionHistoryLimit 控制保留数, 默认10） kubectl rollout history deploy/api # deployment.apps/api # REVISION CHANGE-CAUSE # 3 kubectl set image api=v1.5.0 # 4 kubectl set image api=v1.6.0 # 看某版详情 kubectl rollout history deploy/api --revision=3 # 回上一个版本 kubectl rollout undo deploy/api # 回指定版本 kubectl rollout undo deploy/api --to-revision=3 # 回滚也是一次\u0026#34;滚动\u0026#34;（零中断地滚回去） kubectl rollout status deploy/api --to-revision 超过保留数的版本已不可回——重大配置变更前先备份 YAML： kubectl get deploy api -o yaml \u0026gt; api-v1.6.0.yaml\nCHANGE-CAUSE 哪来的：kubectl apply 时加注解：\nkubectl annotate deploy/api kubernetes.io/change-cause=\u0026#34;release v1.6.0: fix payment bug\u0026#34; 五、暂停与恢复：一次改多个东西 改镜像又改环境变量又改资源——不想触发三轮滚动：\nkubectl rollout pause deploy/api kubectl set image deploy/api api=reg.local/api:v1.6.0 kubectl set env deploy/api LOG_LEVEL=debug kubectl set resources deploy/api -c=api --limits=memory=512Mi kubectl rollout resume deploy/api # 恢复后一轮滚动应用全部变更 注意 pause 期间 HPA 扩容仍会生效，但 Deployment 的 spec 更新不触发——用完记得 resume。\n六、发布卡住排查地图 # 症状: rollout status 停在 \u0026#34;x of y updated\u0026#34; kubectl get pods # 找到非 Ready 的 Pod kubectl describe pod \u0026lt;pod\u0026gt; # 看 Events（九成答案在这） 卡住形态 原因 处理 Pending 资源不够/调度约束 加资源、改 requests、看节点余量 ImagePullBackOff 镜像拉不下来 检查 tag/仓库认证； 国内集群配镜像加速 CrashLoopBackOff 起来就崩 logs --previous；环境变量/配置错误 Running 但不 Ready 就绪探针不过 容器内 curl 探针路径；依赖没起 卡在 Terminating 旧 Pod 删不掉 preStop 卡死/finalizer；优雅关停超时 优雅关停配置（加快滚动、减少请求失败）：\nspec: terminationGracePeriodSeconds: 30 containers: - name: api lifecycle: preStop: exec: {command: [\u0026#34;sleep\u0026#34;, \u0026#34;5\u0026#34;]} # 等 LB/IPVS 规则摘除流量 七、把发布做成\u0026quot;可审计\u0026quot;的 # 谁在什么时候发的什么 kubectl get events --field-selector involvedObject.name=api | tail # 发布后自动验证（smoke test） kubectl run smoke --rm -it --image=curlimages/curl --restart=Never -- \\ curl -fsS http://api:8080/healthz # CI 里的标准段（呼应 CI/CD 篇） kubectl set image deploy/api api=$IMAGE kubectl rollout status deploy/api --timeout=180s || \\ (kubectl rollout undo deploy/api \u0026amp;\u0026amp; exit 1) # 失败自动回滚! 小结 需求 命令/配置 零中断 maxUnavailable=0 + readinessProbe 慢启动 startupProbe 回滚 rollout undo [\u0026ndash;to-revision=N] 批量改 pause → 改 → resume 卡住 describe 看 Events 分诊 兜底 rollout status 超时 → 自动 undo 本文是「云原生」系列第 14 篇。\n","permalink":"https://antidebug.cn/posts/2025/12/k8s-%E6%BB%9A%E5%8A%A8%E6%9B%B4%E6%96%B0%E4%B8%8E%E5%9B%9E%E6%BB%9A%E6%9C%BA%E5%88%B6/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003ekubectl set image\u003c/code\u003e 一敲，服务就完成了一次\u0026quot;无感升级\u0026quot;。这篇解剖滚动更新的完整机制——参数怎么调、探针怎么配合、出事怎么秒回滚，以及发布卡住的 N 种原因。\u003c/p\u003e","title":"K8s 滚动更新与回滚机制"},{"content":"前言 用了这么久容器，是时候回答那个经典问题了：\u0026ldquo;容器到底是什么？\u0026rdquo; 答案：被内核特性\u0026quot;圈\u0026quot;起来的一组进程。三大支柱：Namespace（隔离）、Cgroup（限额）、OverlayFS（镜像分层）。今天亲手搓一个。\n一、Namespace：六重视野隔离 每个 namespace 让进程对某种系统资源产生\u0026quot;独立的幻觉\u0026quot;：\nNamespace 隔离什么 效果 PID 进程编号 容器内 PID 1（看不到宿主机进程） NET 网络栈 独立网卡/IP/路由/iptables MNT 挂载点 独立的文件系统视图 UTS 主机名 容器有自己的 hostname IPC 信号量/共享内存 进程间通信隔离 USER 用户映射 容器内 root ≠ 宿主 root 眼见为实：\ndocker run -d --name box busybox sleep 300 # 1. 宿主机视角: 它就是个普通进程 docker inspect -f \u0026#39;{{.State.Pid}}\u0026#39; box # 假设 8642 ps -p 8642 -o pid,cmd # 2. 容器视角: 自己是 PID 1 docker exec box ps # PID USER COMMAND # 1 root sleep 300 # 3. 网络命名空间证据 ls -l /proc/8642/ns/ # net -\u0026gt; net:[4026532281] ← 与宿主机不同的编号 = 不同空间 docker exec box ip addr # eth0@ifxx 172.17.0.x # 4. UTS: 独立主机名 docker exec box hostname # 一串容器ID hostname # 宿主机自己的 二、Cgroup：资源的水坝 Namespace 管\u0026quot;看得到什么\u0026quot;，cgroup 管\u0026quot;能用多少\u0026quot;：\n# 验证 docker 的限额（容器 0.5 核 / 256M） docker run -d --name lim --cpus=0.5 --memory=256m busybox sleep 300 # cgroup v2 机器（RHEL9/CgroupUnified） cat /sys/fs/cgroup/system.slice/docker-\u0026lt;容器ID\u0026gt;.scope/cpu.max # 50000 100000 ← 每100ms里最多用50ms CPU = 0.5核 cat /sys/fs/cgroup/system.slice/docker-\u0026lt;ID\u0026gt;.scope/memory.max # 268435456 ← 256MB # 压测验证 docker exec lim sh -c \u0026#39;while :; do :; done\u0026#39; \u0026amp; docker stats --no-stream lim # CPU 稳定 ~50% CPU 超限 = 节流（变慢）；内存超限 = OOM Kill（exit 137）——这就是 K8s 里 limits 语义的底层来源。\n三、OverlayFS：镜像的千层饼 docker inspect -f \u0026#39;{{json .GraphDriver.Data}}\u0026#39; box | python3 -m json.tool # LowerDir: 只读镜像层（多个, 用:分隔） # UpperDir: 容器可写层 # MergedDir: 联合挂载点（容器看到的） # 分层实验 mkdir -p /tmp/ovl/{lower,upper,work,merged} echo \u0026#34;from image layer\u0026#34; \u0026gt; /tmp/ovl/lower/app.conf echo \u0026#34;modified by container\u0026#34; \u0026gt; /tmp/ovl/upper/app.conf mount -t overlay overlay \\ -o lowerdir=/tmp/ovl/lower,upperdir=/tmp/ovl/upper,workdir=/tmp/ovl/work \\ /tmp/ovl/merged cat /tmp/ovl/merged/app.conf # modified by container（上层盖住下层） cat /tmp/ovl/lower/app.conf # from image layer（原文件未被改动!） # 写时复制: 在 merged 里改 lower 的文件, 实际写进 upper touch /tmp/ovl/merged/newfile ls /tmp/ovl/upper/ # newfile 出现在可写层 删除也是写：rm 一个镜像层的文件 → upper 层放一个 whiteout 标记盖住它，lower 的文件其实还在。\n四、手搓容器：一百行原理拆解 不用 Docker，用 unshare 亲手圈一个\u0026quot;容器\u0026quot;：\n#!/usr/bin/env bash # mycontainer.sh —— 极简容器原理演示 set -euo pipefail ROOTFS=/tmp/myrootfs # ---- 1. 准备 rootfs（用 busybox 填充最小系统）---- if [ ! -d \u0026#34;$ROOTFS/bin\u0026#34; ]; then mkdir -p \u0026#34;$ROOTFS\u0026#34; dnf install -y busybox 2\u0026gt;/dev/null || true for d in bin sbin etc proc sys dev usr; do mkdir -p \u0026#34;$ROOTFS/$d\u0026#34;; done cp /usr/bin/busybox \u0026#34;$ROOTFS/bin/\u0026#34; ln -sf busybox \u0026#34;$ROOTFS/bin/sh\u0026#34; fi # ---- 2. unshare: 一次性进多个 Namespace ---- # --mount 独立挂载空间 # --uts 独立主机名 # --pid --fork 独立PID（当前命令成为容器内 PID 1） # --net 独立网络（只有 lo） # --map-root-user 用户映射（无特权场景） unshare --mount --uts --pid --fork --net --map-root-user bash -c \u0026#39; # ---- 3. 搭建容器内文件系统 ---- mount -t proc proc /proc 2\u0026gt;/dev/null || true mount -t sysfs sys /sys 2\u0026gt;/dev/null || true hostname mini-container # UTS: 独立主机名 echo \u0026#34;=== 欢迎进入手搓容器 ===\u0026#34; echo \u0026#34;hostname: $(hostname)\u0026#34; echo \u0026#34;我的 PID: $$（容器内视角）\u0026#34; ps aux | head -3 # 只能看到自己! echo \u0026#34;网卡:\u0026#34;; ip addr | grep -E \u0026#34;^[0-9]+:\u0026#34; # 只有 lo \u0026#39; chmod +x mycontainer.sh \u0026amp;\u0026amp; ./mycontainer.sh # === 欢迎进入手搓容器 === # hostname: mini-container # 我的 PID: 1 # 网卡: 1: lo ... 这个 30 行的脚本与 Docker 的关系：Docker = 这些内核特性的自动化封装 + 镜像管理 + 网络/存储插件生态。\n五、加一层资源限额（cgroup 手搓） mkdir /sys/fs/cgroup/mytest echo \u0026#34;100000 200000\u0026#34; \u0026gt; /sys/fs/cgroup/mytest/cpu.max # 0.5 核 echo $$ \u0026gt; /sys/fs/cgroup/mytest/cgroup.procs # 把当前 shell 圈进去 # 死循环烧 CPU, top 观察: 只能到 50% while :; do :; done \u0026amp; top -p $! # 完事后清理 kill %1; rmdir /sys/fs/cgroup/mytest 六、这些原理解释的现象 现象 原理 容器内 top/ps 只看到自己 PID namespace 容器删了数据就没了 数据写在 OverlayFS 可写层 docker cp/exec 能进容器 宿主机进程视角 + nsenter 到目标 namespace CPU limit 下服务变慢 cgroup throttling 容器内 root 映射宿主普通用户 USER namespace（rootless 容器） 容器里看不到宿主磁盘空间 MNT namespace + overlay 挂载 k8s Pod 内容器互通 localhost 同 Pod 共享 NET namespace 七、安全边界提醒 容器隔离弱于虚拟机（共享内核）：\n内核漏洞（如脏管道）可能容器逃逸 → 及时升级内核 别用 --privileged（约等于交出宿主机 root） 挂载 docker.sock 给容器 = 送钥匙 敏感多租户场景：Kata Containers（轻量 VM 跑容器）/ gVisor 小结 支柱 作用 关键词 Namespace 视野隔离 pid/net/mnt/uts/ipc/user Cgroup 资源限额 CPU 节流 / 内存 OOM OverlayFS 分层镜像 lower 只读 + upper 可写 + whiteout 容器本质 一组被圈起来的进程 docker run ≈ unshare + cgroup + overlay 本文是「云原生」系列第 13 篇。\n","permalink":"https://antidebug.cn/posts/2025/11/%E5%AE%B9%E5%99%A8%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86namespace-%E4%B8%8E-cgroup/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e用了这么久容器，是时候回答那个经典问题了：\u003cstrong\u003e\u0026ldquo;容器到底是什么？\u0026rdquo;\u003c/strong\u003e 答案：\u003cstrong\u003e被内核特性\u0026quot;圈\u0026quot;起来的一组进程\u003c/strong\u003e。三大支柱：Namespace（隔离）、Cgroup（限额）、OverlayFS（镜像分层）。今天亲手搓一个。\u003c/p\u003e","title":"容器底层原理：Namespace 与 Cgroup"},{"content":"前言 \u0026ldquo;接口突然变慢\u0026quot;十有八九是慢 SQL。这篇面向运维/后端，把 MySQL 索引原理、执行计划分析和慢查询治理套路讲清楚——不需要 DBA 深度，但足够定位和解决 80% 的问题。\n一、索引是什么：B+ 树 [ 30 | 60 ] ← 根节点（非叶子, 只存键） / | \\ [10|20] [40|50] [70|80] ← 中间节点 / | \\ ... ... 叶子节点（存完整数据或主键指针, 且有链表串联 → 范围查询快） [5,10,15,20] ⇄ [30,35,40] ⇄ [60,65,70] ... 为什么用 B+ 树而不是二叉树/哈希：\n结构 特点 二叉搜索树 树太高（百万数据 20 层 = 20 次磁盘 IO） 哈希 等值快，但不支持范围/排序 B+ 树 矮胖（3~4 层管千万级）、叶子链表利于范围扫描 ✅ 聚簇索引 vs 二级索引：\n聚簇索引（主键）: 叶子节点存整行数据 二级索引（普通索引）: 叶子存主键值 → 命中后还要\u0026#34;回表\u0026#34;查整行 覆盖索引: 查询列全在索引里 → 免回表（explain 出现 Using index） 二、索引的正确用法 -- 建索引 CREATE INDEX idx_status_created ON orders(status, created_at); -- 看表上的索引 SHOW INDEX FROM orders; -- 删除 DROP INDEX idx_status_created ON orders; 最左前缀原则：联合索引 (a, b, c) 相当于建了 (a)、(a,b)、(a,b,c) 三套排序：\nWHERE a = 1 -- ✅ 用上 WHERE a = 1 AND b = 2 -- ✅ 用上 a,b WHERE b = 2 -- ❌ 用不上（跳过了 a） WHERE a = 1 AND c = 3 -- ✅ 只用上 a WHERE a = 1 AND b \u0026gt; 5 AND c = 3 -- ✅ a,b 用上; b 是范围后 c 失效 索引失效的经典写法：\nWHERE YEAR(created_at) = 2025 -- ❌ 函数包裹列 → 全表扫 WHERE created_at \u0026gt;= \u0026#39;2025-01-01\u0026#39; -- ✅ 改成范围 WHERE name LIKE \u0026#39;%abc\u0026#39; -- ❌ 前导通配 WHERE name LIKE \u0026#39;abc%\u0026#39; -- ✅ WHERE phone = 13800138000 -- ❌ 隐式类型转换（phone 是 varchar） WHERE status \u0026lt;\u0026gt; 1 -- ❌ 不等于, 大概率放弃索引 三、explain：执行计划怎么读 EXPLAIN SELECT * FROM orders WHERE status = \u0026#39;paid\u0026#39; AND created_at \u0026gt; \u0026#39;2025-01-01\u0026#39;; 重点看四列：\ntype（访问方式, 好→差）: system \u0026gt; const \u0026gt; eq_ref \u0026gt; ref \u0026gt; range \u0026gt; index \u0026gt; ALL │ │ │ │ └ 全表扫描（优化目标!） │ │ │ └ 扫全索引 │ │ └ 范围扫描 │ └ 非唯一索引等值 └ 唯一索引等值/连接 key: 实际使用的索引（NULL = 没用上） rows: 预估扫描行数（越少越好, 数量级敏感） Extra: Using index 覆盖索引, 极佳 ✅ Using where 服务层再过滤（正常） Using filesort 额外排序 ⚠️（考虑给 ORDER BY 加索引） Using temporary 临时表 ⚠️（GROUP BY 常见） -- 对比优化前后 EXPLAIN SELECT * FROM orders WHERE YEAR(created_at)=2025; -- type=ALL, rows=1000000 ❌ EXPLAIN SELECT * FROM orders WHERE created_at \u0026gt;= \u0026#39;2025-01-01\u0026#39;; -- type=range, key=idx_created, rows=52000 ✅ 四、慢查询治理流程 1. 开启慢日志 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; -- 超过1秒算慢 SET GLOBAL log_queries_not_using_indexes = ON; tail -f /var/lib/mysql/localhost-slow.log # Query_time: 3.2 Lock_time: 0.001 Rows_sent: 1 Rows_examined: 2000000 # ▲ 扫了200万行只回1行 → 典型索引缺失 2. 定位与分析 -- 当前正在跑的语句（抓现行） SHOW PROCESSLIST; KILL \u0026lt;id\u0026gt;; -- 确认是失控查询再杀! -- 表统计信息 SHOW TABLE STATUS LIKE \u0026#39;orders\u0026#39;; 3. 优化三板斧 ① 加对索引: 按查询条件建联合索引, 等值列在前、范围列在后 ② 改写语句: 避免 SELECT * / 函数包列 / 隐式转换 ③ 结构优化: 大分页改\u0026#34;游标翻页\u0026#34;, 历史数据归档分表 深度分页的坑：\nSELECT * FROM orders LIMIT 1000000, 20; -- 要先扫过前100万行! 优化: 记住上次的位置 SELECT * FROM orders WHERE id \u0026gt; 1000020 ORDER BY id LIMIT 20; 五、运维侧的数据库健康检查 -- 连接数与上限 SHOW STATUS LIKE \u0026#39;Threads_connected\u0026#39;; SHOW VARIABLES LIKE \u0026#39;max_connections\u0026#39;; -- 死锁 SHOW ENGINE INNODB STATUS\\G -- LATEST DETECTED DEADLOCK 段 -- 缓冲池命中率（\u0026lt; 99% 要警惕内存不足） SHOW STATUS LIKE \u0026#39;Innodb_buffer_pool_read%\u0026#39;; -- 命中率 = 1 - reads / read_requests -- 最大的几张表（INFORMATION_SCHEMA） SELECT table_name, ROUND(data_length/1024/1024) AS data_mb, ROUND(index_length/1024/1024) AS idx_mb FROM information_schema.tables WHERE table_schema = \u0026#39;mydb\u0026#39; ORDER BY data_length DESC LIMIT 10; # 系统层三查（呼应性能篇） iostat -x 1 3 # 数据盘 await/%util ss -tan state established \u0026#39;( dport = :3306 )\u0026#39; | wc -l # 连接数 df -h /var/lib/mysql 六、索引的代价（别无脑加） 每个索引拖慢写入（INSERT/UPDATE 要维护 B+ 树） 索引占磁盘（index_length 有时比数据还大） 区分度低的列不值得索引（如 status 只有 0/1 两值） 冗余索引清理：(a) 与 (a,b) 并存时前者通常可删 七、案例复盘 现象: 订单列表接口 P99 从 200ms 涨到 5s 排查: 1. 慢日志 → SELECT ... WHERE user_id=? AND status=? ORDER BY created_at DESC LIMIT 20 Query_time 4.8s, Rows_examined 1800000 2. EXPLAIN → type=ALL, 无可用索引 3. 优化: CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at); 等值列(user_id,status)在前, 排序列(created_at)收尾 结果: type=ref, rows=23, 接口 P99 回到 180ms 复盘: 新查询模式上线前, 先看执行计划 小结 场景 动作 慢 开慢日志 → explain 看 type/rows 全表扫 按等值+排序列建联合索引 索引失效 查函数包列/前导%/隐式转换 大分页 游标式翻页 深度诊断 processlist + innodb status + iostat 本文是「数据库」系列第 1 篇。\n","permalink":"https://antidebug.cn/posts/2025/10/mysql-%E7%B4%A2%E5%BC%95%E5%9F%BA%E7%A1%80%E4%B8%8E%E6%85%A2%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;接口突然变慢\u0026quot;十有八九是慢 SQL。这篇面向运维/后端，把 MySQL 索引原理、执行计划分析和慢查询治理套路讲清楚——不需要 DBA 深度，但足够定位和解决 80% 的问题。\u003c/p\u003e","title":"MySQL 索引基础与慢查询优化"},{"content":"前言 日志说不清、ping 测不明的问题，最后都要靠看包。tcpdump 是网络排查的显微镜——这篇从过滤语法到典型故障的\u0026quot;包形态\u0026quot;，练出直接读包排障的能力。\n一、tcpdump 基本功 # 最小可用（-i 网卡, -n 不解析域名, 抓 20 个包） tcpdump -i eth0 -n -c 20 # 常用选项 -nn # 端口也不解析（22 而不是 ssh） -v / -vv # 详细程度 -w dump.pcap # 存文件（拿去 Wireshark 分析） -r dump.pcap # 读文件 -s0 # 抓完整包（默认截断） --immediate-mode # 实时输出（不缓冲, 配合管道 grep） 二、过滤表达式（BPF）速成 # ---- 按主机/端口 ---- tcpdump -i eth0 -nn host 10.0.0.5 tcpdump -i eth0 -nn src 10.0.0.5 # 只看来源 tcpdump -i any -nn port 3306 # 任意网卡的 3306 tcpdump -i eth0 -nn dst port 80 # ---- 组合逻辑 ---- tcpdump -nn host 10.0.0.5 and port 443 tcpdump -nn port 80 or port 8080 tcpdump -nn host 10.0.0.5 and not port 22 # 排除自己的 SSH（防刷屏!） # ---- 按协议与标志位 ---- tcpdump -i eth0 -nn tcp tcpdump -i eth0 -nn icmp tcpdump -i eth0 -nn \u0026#39;tcp[tcpflags] \u0026amp; tcp-syn != 0\u0026#39; # 所有 SYN 包 tcpdump -i eth0 -nn \u0026#39;tcp[tcpflags] \u0026amp; tcp-rst != 0\u0026#39; # 所有 RST tcpdump -i eth0 -nn \u0026#39;tcp[tcpflags] \u0026amp; (tcp-syn|tcp-ack) == tcp-syn|tcp-ack\u0026#39; # SYN+ACK # ---- 实用组合 ---- tcpdump -i any -nn -c 100 \u0026#39;port 443 and host 10.0.0.5\u0026#39; -w /tmp/443.pcap 三、读懂包：一次健康的握手 tcpdump -i eth0 -nn host 10.0.0.5 and port 8080 10.0.0.5.45124 \u0026gt; 10.0.0.1.8080: Flags [S], seq 1234567890 # ① SYN 客户端发起 10.0.0.1.8080 \u0026gt; 10.0.0.5.45124: Flags [S.], seq 987654321, ack 1234567891 # ② SYN+ACK 服务端应答 10.0.0.5.45124 \u0026gt; 10.0.0.1.8080: Flags [.], ack 987654322 # ③ ACK 连接建立 10.0.0.5.45124 \u0026gt; 10.0.0.1.8080: Flags [P.], seq 1:200 # 数据包（PSH+ACK） 10.0.0.1.8080 \u0026gt; 10.0.0.5.45124: Flags [.], ack 200 # 服务端确认收到 10.0.0.5.45124 \u0026gt; 10.0.0.1.8080: Flags [F.], seq 200 # FIN 优雅挥手 10.0.0.1.8080 \u0026gt; 10.0.0.5.45124: Flags [F.], ack 201 # 对端也关 10.0.0.5.45124 \u0026gt; 10.0.0.1.8080: Flags [.], ack 202 # 最后的 ACK 标志速记：[S] SYN 建连 / [.] ACK 确认 / [P] PSH 带数据 / [F] FIN 关闭 / [R] RST 强断。\n四、故障的\u0026quot;包形态\u0026quot;对照表 形态一：SYN 发出去，没有任何回应 10.0.0.5 \u0026gt; 10.0.0.1.8080: [S] # 客户端 SYN （重复多次, 指数退避重试） → 包被丢弃（DROP 型防火墙/安全组），或对端没监听且被丢。查路径上的墙。\n形态二：SYN 回来的是 RST 10.0.0.5 \u0026gt; 10.0.0.1.8080: [S] 10.0.0.1.8080 \u0026gt; 10.0.0.5: [R.] # 服务端直接拒绝 → 对端明确拒绝：端口没监听（ss -tlnp | grep 8080），或 backlog 满。REJECT 型防火墙也是 RST。\n形态三：握手成功，数据发出去没 ACK ... [S] [S.] [.] 正常握手 10.0.0.5 \u0026gt; ...: [P.] seq 1:500 10.0.0.5 \u0026gt; ...: [P.] seq 1:500 # 一直在重传! → 单向丢包（回程被丢）、对端假死。查 conntrack 表满（nf_conntrack: table full）。\n形态四：大量短连接 TIME_WAIT ... [F] [F.] [.] ← 反复高频挥手 → 应用短连接风暴，参考 sysctl 篇的治理方案。\n形态五：三次握手中途大量 SYN+ACK 重传 ... .8080 \u0026gt; client: [S.] # 服务端发 SYN+ACK ... .8080 \u0026gt; client: [S.] # 客户端不回第三次 ACK → SYN Flood 攻击 或 客户端伪造 IP。tcp_syncookies=1 缓解。\n五、实战案例：接口偶发超时 # 1. 现象: 调用 10.0.0.9:6379 偶发 3s 超时 # 2. 抓 SYN 重传证据（只抓关键流量, 避免刷屏） tcpdump -i eth0 -nn host 10.0.0.9 and port 6379 -w /tmp/redis.pcap \u0026amp; # 3. 复现超时后停抓, 分析 tcpdump -r /tmp/redis.pcap -nn | grep -E \u0026#34;retrans|timeout\u0026#34; # 发现: 每次超时前, 有连续 retransmission 记录 # 4. 结合内核计数器确认丢包位置 netstat -s | grep -iE \u0026#34;retrans|drop\u0026#34; ip -s link show eth0 | grep -A2 errors # 网卡层有无错误 # 5. 结论方向: 中间网络丢包 → 抓两端对比, 定位丢在哪一段 # 服务器端抓不到包 = 包没到达; 抓到了但没回 = 应用层问题 两端对抓是定位丢包段的标准手法：A 有出、B 没进 → 中间丢；A 有出、B 有进没回 → B 的问题。\n六、ss 深度用法（tcpdump 的搭档） # 全量概览 ss -s # 监听 + 进程（排障第一命令） ss -tlnp # 指定过滤 ss -tnp dst 10.0.0.9:6379 ss -tan state established \u0026#39;( sport = :8080 )\u0026#39; # 8080 的活跃连接 # 各状态统计（TIME_WAIT 是否堆积一目了然） ss -tan | awk \u0026#39;NR\u0026gt;1 {print $1}\u0026#39; | sort | uniq -c | sort -rn # 看发送/接收缓冲区堆积（应用不读数据的证据!） ss -tnm state established # ... skmem:(r0,rb131072,t87380,tb4737280...) # t=待发送堆积的字节 tb=发送缓冲总大小 # t 逼近 tb → 对端不收 → 对端慢或网络堵 # 定位\u0026#34;哪个进程占着这条连接\u0026#34; ss -tnp state close-wait | head # 大量 CLOSE_WAIT + 具体进程名 → 该进程代码没调 close() 七、抓包注意事项 先收窄过滤条件再抓——生产高流量机器上裸抓会刷爆磁盘和 CPU -w 存 pcap 时过滤条件同样生效（在目标机器上过滤，别拉回来再筛） 抓敏感协议注意合规（密码/token 明文会进 pcap） tcpdump -D 列出可用网卡；容器场景记得 tcpdump -i docker0 或 any 长期分析用 Wireshark 打开 pcap，用 tcp.stream eq N 追踪单条流 小结 症状 包形态 方向 连不上 SYN 无响应 防火墙 DROP Connection refused SYN → RST 端口没监听 偶发卡顿 数据包重传 网络丢包，两端对抓 假死 握手后无 ACK conntrack/应用挂起 连接堆积 ss 看 t/tb、CLOSE_WAIT 应用不收/不关 本文是「Linux 运维」系列第 13 篇。\n","permalink":"https://antidebug.cn/posts/2025/10/linux-%E7%BD%91%E7%BB%9C%E6%8E%92%E6%9F%A5%E5%AE%9E%E6%88%98tcpdump-%E4%B8%8E-ss/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e日志说不清、ping 测不明的问题，最后都要靠\u003cstrong\u003e看包\u003c/strong\u003e。tcpdump 是网络排查的显微镜——这篇从过滤语法到典型故障的\u0026quot;包形态\u0026quot;，练出直接读包排障的能力。\u003c/p\u003e","title":"Linux 网络排查实战：tcpdump 与 ss"},{"content":"前言 会写脚本和写好脚本之间，隔着无数深夜的坑。这篇把 Bash 的函数进阶用法和那些\u0026quot;约定俗成但没人明说\u0026quot;的陷阱一次讲清。\n一、函数的正确写法 #!/usr/bin/env bash # 定义（function 关键字可省, 括号必须有） function log() { # local 变量: 不污染全局 —— 函数内赋值一律 local! local level=$1 msg=$2 printf \u0026#39;[%s] [%s] %s\\n\u0026#39; \u0026#34;$(date \u0026#39;+%F %T\u0026#39;)\u0026#34; \u0026#34;$level\u0026#34; \u0026#34;$msg\u0026#34; } # 调用（参数像脚本参数一样传） log INFO \u0026#34;服务启动\u0026#34; log ERROR \u0026#34;连接超时\u0026#34; 参数与返回值：\nget_status() { local svc=$1 # 函数的\u0026#34;返回数据\u0026#34;用 stdout systemctl is-active \u0026#34;$svc\u0026#34; } # 捕获: 命令替换 status=$(get_status nginx) echo \u0026#34;nginx 状态: $status\u0026#34; # 返回码用 return（0成功/非0失败, 给 if 用） is_alive() { pgrep -x \u0026#34;$1\u0026#34; \u0026amp;\u0026gt;/dev/null } if is_alive nginx; then echo \u0026#34;nginx 在跑\u0026#34; fi 黄金法则：\n输出通道 用途 stdout 函数的\u0026quot;数据返回\u0026quot;（被 $(\u0026hellip;) 捕获） stderr 日志/调试信息（不污染数据流） return 退出码 成功与否（给 if/\u0026amp;\u0026amp; 判断） fetch_data() { echo \u0026#34;正在查询...\u0026#34; \u0026gt;\u0026amp;2 # 日志走 stderr echo \u0026#39;{\u0026#34;ok\u0026#34;:true}\u0026#39; # 数据走 stdout } data=$(fetch_data 2\u0026gt;/dev/null) # 拿到纯数据 二、陷阱一：管道中的子 shell #!/usr/bin/env bash count=0 cat /etc/passwd | while read -r line; do count=$((count + 1)) done echo \u0026#34;$count\u0026#34; # 0 !! 炸裂 原因：管道右侧的 while 跑在子 shell 里，count 的修改留在子进程，退出即丢。\n三种解法：\n# 解法1: 重定向代替管道（同一进程） count=0 while read -r line; do count=$((count + 1)) done \u0026lt; /etc/passwd echo \u0026#34;$count\u0026#34; # ✅ 正确 # 解法2: 进程替换 while read -r line; do ... done \u0026lt; \u0026lt;(some_command) # 解法3: bash 4.2+ 打开 lastpipe（需关闭 job control） shopt -s lastpipe cat file | while read -r line; do ...; done 三、陷阱二：没引号的变量 file=\u0026#34;my document.txt\u0026#34; rm $file # ❌ 实际执行: rm my document.txt → 删了两个不存在的文件 rm \u0026#34;$file\u0026#34; # ✅ # 单词拆分还能引发注入: user_input=\u0026#34;; rm -rf /\u0026#34; eval \u0026#34;echo $user_input\u0026#34; # ❌ 灾难 三条铁律：\n变量展开永远双引号：\u0026quot;$var\u0026quot; eval 是恶魔，能不用就不用（要用则严控输入） 数组展开用 \u0026quot;${arr[@]}\u0026quot;（每元素独立引号） 四、陷阱三：set 选项的细节 set -e # 出错退出 —— 但有豁免区! true # 在 if 条件、\u0026amp;\u0026amp; || 链、管道（见下）中不触发 cmd || true # 手动豁免（明示\u0026#34;允许失败\u0026#34;） set -e 与管道的恩怨：默认只看管道最后一个命令的退出码：\nset -e false | echo \u0026#34;hi\u0026#34; # 打印 hi, 脚本继续! false 的失败被吞了 解法：set -o pipefail（与 -e 联手，这也是骨架里 set -euo pipefail 三件套的由来）。\n# -u: 未定义变量报错 set -u echo \u0026#34;$TYPO_VAR\u0026#34; # 直接报错退出, 而不是静默空串 echo \u0026#34;${SAFE_VAR:-默认值}\u0026#34; # 显式给默认值 # -x: 调试模式（排查脚本神器） set -x cp \u0026#34;$src\u0026#34; \u0026#34;$dst\u0026#34; # + cp /data/a.tar /backup/a.tar ← 每步实际执行什么都看到了 五、陷阱四：cd 与临时目录 #!/usr/bin/env bash set -euo pipefail # 脚本中途 cd 后, 相对路径全变 —— 处理完要回去 cleanup() { cd - \u0026gt;/dev/null; rm -rf \u0026#34;$TMP\u0026#34;; } trap cleanup EXIT TMP=$(mktemp -d) # 用 mktemp 而不是 /tmp/xxx 固定名 cd \u0026#34;$TMP\u0026#34; # trap ... EXIT: 无论正常/报错/被 Ctrl+C 都执行清理 # 常见配对: 临时文件、锁文件、中断恢复 六、陷阱五：数值与字符串比较 [ \u0026#34;10\u0026#34; -gt \u0026#34;9\u0026#34; ] # ✅ -gt 数值比较: true [ \u0026#34;10\u0026#34; \\\u0026gt; \u0026#34;9\u0026#34; ] # 字符串比较: \u0026#34;10\u0026#34; \u0026lt; \u0026#34;9\u0026#34;（字典序!） [[ 10 \u0026gt; 9 ]] # 双括号里 \u0026gt; 不用转义, 但仍是字符串比较 [[ ]] 与 [ ] 的区别: - [[ $a == $b ]] # 不发生单词拆分, 变量可不加引号（还是建议加） - [[ $a =~ ^[0-9]+$ ]]# 支持正则 =~（很实用） - 算术: (( count \u0026gt;= 10 )) if (( count \u0026gt;= 10 )); then echo \u0026#34;够了\u0026#34;; fi if [[ $input =~ ^[0-9]+$ ]]; then echo \u0026#34;是纯数字\u0026#34;; fi 七、陷阱六：空格与换行 # 命令替换会吃掉尾部换行 out=$(echo \u0026#34;hello \u0026#34;) echo \u0026#34;[$out]\u0026#34; # [hello] —— 尾换行没了, 数长度要小心 # 文件名带横杠开头 cat -options.txt # 被当选项 cat ./-options.txt # ✅ 加路径 cat -- -options.txt # ✅ 或用 -- # 文件名含空格的遍历（错误 vs 正确） for f in $(ls); do ... done # ❌ 空格文件名被拆碎 for f in ./*; do ... done # ✅ glob 展开, 认引号 find . -name \u0026#34;*.log\u0026#34; -print0 | while IFS= read -r -d \u0026#39;\u0026#39; f; do ...; done # ✅✅ 终极 八、一个\u0026quot;防御性\u0026quot;的完整示例 #!/usr/bin/env bash # 可靠的日志归档脚本 set -euo pipefail IFS=$\u0026#39;\\n\\t\u0026#39; # 收紧默认分词（防御性编程） readonly LOG_DIR=\u0026#34;/var/log/myapp\u0026#34; readonly KEEP_DAYS=14 readonly ARCHIVE=\u0026#34;/backup/myapp-$(date +%F).tar.gz\u0026#34; log() { printf \u0026#39;[%s] %s\\n\u0026#39; \u0026#34;$(date \u0026#39;+%F %T\u0026#39;)\u0026#34; \u0026#34;$*\u0026#34; \u0026gt;\u0026amp;2; } die() { log \u0026#34;FATAL: $*\u0026#34;; exit 1; } usage() { echo \u0026#34;用法: $0 [-k 保留天数]\u0026#34;; exit 2; } while getopts \u0026#34;:k:h\u0026#34; opt; do case $opt in k) KEEP_DAYS=$OPTARG ;; h) usage ;; :) die \u0026#34;选项 -$OPTARG 缺参数\u0026#34; ;; \\?) die \u0026#34;未知选项 -$OPTARG\u0026#34; ;; esac done [[ -d $LOG_DIR ]] || die \u0026#34;日志目录不存在: $LOG_DIR\u0026#34; trap \u0026#39;log \u0026#34;归档中断, 临时文件已清理\u0026#34;; rm -f \u0026#34;$ARCHIVE.tmp\u0026#34;\u0026#39; ERR log \u0026#34;开始归档 $LOG_DIR\u0026#34; tar -czf \u0026#34;$ARCHIVE.tmp\u0026#34; -C \u0026#34;$LOG_DIR\u0026#34; . mv \u0026#34;$ARCHIVE.tmp\u0026#34; \u0026#34;$ARCHIVE\u0026#34; # 原子替换 log \u0026#34;归档完成: $ARCHIVE ($(du -h \u0026#34;$ARCHIVE\u0026#34; | cut -f1))\u0026#34; find \u0026#34;$LOG_DIR\u0026#34; -name \u0026#34;*.log\u0026#34; -mtime +\u0026#34;$KEEP_DAYS\u0026#34; -delete log \u0026#34;已清理 ${KEEP_DAYS} 天前的旧日志\u0026#34; 亮点：\u0026gt;\u0026amp;2 日志分流、readonly、die 模式、trap ERR、mv tmp 原子化、getopts 严格报错。\n小结 陷阱 解法 管道子 shell 变量丢失 done \u0026lt; file / \u0026lt; \u0026lt;(cmd) 空格拆分 永远 \u0026quot;$var\u0026quot; 管道错误被吞 set -o pipefail 静默失败 set -eu 中断留下垃圾 trap ... EXIT/ERR 魔法数字/路径 readonly 常量 固定临时文件名 mktemp 本文是「Linux 基础」系列第 9 篇。\n","permalink":"https://antidebug.cn/posts/2025/09/shell-%E8%84%9A%E6%9C%AC%E8%BF%9B%E9%98%B6%E5%87%BD%E6%95%B0%E4%B8%8E%E5%B8%B8%E8%A7%81%E9%99%B7%E9%98%B1/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e会写脚本和写好脚本之间，隔着无数深夜的坑。这篇把 Bash 的函数进阶用法和那些\u0026quot;约定俗成但没人明说\u0026quot;的陷阱一次讲清。\u003c/p\u003e\n\u003ch2 id=\"一函数的正确写法\"\u003e一、函数的正确写法\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#!/usr/bin/env bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 定义（function 关键字可省, 括号必须有）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efunction\u003c/span\u003e log\u003cspan class=\"o\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# local 变量: 不污染全局 —— 函数内赋值一律 local!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003elocal\u003c/span\u003e \u003cspan class=\"nv\"\u003elevel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003e$1\u003c/span\u003e \u003cspan class=\"nv\"\u003emsg\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003e$2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprintf\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;[%s] [%s] %s\\n\u0026#39;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003edate \u003cspan class=\"s1\"\u003e\u0026#39;+%F %T\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$level\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$msg\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 调用（参数像脚本参数一样传）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003elog INFO \u003cspan class=\"s2\"\u003e\u0026#34;服务启动\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003elog ERROR \u003cspan class=\"s2\"\u003e\u0026#34;连接超时\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e参数与返回值\u003c/strong\u003e：\u003c/p\u003e","title":"Shell 脚本进阶：函数与常见陷阱"},{"content":"前言 容器生来无常，Pod 一删数据就没了。K8s 存储体系要回答：怎么给易逝的 Pod 一个持久的家。这篇讲透 PV/PVC/StorageClass 三层抽象和实战用法。\n一、问题与分层 开发者视角: \u0026#34;我要 10Gi 读写快的存储\u0026#34; ← PVC（声明） │ 谁来满足? 集群管理员视角: \u0026#34;我有一堆 NFS/云盘供给\u0026#34; ← PV（资源） + StorageClass（供给方式） │ 挂给谁? Pod 视角: volumeMounts 挂载点 ← Volume（使用） 为什么隔一层 PVC？ 开发者不应该关心\u0026quot;存储在哪台 NFS/哪种云盘\u0026quot;——就像申请内存不需要指定物理内存条编号。解耦需求与供给。\n二、临时与半永久卷 # emptyDir: Pod 的\u0026#34;临时便签\u0026#34;（Pod 删除即消失, 崩溃重启仍在） volumes: - name: scratch emptyDir: {} # 用途: 缓存、中间产物、多容器共享文件 # hostPath: 用宿主机目录（危险但实用） volumes: - name: node-log hostPath: {path: /var/log, type: Directory} # 用途: DaemonSet 采集节点日志（node-exporter/promtail） # ⚠️ Pod 会漂移 + 数据跟节点走, 业务数据绝不用它 三、PV 与 PVC：静态供给 管理员预创建 PV（以 NFS 为例）：\napiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-data spec: capacity: {storage: 50Gi} accessModes: [\u0026#34;ReadWriteMany\u0026#34;] # RWX: 多节点读写 persistentVolumeReclaimPolicy: Retain # PVC 删除后 PV 保留（防误删数据!） nfs: server: 10.0.2.100 path: /export/data 开发者提需求：\napiVersion: v1 kind: PersistentClaim metadata: name: app-data spec: accessModes: [\u0026#34;ReadWriteMany\u0026#34;] resources: requests: {storage: 50Gi} storageClassName: \u0026#34;\u0026#34; # 绑定静态 PV Pod 使用：\nspec: containers: - name: app volumeMounts: [{name: data, mountPath: /data}] volumes: - name: data persistentVolumeClaim: {claimName: app-data} accessModes：\n模式 缩写 含义 ReadWriteOnce RWO 单节点读写（云盘类） ReadOnlyMany ROX 多节点只读 ReadWriteMany RWX 多节点读写（NFS/CephFS/文件存储） ReadWriteOncePod RWOP 单 Pod 读写（更严格） 回收策略：Retain（保留，需手动清）/ Recycle（已废弃）/ Delete（动态供给默认，删 PVC 连底层盘一起删——慎用于重要数据）。\n四、StorageClass：动态供给（主流玩法） 不用手动建 PV，PVC 一提，存储插件自动创建底层资源：\napiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: diskplugin.csi.tencent.com # CSI 插件（云厂商内置） parameters: type: CLOUD_SSD reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer # 等首个 Pod 调度后再建盘（同可用区!） --- apiVersion: v1 kind: PersistentClaim metadata: name: db-data spec: storageClassName: fast-ssd accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] resources: requests: {storage: 100Gi} kubectl get pvc # NAME STATUS VOLUME CAPACITY STORAGECLASS # db-data Bound pvc-4f2a... 100Gi fast-ssd kubectl get pv | head # 自动创建的 PV volumeBindingMode 的坑：Immediate 会立刻建盘——若 Pod 与盘不在同可用区就挂不上；用 WaitForFirstConsumer 让存储跟着 Pod 走。\n五、扩容与快照 # 在线扩容（存储类需 allowVolumeExpansion: true） kubectl patch pvc db-data -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;resources\u0026#34;:{\u0026#34;requests\u0026#34;:{\u0026#34;storage\u0026#34;:\u0026#34;200Gi\u0026#34;}}}}\u0026#39; # 快照（需安装 CSI Snapshotter） kubectl apply -f snapshot.yaml apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: name: db-snap-20250802 spec: volumeSnapshotClassName: csi-snapclass source: persistentVolumeClaimName: db-data 六、StatefulSet：有状态的正解 Deployment 的 Pod 是无面人（名字随机、存储共享）；StatefulSet 给每个副本稳定的身份与独立存储：\napiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: serviceName: mysql # 配套 headless service replicas: 3 selector: {matchLabels: {app: mysql}} template: metadata: {labels: {app: mysql}} spec: containers: - name: mysql image: mysql:8.0 volumeMounts: - name: data mountPath: /var/lib/mysql volumeClaimTemplates: # ★ 每个副本自动生成独立 PVC - metadata: name: data spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: fast-ssd resources: {requests: {storage: 100Gi}} kubectl get pvc # data-mysql-0 Bound ... # mysql-0 专属盘 # data-mysql-1 Bound ... # mysql-1 专属盘 # data-mysql-2 Bound ... # Pod 名字稳定 + DNS 稳定: kubectl run t --rm -it --image=busybox -- nslookup mysql-0.mysql.default.svc 适用：数据库主从、Kafka、ES、ZooKeeper 这类\u0026quot;每个节点有自己身份和数据\u0026quot;的应用。\n七、数据保护清单（血泪法则） PVC 删除 ≠ 数据安全：确认 reclaimPolicy，重要数据用 Retain 或快照先行 云盘每天快照策略 + 异地（对象存储）备份 数据库别只靠快照——逻辑备份（mysqldump/xtrabackup）+ 物理快照双保险 测试恢复！没演练过的备份等于没有备份 监控 PVC 使用率（Prometheus: kubelet_volume_stats_used_bytes），别等写满 八、常见坑 现象 原因 PVC 一直 Pending 没匹配的 PV（静态）/ provisioner 没装 / 容量/模式不匹配 Pod 调度不了 + 盘在别的可用区 volumeBindingMode 用 WaitForFirstConsumer 多 Pod 挂不上同一 PVC RWO 只支持单节点；要共享用 RWX（NFS/文件存储） 删了 PVC 数据没了 Delete 回收策略生效——重要数据 Retain StatefulSet 缩容再扩容 新 Pod 复用旧 PVC（数据还在，好事）; 删 PVC 才真清 hostPath 数据\u0026quot;丢失\u0026quot; Pod 漂移到别的节点了 小结 层 对象 一句话 使用 Volume + volumeMounts 挂进容器 声明 PVC 我要多少、什么模式 供给 PV / StorageClass 谁来满足、怎么自动创建 有状态 StatefulSet + volumeClaimTemplates 稳定身份 + 独立盘 保命 快照 + 备份 + Retain 数据无价 本文是「云原生」系列第 12 篇。\n","permalink":"https://antidebug.cn/posts/2025/08/k8s-%E5%AD%98%E5%82%A8%E4%BD%93%E7%B3%BBpv-%E4%B8%8E-pvc/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e容器生来无常，Pod 一删数据就没了。K8s 存储体系要回答：\u003cstrong\u003e怎么给易逝的 Pod 一个持久的家\u003c/strong\u003e。这篇讲透 PV/PVC/StorageClass 三层抽象和实战用法。\u003c/p\u003e\n\u003ch2 id=\"一问题与分层\"\u003e一、问题与分层\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e开发者视角: \u0026#34;我要 10Gi 读写快的存储\u0026#34;        ← PVC（声明）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │ 谁来满足?\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e集群管理员视角: \u0026#34;我有一堆 NFS/云盘供给\u0026#34;      ← PV（资源） + StorageClass（供给方式）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │ 挂给谁?\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePod 视角: volumeMounts 挂载点               ← Volume（使用）\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e为什么隔一层 PVC？\u003c/strong\u003e 开发者不应该关心\u0026quot;存储在哪台 NFS/哪种云盘\u0026quot;——就像申请内存不需要指定物理内存条编号。\u003cstrong\u003e解耦需求与供给\u003c/strong\u003e。\u003c/p\u003e","title":"K8s 存储体系：PV 与 PVC"},{"content":"前言 没有 HTTPS 的网站在 2025 年已经\u0026quot;裸奔\u0026quot;得不能再裸奔：浏览器标\u0026quot;不安全\u0026quot;、备案检查、小程序/H5 全要求 HTTPS。这篇把证书到配置的全流程走通。\n一、证书从哪来 类型 说明 适合 DV（域名验证） 只验域名所有权，免费渠道多 个人站/中小业务 OV（组织验证） 验证公司身份，收费 企业官网 EV（扩展验证） 严格审核、地址栏显示公司名，贵 金融/大厂 自签 自己签给自己 仅内网测试 免费 DV 证书渠道：\nLet\u0026rsquo;s Encrypt（ACME 自动化，90 天有效期，可自动续期） 腾讯云/阿里云免费证书（每年 20 张，一年期） ZeroSSL 二、证书文件的构成 # 典型的一套（Nginx 需要 fullchain + key） your.crt / your.pem # 证书（公钥 + 签发信息） your.key # 私钥（!! 绝不外传, 权限 600） chain.crt / fullchain.crt # 中间证书链（缺它部分客户端报错） # 查看证书信息（到期时间/域名/签发者） openssl x509 -in fullchain.crt -noout -dates -subject -issuer # notAfter=Jan 1 12:00:00 2026 GMT ← 到期时间 openssl x509 -in fullchain.crt -noout -ext subjectAltName 到期监控（配合 cron 篇的知识）：\necho | openssl s_client -serverName antidebug.cn -connect antidebug.cn:443 2\u0026gt;/dev/null \\ | openssl x509 -noout -enddate 三、Let\u0026rsquo;s Encrypt 自动化（certbot） dnf install -y certbot python3-certbot-nginx # 一条命令: 验证域名 + 改 Nginx 配置 + 自动续期定时器 certbot --nginx -d antidebug.cn -d www.antidebug.cn # 续期（90天证书, certbot 自动装了 systemd timer） systemctl list-timers | grep certbot certbot renew --dry-run # 演练续期 前提：域名已解析到本机、80 端口可访问（HTTP-01 验证要走它）。\n四、Nginx HTTPS 配置模板 # ---- HTTP: 全部跳转 HTTPS ---- server { listen 80; server_name antidebug.cn www.antidebug.cn; return 301 https://antidebug.cn$request_uri; } # ---- HTTPS 主站 ---- server { listen 443 ssl; http2 on; # HTTP/2（多路复用, 提速明显） server_name antidebug.cn www.antidebug.cn; # ---- 证书 ---- ssl_certificate /etc/nginx/certs/antidebug.cn/fullchain.crt; ssl_certificate_key /etc/nginx/certs/antidebug.cn/private.key; # ---- 协议与加密套件 ---- ssl_protocols TLSv1.2 TLSv1.3; # 1.0/1.1 已淘汰 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; # ---- 会话复用（减少握手开销）---- ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; # ---- 安全头 ---- add_header Strict-Transport-Security \u0026#34;max-age=31536000\u0026#34; always; # 强制浏览器后续一律走 HTTPS（HSTS） # ---- OCSP Stapling（客户端不用去问 CA, 加快握手）---- ssl_stapling on; ssl_stapling_verify on; root /var/www/blog; index index.html; location / { try_files $uri $uri/ =404; } # TLS 1.3 0-RTT 会重放, 写操作要小心——一般保持默认即可 } nginx -t \u0026amp;\u0026amp; systemctl reload nginx curl -I https://antidebug.cn/ # 200 ✅ 五、验证与评分 # 命令行验证 openssl s_client -connect antidebug.cn:443 -servername antidebug.cn \u0026lt;/dev/null 2\u0026gt;/dev/null \\ | openssl x509 -noout -dates -subject # 协议与套件检查 nmap --script ssl-enum-ciphers -p 443 antidebug.cn # 在线评分 # SSL Labs: https://www.ssllabs.com/ssltest/ 目标 A/A+ # 我的模板实测 A 常见扣分项：\n问题 修复 支持 TLS 1.0/1.1 ssl_protocols 只留 1.2/1.3 弱套件（CBC/3DES） 用上面的 ECDHE 套件 无 HSTS add_header Strict-Transport-Security 链不完整 用 fullchain（带中间证书） 六、自签证书（内网/测试） # 一行生成自签证书（SAN 必须写对, 新浏览器不认 CN） openssl req -x509 -nodes -newkey rsa:2048 -days 365 \\ -keyout self.key -out self.crt \\ -subj \u0026#34;/CN=lab.local\u0026#34; \\ -addext \u0026#34;subjectAltName=DNS:lab.local,DNS:*.lab.local,IP:10.0.0.5\u0026#34; # Nginx 配置同上; 客户端 curl -k 忽略验证 # 内网正规做法: 自建 CA 签发 + 分发 CA 证书到信任库 七、实战坑列表 现象 原因 浏览器警告\u0026quot;链不完整\u0026quot; 只配了 crt 没配 fullchain curl 报 certificate verify failed 系统缺中间证书/CA；update-ca-trust 部分安卓老设备握手失败 TLS 版本太新/套件不兼容，酌情放宽 续期了还是旧证书 certbot renew 后没 reload nginx → 加 --deploy-hook \u0026quot;systemctl reload nginx\u0026quot; 443 不通 安全组/防火墙没放行 http2 on 报错 老 nginx 用 listen 443 ssl http2; 旧语法 mixed content 页面里还引用 http:// 资源，全改 https 或用相对协议 八、性能小结 手段 收益 会话缓存/复用 回访用户省一次完整握手 TLS 1.3 握手 1-RTT（0-RTT 可选） OCSP stapling 客户端少一次查询 HTTP/2 多路复用，页面并发资源加载提速 HTTP/3 (QUIC) 弱网体验更佳（listen 443 quic; 新版本可试） 小结 免费 DV 证书 + certbot 自动续期 = 零成本 HTTPS 模板要点：fullchain、TLS1.2/1.3、HSTS、会话复用、HTTP/2 上线三验证：openssl s_client、nmap ssl-enum、SSL Labs 评分 续期别忘了 deploy-hook reload 本文是「Linux 运维」系列第 12 篇。\n","permalink":"https://antidebug.cn/posts/2025/07/nginx-https-%E9%85%8D%E7%BD%AE%E5%AE%9E%E6%88%98/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e没有 HTTPS 的网站在 2025 年已经\u0026quot;裸奔\u0026quot;得不能再裸奔：浏览器标\u0026quot;不安全\u0026quot;、备案检查、小程序/H5 全要求 HTTPS。这篇把证书到配置的全流程走通。\u003c/p\u003e","title":"Nginx HTTPS 配置实战"},{"content":"前言 服务器卡在启动界面进不去系统时，看懂启动流程就是看懂排障地图。这篇从固件到 shell，一步步走完 Linux 的\u0026quot;出生过程\u0026quot;。\n一、总览：六个阶段 ① 固件自检 (BIOS/UEFI) ▼ ② 引导加载 (GRUB2) ▼ ③ 内核加载 (vmlinuz + initramfs) ▼ ④ PID 1 (systemd 启动) ▼ ⑤ 并行拉起服务 (target + 依赖) ▼ ⑥ 登录 (getty / sshd) 二、阶段一：固件（BIOS/UEFI） BIOS（老） UEFI（现代） 按 MBR 磁盘前512字节引导 读 FAT 格式的 ESP 分区里的 .efi 文件 │ │ ▼ ▼ MBR: 引导代码446B + 分区表64B /boot/efi/EFI/.../grubx64.efi + 魔数 55AA # 看自己的机器是哪种 [ -d /sys/firmware/efi ] \u0026amp;\u0026amp; echo UEFI || echo BIOS # UEFI 下看启动项 efibootmgr -v # 固件层常见故障: 启动顺序错了 / 磁盘没认出来 # → 进 BIOS Setup 检查 Boot Order 与磁盘是否识别 三、阶段二：GRUB2 引导 GRUB 的任务：找到内核与 initramfs，把控制权交出去。\n# GRUB 配置的生成链（别直接改 grub.cfg!） /etc/default/grub # 人类编辑的配置 │ grub2-mkconfig ▼ /boot/grub2/grub.cfg # 机器生成的最终配置 # 改内核启动参数的标准流程 vim /etc/default/grub # GRUB_CMDLINE_LINUX=\u0026#34;... quiet\u0026#34; ← 加参数在这 grub2-mkconfig -o /boot/grub2/grub.cfg 实战：忘记 root 密码（GRUB 是突破口）：\n1. 重启, 在 GRUB 菜单按 e 编辑 2. 找到 linux 行, 末尾追加: rd.break 3. Ctrl+X 启动 → 进入紧急 shell（内存里的系统） 4. mount -o remount,rw /sysroot chroot /sysroot passwd root touch /.autorelabel # SELinux 重新打标签（重要!） exit; exit 四、阶段三：内核与 initramfs GRUB 加载 /boot/vmlinuz-$(uname -r) ← 内核本体 + /boot/initramfs-*.img ← 内存里的迷你根文件系统 initramfs 的存在意义：根文件系统可能需要驱动才能挂载（磁盘阵列、LVM、加密盘）——鸡生蛋问题。initramfs 先在内存里起来，加载必要驱动，挂上真根后执行 switch_root 交接。\n# 看看 initramfs 里有什么 lsinitrd | head -30 # 能看到内核模块、microcode、mkinitrd 脚本... # 磁盘/驱动变更后重建 dracut -f 内核起来后最早期日志看：\ndmesg | head -30 # 内核环形缓冲 journalctl -b -o short-monotonic | head -30 五、阶段四/五：systemd 时代 内核启动的第一个用户态进程 PID 1 = systemd：\nps -p 1 -o comm= # systemd systemd --version 并行启动是 systemd 相对 SysV init 的革命：靠声明依赖（After/Wants/Requires，详见服务管理篇）把串行等待变成并行拉起——这就是现代 Linux 十几秒开机的原因。\ntarget = 一组服务的集合（启动里程碑）：\nsystemctl get-default # 当前默认: multi-user.target 或 graphical.target systemctl list-dependencies multi-user.target | head # 切换运行级别（即时） systemctl isolate multi-user.target # 图形 → 字符界面 target 含义 multi-user.target 多用户字符界面（服务器标配） graphical.target 图形桌面 rescue.target 单用户救援（本地有控制台） emergency.target 紧急模式（只挂 / 只读） 六、阶段六：登录与用户会话 # 本地终端（Ctrl+Alt+F2~F6 切换） systemctl status getty@tty2 # 远程登录 systemctl status sshd # 登录后发生什么? # /etc/profile → /etc/profile.d/*.sh → ~/.bash_profile → ~/.bashrc # （顺序影响环境变量, 排查 PATH 问题时按这条链查） 七、开机慢？逐段计时 systemd-analyze # 总耗时 # Startup finished in 2.1s (kernel) + 4.5s (initrd) + 8.3s (userspace) systemd-analyze blame | head # 每个服务耗时排名 systemd-analyze critical-chain # 关键路径（谁拖累了谁） systemd-analyze plot \u0026gt; boot.svg # 可视化时间线 优化手段：systemctl disable 不需要的服务、NetworkManager-wait-online 常是元凶。\n八、启动故障排障地图 卡住位置 线索 处理 黑屏无 GRUB 固件不认盘 检查磁盘/启动项；修 MBR/ESP GRUB 报错 grub rescue\u0026gt; grub.cfg 丢失/分区变 ls 找到启动分区，手动 insmod normal 卡在 Loading initramfs initramfs 损坏/缺驱动 救援盘 chroot 后 dracut -f 进入 emergency mode fsck 失败/fstab 挂载失败 看 journalctl；修 fstub（常是 UUID 写错） 卡在某个服务 systemd-analyze blame disable 或修该服务 起来了但 ssh 连不上 sshd 未起/防火墙 控制台进 systemctl status sshd emergency/rescue 模式进入方式：\n# GRUB 内核参数追加 systemd.unit=rescue.target systemd.unit=emergency.target # 或已能登录时 systemctl rescue 九、完整排障演练：fstab 写错进不了系统 现象: 开机进 emergency mode, 提示 \u0026#34;Dependency failed for /data\u0026#34; 原因: /etc/fstab 里挂了一块已不存在的盘 处理: 1.emergency shell 下: journalctl -xb | grep -i mount 2. mount -o remount,rw / 3. vim /etc/fstab → 注释掉那行坏盘 4. mount -a → 验证无报错 5. reboot → 顺利进入系统 这就是启动流程知识的实战价值：每一层失败都有对应入口与工具。\n小结 阶段 关键组件 排障工具 固件 BIOS/UEFI + 启动项 Setup / efibootmgr 引导 GRUB2 按 e 编辑 / grub2-mkconfig 内核 vmlinuz + initramfs dmesg / dracut -f 初始化 systemd journalctl -b / systemd-analyze 登录 getty/sshd systemctl status 本文是「Linux 基础」系列第 8 篇。\n","permalink":"https://antidebug.cn/posts/2025/06/linux-%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E8%AF%A6%E8%A7%A3/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e服务器卡在启动界面进不去系统时，看懂启动流程就是看懂排障地图。这篇从固件到 shell，一步步走完 Linux 的\u0026quot;出生过程\u0026quot;。\u003c/p\u003e\n\u003ch2 id=\"一总览六个阶段\"\u003e一、总览：六个阶段\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e① 固件自检 (BIOS/UEFI)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e② 引导加载 (GRUB2)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e③ 内核加载 (vmlinuz + initramfs)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e④ PID 1 (systemd 启动)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e⑤ 并行拉起服务 (target + 依赖)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e⑥ 登录 (getty / sshd)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"二阶段一固件biosuefi\"\u003e二、阶段一：固件（BIOS/UEFI）\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eBIOS（老）                          UEFI（现代）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e按 MBR 磁盘前512字节引导              读 FAT 格式的 ESP 分区里的 .efi 文件\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │                                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ▼                                  ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eMBR: 引导代码446B + 分区表64B          /boot/efi/EFI/.../grubx64.efi\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     + 魔数 55AA\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 看自己的机器是哪种\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e -d /sys/firmware/efi \u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e UEFI \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e BIOS\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# UEFI 下看启动项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eefibootmgr -v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 固件层常见故障: 启动顺序错了 / 磁盘没认出来\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#    → 进 BIOS Setup 检查 Boot Order 与磁盘是否识别\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"三阶段二grub2-引导\"\u003e三、阶段二：GRUB2 引导\u003c/h2\u003e\n\u003cp\u003eGRUB 的任务：\u003cstrong\u003e找到内核与 initramfs，把控制权交出去\u003c/strong\u003e。\u003c/p\u003e","title":"Linux 系统启动流程详解"},{"content":"前言 \u0026ldquo;没测试的代码 = 不知道能不能跑的代码\u0026rdquo;。Go 把测试做成语言级公民：go test 开箱即用，没有选择困难。这篇从规范到进阶一次讲清。\n一、最小测试 规则：测试文件以 _test.go 结尾，函数签名 func TestXxx(t *testing.T)。\n// split.go package strutil import \u0026#34;strings\u0026#34; func Split(s, sep string) []string { return strings.Split(s, sep) } // split_test.go package strutil import \u0026#34;testing\u0026#34; func TestSplit(t *testing.T) { got := Split(\u0026#34;a,b,c\u0026#34;, \u0026#34;,\u0026#34;) want := []string{\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;} if len(got) != len(want) { t.Errorf(\u0026#34;长度不符: got %d, want %d\u0026#34;, len(got), len(want)) } for i := range want { if got[i] != want[i] { t.Errorf(\u0026#34;第%d个元素: got %q, want %q\u0026#34;, i, got[i], want[i]) } } } go test ./... # 跑当前模块全部测试 go test -v # 显示每个用例 go test -run TestSplit # 只跑匹配的 二、表驱动测试（Go 的招牌写法） 一组输入输出写成表，循环断言——新增用例只加一行：\nfunc TestSplit(t *testing.T) { tests := []struct { name string input string sep string want []string }{ {\u0026#34;普通逗号\u0026#34;, \u0026#34;a,b,c\u0026#34;, \u0026#34;,\u0026#34;, []string{\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;}}, {\u0026#34;无分隔符\u0026#34;, \u0026#34;abc\u0026#34;, \u0026#34;,\u0026#34;, []string{\u0026#34;abc\u0026#34;}}, {\u0026#34;空字符串\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;,\u0026#34;, []string{\u0026#34;\u0026#34;}}, {\u0026#34;分隔符在两端\u0026#34;, \u0026#34;,a,\u0026#34;, \u0026#34;,\u0026#34;, []string{\u0026#34;\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;\u0026#34;}}, {\u0026#34;中文\u0026#34;, \u0026#34;你好,世界\u0026#34;, \u0026#34;,\u0026#34;, []string{\u0026#34;你好\u0026#34;, \u0026#34;世界\u0026#34;}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 子测试: 单独报告、单独运行 got := Split(tt.input, tt.sep) if !slices.Equal(got, tt.want) { t.Errorf(\u0026#34;Split(%q, %q) = %v, want %v\u0026#34;, tt.input, tt.sep, got, tt.want) } }) } } go test -v -run TestSplit/中文 # 只跑某个子用例 三、testify：让断言好看点 go get github.com/stretchr/testify import ( \u0026#34;github.com/stretchr/testify/assert\u0026#34; \u0026#34;github.com/stretchr/testify/require\u0026#34; ) func TestSplit(t *testing.T) { got := Split(\u0026#34;a,b,c\u0026#34;, \u0026#34;,\u0026#34;) assert.Equal(t, []string{\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;}, got) // 失败继续跑完 require.Len(t, got, 3, \u0026#34;应切出3段\u0026#34;) // 失败立即终止（防后续 panic） assert.Contains(t, got, \u0026#34;b\u0026#34;) assert.NoError(t, err) } assert（失败继续）vs require（失败即停）：后者用于\u0026quot;后面依赖前面结果\u0026quot;的场景。\n四、覆盖率 go test -cover ./... # ok demo/strutil 0.012s coverage: 85.7% of statements # 生成 HTML 报告, 看哪些行没测到 go test -coverprofile=cover.out ./... go tool cover -html=cover.out -o cover.html # CI 里卡阈值（低于80%失败） go test -cover ./... | awk -F\u0026#39;coverage: \u0026#39; \u0026#39;/coverage/ {gsub(\u0026#34;%.*\u0026#34;,\u0026#34;\u0026#34;,$2); if ($2+0 \u0026lt; 80) exit 1}\u0026#39; 覆盖率是体检指标不是 KPI：80% 且关键路径全测 远胜于刷到 95% 的无脑断言。\n五、Benchmark 基准测试 func BenchmarkXxx(b *testing.B)，框架自动调 b.N 次直到测量稳定：\nfunc BenchmarkSplit(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { // 循环体必须是 b.N 次 Split(\u0026#34;a,b,c,d,e,f,g,h\u0026#34;, \u0026#34;,\u0026#34;) } } go test -bench=. -benchmem ./... # BenchmarkSplit-8 5824932 204.1 ns/op 112 B/op 1 allocs/op # │ │ │ └ 每次操作堆分配次数 # │ │ └ 每次操作分配字节数 # │ └ 单次操作耗时 # └ -8 = GOMAXPROCS 优化对比实战：\n// 优化前: 每次都分配 func Join(b []byte, s string) string { return string(b) + s } // 优化后: 预分配 func Join2(b []byte, s string) string { out := make([]byte, 0, len(b)+len(s)) out = append(out, b...) out = append(out, s...) return string(out) } func BenchmarkJoin(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { Join(x, \u0026#34;y\u0026#34;) } } func BenchmarkJoin2(b *testing.B) { for i := 0; i \u0026lt; b.N; i++ { Join2(x, \u0026#34;y\u0026#34;) } } // BenchmarkJoin-8 8ns/op 16B/op 1 allocs/op # BenchmarkJoin2-8 5ns/op 0B/op 0 allocs/op ← 预分配立功 六、并行测试与超时 func TestParallel(t *testing.T) { tests := loadCases() // 100 个用例 for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() // 子用例并行跑（大幅提速） got := heavy(tt.input) assert.Equal(t, tt.want, got) }) } } go test -timeout 60s ./... # 全局超时（默认10min, CI必配） go test -race ./... # 竞态检测（测试并发代码必开!） 七、测外部依赖：接口 + mock 依赖外部服务时，先抽象成接口（结构体接口篇的招数），测试时注入假实现：\ntype Notifier interface { Notify(title, body string) error } type fakeNotifier struct { calls int err error } func (f *fakeNotifier) Notify(t, b string) error { f.calls++ return f.err } func TestAlerter(t *testing.T) { fn := \u0026amp;fakeNotifier{err: errors.New(\u0026#34;network down\u0026#34;)} a := NewAlerter(fn) err := a.Fire(\u0026#34;cpu high\u0026#34;) require.Error(t, err) // 通知失败要有正确行为 assert.Equal(t, 1, fn.calls) // 且真的调用过一次 } gomock/testify mock 是这套思路的自动化版本——但能手写 fake 时优先手写，可读性更好。\n八、CI 集成（GitLab CI 示例） test: stage: test image: golang:1.22 script: - go vet ./... # 静态检查 - go test -race -coverprofile=cover.out -timeout 120s ./... - go tool cover -func=cover.out | tail -1 coverage: \u0026#39;/coverage: \\d+\\.\\d+/\u0026#39; 九、常见坑 现象 原因 no required module provides package 测试文件 package 声明错了；或没 go mod init benchmark 数值乱跳 机器忙/电源管理；多跑几次取稳定值，加 -benchtime=3s 测试顺序依赖互相污染 用例要幂等；共享状态用 setup/teardown 隔离 t.Parallel 后 data race 闭包变量被并发改；循环变量传参（老版本 Go） mock 之后测了个寂寞 假实现直接返回\u0026quot;想要的\u0026quot;——mock 测的是交互，真集成要 e2e 补 小结 需求 工具 基本测试 TestXxx(t *testing.T) 多用例 表驱动 + t.Run 子测试 断言 testify assert/require 覆盖率 -coverprofile + HTML 性能 -bench + -benchmem 并发正确性 -race 外部依赖 接口 + 手写 fake 本文是「Golang」系列第 9 篇。\n","permalink":"https://antidebug.cn/posts/2025/05/golang-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E4%B8%8E%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;没测试的代码 = 不知道能不能跑的代码\u0026rdquo;。Go 把测试做成语言级公民：\u003ccode\u003ego test\u003c/code\u003e 开箱即用，没有选择困难。这篇从规范到进阶一次讲清。\u003c/p\u003e","title":"Golang 单元测试与基准测试"},{"content":"前言 几十台机器各自 tail -f 的日子该结束了。集中式日志的价值：一处搜索全部日志、跨服务排障关联、图表化趋势、留存审计。ELK 是这个领域的经典答案。\n一、架构与组件分工 应用/中间件 │ 输出日志文件 ▼ Filebeat（轻量采集器, 装在每台机器） │ 汇聚 / 容错缓冲 ▼ [可选] Kafka（削峰 / 解耦 / 丢不起的日志） │ ▼ Logstash（解析: grok 拆字段、过滤、富化） │ ▼ Elasticsearch（索引 + 全文检索） │ ▼ Kibana（查询 / 可视化 / 告警） 组件选型说明：\n组件 职责 备注 Filebeat 采集 Go 写的, 内存占用 ~30MB, 几乎无感 Logstash 解析转换 JVM 重（1G+内存）; 简单场景可用 Filebeat 自带 processor 替代 Elasticsearch 存储检索 JVM 大胃王; 单机实验给 2G 堆即可 Kibana 界面 中规中矩 600MB 内存 二、快速搭建（单节点实验版） # docker-compose.yml services: es: image: elasticsearch:8.14.1 environment: - discovery.type=single-node - xpack.security.enabled=false # 实验方便, 生产必开! - ES_JAVA_OPTS=-Xms1g -Xmx1g ports: [\u0026#34;9200:9200\u0026#34;] volumes: [esdata:/usr/share/elasticsearch/data] kibana: image: kibana:8.14.1 environment: - ELASTICSEARCH_HOSTS=http://es:9200 ports: [\u0026#34;5601:5601\u0026#34;] depends_on: [es] volumes: esdata: docker compose up -d # 浏览器打开 http://IP:5601 —— Kibana 就绪 三、Filebeat：装到每台被采集机 curl -L -o filebeat.rpm https://mirrors.tuna.tsinghua.edu.cn/elasticstack/8.x/yum/... rpm -ivh filebeat.rpm /etc/filebeat/filebeat.yml 核心配置：\nfilebeat.inputs: - type: log enabled: true paths: - /var/log/nginx/access.log* - /var/log/nginx/error.log* fields: service: nginx env: production fields_under_root: true output.elasticsearch: # 简单场景: 直连 ES hosts: [\u0026#34;http://es-node:9200\u0026#34;] index: \u0026#34;nginx-%{+yyyy.MM.dd}\u0026#34; # 简单解析不劳 Logstash, Filebeat 自带模块: filebeat.modules: - module: nginx access: {var.paths: [\u0026#34;/var/log/nginx/access.log*\u0026#34;]} systemctl enable --now filebeat # Kibana -\u0026gt; Discover 创建 data view: nginx-* 即可检索! 四、结构化解析（Logstash 或 Ingest Pipeline） Nginx 日志拆成字段才能按状态码/接口统计。推荐 ES Ingest Pipeline（省掉 Logstash）：\nPUT _ingest/pipeline/nginx-parse { \u0026#34;processors\u0026#34;: [ {\u0026#34;grok\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;patterns\u0026#34;: [ \u0026#34;%{IP:client_ip} - \\\\S+ \\\\[%{HTTPDATE:ts}\\\\] \\\u0026#34;%{WORD:method} %{URIPATHPARAM:uri} HTTP/%{NUMBER:http_ver}\\\u0026#34; %{NUMBER:status:int} %{NUMBER:bytes:int} \\\u0026#34;%{DATA:referer}\\\u0026#34; \\\u0026#34;%{DATA:ua}\\\u0026#34; rt=%{NUMBER:rt:float}\u0026#34; ] }}, {\u0026#34;date\u0026#34;: {\u0026#34;field\u0026#34;: \u0026#34;ts\u0026#34;, \u0026#34;formats\u0026#34;: [\u0026#34;dd/MMM/yyyy:HH:mm:ss Z\u0026#34;]}}, {\u0026#34;remove\u0026#34;: {\u0026#34;field\u0026#34;: [\u0026#34;ts\u0026#34;, \u0026#34;message\u0026#34;]}} ] } # filebeat.yml 里挂上 pipeline output.elasticsearch: hosts: [\u0026#34;http://es:9200\u0026#34;] pipeline: nginx-parse 检索验证：\nGET nginx-*/_search { \u0026#34;query\u0026#34;: {\u0026#34;match\u0026#34;: {\u0026#34;status\u0026#34;: 500}}, \u0026#34;sort\u0026#34;: [{\u0026#34;rt\u0026#34;: \u0026#34;desc\u0026#34;}] } 五、Kibana 实用操作 Discover 按时间轴 + 条件过滤（KQL）: status:500 and service:nginx uri:/api/* and rt\u0026gt;1 Visualize 画图: 状态码饼图 / 每分钟 5xx 趋势线 / Top10 慢接口（数据表） Dashboard 拼大盘: 一屏看完服务健康度 Alerting 告警: \u0026#34;5分钟内 status:500 超过 50 次\u0026#34; → 邮件/webhook 高频 KQL：\nmessage: \u0026#34;connection refused\u0026#34; # 全文 status \u0026gt;= 500 # 数值比较 not service: nginx # 排除 service:(nginx OR mysql) # 多值 六、索引生命周期（ILM）：日志的生老病死 日志无限增长会撑爆磁盘，ILM 自动分阶段管理：\nPUT _ilm/policy/logs-policy { \u0026#34;policy\u0026#34;: { \u0026#34;phases\u0026#34;: { \u0026#34;hot\u0026#34;: {\u0026#34;actions\u0026#34;: {}}, \u0026#34;warm\u0026#34;: {\u0026#34;min_age\u0026#34;: \u0026#34;1d\u0026#34;, \u0026#34;actions\u0026#34;: {\u0026#34;shrink\u0026#34;: {\u0026#34;number_of_shards\u0026#34;: 1}}}, \u0026#34;delete\u0026#34;: {\u0026#34;min_age\u0026#34;: \u0026#34;30d\u0026#34;, \u0026#34;actions\u0026#34;: {\u0026#34;delete\u0026#34;: {}}} } } } hot（当天，可查询）→ warm（1天后收缩副本降配）→ delete（30天删除）。任何 ELK 上线都必须配 ILM，这是磁盘的保命符。\n七、规模化的中间形态 \u0026lt; 10 GB/天: Filebeat → ES 直连（本文方案） 10-100 GB/天: Filebeat → Logstash → ES（解析富化） \u0026gt; 100 GB/天: Filebeat → Kafka → Logstash 集群 → ES 集群 Kafka 层的价值：削峰（日志洪峰不冲垮 ES）、缓冲（ES 故障时日志不丢）、多消费（同时给实时告警和离线数仓）。\n八、替代方案速览 方案 特点 适合 ELK 功能全, 生态最广, 资源吃得多 全文检索刚需、复杂分析 Loki + Grafana 只索引标签不索引全文, 便宜 10 倍 日志量随查询少的团队（云原生新宠） ClickHouse 列存 SQL 分析, 极快 结构化日志分析为主 云托管 阿里云 SLS / 腾讯云 CLS 不想自运维 我的建议：中小团队直接 Loki（与 Grafana 监控同一面板），除非你重度依赖全文检索。\n九、常见坑 现象 原因 Filebeat 收不到日志 权限（nginx 日属 root）；paths 通配写错 ES 起不来 vm.max_map_count=262144（sysctl） Kibana 无数据 data view 的 index pattern 没建/时间范围不对 磁盘水位锁只读 _disk watermark 触发 → 清索引 + 扩容；先配 ILM 预防 日志乱序/丢失 加 Kafka 层；确认 filebeat registry 正常推进 小结 层 组件 核心配置 采集 Filebeat inputs.paths + fields 解析 Ingest/Logstash grok 拆字段 + date 时间 存储 ES 索引模板 + ILM 生命周期 使用 Kibana Discover KQL + Dashboard + Alerting 本文是「云原生」系列第 11 篇。\n","permalink":"https://antidebug.cn/posts/2025/05/elk-%E6%97%A5%E5%BF%97%E6%94%B6%E9%9B%86%E5%88%86%E6%9E%90%E5%B9%B3%E5%8F%B0%E5%85%A5%E9%97%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e几十台机器各自 \u003ccode\u003etail -f\u003c/code\u003e 的日子该结束了。集中式日志的价值：\u003cstrong\u003e一处搜索全部日志、跨服务排障关联、图表化趋势、留存审计\u003c/strong\u003e。ELK 是这个领域的经典答案。\u003c/p\u003e\n\u003ch2 id=\"一架构与组件分工\"\u003e一、架构与组件分工\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e应用/中间件\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  输出日志文件\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eFilebeat（轻量采集器, 装在每台机器）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  汇聚 / 容错缓冲\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e[可选] Kafka（削峰 / 解耦 / 丢不起的日志）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eLogstash（解析: grok 拆字段、过滤、富化）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eElasticsearch（索引 + 全文检索）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eKibana（查询 / 可视化 / 告警）\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e组件选型说明：\u003c/p\u003e","title":"ELK 日志收集分析平台入门"},{"content":"前言 \u0026ldquo;改个数据库地址要重新打镜像？\u0026quot;——配置与代码分离是十二要素应用的核心原则。K8s 用 ConfigMap（普通配置）+ Secret（敏感配置） 承载这一切。\n一、创建 # ---- ConfigMap 三种创建姿势 ---- # 1. 字面量 kubectl create configmap app-conf \\ --from-literal=APP_ENV=production \\ --from-literal=LOG_LEVEL=info # 2. 从文件 kubectl create configmap nginx-conf --from-file=nginx.conf # 3. 从目录（每个文件一个 key） kubectl create configmap site --from-file=./conf/ 声明式 YAML（推荐）：\napiVersion: v1 kind: ConfigMap metadata: name: app-conf data: # 简单键值 APP_ENV: \u0026#34;production\u0026#34; LOG_LEVEL: \u0026#34;info\u0026#34; # 整文件内容（多行用 |） redis.conf: | bind 0.0.0.0 maxmemory 512mb Secret 同理（base64 编码，注意是编码不是加密）：\nkubectl create secret generic db-cred \\ --from-literal=password=\u0026#39;S3cret!\u0026#39; \\ --from-file=tls.crt=server.crt # stringData 写明文（创建时自动编码, 更友好） apiVersion: v1 kind: Secret metadata: name: db-cred stringData: username: app password: \u0026#34;S3cret!\u0026#34; 二、注入方式一：环境变量 spec: containers: - name: app image: myapp:v1.0 env: - name: APP_ENV # 单个取值 valueFrom: configMapKeyRef: {name: app-conf, key: APP_ENV} - name: DB_PASSWORD # 从 Secret 取 valueFrom: secretKeyRef: {name: db-cred, key: password} envFrom: # 一把全导（key 变量名） - configMapRef: {name: app-conf} - secretRef: {name: db-cred} 特点：简单直接，但 Pod 启动后不更新（需要重启 Pod）。\n三、注入方式二：挂载文件（推荐） spec: containers: - name: app image: myapp:v1.0 volumeMounts: - name: conf mountPath: /etc/app # 目录下每个 key 一个文件 volumes: - name: conf configMap: name: app-conf 挂出来长这样：\nkubectl exec -it \u0026lt;pod\u0026gt; -- ls /etc/app # APP_ENV LOG_LEVEL redis.conf ← 每个key都是文件 kubectl exec -it \u0026lt;pod\u0026gt; -- cat /etc/app/APP_ENV # production 只想挂单个文件（别盖掉目录里其他文件）：\nvolumeMounts: - name: conf mountPath: /etc/nginx/nginx.conf # 精确到文件名 subPath: nginx.conf # 对应 configMap 里的 key ⚠️ SubPath 的坑：用了 subPath 的挂载不会热更新（详见下节）。需要热更新的文件别用 subPath。\n四、热更新机制 ConfigMap 变更后，volume 挂载方式会自动同步（约 1 分钟内，实际是 kubelet 的同步周期）：\n# 修改 kubectl edit configmap app-conf # 把 LOG_LEVEL 改成 debug # 等 ~60s, 容器里文件已变（但应用是否重读是应用自己的事!） kubectl exec -it \u0026lt;pod\u0026gt; -- cat /etc/app/LOG_LEVEL 三种应用感知策略：\n策略 做法 应用自己 watch 文件 优雅但要求开发配合（如 nginx -s reload） sidecar 检测变化发信号 Reloader / 注入一个 watcher 容器 滚动重启 kubectl rollout restart deploy/app（简单粗暴, 大多数场景够用） 环境变量方式永远不热更新；Secret 挂载同理自动同步。\n五、不可变 ConfigMap/Secret（性能优化） 高频创建的场景可以锁死：\napiVersion: v1 kind: ConfigMap metadata: name: app-conf immutable: true data: {...} 好处：kubelet 不再 watch，大幅降低 API Server 压力（集群级优化）；代价：改配置必须重建对象。\n六、实战：一个完整的\u0026quot;配置分离\u0026quot;应用 apiVersion: v1 kind: ConfigMap metadata: name: web-conf data: default.conf: | server { listen 80; server_name _; location / { root /usr/share/nginx/html; } location /healthz { return 200 \u0026#34;ok\u0026#34;; } } --- apiVersion: v1 kind: Secret metadata: name: web-basic-auth stringData: auth: \u0026#34;admin:$apr1$xyz...\u0026#34; --- apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 2 selector: {matchLabels: {app: web}} template: metadata: {labels: {app: web}} spec: containers: - name: nginx image: nginx:1.25 volumeMounts: - name: conf mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf volumes: - name: conf configMap: {name: web-conf} kubectl apply -f web.yaml kubectl port-forward deploy/web 8080:80 curl http://localhost:8080/healthz 七、安全要点（Secret 篇） base64 ≠ 加密：拿到 YAML 就能解码。真正防护靠： etcd 加密配置（--encryption-provider-config） RBAC 限制 get secrets 权限 生产进阶：外部密钥管理——External Secrets Operator（对接 Vault/云 KMS）或 Sealed Secrets（加密后可进 Git） 不用的 Secret 及时删：kubectl delete secret old-cred 审计谁读了 Secret：开 API audit log 八、常见坑速查 现象 原因 Pod 起不来：CreateContainerConfigError 引用的 key 不存在（describe 看 Events） 挂载文件是目录 mountPath 写到目录但想挂单文件——加 subPath 改了配置没生效 env 方式/SubPath 挂载不热更新 挂载后原目录文件消失 目录被 volume 整个盖掉，逐文件用 subPath Secret 中文乱码 base64 编码前先确认 UTF-8 小结 需求 方案 少量简单配置 envFrom 环境变量 配置文件 volume 挂载（免 subPath 可热更） 敏感信息 Secret + RBAC（进阶: 外部密钥系统） 高频变更 immutable 锁定 + 重建 应用不重读配置 rollout restart 或 Reloader 本文是「云原生」系列第 10 篇。\n","permalink":"https://antidebug.cn/posts/2025/04/k8s-%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86configmap-%E4%B8%8E-secret/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;改个数据库地址要重新打镜像？\u0026quot;——配置与代码分离是十二要素应用的核心原则。K8s 用 \u003cstrong\u003eConfigMap（普通配置）+ Secret（敏感配置）\u003c/strong\u003e 承载这一切。\u003c/p\u003e","title":"K8s 配置管理：ConfigMap 与 Secret"},{"content":"前言 虚拟化基础篇讲了 KVM 原理，这篇进入实战进阶：用 cloud-init 云镜像几分钟拉起一台可 SSH 的虚拟机，玩转快照、克隆、桥接——自建小私有云/Malloc 一套测试环境的完整技能。\n一、环境准备（宿主机） dnf install -y qemu-kvm libvirt virt-install virt-top libguestfs-tools systemctl enable --now libvirtd lsmod | grep kvm # kvm_intel / kvm_amd 已加载 # 默认 NAT 网络（192.168.122.0/24, 自带 DHCP） virsh net-start default virsh net-autostart default 二、cloud-init：告别装系统 传统 virt-install 挂 ISO 装系统要 20 分钟；云镜像（Cloud Image）自带 cloud-init，首次开机自动完成：扩盘、设密码、注入密钥、改主机名——1 分钟出机。\n# 1. 下载云镜像（以 Rocky 9 为例） cd /var/lib/libvirt/images wget https://mirrors.nju.edu.cn/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 # 2. 准备\u0026#34;启动配置\u0026#34; cat \u0026gt; /root/seed.iso.cmd \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; cloud-localds /var/lib/libvirt/images/seed.img /root/user-data EOF /root/user-data（cloud-init 配置）：\n#cloud-config hostname: vm-lab01 users: - name: zy sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAAC3... your@laptop ssh_pwauth: true chpasswd: expire: false users: - name: zy password: Passw0rd! type: text runcmd: - dnf install -y nginx - systemctl enable --now nginx growpart: mode: auto devices: [\u0026#39;/\u0026#39;] # 3. 生成 seed 盘（需要 cloud-utils 软件包） dnf install -y cloud-utils cloud-localds /var/lib/libvirt/images/seed.img /root/user-data # 4. 从云镜像启动虚拟机（注意: 用副本, 别直接用原镜像） cp Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 vm-lab01.qcow2 virt-install \\ --name vm-lab01 \\ --vcpus 2 --memory 2048 \\ --disk vm-lab01.qcow2,bus=virtio,format=qcow2 \\ --disk seed.img,device=cdrom \\ --network network=default,model=virtio \\ --os-variant rocky9 \\ --import --noautoconsole # --import: 不装系统直接启动 # 5. 一分钟后拿到 IP 并登录 virsh domifaddr vm-lab01 ssh zy@192.168.122.xxx # nginx 都已经装好了 —— runcmd 的功劳 三、日常生命周期管理 virsh start vm-lab01 / shutdown / destroy # 优雅关 / 拔电源 virsh reboot vm-lab01 virsh list --all virsh autostart vm-lab01 # 宿主机开机自启 # 进控制台（没网络时的救命稻草, 退出 Ctrl+]） virsh console vm-lab01 # 删除虚拟机（先 undefine 再删盘） virsh undefine vm-lab01 --nvram rm /var/lib/libvirt/images/vm-lab01.qcow2 四、资源调整 # ---- 内存 ---- virsh setmem vm-lab01 4G --config # 下次启动生效 virsh setmem vm-lab01 2G --live # 立即生效（在线! 只能调小于分配值） # ---- CPU ---- virsh setvcpus vm-lab01 4 --maximum --config # 上限 virsh setvcpus vm-lab01 2 --live # 在线减少 # ---- 磁盘扩容三步走 ---- # 1. 扩 qcow2 文件 qemu-img resize vm-lab01.qcow2 +20G # 2. 通知虚拟机重读磁盘（或重启） virsh blockresize vm-lab01 /path/vm-lab01.qcow2 40G # 3. 进虚拟机扩分区和文件系统 growpart /dev/vda 1 \u0026amp;\u0026amp; xfs_growfs / # cloud 镜像自带 growpart 五、快照：虚拟机的\u0026quot;存档点\u0026quot; # 创建（拍快照前磁盘会自动暂停一下, 建议关机时拍更稳） virsh snapshot-create-as vm-lab01 before-upgrade \u0026#34;升级前存档\u0026#34; # 查看 / 恢复 / 删除 virsh snapshot-list vm-lab01 virsh snapshot-revert vm-lab01 before-upgrade # 时光倒流 virsh snapshot-delete vm-lab01 before-upgrade # qcow2 快照链原理 qemu-img info vm-lab01.qcow2 # backing file: Rocky-9-GenericCloud...qcow2 ← 我们 cp 出来的其实是\u0026#34;基于模板的差分盘\u0026#34; 注意快照链会让性能下降，重要数据别依赖快照当天备份，还是要走外部备份（qemu-img convert 导出）。\n六、模板与克隆：批量开机房 # 1. 把一台调好的机器做成模板（先关机） virsh shutdown vm-template virt-sysprep -d vm-template # 清洗机器指纹（ssh host key、machine-id、日志） # 2. 基于模板克隆（差分盘, 秒级, 几乎不占空间） virt-clone \\ --original vm-template \\ --name vm-web01 \\ --file /var/lib/libvirt/images/vm-web01.qcow2 # 3. 改主机名/IP（避免克隆冲突） virt-customize -a /var/lib/libvirt/images/vm-web01.qcow2 \\ --hostname vm-web01 \\ --run-command \u0026#39;nmcli con mod eth0 ipv4.addresses 192.168.122.101/24\u0026#39; virsh start vm-web01 # 重复 2-3 步, 5 分钟拉起一个 10 台机器的实验集群 七、桥接网络：让虚拟机进物理网络 默认 NAT 网络下虚拟机只能\u0026quot;单向出\u0026quot;；桥接后虚拟机与宿主机同网段平级，外部可直接访问：\n# 宿主机建网桥 br0（把物理网卡 eth0 挂上去） nmcli con add type bridge ifname br0 ipv4.method auto nmcli con add type bridge-slave ifname eth0 master br0 nmcli con up br0 # 虚拟机用桥接 virt-install ... --network bridge=br0,model=virtio # 或修改现有机器: virsh edit vm-lab01 把 interface source 改为 bridge 八、性能观测与常见坑 virt-top # 虚拟机版 top（CPU/内存实时） virsh dominfo vm-lab01 # 配置详情 virsh domstats vm-lab01 --cpu-total virsh cpu-stats vm-lab01 | head qemu-img check vm-lab01.qcow2 # 磁盘一致性检查 坑 解 domain is already running virsh destroy 后再 start cloud-init 没生效 看 /var/log/cloud-init.log；seed 盘必须是 device=cdrom 虚拟机时间漂移 装 chrony；virsh edit 确认 \u0026lt;clock offset='utc'/\u0026gt; 克隆机网络起不来 machine-id 重复，virt-sysprep 或手动 truncate 性能差 确认 virtio 磁盘/网卡、KVM 嵌套没开虚拟化 小结 任务 命令 快速建机 云镜像 + cloud-localds + --import 快照 snapshot-create-as / revert 批量 virt-sysprep 模板 + virt-clone 桥接 nmcli bridge + --network bridge=br0 观测 virt-top / domstats 本文是「云计算」系列第 4 篇。\n","permalink":"https://antidebug.cn/posts/2025/03/kvm-%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%9B%E5%BB%BA%E4%B8%8E%E7%AE%A1%E7%90%86%E5%AE%9E%E6%88%98/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e虚拟化基础篇讲了 KVM 原理，这篇进入实战进阶：用 \u003cstrong\u003ecloud-init 云镜像\u003c/strong\u003e几分钟拉起一台可 SSH 的虚拟机，玩转快照、克隆、桥接——自建小私有云/Malloc 一套测试环境的完整技能。\u003c/p\u003e","title":"KVM 虚拟机创建与管理实战"},{"content":"前言 \u0026ldquo;最小权限原则\u0026quot;落地的第一站就是账号体系：日常用普通用户 + 按需 sudo，而不是人人 root。这篇把用户、组、sudo 的管理一次讲全。\n一、用户与组的本质 一切都在这三个文件里：\n/etc/passwd # 用户账本 /etc/shadow # 密码哈希（只有 root 可读） /etc/group # 组账本 # passwd 每行七段 zy:x:1000:1000::/home/zy:/bin/bash │ │ │ │ │ │ └ 登录 shell │ │ │ │ │ └ 家目录 │ │ │ │ └ 备注（GECOS） │ │ │ └ 主组 GID │ │ └ UID（\u0026lt;1000 多为系统账号） │ └ 密码占位（真密码在 shadow） └ 用户名 # 快速查某个用户 id zy # uid=1000(zy) gid=1000(zy) groups=1000(zy),10(wheel),994(docker) 二、用户管理命令 # 创建（RHEL 系默认建同名主组） useradd -m -s /bin/bash -c \u0026#34;运维-zy\u0026#34; alice # -m 建家目录 useradd -r -s /sbin/nologin deploy # -r 系统账号（跑服务用, 不能登录） # 设置/改密码 passwd alice # 交互式 echo \u0026#34;alice:Str0ngPass\u0026#34; | chpasswd # 脚本式 # 修改属性 usermod -aG docker,wheel alice # 追加附加组（-aG 别写成 -G, 会覆盖!） usermod -L alice # 锁定账号（禁登录） usermod -U alice # 解锁 usermod -s /sbin/nologin alice # 换 shell（禁交互登录） # 删除 userdel -r alice # -r 连家目录和邮箱一起删 # 查看登录情况 last | head who / w # 当前在线 给服务建运行用户的惯例：\nuseradd -r -s /sbin/nologin -d /opt/myapp myapp # 之后 systemd unit 里 User=myapp, chown -R myapp:myapp /opt/myapp 三、组管理 groupadd developers groupmod -n devs developers # 改名 groupdel devs gpasswd -a alice developers # 加成员 gpasswd -d alice developers # 移除成员 # 临时切换主组（新建文件属组生效） newgrp developers # 组密码极少用; 现代用法基本是: 附加组 + sudo 组的典型玩法——目录共享（回顾权限篇）：\ngroupadd devops usermod -aG devops alice; usermod -aG devops bob mkdir -p /data/share \u0026amp;\u0026amp; chgrp devops /data/share chmod 2770 /data/share # SGID: 新文件自动继承 devops 组 四、sudo：受控的提权 su - 是\u0026quot;整个 root 身份交出去\u0026rdquo;，sudo 是\u0026quot;按规则放行特定命令并留审计日志\u0026quot;。\n# 编辑 sudoers 永远用 visudo（保存前做语法检查, 防止把自己锁死!） visudo # 或子文件（推荐, 便于管理） visudo -f /etc/sudoers.d/deploy 规则语法：\n谁 在哪台主机=(以谁的身份) 可以跑什么命令 实用规则示例：\n# wheel 组成员可全权 sudo（RHEL 默认存在） %wheel ALL=(ALL) ALL # 免密 sudo（自动化脚本常用, 但要评估风险） zy ALL=(ALL) NOPASSWD: ALL # 只允许跑特定命令 deploy ALL=(root) /usr/bin/systemctl restart myapp, \\ /usr/bin/systemctl status myapp # 只允许以 app 用户身份执行（不给 root!） tester ALL=(myapp) NOPASSWD: /opt/myapp/bin/test.sh # 组规则 + 排除法（慎用, 可被绕过, 尽量白名单） %devs ALL=(ALL) ALL, !/usr/bin/su, !/usr/sbin/visudo sudo 的高级用法：\nsudo -l # 列出我当前能跑哪些命令 sudo -u myapp ./run.sh # 以其他用户执行（不只 root） sudo -i # 完整 root 登录环境（≈su -） sudo !! # 以 sudo 重跑上一条命令（万能急救） # 审计: 谁在什么时候跑了什么 journalctl -t sudo | tail grep sudo /var/log/secure | tail 五、密码策略 chage -l alice # 查看策略 # 密码 90 天过期, 提前 7 天警告, 5 天内不能改第二次 chage -M 90 -W 7 -m 5 alice chage -d 0 alice # 强制下次登录改密码 chage -E 2025-12-31 contractor # 账号到期自动禁用（外包神器） # 全局策略（login.defs） sed -i \u0026#39;s/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/\u0026#39; /etc/login.defs # 密码复杂度（pam pwquality） # /etc/security/pwquality.conf minlen = 12 minclass = 3 # 至少3类字符 六、账号健康巡检 # 1. 能登录的系统账号（危险! 应为空, 至少无 shell） awk -F: \u0026#39;$3\u0026lt;1000 \u0026amp;\u0026amp; $7!~/(nologin|false)/ {print $1, $7}\u0026#39; /etc/passwd # 2. 空密码账号（必须为空!） awk -F: \u0026#39;($2==\u0026#34;\u0026#34;){print $1}\u0026#39; /etc/shadow # 3. UID=0 的账号（应只有 root） awk -F: \u0026#39;$3==0 {print $1}\u0026#39; /etc/passwd # 4. 90天未登录的账号（僵尸候选） lastlog -b 90 | grep -v \u0026#34;Never logged in\u0026#34; | awk \u0026#39;NR\u0026gt;1 \u0026amp;\u0026amp; $2!=\u0026#34;\u0026#34; {print $1}\u0026#39; # 5. sudoers 里的 NOPASSWD 盘点 grep -rn NOPASSWD /etc/sudoers /etc/sudoers.d/ 2\u0026gt;/dev/null 把这几条丢进巡检脚本（见我那篇 Bash 巡检），每周自动跑。\n七、实战场景 给新同事开账号（标准动作）：\nUSERNAME=alice useradd -m -s /bin/bash $USERNAME echo \u0026#34;$USERNAME:初始密码\u0026#34; | chpasswd chage -d 0 $USERNAME # 首登强制改密 usermod -aG developers $USERNAME # 业务组 # 需要提权时: visudo 加白名单, 而不是塞进 wheel 回收离职账号：\nusermod -L alice # 1. 先锁定（立即失效） usermod -s /sbin/nologin alice # 2. 禁 shell（双保险） crontab -r -u alice 2\u0026gt;/dev/null # 3. 清定时任务 # 4. 归档家目录后删除（保留审计期数据） tar -czf /backup/userdata/alice-$(date +%F).tar.gz /home/alice userdel -r alice grep -rn alice /etc/sudoers.d/ # 5. 清 sudoers 残留 小结 任务 命令 建用户 useradd -m -s /bin/bash 调组 usermod -aG（记得 -a） 授权 visudo -f /etc/sudoers.d/xx 白名单 审计 journalctl -t sudo 巡检 UID=0 / 空密码 / 可登录系统账号 服务账号 -r -s /sbin/nologin 本文是「Linux 基础」系列第 7 篇。\n","permalink":"https://antidebug.cn/posts/2025/03/linux-%E7%94%A8%E6%88%B7%E7%BB%84%E4%B8%8E-sudo-%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;最小权限原则\u0026quot;落地的第一站就是账号体系：日常用普通用户 + 按需 sudo，而不是人人 root。这篇把用户、组、sudo 的管理一次讲全。\u003c/p\u003e","title":"Linux 用户、组与 sudo 权限管理"},{"content":"前言 运维和后端日常都绕不开\u0026quot;用程序取数据\u0026quot;：调 API、抓页面、做监控探测。这篇把 Python 网络请求从入门到工程化讲一遍。\n一、requests：把 HTTP 写成人话 import requests # GET（永远带 timeout!） r = requests.get( \u0026#34;https://httpbin.org/get\u0026#34;, params={\u0026#34;page\u0026#34;: 2, \u0026#34;size\u0026#34;: 10}, # ?page=2\u0026amp;size=10 headers={\u0026#34;User-Agent\u0026#34;: \u0026#34;my-spider/1.0\u0026#34;}, timeout=5, ) r.status_code # 200 r.json() # 解析响应体 r.text # 文本 r.headers[\u0026#34;Content-Type\u0026#34;] # POST JSON r = requests.post( \u0026#34;https://httpbin.org/post\u0026#34;, json={\u0026#34;name\u0026#34;: \u0026#34;zy\u0026#34;, \u0026#34;level\u0026#34;: 5}, # 自动序列化 + Content-Type timeout=5, ) # POST 表单 / 文件上传 requests.post(url, data={\u0026#34;k\u0026#34;: \u0026#34;v\u0026#34;}, timeout=5) requests.post(url, files={\u0026#34;f\u0026#34;: open(\u0026#34;a.png\u0026#34;, \u0026#34;rb\u0026#34;)}, timeout=5) # 常见 HTTP 动词 requests.put(url, json={...}); requests.delete(url); requests.head(url) 没有 timeout 的请求 = 随机挂死的脚本。timeout=(3, 10) 分别指连接和读取超时。\n二、Session：复用连接 with requests.Session() as s: s.headers.update({\u0026#34;Authorization\u0026#34;: \u0026#34;Bearer xxx\u0026#34;}) for i in range(100): r = s.get(f\u0026#34;https://api.example.com/items/{i}\u0026#34;, timeout=5) process(r.json()) # TCP 连接复用: 快, 且对目标服务器友好 三、健壮化：重试 + 退避 from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry retry = Retry( total=3, backoff_factor=1, # 退避: 1s, 2s, 4s... status_forcelist=[429, 500, 502, 503, 504], allowed_methods=[\u0026#34;GET\u0026#34;], ) s = requests.Session() s.mount(\u0026#34;https://\u0026#34;, HTTPAdapter(max_retries=retry)) s.mount(\u0026#34;http://\u0026#34;, HTTPAdapter(max_retries=retry)) try: r = s.get(\u0026#34;https://api.example.com/data\u0026#34;, timeout=(3, 10)) r.raise_for_status() # 4xx/5xx 直接抛异常 data = r.json() except requests.Timeout: print(\u0026#34;超时\u0026#34;) except requests.HTTPError as e: print(f\u0026#34;HTTP错误: {e.response.status_code}\u0026#34;) except requests.RequestException as e: print(f\u0026#34;请求失败: {e}\u0026#34;) 四、解析 HTML：BeautifulSoup pip install beautifulsoup4 lxml -i https://mirrors.aliyun.com/pypi/simple/ from bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;div class=\u0026#34;post-list\u0026#34;\u0026gt; \u0026lt;article class=\u0026#34;post\u0026#34;\u0026gt; \u0026lt;h2\u0026gt;\u0026lt;a href=\u0026#34;/posts/1\u0026#34;\u0026gt;Linux 权限详解\u0026lt;/a\u0026gt;\u0026lt;/h2\u0026gt; \u0026lt;span class=\u0026#34;date\u0026#34;\u0026gt;2023-03-05\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;tag\u0026#34;\u0026gt;Linux\u0026lt;/span\u0026gt; \u0026lt;/article\u0026gt; \u0026lt;article class=\u0026#34;post\u0026#34;\u0026gt; \u0026lt;h2\u0026gt;\u0026lt;a href=\u0026#34;/posts/2\u0026#34;\u0026gt;Python 虚拟环境\u0026lt;/a\u0026gt;\u0026lt;/h2\u0026gt; \u0026lt;span class=\u0026#34;date\u0026#34;\u0026gt;2023-03-18\u0026lt;/span\u0026gt; \u0026lt;span class=\u0026#34;tag\u0026#34;\u0026gt;Python\u0026lt;/span\u0026gt; \u0026lt;/article\u0026gt; \u0026lt;/div\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html, \u0026#34;lxml\u0026#34;) # lxml 解析器最快 # CSS 选择器（推荐, 和前端知识通用） for art in soup.select(\u0026#34;article.post\u0026#34;): title = art.select_one(\u0026#34;h2 a\u0026#34;).get_text(strip=True) link = art.select_one(\u0026#34;h2 a\u0026#34;)[\u0026#34;href\u0026#34;] date = art.select_one(\u0026#34;.date\u0026#34;).get_text() tag = art.select_one(\u0026#34;.tag\u0026#34;).get_text() print(date, tag, title, link) # 常用 API soup.find(\u0026#34;h2\u0026#34;) # 第一个 h2 soup.find_all(\u0026#34;span\u0026#34;, class_=\u0026#34;tag\u0026#34;) # 全部匹配 soup.find(id=\u0026#34;main\u0026#34;) tag.get_text(), tag[\u0026#34;href\u0026#34;], tag.attrs 五、第一个完整爬虫：抓取博客列表 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;抓取博客文章列表 -\u0026gt; 存 JSON\u0026#34;\u0026#34;\u0026#34; import json, time import requests from bs4 import BeautifulSoup from pathlib import Path BASE = \u0026#34;https://example-blog.cn\u0026#34; def fetch_page(session: requests.Session, url: str) -\u0026gt; list[dict]: r = session.get(url, timeout=10) r.raise_for_status() r.encoding = r.apparent_encoding # 自动修正乱码 soup = BeautifulSoup(r.text, \u0026#34;lxml\u0026#34;) items = [] for art in soup.select(\u0026#34;article.post\u0026#34;): items.append({ \u0026#34;title\u0026#34;: art.select_one(\u0026#34;h2 a\u0026#34;).get_text(strip=True), \u0026#34;url\u0026#34;: BASE + art.select_one(\u0026#34;h2 a\u0026#34;)[\u0026#34;href\u0026#34;], \u0026#34;date\u0026#34;: art.select_one(\u0026#34;.date\u0026#34;).get_text(strip=True), \u0026#34;tags\u0026#34;: [t.get_text() for t in art.select(\u0026#34;.tag\u0026#34;)], }) return items def crawl(max_pages: int = 5) -\u0026gt; list[dict]: results = [] with requests.Session() as s: s.headers[\u0026#34;User-Agent\u0026#34;] = \u0026#34;Mozilla/5.0 (compatible; zy-crawler/1.0)\u0026#34; for page in range(1, max_pages + 1): url = f\u0026#34;{BASE}/page/{page}/\u0026#34; try: items = fetch_page(s, url) except requests.RequestException as e: print(f\u0026#34;[!] 第{page}页失败: {e}\u0026#34;) continue if not items: break # 没有更多了 results.extend(items) print(f\u0026#34;[+] 第{page}页: {len(items)} 条\u0026#34;) time.sleep(2) # 爬虫礼仪: 别把人家打挂 return results if __name__ == \u0026#34;__main__\u0026#34;: data = crawl() Path(\u0026#34;posts.json\u0026#34;).write_text( json.dumps(data, ensure_ascii=False, indent=2), encoding=\u0026#34;utf-8\u0026#34;) print(f\u0026#34;共抓取 {len(data)} 条, 已存 posts.json\u0026#34;) 六、并发提速 # 方案1: 线程池（IO 密集型首选, 简单） from concurrent.futures import ThreadPoolExecutor, as_completed urls = [f\u0026#34;https://httpbin.org/delay/1?i={i}\u0026#34; for i in range(20)] with requests.Session() as s, ThreadPoolExecutor(max_workers=8) as pool: futures = {pool.submit(s.get, u, timeout=10): u for u in urls} for f in as_completed(futures): try: print(f.result().status_code) except Exception as e: print(\u0026#34;失败:\u0026#34;, futures[f], e) # 20 个耗时1s的请求: 串行20s -\u0026gt; 并发约3s # 方案2: httpx 异步（大规模时更省资源） import httpx, asyncio async def fetch(client, url): r = await client.get(url, timeout=10) return r.status_code async def main(): async with httpx.AsyncClient() as client: results = await asyncio.gather(*[ fetch(client, u) for u in urls[:20] ]) print(results) asyncio.run(main()) 七、爬虫礼仪与边界 看 robots.txt：https://site/robots.txt，尊重 Disallow 限速：time.sleep 或令牌桶，别当 DDoS UA 标识：别伪装成浏览器干重活 优先走官方 API / RSS，别硬爬页面 法律边界：不碰个人隐私数据、不绕过登录/付费墙、遵守目标站点条款 八、常见坑 现象 原因 乱码 r.encoding = r.apparent_encoding SSLError 公司代理证书问题 verify=False（仅测试）或装 certifi 拿到的页面和浏览器不一样 JS 动态渲染 → 上 playwright/selenium 或找数据接口 403 被 ban UA/频率问题；降低频率、换 UA 内存暴涨 用流式下载 stream=True + 迭代写盘 小结 需求 工具 调 API requests + Session + timeout 要重试 HTTPAdapter + Retry 并发抓取 ThreadPoolExecutor / httpx.AsyncClient 解析 HTML BeautifulSoup + CSS 选择器 动态页面 playwright 本文是「Python」系列第 8 篇。\n","permalink":"https://antidebug.cn/posts/2025/02/python-%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82%E4%B8%8E%E7%88%AC%E8%99%AB%E5%9F%BA%E7%A1%80/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e运维和后端日常都绕不开\u0026quot;用程序取数据\u0026quot;：调 API、抓页面、做监控探测。这篇把 Python 网络请求从入门到工程化讲一遍。\u003c/p\u003e\n\u003ch2 id=\"一requests把-http-写成人话\"\u003e一、requests：把 HTTP 写成人话\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003erequests\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# GET（永远带 timeout!）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequests\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;https://httpbin.org/get\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eparams\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;page\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;size\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e        \u003cspan class=\"c1\"\u003e# ?page=2\u0026amp;size=10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;User-Agent\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;my-spider/1.0\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e          \u003cspan class=\"c1\"\u003e# 200\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e               \u003cspan class=\"c1\"\u003e# 解析响应体\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e                 \u003cspan class=\"c1\"\u003e# 文本\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eheaders\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# POST JSON\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequests\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epost\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;https://httpbin.org/post\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;zy\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;level\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e       \u003cspan class=\"c1\"\u003e# 自动序列化 + Content-Type\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# POST 表单 / 文件上传\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003erequests\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epost\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;k\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;v\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003erequests\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epost\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;f\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a.png\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;rb\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)},\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 常见 HTTP 动词\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003erequests\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eput\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e \u003cspan class=\"n\"\u003erequests\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"n\"\u003erequests\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e没有 timeout 的请求 = 随机挂死的脚本\u003c/strong\u003e。\u003ccode\u003etimeout=(3, 10)\u003c/code\u003e 分别指连接和读取超时。\u003c/p\u003e","title":"Python 网络请求与爬虫基础"},{"content":"前言 标准库 net/http 就能写出生产级 Web 服务。这篇从路由到优雅关停走一遍，再聊什么时候需要框架。\n一、五分钟起步 package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { mux := http.NewServeMux() mux.HandleFunc(\u0026#34;GET /healthz\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintln(w, \u0026#34;ok\u0026#34;) }) // JSON API mux.HandleFunc(\u0026#34;GET /api/users/{id}\u0026#34;, func(w http.ResponseWriter, r *http.Request) { id := r.PathValue(\u0026#34;id\u0026#34;) // go1.22+ 原生路径参数! json.NewEncoder(w).Encode(map[string]string{ \u0026#34;id\u0026#34;: id, \u0026#34;name\u0026#34;: \u0026#34;zy\u0026#34;, }) }) log.Println(\u0026#34;listening :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, mux)) } curl http://localhost:8080/healthz curl http://localhost:8080/api/users/42 Go 1.22 后 ServeMux 支持方法限定 + 路径参数（GET /users/{id}），日常路由基本不用框架了。\n二、请求与响应的完整姿势 type CreateUserReq struct { Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } mux.HandleFunc(\u0026#34;POST /api/users\u0026#34;, func(w http.ResponseWriter, r *http.Request) { // 1. 解析 JSON body（限制大小防滥用） r.Body = http.MaxBytesReader(w, r.Body, 1\u0026lt;\u0026lt;20) // 1MB var req CreateUserReq if err := json.NewDecoder(r.Body).Decode(\u0026amp;req); err != nil { http.Error(w, `{\u0026#34;error\u0026#34;:\u0026#34;bad json\u0026#34;}`, http.StatusBadRequest) return } // 2. 校验 if req.Name == \u0026#34;\u0026#34; { http.Error(w, `{\u0026#34;error\u0026#34;:\u0026#34;name required\u0026#34;}`, http.StatusUnprocessableEntity) return } // 3. 响应 JSON（统一头） w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]any{ \u0026#34;id\u0026#34;: 1, \u0026#34;name\u0026#34;: req.Name, }) }) 读取请求的各部位：\nr.URL.Query().Get(\u0026#34;page\u0026#34;) // ?page=2 r.Header.Get(\u0026#34;Authorization\u0026#34;) // 请求头 r.PathValue(\u0026#34;id\u0026#34;) // 路径参数 r.RemoteAddr // 客户端地址（注意代理场景读 X-Forwarded-For） r.ParseForm(); r.FormValue(\u0026#34;k\u0026#34;) // 表单 三、中间件：洋葱模型 type Middleware func(http.Handler) http.Handler func Chain(h http.Handler, mws ...Middleware) http.Handler { for i := len(mws) - 1; i \u0026gt;= 0; i-- { // 倒序包裹 h = mws[i](h) } return h } // 日志中间件 func withLogging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) // 调用下一层 log.Printf(\u0026#34;%s %s %v\u0026#34;, r.Method, r.URL.Path, time.Since(start)) }) } // 恢复 panic（保命） func withRecover(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf(\u0026#34;panic: %v\u0026#34;, err) http.Error(w, `{\u0026#34;error\u0026#34;:\u0026#34;internal\u0026#34;}`, http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } // 用法 handler := Chain(mux, withLogging, withRecover) http.ListenAndServe(\u0026#34;:8080\u0026#34;, handler) 四、优雅关停（生产必备） 收到 SIGTERM 时：停止接收新请求 → 处理完存量请求 → 退出（K8s 滚动更新的前提）：\nfunc main() { srv := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: Chain(mux, withLogging, withRecover), ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } go func() { if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf(\u0026#34;listen: %v\u0026#34;, err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT) \u0026lt;-quit // 阻塞等到信号 log.Println(\u0026#34;shutting down...\u0026#34;) ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { // 最多等25s log.Printf(\u0026#34;forced exit: %v\u0026#34;, err) } } 五、调用外部 API（客户端） client := \u0026amp;http.Client{Timeout: 5 * time.Second} // 必设超时! req, _ := http.NewRequest(\u0026#34;GET\u0026#34;, \u0026#34;https://api.example.com/v1/x\u0026#34;, nil) req.Header.Set(\u0026#34;Authorization\u0026#34;, \u0026#34;Bearer \u0026#34;+token) resp, err := client.Do(req) if err != nil { return fmt.Errorf(\u0026#34;call api: %w\u0026#34;, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) return fmt.Errorf(\u0026#34;api status %d: %s\u0026#34;, resp.StatusCode, body) } var result Result json.NewDecoder(resp.Body).Decode(\u0026amp;result) 六、结构化日志：slog（go1.21+） import \u0026#34;log/slog\u0026#34; logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) slog.SetDefault(logger) slog.Info(\u0026#34;user created\u0026#34;, \u0026#34;user_id\u0026#34;, 42, \u0026#34;ip\u0026#34;, r.RemoteAddr, ) // {\u0026#34;time\u0026#34;:\u0026#34;...\u0026#34;,\u0026#34;level\u0026#34;:\u0026#34;INFO\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;user created\u0026#34;,\u0026#34;user_id\u0026#34;:42,\u0026#34;ip\u0026#34;:\u0026#34;10.0.0.5:3342\u0026#34;} JSON 日志可以直接进 ELK/Loki 分析，替代传统行式日志。\n七、要不要用框架？ 方案 适合 代表 纯标准库 中小服务、依赖洁癖、学习底层 net/http (1.22+) 轻路由器 要中间件生态但要轻 chi（100% 兼容标准库接口） 全家桶 快速开发 CRUD、团队协作 gin（生态最大） // chi 风格示例（和标准库无缝） r := chi.NewRouter() r.Use(middleware.Logger, middleware.Recoverer) r.Get(\u0026#34;/users/{id}\u0026#34;, getUser) r.Group(func(r chi.Router) { r.Use(requireAuth) // 一组路由统一加鉴权 r.Post(\u0026#34;/api/users\u0026#34;, createUser) }) 我的建议：先写纯标准库理解原理，业务提速再上 chi/gin——框架会换，标准库一辈子。\n八、压测验证 go build -o app . \u0026amp;\u0026amp; ./app \u0026amp; wrk -t4 -c200 -d15s http://localhost:8080/healthz # 单机几十万 QPS 的底子, 超过多数业务需求 小结 能力 标准库姿势 路由 mux.HandleFunc(\u0026quot;GET /x/{id}\u0026quot;, h) JSON json.NewDecoder/Encoder + Content-Type 中间件 func(http.Handler) http.Handler 链 优雅关停 srv.Shutdown(ctx) + 信号监听 客户端 http.Client{Timeout} 日志 slog JSON 本文是「Golang」系列第 8 篇。\n","permalink":"https://antidebug.cn/posts/2025/01/golang-web-%E7%BC%96%E7%A8%8Bnet/http-%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e标准库 \u003ccode\u003enet/http\u003c/code\u003e 就能写出生产级 Web 服务。这篇从路由到优雅关停走一遍，再聊什么时候需要框架。\u003c/p\u003e\n\u003ch2 id=\"一五分钟起步\"\u003e一、五分钟起步\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e \u003cspan class=\"nx\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"s\"\u003e\u0026#34;encoding/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"s\"\u003e\u0026#34;log\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"s\"\u003e\u0026#34;net/http\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003emux\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewServeMux\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003emux\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eHandleFunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;GET /healthz\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eResponseWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWriteHeader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eStatusOK\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ok\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e// JSON API\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003emux\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eHandleFunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;GET /api/users/{id}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eResponseWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"nx\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePathValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e               \u003cspan class=\"c1\"\u003e// go1.22+ 原生路径参数!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"nx\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewEncoder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003eEncode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\t\u003cspan class=\"s\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;zy\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;listening :8080\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFatal\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eListenAndServe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;:8080\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003emux\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl http://localhost:8080/healthz\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl http://localhost:8080/api/users/42\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eGo 1.22 后 ServeMux 支持\u003cstrong\u003e方法限定 + 路径参数\u003c/strong\u003e（\u003ccode\u003eGET /users/{id}\u003c/code\u003e），日常路由基本不用框架了。\u003c/p\u003e","title":"Golang Web 编程：net/http 快速上手"},{"content":"前言 手工部署的痛苦：SSH → 拉代码 → 编译 → 覆盖文件 → 重启 → 祈祷。CI/CD 把这一切变成\u0026quot;git push 后自动完成\u0026quot;。这篇讲概念、工具链和一条可落地的流水线。\n一、核心概念 CI（持续集成） CD（持续交付/部署） 代码提交自动: 通过流水线自动: └─► 拉取代码 └─► 制品投递到环境 └─► 编译构建 ├─ 持续交付: 到生产\u0026#34;门前\u0026#34;，手动点一下发布 └─► 单元测试 └─ 持续部署: 全自动到生产 └─► 静态检查/安全扫描 └─► 打包制品（镜像/包）→ 推仓库 一切皆制品（Artifact）：流水线的产物是不可变的镜像/tar 包——部署不是\u0026quot;到服务器上编译\u0026quot;，而是\u0026quot;把指定版本的制品放到指定环境\u0026quot;。同一个镜像走完全程是环境一致性的根基。\n二、工具版图 环节 工具 说明 代码托管 GitLab / Gitea / GitHub GitLab 一站式最常见 CI 引擎 GitLab CI / Jenkins / Drone .gitlab-ci.yml 声明式最省心 制品仓库 Harbor / Nexus / Registry 镜像与包的版本化仓库 部署执行 K8s（GitOps: ArgoCD） 声明式发布 质量门禁 SonarQube / Trivy 代码质量与镜像漏洞扫描 选型建议：小团队 GitLab + Harbor + K8s 原生滚动；上规模后加 ArgoCD 玩 GitOps。\n三、一条典型流水线 git push → [lint] → [test] → [build] → [scan] → [deploy-staging] → [deploy-prod] │ │ │ │ │ │ │ │ 语法检查 单测+覆盖率 镜像构建 Trivy 自动 手动审批 │ gofmt -race 多阶段 镜像扫描 部署 (保护生产) └─ 触发：main 分支或 MR 四、GitLab CI 实战 项目根目录 .gitlab-ci.yml：\nstages: [lint, test, build, deploy] variables: IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA # 每次提交一个镜像 tag lint: stage: lint image: golang:1.22 script: - gofmt -l . | tee /dev/stderr | (! read) # 有未格式化文件则失败 - go vet ./... test: stage: test image: golang:1.22 script: - go test ./... -race -coverprofile=cover.out - go tool cover -func=cover.out | tail -1 build: stage: build image: docker:26 services: [docker:26-dind] script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $IMAGE . - docker push $IMAGE rules: - if: $CI_COMMIT_BRANCH == \u0026#34;main\u0026#34; # 只有 main 才构建 deploy-staging: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deploy/app app=$IMAGE -n staging - kubectl rollout status deploy/app -n staging environment: staging rules: - if: $CI_COMMIT_BRANCH == \u0026#34;main\u0026#34; deploy-prod: stage: deploy extends: deploy-staging script: - kubectl set image deploy/app app=$IMAGE -n prod - kubectl rollout status deploy/app -n prod environment: production when: manual # 手动点击确认才发生产 rules: - if: $CI_COMMIT_TAG # 打 tag 才允许发生产 要点：\n镜像 tag 用 commit SHA：版本可追溯，不用 latest stages 分层：失败早停（lint 不过不浪费 test 资源） when: manual + tag：生产发布有人工闸门 environment：GitLab 里能看每次部署历史，支持一键回滚 五、发布策略 策略 做法 特点 滚动 逐批替换实例 K8s 默认；简单，但新旧共存一段时间 蓝绿 新版本整套并行部署，验证后切流量 回滚秒级（切回蓝），资源双份 金丝雀 1% → 10% → 50% → 100% 渐进放量 风险最小，需要指标观测支撑 # K8s 金丝雀简版：两个 Deployment 手动调比例 kubectl scale deploy/app-v1 --replicas=9 kubectl scale deploy/app-v2 --replicas=1 # 观察 v2 的错误率/延迟 # 指标 OK 再继续倾斜…（成熟做法: Argo Rollouts 自动化） 六、质量与安全门禁 # 流水线里加扫描段 scan: stage: build image: aquasec/trivy script: - trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE # 有高危漏洞 → 流水线失败 → 挡在发布前 再加：单元测试覆盖率阈值、gosec 代码安全扫描、镜像签名（cosign）——门禁越早，修复越便宜。\n七、从零起步路线 第1周: 代码进 Git（哪怕先不 CI） 第2周: 加 test + lint 段 → 提交即检查 第3周: 构建镜像推 Harbor, 打 SHA tag 第4周: 自动部署 staging; 生产留手动 第5周+: 金丝雀、回滚演练、ArgoCD… 不要一上来追求全自动到生产——先把\u0026quot;每次提交都被验证\u0026quot;这个习惯建立起来。\n八、常见坑 现象 原因 CI 里拉依赖超时 Runner 没配国内代理（GOPROXY/pip 源） 镜像构建巨慢 没利用层缓存；依赖层与代码层没分离 staging 好的生产挂 环境配置漂移 → 配置全走 ConfigMap/环境变量注入 密钥泄漏进仓库 用 CI Variables（masked）+ .gitignore 发布后才知道挂了 没有发布后自动验证（smoke test） 小结 CI 解决\u0026quot;合进去的是好的\u0026quot;，CD 解决\u0026quot;发出去的是对的\u0026quot; 制品不可变 + 同一镜像走全程是一致性根基 流水线分段、生产留闸门、发布有回滚 从小做起：先让每次 push 都跑测试 本文是「云原生」系列第 9 篇。\n","permalink":"https://antidebug.cn/posts/2025/01/%E4%BA%91%E5%8E%9F%E7%94%9F-ci/cd-%E5%85%A5%E9%97%A8%E6%A6%82%E5%BF%B5%E4%B8%8E%E5%B7%A5%E5%85%B7%E9%93%BE/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e手工部署的痛苦：SSH → 拉代码 → 编译 → 覆盖文件 → 重启 → 祈祷。CI/CD 把这一切变成\u0026quot;git push 后自动完成\u0026quot;。这篇讲概念、工具链和一条可落地的流水线。\u003c/p\u003e","title":"云原生 CI/CD 入门：概念与工具链"},{"content":"前言 压测时出现 Too many open files、大量 TIME_WAIT、连接队列溢出——这些都不是应用 bug，是内核默认参数太保守。生产服务器上线清单里，sysctl 调优应有一席之地。\n一、sysctl 机制 # 查看 sysctl net.ipv4.tcp_tw_reuse # 单个参数 sysctl -a | grep somaxconn # 搜索 # 临时生效（重启丢失, 调试用） sysctl -w net.core.somaxconn=4096 # 永久生效: 写入 /etc/sysctl.d/*.conf echo \u0026#34;net.core.somaxconn = 4096\u0026#34; \u0026gt; /etc/sysctl.d/99-tuning.conf sysctl --system # 重载全部配置 调整前先看现状：\nulimit -n # 当前 shell 的 fd 上限 cat /proc/sys/fs/file-nr # 已分配/未用/最大 fd ss -s # 连接概览 netstat -s | grep -iE \u0026#34;overflow|drop\u0026#34; # 队列溢出统计（调优依据!） 二、必调参数详解 1. 文件描述符（万物的前提） # /etc/sysctl.d/99-tuning.conf fs.file-max = 1048576 # 全系统 fd 上限 # 还要改用户级限制（systemd 服务走另一条路!） cat \u0026gt;\u0026gt; /etc/security/limits.conf \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; * soft nofile 65535 * hard nofile 65535 EOF # systemd 管理的服务: 在 unit 里加 [Service] LimitNOFILE=65535 常见坑：改了 limits.conf，systemd 服务没生效——cat /proc/\u0026lt;pid\u0026gt;/limits 验证实际值。\n2. TCP 连接复用与 TIME_WAIT net.ipv4.tcp_tw_reuse = 1 # 允许复用 TIME_WAIT 端口用于出站连接（客户端侧） net.ipv4.tcp_fin_timeout = 30 # FIN_WAIT2 超时（默认60s） net.ipv4.ip_local_port_range = 10240 65000 # 出站端口范围（短连接多时扩大） TIME_WAIT 认知纠偏：\nTIME_WAIT 是主动关闭方的正常状态（2MSL，约60s），是 TCP 可靠性的保障 几万个 TIME_WAIT 本身不是故障；先治连接方式再调参数： 短连接风暴 → 改长连接/连接池（应用层根治） 出站端口耗尽 → tcp_tw_reuse=1 + 扩 ip_local_port_range 服务器被动TIME_WAIT堆积 → 检查是否应用频繁主动断连 3. 监听与 accept 队列 net.core.somaxconn = 4096 # 全局监听队列上限（nginx listen backlog 不能超过它） net.ipv4.tcp_max_syn_backlog = 8192 # 半连接队列（SYN 待完成握手数） net.core.netdev_max_backlog = 16384 # 网卡收包队列 队列溢出的证据：\nnetstat -s | grep -i \u0026#34;listen\u0026#34; # XXX times the listen queue of a socket overflowed ← 就是它! 4. TCP 缓冲区（大流量传输） net.core.rmem_max = 16777216 # 套接字读缓冲上限 net.core.wmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 # 最小/默认/最大（自适应） net.ipv4.tcp_wmem = 4096 65536 16777216 net.ipv4.tcp_mtu_probing = 1 # MTU 黑洞探测（云上跨网传输卡住时救急） 5. TCP 保活与快速失败 net.ipv4.tcp_keepalive_time = 600 # 空闲600s后开始探测（默认7200太久） net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 3 # 3次失败即断（90s发现死连接） net.ipv4.tcp_syn_retries = 3 # 出站SYN重试（默认6次≈2分钟才报错） 6. 内存与 SWAP vm.swappiness = 10 # 尽量少用swap（数据库/延迟敏感服务设1-10） vm.overcommit_memory = 1 # Redis等场景需要（按需） vm.max_map_count = 262144 # ES等大量mmap的程序需要 7. 安全相关（顺手加固） net.ipv4.conf.all.rp_filter = 1 # 反向路径校验（防源地址欺骗） net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.tcp_syncookies = 1 # SYN Flood 防护（一般默认开） 三、落地模板 /etc/sysctl.d/99-tuning.conf（通用 Web/中间件服务器）：\n# ---- fs ---- fs.file-max = 1048576 # ---- core ---- net.core.somaxconn = 4096 net.core.netdev_max_backlog = 16384 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 # ---- ipv4 tcp ---- net.ipv4.tcp_max_syn_backlog = 8192 net.ipv4.ip_local_port_range = 10240 65000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30 net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_mtu_probing = 1 # ---- vm ---- vm.swappiness = 10 sysctl --system # 应用并检查有无报错 sysctl net.core.somaxconn # 验证 四、验证调优效果 # 1. 压测前后对比队列溢出计数 netstat -s | grep -i overflow # 2. fd 使用量趋势 watch -n1 cat /proc/sys/fs/file-nr # 3. TIME_WAIT 变化 ss -tan state time-wait | wc -l # 4. 实测: ab/wrk 压 QPS wrk -t4 -c1000 -d30s http://target/ 五、调优原则（比参数本身重要） 有依据再调：netstat -s 的溢出/丢弃计数、ss -s 的连接分布——先诊断后开药 一次改一组：改完记录基准，可对比回滚 应用层优先：连接池、长连接、异步化能解决的，不推给内核 别抄\u0026quot;万能优化脚本\u0026quot;：tcp_tw_recycle（已在 4.12 内核移除，开了反而 NAT 环境丢连接）就是血案 小结 症状 药方 Too many open files fs.file-max + limits + systemd LimitNOFILE listen queue overflow somaxconn + 应用 backlog 出站端口耗尽 tcp_tw_reuse + ip_local_port_range 大文件传输慢 tcp_rmem/wmem 死连接挂半小时 keepalive 三件套 内存吃紧狂 swap vm.swappiness 本文是「Linux 运维」系列第 11 篇。\n","permalink":"https://antidebug.cn/posts/2024/12/linux-%E5%86%85%E6%A0%B8%E5%8F%82%E6%95%B0%E8%B0%83%E4%BC%98sysctl/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e压测时出现 \u003ccode\u003eToo many open files\u003c/code\u003e、大量 \u003ccode\u003eTIME_WAIT\u003c/code\u003e、连接队列溢出——这些都不是应用 bug，是内核默认参数太保守。生产服务器上线清单里，\u003ccode\u003esysctl\u003c/code\u003e 调优应有一席之地。\u003c/p\u003e","title":"Linux 内核参数调优：sysctl"},{"content":"前言 Pod 会生会死，IP 说变就变——\u0026ldquo;怎么找到服务\u0026quot;是分布式系统的第一问题。K8s 的答案是 Service（四层）+ Ingress（七层） 双层体系。这篇把流量链路彻底打通。\n一、为什么需要 Service Deployment v1: Pod 10.244.1.5 ──┐ 滚动更新 ┌─ Pod 10.244.2.9 Pod 10.244.2.3 ──┼──────► 期间 IP 漂移 ─► Pod 10.244.1.8 ──┘ └─ Pod 10.244.1.12 Pod IP 是临时的（重建就变） 扩缩容时 Pod 列表动态变化 需要稳定的名字 + 虚拟 IP + 负载均衡 → Service 二、Service 工作原理 apiVersion: v1 kind: Service metadata: name: api spec: selector: {app: api} # 圈定后端 Pod（靠 label 匹配） ports: - port: 80 # Service 端口 targetPort: 8080 # 容器端口 幕后发生的事：\n1. 创建时: 分配稳定 ClusterIP（如 10.96.10.5, 全集群虚拟网段） 2. Endpoints 控制器: 把 selector 匹配到的 Pod IP 写入 Endpoints 对象 （Pod 增减, Endpoints 实时更新） 3. kube-proxy（每个节点）: 监听 Service/Endpoints 变化, 在节点上写 iptables/IPVS 规则 → ClusterIP:80 的流量被 DNAT 到某个 PodIP:8080 4. 流量到达: 按规则负载均衡（默认随机）; 连接级别的会话保持可用 kubectl get svc api kubectl get endpoints api # 后端真实 IP 列表 kubectl exec debug-pod -- curl http://10.96.10.5/ 三、三种类型 + Headless ClusterIP（默认）：集群内访问 spec: type: ClusterIP 访问方式——CoreDNS 域名：\n\u0026lt;service\u0026gt;.\u0026lt;namespace\u0026gt;.svc.cluster.local api.default.svc.cluster.local 同 namespace 内直接: http://api 跨 namespace: http://api.prod.svc kubectl run dnstest --rm -it --image=busybox -- nslookup api NodePort：每个节点开高位端口 spec: type: NodePort ports: - port: 80 nodePort: 30080 # 可不指定自动分配（30000-32767） 访问: 任意节点IP:30080 → (经 Service 规则) → Pod 缺点: 端口难看、暴露所有节点、无 TLS/域名路由 → 只适合临时/内网 LoadBalancer：云上正式入口 spec: type: LoadBalancer 云厂商自动创建一个公网 LB 指向 Service（EXTERNAL-IP 列出现公网 IP）。每个 LB 一个服务成本高——正经做法是一个 LB + Ingress 路由所有服务。\nHeadless Service：不要 VIP，给我 Pod 列表 spec: clusterIP: None # 不分配虚拟 IP DNS 查询直接返回所有 Pod IP（而不是 VIP）——用于：\n有状态服务直连（StatefulSet + 固定域名 pod-0.svc-name） 客户端自己做负载均衡（如 gRPC 长连接场景） 四、Ingress：七层的大门 NodePort/LB 是四层转发，域名路由、路径路由、TLS、重写这些七层能力靠 Ingress：\n┌─ Ingress Controller（真正干活的 Pod, 如 nginx-ingress） 用户 ─► LoadBalancer ────►│ 读取 Ingress 规则, 转成 nginx.conf │ │ antidebug.cn/ → blog-svc:80 │ antidebug.cn/api/ → api-svc:8080 │ www.antidebug.cn → 301 → antidebug.cn └─ TLS 证书统一在这里终结 先装 Controller（Ingress 对象本身只是规则说明书，没 Controller 不生效）：\n# 以 ingress-nginx 为例 kubectl apply -f https://ghfast.top/https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml kubectl -n ingress-nginx get svc # 等待 EXTERNAL-IP 就绪 Ingress 规则：\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: main annotations: nginx.ingress.kubernetes.io/ssl-redirect: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/proxy-body-size: \u0026#34;50m\u0026#34; spec: ingressClassName: nginx tls: - hosts: [antidebug.cn, www.antidebug.cn] secretName: blog-tls # 证书存 Secret rules: - host: antidebug.cn http: paths: - path: / pathType: Prefix backend: {service: {name: blog, port: {number: 80}}} - path: /api pathType: Prefix backend: {service: {name: api, port: {number: 8080}}} - host: www.antidebug.cn http: paths: - path: / pathType: Prefix backend: {service: {name: blog, port: {number: 80}}} kubectl apply -f ingress.yaml curl -H \u0026#34;Host: antidebug.cn\u0026#34; http://\u0026lt;LB_IP\u0026gt;/ 五、流量全链路图（背下来） 外部用户 │ DNS 解析 → LB 公网 IP ▼ LoadBalancer Service ──► Ingress Controller Pod（nginx） │ │ 读 Ingress 规则: 域名/路径/TLS ▼ ▼ Service（ClusterIP, CoreDNS 名字解析） │ │ Endpoints → 健康 Pod 列表 ▼ ▼ Pod（readiness 通过才在列表里） 一句话版本：Ingress 管七层路由，Service 管四层均衡，CoreDNS 管名字，Endpoints 管名单。\n六、排障速查 # Service 不通四步定位 # 1. Endpoints 空? → selector 与 Pod label 不匹配（最常见!） kubectl get endpoints api kubectl get pods -l app=api --show-labels # 2. Pod 自身好吗? kubectl exec -it \u0026lt;pod\u0026gt; -- curl localhost:8080/healthz # 3. Service VIP 通吗? kubectl run t --rm -it --image=busybox -- wget -qO- http://api # 4. DNS 解析对吗? kubectl run t --rm -it --image=busybox -- nslookup api.default.svc.cluster.local # Ingress 排查 kubectl describe ingress main # 看 Backend 列表 kubectl -n ingress-nginx logs -l app.kubernetes.io/name=ingress-nginx --tail=50 小结 组件 层 职责 Service ClusterIP 四层 稳定 VIP + 均衡（集群内） NodePort 四层 节点高位端口暴露 LoadBalancer 四层 云 LB 公网入口 Headless — 返回 Pod IP 列表 Ingress + Controller 七层 域名/路径/TLS 路由 CoreDNS — 服务名 → VIP 本文是「云原生」系列第 8 篇。\n","permalink":"https://antidebug.cn/posts/2024/12/k8s-%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0service-%E4%B8%8E-ingress/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003ePod 会生会死，IP 说变就变——\u0026ldquo;怎么找到服务\u0026quot;是分布式系统的第一问题。K8s 的答案是 \u003cstrong\u003eService（四层）+ Ingress（七层）\u003c/strong\u003e 双层体系。这篇把流量链路彻底打通。\u003c/p\u003e","title":"K8s 服务发现：Service 与 Ingress"},{"content":"前言 写 Go 几乎不需要\u0026quot;装依赖起步\u0026quot;——标准库覆盖了日常大头。这篇按使用频率盘点高频标准库，都是抄了就能用的片段。\n一、strings / strconv / unicode import (\u0026#34;strings\u0026#34;; \u0026#34;strconv\u0026#34;; \u0026#34;unicode\u0026#34;) s := \u0026#34;Hello, Golang 世界\u0026#34; strings.Contains(s, \u0026#34;Golang\u0026#34;) // true strings.HasPrefix(s, \u0026#34;He\u0026#34;) // true strings.Split(\u0026#34;a,b,c\u0026#34;, \u0026#34;,\u0026#34;) // [a b c] strings.Join([]string{\u0026#34;a\u0026#34;,\u0026#34;b\u0026#34;}, \u0026#34;-\u0026#34;) // a-b strings.ToUpper(s) strings.TrimSpace(\u0026#34; hi \\n\u0026#34;) // hi strings.Replace(s, \u0026#34;o\u0026#34;, \u0026#34;0\u0026#34;, -1) // 全部替换 strings.Fields(\u0026#34; a b c \u0026#34;) // [a b c]（按空白切, 超常用） strings.Repeat(\u0026#34;=\u0026#34;, 20) fmt.Sprintf(\u0026#34;%s=%d\u0026#34;, \u0026#34;k\u0026#34;, 1) // Builder: 循环拼字符串用它（比 += 快得多） var b strings.Builder for i := 0; i \u0026lt; 1000; i++ { b.WriteString(\u0026#34;x\u0026#34;) } b.String() // strconv: 字符串与数字互转 strconv.Itoa(42) // \u0026#34;42\u0026#34; n, err := strconv.Atoi(\u0026#34;42\u0026#34;) f, err := strconv.ParseFloat(\u0026#34;3.14\u0026#34;, 64) 二、slices / maps / sort（go1.21+ 泛型库） import (\u0026#34;slices\u0026#34;; \u0026#34;maps\u0026#34;; \u0026#34;sort\u0026#34;) nums := []int{3, 1, 4, 1, 5} slices.Contains(nums, 4) // true slices.Index(nums, 4) // 2 slices.Sort(nums) slices.Reverse(nums) slices.Equal([]int{1,2}, []int{1,2}) // true slices.Max(nums), slices.Min(nums) // 5, 1 // 结构体排序 type User struct{ Name string; Age int } users := []User{{\u0026#34;b\u0026#34;, 30}, {\u0026#34;a\u0026#34;, 25}} slices.SortFunc(users, func(x, y User) int { return x.Age - y.Age }) // maps m := map[string]int{\u0026#34;a\u0026#34;: 1, \u0026#34;b\u0026#34;: 2} keys := slices.Collect(maps.Keys(m)) // 迭代器收集 maps.Clone(m) // 浅拷贝 三、time：时间处理 now := time.Now() now.Format(\u0026#34;2006-01-02 15:04:05\u0026#34;) // 记住: Go 的格式化模板就是这个参考时间! t, err := time.Parse(\u0026#34;2006-01-02\u0026#34;, \u0026#34;2024-11-16\u0026#34;) now.Add(24 * time.Hour) // 明天 now.Sub(startTime) // time.Duration d := 2*time.Hour + 30*time.Minute d.Seconds() // 9000 // 休眠与超时 time.Sleep(100 * time.Millisecond) // 定时器 ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { fmt.Println(\u0026#34;每5秒执行\u0026#34;, time.Now()) } // 耗时测量 start := time.Now() heavyWork() log.Printf(\u0026#34;cost %v\u0026#34;, time.Since(start)) // 1.234ms 四、encoding/json（序列化基石） type Server struct { Host string `json:\u0026#34;host\u0026#34;` Port int `json:\u0026#34;port,omitempty\u0026#34;` // 零值时省略 Tags []string `json:\u0026#34;tags,omitempty\u0026#34;` } // 结构体 -\u0026gt; JSON s := Server{Host: \u0026#34;10.0.0.1\u0026#34;, Port: 8080, Tags: []string{\u0026#34;prod\u0026#34;}} data, _ := json.Marshal(s) data2, _ := json.MarshalIndent(s, \u0026#34;\u0026#34;, \u0026#34; \u0026#34;) // 带缩进（好读） // JSON -\u0026gt; 结构体 var s2 Server json.Unmarshal([]byte(`{\u0026#34;host\u0026#34;:\u0026#34;h1\u0026#34;,\u0026#34;port\u0026#34;:80}`), \u0026amp;s2) // JSON -\u0026gt; map（结构未知时） var m map[string]any json.Unmarshal([]byte(`{\u0026#34;a\u0026#34;:1}`), \u0026amp;m) 五、net/http：自带 Web 能力 // 服务端: 十行起一个生产可用的 API http.HandleFunc(\u0026#34;/healthz\u0026#34;, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintln(w, \u0026#34;ok\u0026#34;) }) http.HandleFunc(\u0026#34;/api/hello\u0026#34;, func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get(\u0026#34;name\u0026#34;) // ?name=zy if name == \u0026#34;\u0026#34; { http.Error(w, \u0026#34;missing name\u0026#34;, http.StatusBadRequest) return } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) json.NewEncoder(w).Encode(map[string]string{\u0026#34;msg\u0026#34;: \u0026#34;hello \u0026#34; + name}) }) log.Println(\u0026#34;listen :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) // 客户端 resp, err := http.Get(\u0026#34;https://httpbin.org/get\u0026#34;) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(resp.StatusCode, len(body)) // 带超时的 client（生产必配! 默认没有超时） client := \u0026amp;http.Client{Timeout: 5 * time.Second} 六、os / os/exec / filepath import (\u0026#34;os\u0026#34;; \u0026#34;os/exec\u0026#34;; \u0026#34;path/filepath\u0026#34;) // 文件 data, err := os.ReadFile(\u0026#34;app.conf\u0026#34;) os.WriteFile(\u0026#34;out.txt\u0026#34;, []byte(\u0026#34;hi\u0026#34;), 0644) f, _ := os.Open(\u0026#34;big.log\u0026#34;) defer f.Close() scanner := bufio.NewScanner(f) // 逐行读（大文件） for scanner.Scan() { fmt.Println(scanner.Text()) } // 目录 entries, _ := os.ReadDir(\u0026#34;/var/log\u0026#34;) for _, e := range entries { fmt.Println(e.Name(), e.IsDir()) } // 路径（ filepath 跨平台） filepath.Join(\u0026#34;/etc\u0026#34;, \u0026#34;app\u0026#34;, \u0026#34;a.conf\u0026#34;) // /etc/app/a.conf filepath.Ext(\u0026#34;a.tar.gz\u0026#34;) // .gz filepath.Base(\u0026#34;/x/y/z.txt\u0026#34;) // z.txt filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if !d.IsDir() \u0026amp;\u0026amp; filepath.Ext(path) == \u0026#34;.log\u0026#34; { fmt.Println(path) } return nil }) // 执行命令 out, err := exec.Command(\u0026#34;df\u0026#34;, \u0026#34;-h\u0026#34;).Output() if ee, ok := err.(*exec.ExitError); ok { log.Println(\u0026#34;exit code:\u0026#34;, ee.ExitCode()) } 七、flag / env：程序配置 import \u0026#34;flag\u0026#34; port := flag.Int(\u0026#34;port\u0026#34;, 8080, \u0026#34;监听端口\u0026#34;) env := flag.String(\u0026#34;env\u0026#34;, \u0026#34;dev\u0026#34;, \u0026#34;运行环境\u0026#34;) flag.Parse() fmt.Println(*port, *env) // ./app -port 9090 -env prod // 环境变量 if v := os.Getenv(\u0026#34;APP_ENV\u0026#34;); v == \u0026#34;\u0026#34; { os.Setenv(\u0026#34;APP_ENV\u0026#34;, \u0026#34;dev\u0026#34;) // 或 log.Fatal(\u0026#34;必须设置 APP_ENV\u0026#34;) } 八、context（超时与取消，下篇细讲，先混个脸熟） ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, \u0026#34;GET\u0026#34;, url, nil) // 请求级超时 resp, err := client.Do(req) 九、综合：一个小工具 // urlcheck.go —— 批量探测 URL 可用性 package main import ( \u0026#34;context\u0026#34;; \u0026#34;fmt\u0026#34;; \u0026#34;net/http\u0026#34;; \u0026#34;os\u0026#34;; \u0026#34;sync\u0026#34;; \u0026#34;time\u0026#34; ) func main() { client := \u0026amp;http.Client{Timeout: 3 * time.Second} urls := os.Args[1:] var wg sync.WaitGroup for _, u := range urls { wg.Add(1) go func(u string) { defer wg.Done() ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, \u0026#34;GET\u0026#34;, u, nil) _, err := client.Do(req) status := \u0026#34;OK\u0026#34; if err != nil { status = \u0026#34;FAIL: \u0026#34; + err.Error() } fmt.Printf(\u0026#34;%-40s %s\\n\u0026#34;, u, status) }(u) } wg.Wait() } // go run urlcheck.go https://a.com https://b.com 十几行标准库 = 并发 + 超时 + CLI 工具，Go 的\u0026quot;自带电池\u0026quot;含金量。\n小结 库 高频点 strings/strconv Fields/Split/Builder/Atoi slices/maps 泛型工具，新代码优先 time Format 参考时间模板、Since 计时 encoding/json Marshal/Unmarshal + tag net/http 服务端 HandleFunc、客户端带 Timeout os/exec/filepath 文件/命令/路径 flag 命令行参数 本文是「Golang」系列第 7 篇。\n","permalink":"https://antidebug.cn/posts/2024/11/golang-%E5%B8%B8%E7%94%A8%E6%A0%87%E5%87%86%E5%BA%93%E9%80%9F%E8%A7%88/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e写 Go 几乎不需要\u0026quot;装依赖起步\u0026quot;——标准库覆盖了日常大头。这篇按使用频率盘点高频标准库，都是抄了就能用的片段。\u003c/p\u003e\n\u003ch2 id=\"一strings--strconv--unicode\"\u003e一、strings / strconv / unicode\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;strings\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;strconv\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;unicode\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Hello, Golang 世界\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eContains\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Golang\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e          \u003cspan class=\"c1\"\u003e// true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eHasPrefix\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;He\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e             \u003cspan class=\"c1\"\u003e// true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSplit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;a,b,c\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e            \u003cspan class=\"c1\"\u003e// [a b c]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eJoin\u003c/span\u003e\u003cspan class=\"p\"\u003e([]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;-\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e   \u003cspan class=\"c1\"\u003e// a-b\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eToUpper\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrimSpace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;  hi \\n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e           \u003cspan class=\"c1\"\u003e// hi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eReplace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;o\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;0\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e       \u003cspan class=\"c1\"\u003e// 全部替换\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFields\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;  a  b  c \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e           \u003cspan class=\"c1\"\u003e// [a b c]（按空白切, 超常用）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eRepeat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;=\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%s=%d\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;k\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Builder: 循环拼字符串用它（比 += 快得多）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003eb\u003c/span\u003e \u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eBuilder\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"nx\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e1000\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWriteString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;x\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// strconv: 字符串与数字互转\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estrconv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eItoa\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e42\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                        \u003cspan class=\"c1\"\u003e// \u0026#34;42\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003estrconv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eAtoi\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;42\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003estrconv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eParseFloat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;3.14\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"二slices--maps--sortgo121-泛型库\"\u003e二、slices / maps / sort（go1.21+ 泛型库）\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;slices\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;maps\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;sort\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003enums\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eContains\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                \u003cspan class=\"c1\"\u003e// true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eIndex\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                   \u003cspan class=\"c1\"\u003e// 2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSort\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eReverse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eEqual\u003c/span\u003e\u003cspan class=\"p\"\u003e([]\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e    \u003cspan class=\"c1\"\u003e// true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e      \u003cspan class=\"c1\"\u003e// 5, 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 结构体排序\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eUser\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eName\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"nx\"\u003eAge\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eusers\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e{{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e30\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e25\u003c/span\u003e\u003cspan class=\"p\"\u003e}}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSortFunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eusers\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"nx\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eAge\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eAge\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// maps\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ekeys\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eslices\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eCollect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003emaps\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eKeys\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e     \u003cspan class=\"c1\"\u003e// 迭代器收集\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emaps\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eClone\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                            \u003cspan class=\"c1\"\u003e// 浅拷贝\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"三time时间处理\"\u003e三、time：时间处理\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;2006-01-02 15:04:05\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e        \u003cspan class=\"c1\"\u003e// 记住: Go 的格式化模板就是这个参考时间!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eParse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;2006-01-02\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;2024-11-16\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e24\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHour\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                  \u003cspan class=\"c1\"\u003e// 明天\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSub\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003estartTime\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                       \u003cspan class=\"c1\"\u003e// time.Duration\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ed\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHour\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e30\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMinute\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ed\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSeconds\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e                              \u003cspan class=\"c1\"\u003e// 9000\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 休眠与超时\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSleep\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e100\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMillisecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 定时器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eticker\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewTicker\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eSecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nx\"\u003eticker\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eStop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"nx\"\u003eticker\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eC\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;每5秒执行\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNow\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 耗时测量\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estart\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nf\"\u003eheavyWork\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;cost %v\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSince\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e    \u003cspan class=\"c1\"\u003e// 1.234ms\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"四encodingjson序列化基石\"\u003e四、encoding/json（序列化基石）\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eServer\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHost\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e  \u003cspan class=\"s\"\u003e`json:\u0026#34;host\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e     \u003cspan class=\"s\"\u003e`json:\u0026#34;port,omitempty\u0026#34;`\u003c/span\u003e      \u003cspan class=\"c1\"\u003e// 零值时省略\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eTags\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;tags,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 结构体 -\u0026gt; JSON\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eServer\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;10.0.0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e8080\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eTags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;prod\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e}}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMarshal\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003edata2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMarshalIndent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;  \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e       \u003cspan class=\"c1\"\u003e// 带缩进（好读）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// JSON -\u0026gt; 结构体\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003es2\u003c/span\u003e \u003cspan class=\"nx\"\u003eServer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eUnmarshal\u003c/span\u003e\u003cspan class=\"p\"\u003e([]\u003c/span\u003e\u003cspan class=\"nb\"\u003ebyte\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e`{\u0026#34;host\u0026#34;:\u0026#34;h1\u0026#34;,\u0026#34;port\u0026#34;:80}`\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"nx\"\u003es2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// JSON -\u0026gt; map（结构未知时）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003eany\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eUnmarshal\u003c/span\u003e\u003cspan class=\"p\"\u003e([]\u003c/span\u003e\u003cspan class=\"nb\"\u003ebyte\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e`{\u0026#34;a\u0026#34;:1}`\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"五nethttp自带-web-能力\"\u003e五、net/http：自带 Web 能力\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 服务端: 十行起一个生产可用的 API\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eHandleFunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/healthz\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eResponseWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWriteHeader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eStatusOK\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ok\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eHandleFunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/api/hello\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eResponseWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eQuery\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"nf\"\u003eGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e              \u003cspan class=\"c1\"\u003e// ?name=zy\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;missing name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eStatusBadRequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eHeader\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"nf\"\u003eSet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ejson\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewEncoder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003eEncode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;hello \u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"nx\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;listen :8080\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFatal\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eListenAndServe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;:8080\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 客户端\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://httpbin.org/get\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFatal\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nx\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eBody\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eClose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eio\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eReadAll\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eBody\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eStatusCode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 带超时的 client（生产必配! 默认没有超时）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eclient\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eClient\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eTimeout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e5\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eSecond\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"六os--osexec--filepath\"\u003e六、os / os/exec / filepath\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;os\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;os/exec\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;path/filepath\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eReadFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;app.conf\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWriteFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;out.txt\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nb\"\u003ebyte\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;hi\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"mo\"\u003e0644\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eOpen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;big.log\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nx\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eClose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003escanner\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003ebufio\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewScanner\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e          \u003cspan class=\"c1\"\u003e// 逐行读（大文件）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003escanner\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eScan\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003escanner\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eText\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 目录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eentries\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eReadDir\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/var/log\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ee\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"nx\"\u003eentries\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eIsDir\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 路径（ filepath 跨平台）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efilepath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eJoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/etc\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;a.conf\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e   \u003cspan class=\"c1\"\u003e// /etc/app/a.conf\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efilepath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eExt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;a.tar.gz\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                 \u003cspan class=\"c1\"\u003e// .gz\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efilepath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eBase\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/x/y/z.txt\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e              \u003cspan class=\"c1\"\u003e// z.txt\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efilepath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWalkDir\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003epath\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ed\u003c/span\u003e \u003cspan class=\"nx\"\u003efs\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eDirEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003ed\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eIsDir\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003efilepath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eExt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;.log\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 执行命令\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eexec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eCommand\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;df\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;-h\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nf\"\u003eOutput\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eee\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eok\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e.(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eexec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eExitError\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nx\"\u003eok\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;exit code:\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eExitCode\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"七flag--env程序配置\"\u003e七、flag / env：程序配置\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;flag\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eport\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eflag\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;port\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e8080\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;监听端口\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eflag\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;env\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;dev\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;运行环境\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eflag\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eParse\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eport\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// ./app -port 9090 -env prod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 环境变量\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003ev\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGetenv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;APP_ENV\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nx\"\u003ev\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSetenv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;APP_ENV\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;dev\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e     \u003cspan class=\"c1\"\u003e// 或 log.Fatal(\u0026#34;必须设置 APP_ENV\u0026#34;)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"八context超时与取消下篇细讲先混个脸熟\"\u003e八、context（超时与取消，下篇细讲，先混个脸熟）\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ecancel\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWithTimeout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eBackground\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eSecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nf\"\u003ecancel\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewRequestWithContext\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e   \u003cspan class=\"c1\"\u003e// 请求级超时\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eclient\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eDo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"九综合一个小工具\"\u003e九、综合：一个小工具\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// urlcheck.go —— 批量探测 URL 可用性\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e \u003cspan class=\"nx\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s\"\u003e\u0026#34;context\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;fmt\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;net/http\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;os\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;sync\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eclient\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eClient\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eTimeout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eSecond\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eurls\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eArgs\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e:]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003ewg\u003c/span\u003e \u003cspan class=\"nx\"\u003esync\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eWaitGroup\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eu\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"nx\"\u003eurls\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ewg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ego\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eu\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nx\"\u003ewg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eDone\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ecancel\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWithTimeout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eBackground\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eSecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nf\"\u003ecancel\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewRequestWithContext\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eu\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eclient\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eDo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003estatus\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;OK\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003estatus\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;FAIL: \u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%-40s %s\\n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eu\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003estatus\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}(\u003c/span\u003e\u003cspan class=\"nx\"\u003eu\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ewg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWait\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// go run urlcheck.go https://a.com https://b.com\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e十几行标准库 = 并发 + 超时 + CLI 工具，Go 的\u0026quot;自带电池\u0026quot;含金量。\u003c/p\u003e","title":"Golang 常用标准库速览"},{"content":"前言 学了变量、条件、三剑客，是时候写个真正有用的东西了。下面是一个我维护了两年、持续迭代的服务器巡检脚本——每台机器上一份，cron 每天跑，异常发告警。\n一、需求设计 检查六项：负载、内存、磁盘、网络、关键服务、安全项 分级输出：OK / WARN（阈值临界）/ FAIL（必须处理） 退出码语义：0 全绿 / 1 有告警（供 cron 判断是否发通知） 兼容 RHEL 系（OpenCloudOS/CentOS/Rocky） 二、完整脚本 #!/usr/bin/env bash #============================================================= # server-inspect.sh —— 服务器每日巡检 # 用法: ./server-inspect.sh [-c cpu阈值] [-m 内存阈值] [-d 磁盘阈值] #============================================================= set -uo pipefail # 注意不用 -e: 巡检要\u0026#34;逐项跑完\u0026#34;, 不能一条失败就退出 VERSION=\u0026#34;2.3\u0026#34; LOGFILE=\u0026#34;/var/log/inspect.log\u0026#34; # ---------- 可调参数 ---------- CPU_LIMIT=80 # CPU 使用率告警线 % MEM_LIMIT=80 # 内存使用率告警线 % DISK_LIMIT=80 # 磁盘使用率告警线 % LOAD_FACTOR=2 # load 告警线 = 核数 * 该系数 SERVICES=(sshd nginx crond) # 关键服务白名单 while getopts \u0026#34;c:m:d:\u0026#34; opt; do case $opt in c) CPU_LIMIT=$OPTARG ;; m) MEM_LIMIT=$OPTARG ;; d) DISK_LIMIT=$OPTARG ;; *) echo \u0026#34;用法: $0 [-c n] [-m n] [-d n]\u0026#34;; exit 2 ;; esac done # ---------- 输出工具 ---------- RED=$\u0026#39;\\033[31m\u0026#39;; YELLOW=$\u0026#39;\\033[33m\u0026#39;; GREEN=$\u0026#39;\\033[32m\u0026#39;; NC=$\u0026#39;\\033[0m\u0026#39; FAIL_COUNT=0; WARN_COUNT=0 ok() { printf \u0026#34;%s[ OK ]%s %s\\n\u0026#34; \u0026#34;$GREEN\u0026#34; \u0026#34;$NC\u0026#34; \u0026#34;$*\u0026#34;; } warn() { printf \u0026#34;%s[WARN]%s %s\\n\u0026#34; \u0026#34;$YELLOW\u0026#34; \u0026#34;$NC\u0026#34; \u0026#34;$*\u0026#34;; WARN_COUNT=$((WARN_COUNT+1)); } fail() { printf \u0026#34;%s[FAIL]%s %s\\n\u0026#34; \u0026#34;$RED\u0026#34; \u0026#34;$NC\u0026#34; \u0026#34;$*\u0026#34;; FAIL_COUNT=$((FAIL_COUNT+1)); } info() { printf \u0026#34;%s[INFO]%s %s\\n\u0026#34; \u0026#34;\u0026#34; \u0026#34;$NC\u0026#34; \u0026#34;$*\u0026#34;; } section() { printf \u0026#34;\\n===== %s =====\\n\u0026#34; \u0026#34;$*\u0026#34;; } # ---------- 1. 系统概览 ---------- section \u0026#34;系统概览\u0026#34; info \u0026#34;主机名: $(hostname)\u0026#34; info \u0026#34;系统: $(grep PRETTY_NAME /etc/os-release | cut -d\u0026#39;\u0026#34;\u0026#39; -f2)\u0026#34; info \u0026#34;内核: $(uname -r)\u0026#34; info \u0026#34;运行: $(uptime -p)\u0026#34; # ---------- 2. 负载与 CPU ---------- section \u0026#34;负载与 CPU\u0026#34; CORES=$(nproc) LOAD1=$(awk \u0026#39;{print int($1)}\u0026#39; /proc/loadavg) LOAD_LIMIT=$((CORES * LOAD_FACTOR)) if (( LOAD1 \u0026gt; LOAD_LIMIT )); then fail \u0026#34;1分钟负载 ${LOAD1} 超限 (阈值=${LOAD_LIMIT}, ${CORES}核)\u0026#34; else ok \u0026#34;负载正常: ${LOAD1} / ${CORES}核 (阈值=${LOAD_LIMIT})\u0026#34; fi CPU_USAGE=$(top -bn1 | awk \u0026#39;/^%Cpu/ {print int($2+$4)}\u0026#39;) # us+sy if (( CPU_USAGE \u0026gt; CPU_LIMIT )); then warn \u0026#34;CPU 使用率 ${CPU_USAGE}% 超过 ${CPU_LIMIT}%\u0026#34; info \u0026#34;Top3 进程: $(ps aux --sort=-%cpu | awk \u0026#39;NR\u0026gt;1\u0026amp;\u0026amp;NR\u0026lt;5 {printf \u0026#34;%s(%s%%) \u0026#34;, $11, $3}\u0026#39;)\u0026#34; else ok \u0026#34;CPU 使用率 ${CPU_USAGE}%\u0026#34; fi # ---------- 3. 内存与 swap ---------- section \u0026#34;内存\u0026#34; MEM_PCT=$(free | awk \u0026#39;/Mem:/ {printf \u0026#34;%d\u0026#34;, $3/$2*100}\u0026#39;) if (( MEM_PCT \u0026gt; MEM_LIMIT )); then warn \u0026#34;内存使用率 ${MEM_PCT}% 超过 ${MEM_LIMIT}%\u0026#34; info \u0026#34;Top3 进程: $(ps aux --sort=-%mem | awk \u0026#39;NR\u0026gt;1\u0026amp;\u0026amp;NR\u0026lt;5 {printf \u0026#34;%s(%s%%) \u0026#34;, $11, $4}\u0026#39;)\u0026#34; else ok \u0026#34;内存使用率 ${MEM_PCT}%\u0026#34; fi SWAP_USED=$(free -m | awk \u0026#39;/Swap:/ {print $3}\u0026#39;) if (( SWAP_USED \u0026gt; 100 )); then warn \u0026#34;Swap 已用 ${SWAP_USED}MB（可能存在内存压力）\u0026#34; else ok \u0026#34;Swap 使用 ${SWAP_USED}MB\u0026#34; fi # ---------- 4. 磁盘 ---------- section \u0026#34;磁盘空间\u0026#34; df -h -x tmpfs -x devtmpfs | awk \u0026#39;NR\u0026gt;1 { gsub(/%/,\u0026#34;\u0026#34;,$5) if ($5+0 \u0026gt;= 85) printf \u0026#34;\u0026gt;\u0026gt;\u0026gt; [FAIL] %s 使用率 %s%%\\n\u0026#34;, $6, $5 else if ($5+0 \u0026gt;= 70) printf \u0026#34;\u0026gt;\u0026gt;\u0026gt; [WARN] %s 使用率 %s%%\\n\u0026#34;, $6, $5 }\u0026#39; | while read -r line; do echo \u0026#34;$line\u0026#34;; done df -h -x tmpfs -x devtmpfs | awk \u0026#39;NR\u0026gt;1 {gsub(/%/,\u0026#34;\u0026#34;,$5); if($5+0\u0026lt;\u0026#39;$DISK_LIMIT\u0026#39;) exit 0; else exit 1}\u0026#39; \\ \u0026amp;\u0026amp; ok \u0026#34;所有分区使用率低于 ${DISK_LIMIT}%\u0026#34; \\ || warn \u0026#34;存在分区使用率超过 ${DISK_LIMIT}%（详见上方明细）\u0026#34; INODE_PCT=$(df -i / | awk \u0026#39;NR==2 {gsub(/%/,\u0026#34;\u0026#34;,$5); print $5}\u0026#39;) if (( INODE_PCT \u0026gt; DISK_LIMIT )); then warn \u0026#34;根分区 inode 使用率 ${INODE_PCT}%（小文件过多?）\u0026#34; else ok \u0026#34;根分区 inode 使用率 ${INODE_PCT}%\u0026#34; fi # ---------- 5. 网络 ---------- section \u0026#34;网络\u0026#34; GATEWAY=$(ip route | awk \u0026#39;/default/ {print $3; exit}\u0026#39;) if ping -c2 -W2 \u0026#34;$GATEWAY\u0026#34; \u0026amp;\u0026gt;/dev/null; then ok \u0026#34;网关 ${GATEWAY} 可达\u0026#34; else fail \u0026#34;网关 ${GATEWAY} 不可达\u0026#34; fi EXT_IF=$(ip -br route get 1.1.1.1 2\u0026gt;/dev/null | awk \u0026#39;{print $5; exit}\u0026#39;) if ip link show \u0026#34;$EXT_IF\u0026#34; | grep -q \u0026#34;state UP\u0026#34;; then ok \u0026#34;出口网卡 ${EXT_IF} 状态 UP\u0026#34; else fail \u0026#34;出口网卡 ${EXT_IF} 异常\u0026#34; fi ERRS=$(ip -s link show \u0026#34;$EXT_IF\u0026#34; 2\u0026gt;/dev/null | awk \u0026#39;/RX:/{getline; print $3+$4}\u0026#39;) if (( ${ERRS:-0} \u0026gt; 1000 )); then warn \u0026#34;${EXT_IF} 收发错误/丢包累计 ${ERRS}\u0026#34; else ok \u0026#34;网卡无异常计数\u0026#34; fi # ---------- 6. 关键服务 ---------- section \u0026#34;关键服务\u0026#34; for svc in \u0026#34;${SERVICES[@]}\u0026#34;; do if systemctl is-active --quiet \u0026#34;$svc\u0026#34;; then ok \u0026#34;服务 ${svc} 运行中\u0026#34; else fail \u0026#34;服务 ${svc} 未运行\u0026#34; fi done # ---------- 7. 安全项 ---------- section \u0026#34;安全检查\u0026#34; SSH_ROOT=$(sshd -T 2\u0026gt;/dev/null | awk \u0026#39;/^permitrootlogin/ {print $2}\u0026#39;) [[ \u0026#34;$SSH_ROOT\u0026#34; == \u0026#34;no\u0026#34; ]] \u0026amp;\u0026amp; ok \u0026#34;SSH 已禁止 root 登录\u0026#34; \\ || warn \u0026#34;SSH 允许 root 登录（建议关闭）\u0026#34; SSH_PWD=$(sshd -T 2\u0026gt;/dev/null | awk \u0026#39;/^passwordauthentication/ {print $2}\u0026#39;) [[ \u0026#34;$SSH_PWD\u0026#34; == \u0026#34;no\u0026#34; ]] \u0026amp;\u0026amp; ok \u0026#34;SSH 已禁用密码登录\u0026#34; \\ || warn \u0026#34;SSH 仍允许密码登录（建议改密钥）\u0026#34; LAST_FAIL=$(grep -c \u0026#34;Failed password\u0026#34; /var/log/secure 2\u0026gt;/dev/null || echo 0) if (( LAST_FAIL \u0026gt; 100 )); then warn \u0026#34;本日 SSH 爆破尝试 ${LAST_FAIL} 次（建议 fail2ban）\u0026#34; else ok \u0026#34;SSH 爆破尝试 ${LAST_FAIL} 次\u0026#34; fi REBOOT_REQ=$(dnf -q needs-restarting 2\u0026gt;/dev/null | wc -l) if (( REBOOT_REQ \u0026gt; 0 )); then warn \u0026#34;有 ${REBOOT_REQ} 个更新待重启生效\u0026#34; else ok \u0026#34;无待重启的更新\u0026#34; fi # ---------- 汇总 ---------- section \u0026#34;巡检结果汇总 ($(date \u0026#39;+%F %T\u0026#39;))\u0026#34; info \u0026#34;告警: ${WARN_COUNT} 项 / 失败: ${FAIL_COUNT} 项\u0026#34; if (( FAIL_COUNT \u0026gt; 0 )); then fail \u0026#34;巡检不通过, 请立即处理\u0026#34; exit 1 elif (( WARN_COUNT \u0026gt; 0 )); then warn \u0026#34;巡检通过, 但存在告警项\u0026#34; exit 0 else ok \u0026#34;全部通过, 系统健康\u0026#34; exit 0 fi 三、用到的技术点回放 技术 位置 set -uo pipefail（不用 -e） 巡检要跑完全部项目 getopts 命令行阈值参数 数组 + for 服务白名单遍历 awk 算术 df/free/top 的百分比提取 管道 + while read 分区逐行判断 条件兜底 ${ERRS:-0} 变量可能为空的防御 退出码 供上游判断 四、接入 cron 与告警 install -m 750 server-inspect.sh /opt/scripts/ # /etc/cron.d/inspect 0 9 * * * root /opt/scripts/server-inspect.sh \u0026gt; /var/log/inspect-$(date +\\%F).log 2\u0026gt;\u0026amp;1 # 只在异常时发告警（钉钉 webhook） curl -s -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#34;{\\\u0026#34;msgtype\\\u0026#34;:\\\u0026#34;text\\\u0026#34;,\\\u0026#34;text\\\u0026#34;:{\\\u0026#34;content\\\u0026#34;:\\\u0026#34;$(hostname) 巡检FAIL\\\u0026#34;}}\u0026#34; \\ \u0026#34;https://oapi.dingtalk.com/robot/send?token=xxx\u0026#34; 五、扩展方向 HTML 报告（printf 拼 \u0026lt;table\u0026gt;，邮件正文直接看） 多机汇总（fetch 各机报告到中控，awk 拼大表） 趋势记录（结果追加 CSV，画使用率曲线） 重构成 Ansible role / Python 版本（机器多时） 小结 一个好的巡检脚本标准：退出码可靠、输出可分级、阈值可配置、误报少。把这个脚本扔到你的每台服务器上，明早起来看 FAIL 项——你大概率会发现自己忽略已久的问题。\n本文是「Linux 运维」系列第 10 篇。\n","permalink":"https://antidebug.cn/posts/2024/11/bash-%E5%AE%9E%E6%88%98%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%B7%A1%E6%A3%80%E8%84%9A%E6%9C%AC/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e学了变量、条件、三剑客，是时候写个真正有用的东西了。下面是一个我维护了两年、持续迭代的服务器巡检脚本——每台机器上一份，cron 每天跑，异常发告警。\u003c/p\u003e","title":"Bash 实战：服务器巡检脚本"},{"content":"前言 买云盘时面对\u0026quot;云硬盘 / NAS / 对象存储\u0026quot;三个菜单不知道选谁？它们对应存储世界的三种抽象：块、文件、对象。理解差异，选型就是送分题。\n一、三种抽象一览 ┌─ 块存储 (Block) ──────┐ ┌─ 文件存储 (File) ─────┐ ┌─ 对象存储 (Object) ─────┐ │ 裸的磁盘块 │ │ 目录/文件层级 │ │ 扁平的海量键值 │ │ 云硬盘 CBS/EBS │ │ NAS/CIFS/NFS │ │ OSS/COS/S3 │ │ │ │ │ │ │ │ OS 直接 mkfs+mount │ │ 多机同时挂载 │ │ HTTP API 增删查 │ │ 挂给虚拟机当 C盘/D盘 │ │ POSIX 文件接口 │ │ bucket + object key │ └───────────────────────┘ └────────────────────────┘ └──────────────────────────┘ 像\u0026#34;本地硬盘\u0026#34; 像\u0026#34;共享文件夹\u0026#34; 像\u0026#34;网盘+CDN\u0026#34; 二、块存储：性能担当 是什么：把存储切成固定大小的\u0026quot;块\u0026quot;提供给主机，主机在上面自建文件系统。云硬盘对虚拟机来说就是一块 /dev/vdb。\n# 云硬盘的日常（虚拟机视角） lsblk mkfs.xfs /dev/vdb1 mount /dev/vdb1 /data # 完全是本地盘的体验 —— 数据实际落在分布式存储集群上 特点：\n低延迟（云硬盘分等级）：ESSD PL1 \u0026lt; 1ms 级 单盘挂给单个实例（共享盘是特殊形态） 需要自己管文件系统（快照、扩容走云 API） 计费：容量 + 预置性能（IOPS/吞吐） 性能等级对比（以某厂为例）：\n类型 典型 IOPS 场景 普通云盘 数百 温数据、开发测试 SSD 云盘 2万+ 常规生产 ESSD 10万+（分 PL 等级） 数据库、核心业务 适用：系统盘、数据库、任何\u0026quot;要像本地盘一样用\u0026quot;的场景。\n三、文件存储：共享担当 是什么：存储服务端维护目录树，多台机器同时通过网络挂载同一份数据（NFS/SMB/CIFS 协议）。\n# NFS 挂载（客户端） dnf install -y nfs-utils mount -t nfs 10.0.2.100:/share /mnt/share # 20 台 web 服务器挂同一个 NAS 目录 —— 上传一次, 全部可见 特点：\n多机共享读写（这是核心价值） POSIX 接口兼容，应用几乎无感 吞吐型（大带宽），但随机小 IO 与延迟不如块存储 按容量计费，比块存储贵 适用：Web 集群共享静态资源、容器共享目录（K8s 的 NFS CSI）、AI 训练数据集共享、内容管理系统。\n常见坑：NFS 锁机制较弱，数据库类应用放 NFS 是自找麻烦；挂载要配 _netdev 避免开机阻塞。\n四、对象存储：海量担当 是什么：数据以\u0026quot;对象\u0026quot;（数据+元数据+键）形式存进扁平的 bucket，通过 HTTP REST API 读写——没有目录树，a/b/c.jpg 只是名叫 a/b/c.jpg 的 key。\n# OSS 工具示例（ossutil / aws cli 同理） ossutil cp backup.tar.gz oss://my-bucket/backup/2024-10-19.tar.gz ossutil ls oss://my-bucket/backup/ ossutil sign oss://my-bucket/report.pdf --timeout 600 # 生成临时访问 URL # Python SDK（阿里云 OSS 示意） import oss2 bucket = oss2.Bucket(auth, \u0026#34;https://oss-cn-hangzhou.aliyuncs.com\u0026#34;, \u0026#34;my-bucket\u0026#34;) bucket.put_object(\u0026#34;logs/2024-10-19/app.log\u0026#34;, open(\u0026#34;app.log\u0026#34;, \u0026#34;rb\u0026#34;)) data = bucket.get_object(\u0026#34;logs/2024-10-19/app.log\u0026#34;).read() 特点：\n无限容量、按用量计费（每 GB 单价最低） 单对象最大 TB 级；不能修改只能整体重写（覆盖上传） 不支持 POSIX：不能 mount 后随机读写（有特殊工具模拟，但别当真盘用） 天然多副本/纠删码，持久性 11 个 9 与 CDN 无缝配合：回源到 OSS，静态资源全球加速 适用：图片/视频等静态资源、备份归档、大数据湖、日志归档、App 安装包分发。\n五、对比与决策 维度 块存储 文件存储 对象存储 接口 块设备 POSIX 文件 HTTP API 共享 单机（默认） ✅ 多机 ✅ 全球 延迟 最低（\u0026lt;1ms） 中（ms 级） 高（几十~几百 ms） 容量 单盘有限（几十 TB） 大 无限 随机改写 ✅ ✅ ❌（整体覆盖） 单位成本 高 更高 最低 典型负载 数据库/系统盘 共享目录 静态资源/备份 一句话决策：\n要低延迟随机读写（数据库）→ 块存储 要多机共享的目录→ 文件存储 要海量便宜 + 一次写入多次读取 → 对象存储 六、经典组合架构 ┌─ CDN ─┐ 用户 ──► 域名 ──────►│ 边缘缓存 │──未命中──► 对象存储 OSS（图片/JS/CSS） └────────┘ 应用服务器 ──► 数据库 ──► 块存储（云硬盘, ESSD） │ └──► /shared/upload ──► 文件存储 NAS（多机共享） │ └──► 每日备份 ──► 对象存储归档（低频/冷存储类型, 成本再砍 50%+） 存储分层：热数据块存储 → 温数据 NAS → 冷数据对象归档——按访问频率摊薄成本。\n七、实操建议 1. 对象存储安全三原则: - bucket 默认私有, 公开要单独授权 - 临时访问用签名 URL, 别长期放公开链接 - 敏感数据开服务端加密 2. 数据生命周期规则: - logs/ 前缀 30 天后转低频, 90 天删除 - backup/ 前缀保留 4 份版本 3. 云硬盘记得做快照策略（每日增量 + 保留 7 天） 小结 存储 记忆点 块 云上\u0026quot;本地盘\u0026quot;，快，给数据库 文件 云上\u0026quot;共享文件夹\u0026quot;，多机挂载 对象 云上\u0026quot;无限网盘\u0026quot;，便宜，配 CDN，只能整体读写 分层 热→块、温→文件、冷→对象 本文是「云计算」系列第 3 篇。\n","permalink":"https://antidebug.cn/posts/2024/10/%E4%BA%91%E5%AD%98%E5%82%A8%E4%B8%89%E7%A7%8D%E5%BD%A2%E6%80%81%E5%9D%97%E6%96%87%E4%BB%B6%E4%B8%8E%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e买云盘时面对\u0026quot;云硬盘 / NAS / 对象存储\u0026quot;三个菜单不知道选谁？它们对应存储世界的三种抽象：\u003cstrong\u003e块、文件、对象\u003c/strong\u003e。理解差异，选型就是送分题。\u003c/p\u003e","title":"云存储三种形态：块、文件与对象存储"},{"content":"前言 装饰器和生成器是 Python 进阶的两张门票。前者是 AOP（切面编程）的 Python 实现，后者是\u0026quot;惰性计算\u0026quot;的根基。这篇把原理掰开揉碎。\n一、装饰器：从零推导 本质：装饰器就是一个\u0026quot;接收函数、返回函数\u0026quot;的函数。\ndef my_decorator(func): def wrapper(*args, **kwargs): print(\u0026#34;\u0026gt;\u0026gt;\u0026gt; 调用前\u0026#34;) result = func(*args, **kwargs) # 调用原函数 print(\u0026#34;\u0026gt;\u0026gt;\u0026gt; 调用后\u0026#34;) return result return wrapper # 手动版 def hello(): print(\u0026#34;hello\u0026#34;) hello = my_decorator(hello) # 手动包一层 hello() # 打印前后缀 + hello # 语法糖版（完全等价） @my_decorator def hello(): print(\u0026#34;hello\u0026#34;) @ 语法糖只是 hello = my_decorator(hello) 的缩写——想通这一点，一切装饰器都能自己写出来。\nfunctools.wraps：必写的礼仪 from functools import wraps def my_decorator(func): @wraps(func) # 把原函数的 __name__/docstring 拷给 wrapper def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @my_decorator def hello(): \u0026#34;\u0026#34;\u0026#34;say hello\u0026#34;\u0026#34;\u0026#34; print(hello.__name__) # 有 @wraps: \u0026#39;hello\u0026#39;; 没有: \u0026#39;wrapper\u0026#39;（文档/调试全乱） 二、常用装饰器实战 计时器 import time, functools def timer(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) cost = time.perf_counter() - start print(f\u0026#34;{func.__name__} 耗时 {cost:.3f}s\u0026#34;) return result return wrapper @timer def train_model(): time.sleep(1.2) 重试器（带参数的装饰器 = 三层嵌套） def retry(times=3, delay=1): def decorator(func): # 第2层: 收函数 @functools.wraps(func) def wrapper(*args, **kwargs): # 第3层: 收调用参数 for attempt in range(1, times + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == times: raise time.sleep(delay) return wrapper return decorator @retry(times=5, delay=2) # 先调 retry(...) 得到真正的装饰器 def fetch(url): ... 记忆化：@functools.lru_cache 直接用：\n@functools.lru_cache(maxsize=1024) def fib(n): return n if n \u0026lt; 2 else fib(n-1) + fib(n-2) fib(200) # 瞬间出结果 注册器（框架的路由就是这原理） routes = {} def route(path): def decorator(func): routes[path] = func # 注册进表, 不改函数行为 return func return decorator @route(\u0026#34;/index\u0026#34;) def index(): return \u0026#34;home\u0026#34; @route(\u0026#34;/api/health\u0026#34;) def health(): return \u0026#34;ok\u0026#34; print(routes) # {\u0026#39;/index\u0026#39;: \u0026lt;function index\u0026gt;, ...} 三、生成器：yield 的魔法 普通函数一次性返回；生成器函数\u0026quot;暂停执行、分次产出\u0026quot;：\ndef counter(n): i = 0 while i \u0026lt; n: yield i # 到这里暂停, 把 i 交给调用方 i += 1 # 下次要下一个值时, 从这里继续 g = counter(3) print(g) # \u0026lt;generator object counter at 0x...\u0026gt; next(g) # 0（执行到 yield 暂停） next(g) # 1（从上次暂停处继续） next(g) # 2 next(g) # StopIteration（耗尽） for x in counter(5): ... # for 循环自动处理 StopIteration 为什么用生成器：内存 # 一次性物化 1000 万个数: 瞬间吃掉几百 MB squares_list = [x * x for x in range(10_000_000)] sum(squares_list) # 生成器表达式: 几乎不占内存, 边产边算 squares_gen = (x * x for x in range(10_000_000)) sum(squares_gen) 注意 [] 与 () 的区别：列表推导式 vs 生成器表达式。\n逐行读大文件（生成器经典场景） def error_lines(path): with open(path, encoding=\u0026#34;utf-8\u0026#34;) as f: for line in f: # 文件对象本身就是生成器 if \u0026#34;ERROR\u0026#34; in line: yield line.strip() # 100GB 的日志也稳: 内存里永远只有一行 for line in error_lines(\u0026#34;/var/log/huge.log\u0026#34;): handle(line) 生成器管道（Unix 管道思想） def read(path): with open(path) as f: yield from f # yield from: 委托给子生成器 def strip(lines): for l in lines: yield l.strip() def only_error(lines): for l in lines: if \u0026#34;ERROR\u0026#34; in l: yield l pipeline = only_error(strip(read(\u0026#34;app.log\u0026#34;))) for line in pipeline: print(line) 每层只做一件事，数据像水流过管道，全程 O(1) 内存。\n四、进阶：send 与协程雏形 def averager(): total = count = 0 average = None while True: value = yield average # 产出平均值, 并接收 send 进来的新值 if value is None: break total += value count += 1 average = total / count avg = averager() next(avg) # 启动（执行到第一个 yield） avg.send(10) # 10.0 avg.send(20) # 15.0 avg.send(30) # 20.0 yield 既能产出又能接收——这就是早期协程的原型，也是 async/await 的思想前身（现代异步编程后面专篇讲）。\n五、使用决策 需求 工具 通用横切逻辑（计时/日志/重试/鉴权） 装饰器 函数结果缓存 @lru_cache 大数据流式处理 生成器 惰性无限序列 生成器 组装多步处理管线 生成器管道 小结 装饰器 = 接收函数返回函数；@x 就是 f = x(f) 写装饰器必带 functools.wraps 带参数的装饰器要多包一层 生成器 = 可暂停的函数；yield from 委托子生成器 大数据永远想生成器：内存 O(1) 本文是「Python」系列第 7 篇。\n","permalink":"https://antidebug.cn/posts/2024/10/python-%E8%A3%85%E9%A5%B0%E5%99%A8%E4%B8%8E%E7%94%9F%E6%88%90%E5%99%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e装饰器和生成器是 Python 进阶的两张门票。前者是 AOP（切面编程）的 Python 实现，后者是\u0026quot;惰性计算\u0026quot;的根基。这篇把原理掰开揉碎。\u003c/p\u003e\n\u003ch2 id=\"一装饰器从零推导\"\u003e一、装饰器：从零推导\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e本质：装饰器就是一个\u0026quot;接收函数、返回函数\u0026quot;的函数。\u003c/strong\u003e\u003c/p\u003e","title":"Python 装饰器与生成器"},{"content":"前言 mkfs 时的灵魂拷问：ext4 还是 xfs？两者都是成熟的日志式文件系统，但性格差异明显。这篇从原理到选型讲清楚。\n一、文件系统要解决什么 数据的逻辑组织 + 崩溃一致性：\n应用写入 \u0026#34;hello\u0026#34; │ ▼ 文件系统: 把数据拆成块 + 维护元数据(inode 表、目录项、空闲块位图) │ ▼ 日志(Journal): 先把\u0026#34;要做的修改\u0026#34;记一笔, 再真正落盘 │ 断电重启后: 重放日志或回滚 —— 保证一致性 ▼ 磁盘块 inode：存文件元数据（权限、时间戳、数据块指针），不含文件名。经典问题——\u0026ldquo;磁盘有空间但创建不了文件\u0026rdquo;= inode 耗尽（海量小文件）：\ndf -i # 看 inode 使用率 df -h # 空间正常但 df -i 满 → 找出小文件目录清理 二、核心对比 维度 ext4 XFS 出生 2008（ext 系第四代） 1994 SGI，2001 入 Linux 最大卷 1EB / 单文件 16TB 8EB / 单文件 8EB 日志 固定区域，可独立设备 元数据日志，更精细 元数据规模 mkfs 时定死 inode 数 动态分配 inode 大文件性能 良好 优秀（并行 IO、分配组） 海量小文件 良好 略逊（但动态 inode 救场） 在线扩容 ✅ resize2fs ✅ xfs_growfs 在线缩容 ❌（离线也高危） ❌（根本不支持） 延迟分配 ✅ ✅ RHEL 默认 RHEL6 RHEL7+ 默认 注意：两者都不能在线缩容。规划容量时宁可先大后扩，别指望缩回来。\n三、性能差异的本质 XFS 的分配组（AG） XFS 把卷切成多个 Allocation Group，每个 AG 有独立的元数据与锁——多线程写不同区域时天然并行，大文件吞吐和并发扩展性强。\nmkfs.xfs -d agcount=8 /dev/vdb1 # 大阵列可显式调多 AG ext4 的稳 ext4 在单线程小 IO、元数据操作、碎片控制上表现均衡，社区生态最长（fsck 工具链、文档、极端案例经验都最丰富）。\n简单实测（fio） dnf install -y fio # 顺序大文件写 fio -name=seq -rw=write -bs=1M -size=4G -numjobs=1 -directory=/data -direct=1 # 随机小 IO（数据库形态） fio -name=rand -rw=randwrite -bs=4k -size=2G -numjobs=8 -iodepth=32 -directory=/data -direct=1 典型结果：顺序大文件 XFS 略优；4K 随机写两者接近（瓶颈在设备本身）。\n四、日常操作 # 创建 mkfs.ext4 -L appdata /dev/vdb1 mkfs.xfs -L bigdata /dev/vdb1 # 挂载（XFS 常用 noatime 减少元数据写） mount -o noatime /dev/vdb1 /data # 在线扩容（云盘扩容后） resize2fs /dev/vdb1 # ext4 xfs_growfs /data # xfs（注意: 参数是挂载点!） # 查看信息 dumpe2fs /dev/vdb1 | head -20 # ext4 详情 xfs_info /data # xfs 详情（会显示 agcount/inosize） # 检查修复（先卸载!） e2fsck -f /dev/vdb1 # ext4 xfs_repair /dev/vdb1 # xfs（严重时 -L 丢日志, 最后手段） 挂载选项实践：\nnoatime 不更新访问时间（几乎所有场景推荐） nodiratime 不更新目录访问时间（noatime 已含） discard TRIM（SSD/云盘适用） 五、怎么选（结论向） 场景 推荐 RHEL 系默认跟随 XFS（发行版已替你选好） 大文件为主（视频、备份、镜像仓库、日志归档） XFS 海量小文件 + 需要极致小文件性能调研 ext4 / 特殊方案 通用业务盘、拿不准 XFS 需要离线缩容可能性的极少数场景 都不支持，重新规划 U盘/移动盘跨平台交换 ext4（兼容面更广） 一句话：跟随发行版默认（XFS），大文件场景更坚定 XFS，纠结时选 XFS。\n六、通用排障 # 只读重挂载（磁盘错误时内核的保护动作） mount -o remount,rw /data # 恢复读写前先看 dmesg # 看文件系统错误 dmesg | grep -iE \u0026#34;ext4|xfs|error\u0026#34; # 碎片情况 filefrag -v /data/bigfile # ext4 单文件碎片 xfs_db -c frag -r /dev/vdb1 # xfs 碎片率 # 被删但占空间的文件（进程仍持有句柄） lsof +L4 | grep deleted 小结 两者都是日志文件系统，稳定性都过关 XFS 强在大文件/高并发/动态 inode，是现代 RHEL 默认 ext4 强在均衡与生态，小文件友好 都只扩不缩，容量规划宁大勿小 df -h 满先看 df -i，空间与 inode 是两条账 本文是「Linux 基础」系列第 6 篇。\n","permalink":"https://antidebug.cn/posts/2024/09/linux-%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%AF%B9%E6%AF%94ext4-%E4%B8%8E-xfs/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003emkfs\u003c/code\u003e 时的灵魂拷问：ext4 还是 xfs？两者都是成熟的日志式文件系统，但性格差异明显。这篇从原理到选型讲清楚。\u003c/p\u003e\n\u003ch2 id=\"一文件系统要解决什么\"\u003e一、文件系统要解决什么\u003c/h2\u003e\n\u003cp\u003e数据的逻辑组织 + 崩溃一致性：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e应用写入 \u0026#34;hello\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e文件系统: 把数据拆成块 + 维护元数据(inode 表、目录项、空闲块位图)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e日志(Journal): 先把\u0026#34;要做的修改\u0026#34;记一笔, 再真正落盘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │          断电重启后: 重放日志或回滚 —— 保证一致性\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e磁盘块\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003einode\u003c/strong\u003e：存文件元数据（权限、时间戳、数据块指针），不含文件名。经典问题——\u0026ldquo;磁盘有空间但创建不了文件\u0026rdquo;= \u003cstrong\u003einode 耗尽\u003c/strong\u003e（海量小文件）：\u003c/p\u003e","title":"Linux 文件系统对比：ext4 与 XFS"},{"content":"前言 \u0026ldquo;没有监控的运维等于裸奔\u0026rdquo;。Prometheus + Grafana 是云原生时代监控的事实标准，这篇从架构到实操搭一套最小可用体系。\n一、架构与核心概念 ┌─ 被监控目标 ────────────┐ │ node_exporter (主机) │ ┌─────────────┐ ┌───────────┐ │ mysqld_exporter (DB) │◄────┤ Prometheus │───►│ Alertmanager ─► 钉钉/邮件 │ 应用 /metrics 端点 │ 拉取 │ (TSDB+采集) │ └───────────┘ └─────────────────────────┘ └──────┬──────┘ │ PromQL 查询 ┌────▼─────┐ │ Grafana │ ── 可视化大盘 └──────────┘ 关键设计：Pull 模型——Prometheus 主动周期性抓取目标的 /metrics 端点，数据存本地时序库（TSDB）。\n指标四类型：\n类型 语义 示例 Counter 只增不减的计数器 http_requests_total Gauge 可增可减的瞬时值 memory_usage_bytes Histogram 分桶统计分布 请求耗时分布（P99 就靠它） Summary 分位数 直接给 P50/P99 指标命名惯例：\u0026lt;namespace\u0026gt;_\u0026lt;name\u0026gt;_\u0026lt;unit\u0026gt;，如 http_requests_total。\n二、快速搭建（Docker Compose） # docker-compose.yml services: prometheus: image: prom/prometheus:v2.53.0 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - ./rules/:/etc/prometheus/rules/ ports: [\u0026#34;9090:9090\u0026#34;] node-exporter: image: prom/node-exporter:v1.8.1 pid: \u0026#34;host\u0026#34; command: - --path.rootfs=/host volumes: - /:/host:ro,readonly alertmanager: image: prom/alertmanager:v0.27.0 ports: [\u0026#34;9093:9093\u0026#34;] grafana: image: grafana/grafana:11.1.0 ports: [\u0026#34;3000:3000\u0026#34;] volumes: - grafana-data:/var/lib/grafana volumes: grafana-data: prometheus.yml：\nglobal: scrape_interval: 15s rule_files: - /etc/prometheus/rules/*.yml alerting: alertmanagers: - static_configs: - targets: [\u0026#34;alertmanager:9093\u0026#34;] scrape_configs: - job_name: \u0026#34;prometheus\u0026#34; static_configs: [{targets: [\u0026#34;localhost:9090\u0026#34;]}] - job_name: \u0026#34;node\u0026#34; static_configs: [{targets: [\u0026#34;node-exporter:9100\u0026#34;]}] docker compose up -d # 验证: 浏览器开 http://服务器IP:9090/targets —— 两个 job 都是 UP 三、PromQL 入门（核心中的核心） # 在 Prometheus Web UI (9090) 的 Graph 里练习 # 1. 即时查询 node_cpu_seconds_total # 原始序列(按 cpu/mode 多条) # 2. 过滤标签 {} node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;} # 3. rate(): Counter 必须配 rate 才有意义（每秒增长率） rate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m]) # 4. 聚合 avg by (instance) (rate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) # 5. 运算: CPU 使用率 = 1 - 空闲率 100 - avg by (instance) (rate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100 # 6. 常用函数 node_memory_MemAvailable_bytes / 1024 / 1024 / 1024 # 可用内存(GB) 100 - node_filesystem_avail_bytes{mountpoint=\u0026#34;/\u0026#34;} / node_filesystem_size_bytes{mountpoint=\u0026#34;/\u0026#34;} * 100 # 根盘使用率% # 7. histogram 的 P99（假设暴露了 http_request_duration_seconds） histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) # 8. 一分钟增量 increase(node_network_receive_bytes_total[1m]) 四、告警规则 rules/host.yml：\ngroups: - name: host-alerts rules: - alert: HostDown expr: up{job=\u0026#34;node\u0026#34;} == 0 for: 1m labels: {severity: critical} annotations: summary: \u0026#34;主机 {{ $labels.instance }} 宕机\u0026#34; description: \u0026#34;已失联超过 1 分钟\u0026#34; - alert: HighCPU expr: 100 - avg by (instance) (rate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100 \u0026gt; 85 for: 5m # 持续 5 分钟才报（防毛刺） labels: {severity: warning} annotations: summary: \u0026#34;{{ $labels.instance }} CPU 过高\u0026#34; description: \u0026#34;当前使用率 {{ $value | printf \\\u0026#34;%.1f\\\u0026#34; }}%\u0026#34; - alert: DiskWillFull expr: predict_linear(node_filesystem_avail_bytes{mountpoint=\u0026#34;/\u0026#34;}[1h], 4*3600) \u0026lt; 0 for: 10m annotations: summary: \u0026#34;{{ $labels.instance }} 根盘预计 4 小时内写满\u0026#34; for 的意义：条件先持续满足一段时间才触发，大幅减少误报。\nAlertmanager 路由（分组/抑制/静默）：\nroute: group_by: [\u0026#34;alertname\u0026#34;, \u0026#34;instance\u0026#34;] group_wait: 30s receiver: dingtalk receivers: - name: dingtalk webhook_configs: - url: \u0026#34;http://dingtalk-webhook/...\u0026#34; # 或用 prometheus-webhook-dingtalk 五、Grafana 出图 1. 打开 http://IP:3000 (admin/admin 首次改密) 2. Connections → Data source → Prometheus → URL: http://prometheus:9090 3. Dashboards → Import → 输入 1860（Node Exporter Full 官方大盘） 4. 秒得一张专业主机监控大屏 自建 Panel 的查询示例——\u0026ldquo;各实例内存使用率\u0026rdquo;：\n(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 六、应用侧暴露指标 Go 应用一行接入：\nimport \u0026#34;github.com/prometheus/client_golang/prometheus/promhttp\u0026#34; http.Handle(\u0026#34;/metrics\u0026#34;, promhttp.Handler()) 四个黄金指标（Google SRE）：延迟、流量、错误率、饱和度——给自己的服务都暴露这四类，监控就及格了。\n七、常见坑 现象 原因 targets 显示 DOWN 网络不通/端口错；点开 error 看具体原因 图表没数据 Counter 没 rate；时间范围/步长不合理 告警风暴 没配 for、没分组 长期存储 本地 TSDB 有限；上 VictoriaMetrics/Thanos（进阶） 容器里看不到主机指标 node-exporter 要挂载宿主 / 且 pid: host 小结 组件 职责 exporter 把系统/应用状态翻译成 /metrics Prometheus 拉取 + 存储 + 规则计算 PromQL rate 配 Counter、by 聚合 Alertmanager 分组、抑制、路由通知 Grafana 大盘可视化（1860 直接抄） 本文是「云原生」系列第 7 篇。\n","permalink":"https://antidebug.cn/posts/2024/09/prometheus--grafana-%E7%9B%91%E6%8E%A7%E4%BD%93%E7%B3%BB%E5%85%A5%E9%97%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;没有监控的运维等于裸奔\u0026rdquo;。Prometheus + Grafana 是云原生时代监控的事实标准，这篇从架构到实操搭一套最小可用体系。\u003c/p\u003e","title":"Prometheus + Grafana 监控体系入门"},{"content":"前言 Nginx 一半的生命在当\u0026quot;流量二传手\u0026quot;：把请求转发给后端服务，并把多台后端\u0026quot;捏\u0026quot;成一个池子。这篇讲透反向代理与负载均衡的配置细节。\n一、正向 vs 反向代理 正向代理（代理客户端）: 反向代理（代理服务端）: 客户端 → [代理] → 互联网 客户端 → [Nginx] → 后端集群 翻墙软件、公司出口 负载均衡、SSL卸载、缓存 服务器不知道真实客户端 客户端不知道真实后端 二、反向代理基础：proxy_pass upstream app { server 127.0.0.1:8080; server 127.0.0.1:8081; } server { listen 80; server_name api.example.com; location / { proxy_pass http://app; # ---- 标准转发头（几乎必配）---- proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # ---- 超时与缓冲 ---- proxy_connect_timeout 5s; proxy_read_timeout 60s; # 后端慢接口调大 proxy_send_timeout 30s; proxy_buffering on; # 响应先缓冲再发（默认） } } proxy_pass 的斜杠陷阱：\n# 带路径的 proxy_pass: /api/xxx -\u0026gt; 后端 /v1/xxx（替换 location 前缀） location /api/ { proxy_pass http://app/v1/; } # 不带路径（只有 host）: 原样透传 /api/xxx location /api/ { proxy_pass http://app; } 给后端传真实客户端 IP：后端拿到的 RemoteAddr 是 Nginx。后端应用需读 X-Real-IP 或解析 X-Forwarded-For（第一个 IP 是最初客户端）。\n三、upstream 负载均衡策略 # 1. 轮询（默认） + 权重 upstream app { server 10.0.1.11:8080 weight=3; # 扛 3/4 流量（机器好就多分） server 10.0.1.12:8080 weight=1; } # 2. ip_hash: 同一客户端 IP 恒定打到同一后端（会话保持的土办法） upstream app { ip_hash; server 10.0.1.11:8080; server 10.0.1.12:8080; } # 3. least_conn: 转给当前连接数最少的（长短连接混合场景更公平） upstream app { least_conn; server 10.0.1.11:8080; server 10.0.1.12:8080; } server 参数（被动健康检查）：\nupstream app { server 10.0.1.11:8080 max_fails=3 fail_timeout=30s; # 30 秒内失败 3 次 → 标记不可用 30 秒 server 10.0.1.12:8080 backup; # 备胎: 全挂了才上 server 10.0.1.13:8080 down; # 手动摘除（维护模式） } 开源版 Nginx 只有被动检查（靠真实流量发现失败）；主动健康检查需要 Nginx Plus 或 tengine，或改用 keepalived+VIP / K8s Ingress 的方案。\n四、典型场景配置 动静分离 server { listen 80; server_name example.com; location /static/ { # 静态: 本地直出 root /var/www; expires 30d; } location /api/ { # 动态: 转后端 proxy_pass http://app; } } 路径重写 # 老路径迁移 location /old/ { rewrite ^/old/(.*)$ /new/$1 permanent; # 301 } # 带正则的转发 location ~ ^/v(\\d+)/ { proxy_pass http://app; # 正则 location 里 proxy_pass 不能带 URI proxy_set_header X-API-Version $1; # 捕获组传给后端 } 本地缓存（给慢后端减负） proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=10m; server { location /api/public/ { proxy_pass http://app; proxy_cache api_cache; proxy_cache_valid 200 5m; # 200 响应缓存 5 分钟 proxy_cache_key $uri$is_args$args; add_header X-Cache-Status $upstream_cache_status; # HIT/MISS 调试头 } } 四层代理（TCP/UDP，转发 MySQL/Redis） # nginx.conf 顶层（与 http 块平级） stream { upstream mysql { server 10.0.2.21:3306; } server { listen 3306; proxy_pass mysql; } } WebSocket location /ws/ { proxy_pass http://app; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; # 协议升级头 proxy_set_header Connection \u0026#34;upgrade\u0026#34;; proxy_read_timeout 3600s; # 长连接别太早掐 } 五、验证与排障 nginx -t \u0026amp;\u0026amp; systemctl reload nginx # 看负载均衡实际效果 for i in $(seq 10); do curl -s http://api.example.com/backend-id; done # 轮询应轮流出现不同后端标识 # 关键日志变量（加进 log_format 排障神器） log_format upstream \u0026#39;$remote_addr -\u0026gt; $upstream_addr \u0026#39; \u0026#39;status=$status ups=$upstream_status rt=$upstream_response_time\u0026#39;; # 10.0.0.5 -\u0026gt; 10.0.1.11:8080 status=200 ups=200 rt=0.012 # 常见错误码 # 502: 后端连接失败（进程挂/端口错/防火墙） # 504: 后端超时（proxy_read_timeout 调大或优化后端） # 499: 客户端等不及主动断开（后端太慢） 502 排查链路：curl 后端端口 → ss -tlnp 看监听 → /var/log/nginx/error.log 看具体 connect 错误。\n六、架构位置示意 用户 ─► DNS/CDN ─► [LB/VIP] ─► Nginx 集群（七层: TLS/路由/缓存） │ ├─► 静态文件（本地/对象存储） ├─► App 集群（upstream 轮询） └─► 内部服务（stream 四层转发） 小结 需求 配置 转发 proxy_pass + 四个 proxy_set_header 负载策略 weight / ip_hash / least_conn 健康检查 max_fails + fail_timeout（被动） 会话保持 ip_hash（或应用层 token） 真实 IP X-Real-IP / X-Forwarded-For 排障 $upstream_addr/$upstream_response_time 写进日志 本文是「Linux 运维」系列第 9 篇。\n","permalink":"https://antidebug.cn/posts/2024/08/nginx-%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E4%B8%8E%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E9%85%8D%E7%BD%AE/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eNginx 一半的生命在当\u0026quot;流量二传手\u0026quot;：把请求转发给后端服务，并把多台后端\u0026quot;捏\u0026quot;成一个池子。这篇讲透反向代理与负载均衡的配置细节。\u003c/p\u003e","title":"Nginx 反向代理与负载均衡配置"},{"content":"前言 Go 没有 try/catch，错误是返回值，必须逐个处理。初看啰嗦，实则强迫你思考每一个失败路径。这篇把现代 Go（1.13+）的错误处理体系讲全。\n一、error 是什么 // 标准库定义: 就一个接口 type error interface { Error() string } // 最常见的形态 func ReadConfig(path string) ([]byte, error) { return os.ReadFile(path) // os.ReadFile 的签名: ( []byte, error) } func main() { data, err := ReadConfig(\u0026#34;app.conf\u0026#34;) if err != nil { log.Fatal(err) // open app.conf: no such file or directory } fmt.Println(len(data)) } 铁律：err != nil 检查永远紧跟在产生它的调用后；不要用 _ 吞掉错误（除非明确说明为何可忽略）。\n二、创建错误与错误包装 import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) // 创建 err1 := errors.New(\u0026#34;disk full\u0026#34;) err2 := fmt.Errorf(\u0026#34;port %d 非法\u0026#34;, 808000) // 带格式化 // 包装（wrap）: 加上下文, 同时保留根因 —— %w 是关键! if err := connect(db); err != nil { return fmt.Errorf(\u0026#34;init db: %w\u0026#34;, err) } // 输出: init db: dial tcp 10.0.0.2:3306: i/o timeout // 内部仍可透传判断出根因 %v vs %w：%v 只是拼接文字（丢失类型信息）；%w 形成错误链，允许 errors.Is/As 沿链查找。\n三、errors.Is / As / Join // sentinel 错误: 预定义的标志性错误值 var ErrNotFound = errors.New(\u0026#34;not found\u0026#34;) func GetUser(id int) error { // ... 查库失败时: return fmt.Errorf(\u0026#34;get user %d: %w\u0026#34;, id, ErrNotFound) } if err := GetUser(42); err != nil { if errors.Is(err, ErrNotFound) { // 沿错误链找相同值 // 处理\u0026#34;不存在\u0026#34;: 返回 404 等 } // 其他错误继续上抛 } // 判断具体类型（结构化错误） type TimeoutError struct { Op string Dur time.Duration } func (e *TimeoutError) Error() string { return fmt.Sprintf(\u0026#34;%s timed out after %s\u0026#34;, e.Op, e.Dur) } var te *TimeoutError if errors.As(err, \u0026amp;te) { // 沿链找可赋值给 *TimeoutError 的 log.Printf(\u0026#34;操作 %s 超时, 时长 %s\u0026#34;, te.Op, te.Dur) } // 合并多个错误（并行任务汇总场景, go1.20+） var errs []error for _, u := range urls { errs = append(errs, check(u)) } if err := errors.Join(errs...); err != nil { // nil 会被跳过 return err // 多行错误信息一次输出 } 四、自定义错误类型 // 好的错误类型: 携带结构化上下文 type HTTPError struct { StatusCode int URL string Err error // 内层错误 } func (e *HTTPError) Error() string { return fmt.Sprintf(\u0026#34;http %s: %d: %v\u0026#34;, e.URL, e.StatusCode, e.Err) } func (e *HTTPError) Unwrap() error { // 支持 errors.Is/As 沿链下钻 return e.Err } func Fetch(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, \u0026amp;HTTPError{URL: url, Err: err} // 500? 不, 网络层错 } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, \u0026amp;HTTPError{URL: url, StatusCode: resp.StatusCode} } return io.ReadAll(resp.Body) } // 调用方精确处理 var he *HTTPError if errors.As(err, \u0026amp;he) \u0026amp;\u0026amp; he.StatusCode == 429 { time.Sleep(time.Second) // 限流: 退避重试 } 五、panic / recover：留给真正的异常 Go 中 error 是业务的一部分，panic 是程序员的 bug：\nfunc mustParse(cfg string) Config { c, err := parse(cfg) if err != nil { panic(fmt.Sprintf(\u0026#34;配置无法解析: %v\u0026#34;, err)) // 程序启动期的不变量被破坏 } return c } // recover: 只在 defer 中生效, 用于兜底防止进程崩溃 func safeCall() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf(\u0026#34;panic recovered: %v\u0026#34;, r) } }() panic(\u0026#34;boom\u0026#34;) } 什么时候用 panic：\n✅ 程序初始化阶段的致命错误（配置错误、依赖缺失）——快速失败 ✅ 库内部不变量被破坏（不可能的分支） ❌ 日常业务错误（文件不存在、网络超时）——用 error ❌ 通过 panic 跨层传业务错误——性能差且语义混乱 六、工程化惯例 // 1. 包级 sentinel 错误集中定义（Err 前缀） package store var ( ErrNotFound = errors.New(\u0026#34;not found\u0026#34;) ErrConflict = errors.New(\u0026#34;conflict\u0026#34;) ) // 2. 返回错误时永远 wrap 上下文 if err := s.db.Query(...); err != nil { return fmt.Errorf(\u0026#34;get user %d: %w\u0026#34;, id, err) } // 3. 最外层统一记录（带堆栈） log.Printf(\u0026#34;handle error: %+v\u0026#34;, err) // %+v 打印详细信息 // 4. 处理不了的错误, 原样返回, 不要重复包装 错误信息规范：小写开头、不以标点结尾、包含关键参数（get user 42: connection refused）。\n七、与 Python 异常的对比 维度 Go error Python exception 传递方式 返回值，显式逐层处理 抛出，隐式向上冒泡 忽略风险 _ = err 显式写出来 裸 except 静默吞掉 分类判断 errors.Is/As 沿链查 except 具体类型 崩溃机制 panic（仅 bug 级） 未捕获异常即崩溃 样板代码 多（if err != nil） 少 啰嗦是特性不是缺陷——每个错误都在你眼前过一遍。\n小结 工具 用途 fmt.Errorf(\u0026quot;...: %w\u0026quot;, err) 包装 + 保留根因 errors.Is 判断链上是否有 sentinel errors.As 提取链上特定类型 errors.Join 汇总多个错误 自定义类型 + Unwrap 结构化错误 panic/recover 只留给 bug 与初始化失败 本文是「Golang」系列第 6 篇。\n","permalink":"https://antidebug.cn/posts/2024/08/golang-%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eGo 没有 try/catch，错误是\u003cstrong\u003e返回值\u003c/strong\u003e，必须逐个处理。初看啰嗦，实则强迫你思考每一个失败路径。这篇把现代 Go（1.13+）的错误处理体系讲全。\u003c/p\u003e","title":"Golang 错误处理机制详解"},{"content":"前言 管 3 台机器可以 SSH 手敲，管 30 台就需要 Ansible 了。无 Agent 架构（SSH 即通道）、声明式剧本（写\u0026quot;要什么\u0026quot;而不是\u0026quot;怎么做\u0026quot;），是自动化运维入门性价比最高的工具。\n一、架构与安装 控制机（装 Ansible） --SSH--\u0026gt; 被管机们（啥都不用装） dnf install -y python3-pip pip3 install ansible-core # 或 dnf install -y ansible # 前提: 控制机密钥能登录被管机（SSH 加固篇配过） ssh-keygen -t ed25519 ssh-copy-id root@10.0.1.11 # 对每台被管机执行 二、Inventory：管谁 # /etc/ansible/hosts 或项目里的 inventory.ini [webservers] 10.0.1.11 ansible_host=10.0.1.11 hostname=web01 10.0.1.12 hostname=web02 [dbservers] 10.0.2.21 hostname=db01 [prod:children] # 组嵌套 webservers dbservers [prod:vars] # 组变量 env=production 连通性测试：\nansible all -i inventory.ini -m ping # 10.0.1.11 | SUCCESS =\u0026gt; {\u0026#34;changed\u0026#34;: false, \u0026#34;ping\u0026#34;: \u0026#34;pong\u0026#34;} 三、Ad-hoc：临时命令（替代批量 SSH） # -m 模块, -a 参数 ansible webservers -i inventory.ini -m command -a \u0026#34;uptime\u0026#34; ansible webservers -m shell -a \u0026#34;df -h | grep data\u0026#34; # 需要管道时 # 最常用模块速览 ansible all -m setup -a \u0026#34;filter=ansible_memory_mb\u0026#34; # 采集 facts（系统信息） ansible all -m copy -a \u0026#34;src=app.conf dest=/etc/app/app.conf\u0026#34; ansible all -m yum -a \u0026#34;name=nginx state=present\u0026#34; # 装包 ansible all -m service -a \u0026#34;name=nginx state=started enabled=yes\u0026#34; ansible all -m user -a \u0026#34;name=zy groups=wheel\u0026#34; ansible all -m file -a \u0026#34;path=/opt/app state=directory mode=0755\u0026#34; ansible all -m fetch -a \u0026#34;src=/etc/nginx/nginx.conf dest=backup/ flat=yes\u0026#34; # 拉文件 四、Playbook：可复用的剧本 deploy_nginx.yml：\n--- - name: 批量部署 Nginx 并上线静态站点 hosts: webservers become: yes # 提权（sudo） vars: nginx_port: 80 site_dir: /var/www/site tasks: - name: 安装 nginx ansible.builtin.dnf: name: nginx state: present - name: 创建站点目录 ansible.builtin.file: path: \u0026#34;{{ site_dir }}\u0026#34; state: directory mode: \u0026#34;0755\u0026#34; - name: 分发站点文件 ansible.builtin.copy: src: files/index.html dest: \u0026#34;{{ site_dir }}/index.html\u0026#34; notify: restart nginx # 变更时通知 handler - name: 渲染 nginx 配置 ansible.builtin.template: src: templates/site.conf.j2 dest: /etc/nginx/conf.d/site.conf notify: restart nginx - name: 启动并自启 nginx ansible.builtin.service: name: nginx state: started enabled: yes handlers: # 被通知才执行, 多次通知只跑一次 - name: restart nginx ansible.builtin.service: name: nginx state: restarted templates/site.conf.j2（Jinja2 模板，喝出变量的味道）：\nserver { listen {{ nginx_port }}; server_name {{ hostname }}.example.com; root {{ site_dir }}; index index.html; } ansible-playbook -i inventory.ini deploy_nginx.yml # 先演练看会发生什么（不做真改动） ansible-playbook --check --diff -i inventory.ini deploy_nginx.yml 五、变量与条件循环 tasks: - name: 只在 CentOS 上执行 ansible.builtin.dnf: name: htop state: present when: ansible_facts[\u0026#39;os_family\u0026#39;] == \u0026#34;RedHat\u0026#34; - name: 批量建用户 ansible.builtin.user: name: \u0026#34;{{ item.name }}\u0026#34; groups: \u0026#34;{{ item.groups }}\u0026#34; loop: - { name: alice, groups: wheel } - { name: bob, groups: docker } - name: 收集事实并打印 ansible.builtin.setup: register: facts - name: 依据注册结果决定动作 ansible.builtin.debug: msg: \u0026#34;内存充足\u0026#34; when: facts.ansible_facts.memtotal_mb \u0026gt; 4000 - name: 失败即停（自定义条件） ansible.builtin.fail: msg: \u0026#34;磁盘太小, 终止部署\u0026#34; when: ansible_facts[\u0026#39;devices\u0026#39;][\u0026#39;vdb\u0026#39;] is not defined 六、Roles：工程化组织 项目大了把 Playbook 拆成角色，目录即规范：\nsite.yml # 入口: 引用各 role inventory/ roles/ └── nginx/ ├── tasks/main.yml # 核心任务 ├── templates/ # j2 模板 ├── files/ # 静态文件 ├── vars/main.yml # 角色变量 ├── handlers/main.yml └── defaults/main.yml # 可被覆盖的默认值 # site.yml - hosts: webservers roles: - role: nginx vars: nginx_port: 8080 # 覆盖 defaults 配置查找顺序（高 → 低）：-e 命令行 \u0026gt; playbook vars \u0026gt; role vars \u0026gt; role defaults。\n也可以用现成轮子：ansible-galaxy install geerlingguy.nginx。\n七、实战场景清单 场景 核心模块 批量改配置 template + notify handler 批量打补丁 dnf name=* state=latest 发布应用 copy/synchronize + service restart 巡检采集 setup + fetch 用户管理 user / authorized_key 定时巡检 ansible-pull 或 cron 调 playbook 八、常见坑 现象 原因 UNREACHABLE SSH 不通/密钥没配/端口不对 改了配置没重启 忘写 notify/handlers 幂等性差，二跑报错 用模块而非 shell；shell 任务加 creates= 参数 变量不生效 查找顺序覆盖了；-v 看 variable 来源 模板渲染错 Jinja2 语法；{{ }} 里引号嵌套问题 小结 ad-hoc 管临时事，Playbook 管复用事，Roles 管工程事 幂等是灵魂：同一剧本跑 N 遍，结果一致 上线前永远 --check 演练 handler + notify 实现\u0026quot;按需重启\u0026quot; 本文是「Linux 运维」系列第 8 篇。\n","permalink":"https://antidebug.cn/posts/2024/07/ansible-%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E5%85%A5%E9%97%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e管 3 台机器可以 SSH 手敲，管 30 台就需要 Ansible 了。\u003cstrong\u003e无 Agent 架构\u003c/strong\u003e（SSH 即通道）、\u003cstrong\u003e声明式剧本\u003c/strong\u003e（写\u0026quot;要什么\u0026quot;而不是\u0026quot;怎么做\u0026quot;），是自动化运维入门性价比最高的工具。\u003c/p\u003e","title":"Ansible 自动化运维入门"},{"content":"前言 概念篇过了理论，这次动手：把一个带健康检查、资源限额、优雅升级能力的生产级 Deployment 从零部署起来。环境用 kind（本地单机 K8s）。\n# 5 分钟准备实验环境 curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64 install ./kind /usr/local/bin/kind kind create cluster --name lab kubectl cluster-info 一、完整的生产级清单 web.yaml——每一块都是实战标配：\napiVersion: apps/v1 kind: Deployment metadata: name: demo-web labels: app: demo-web spec: replicas: 3 revisionHistoryLimit: 5 # 保留 5 个历史版本（回滚用） strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 滚动时最多多起 1 个 maxUnavailable: 0 # 滚动时不允许少副本（零中断） selector: matchLabels: app: demo-web template: metadata: labels: app: demo-web spec: containers: - name: web image: registry.cn-hangzhou.aliyuncs.com/zy-repo/demo-web:v1.0.0 ports: - containerPort: 8080 env: - name: APP_ENV value: \u0026#34;production\u0026#34; resources: requests: # 调度依据（ 保证值） cpu: 100m # 100m = 0.1 核 memory: 128Mi limits: # 硬顶（超内存会被 OOMKill） cpu: 500m memory: 256Mi startupProbe: # 启动探针: 先等它通过, 再开始健康检查 httpGet: {path: /healthz, port: 8080} failureThreshold: 30 periodSeconds: 2 livenessProbe: # 存活探针: 失败重启容器 httpGet: {path: /healthz, port: 8080} periodSeconds: 10 failureThreshold: 3 readinessProbe: # 就绪探针: 失败摘除流量（不重启） httpGet: {path: /ready, port: 8080} periodSeconds: 5 lifecycle: preStop: # 优雅退出: 先等流量排空 exec: {command: [\u0026#34;sleep\u0026#34;, \u0026#34;5\u0026#34;]} terminationGracePeriodSeconds: 30 --- apiVersion: v1 kind: Service metadata: name: demo-web spec: selector: app: demo-web ports: - port: 80 targetPort: 8080 kubectl apply -f web.yaml kubectl get pods -o wide kubectl get deploy,svc 二、三个探针的分工（面试高频） 探针 失败后果 用来发现 livenessProbe 重启容器 死锁、进程假死 readinessProbe 摘除流量（不重启） 依赖未就绪、过载降级 startupProbe 推迟上面两个的计时 慢启动应用（JVM 老铁都懂） 流量路径: Service → 只发给 ready 的 Pod 自愈路径: liveness 挂 → kubelet 重启容器 → ready 才回流量 探针方式：httpGet（状态码 2xx/3xx 算过）/ exec（退出码 0 算过）/ tcpSocket（能连通算过）。\n三、资源限额的学问 resources: requests: {cpu: 100m, memory: 128Mi} limits: {cpu: 500m, memory: 256Mi} requests：调度器按它找节点；节点超卖（所有 pod requests 之和 \u0026gt; 容量才拒绝） limits：CPU 超限被节流（throttle，变慢不挂）；内存超限被 OOMKill（exit code 137） QoS 等级：requests == limits 时是 Guaranteed，资源紧张时最后被驱逐 # 观察实际用量 kubectl top pods # 排查 OOMKill kubectl describe pod xxx | grep -A2 \u0026#34;Last State\u0026#34; 四、发布与回滚 # 触发滚动更新（改镜像） kubectl set image deploy/demo-web web=.../demo-web:v1.1.0 # 实时看滚动过程 kubectl rollout status deploy/demo-web # Waiting for deployment \u0026#34;demo-web\u0026#34; rollout to finish: 2 of 3 updated... # 零中断的秘密: maxUnavailable=0 + readinessProbe # 新 Pod 没 ready 前, 老 Pod 一个都不删 # 查看历史与回滚 kubectl rollout history deploy/demo-web kubectl rollout undo deploy/demo-web # 回上一个 kubectl rollout undo deploy/demo-web --to-revision=2 # 回指定版本 # 暂停/恢复（灰度改多字段时用） kubectl rollout pause deploy/demo-web kubectl set image ... ; kubectl set env ... kubectl rollout resume deploy/demo-web 五、扩缩容 kubectl scale deploy/demo-web --replicas=5 # 手动 # HPA 自动扩缩容（按 CPU） kubectl autoscale deploy/demo-web --min=3 --max=10 --cpu-percent=70 kubectl get hpa 压测验证：ab -n 100000 -c 200 http://$NODE_IP:$PORT/，看副本数随 CPU 爬升。\n六、排障命令流（背下来） # 1. Pod 卡在 Pending → 资源不够/节点选择器没匹配 kubectl describe pod xxx | tail -20 # 看 Events 段! # 2. Pod 卡 ImagePullBackOff → 镜像名/仓库认证问题 kubectl describe pod xxx | grep -A5 Events # 3. CrashLoopBackOff → 容器起来就崩 kubectl logs xxx --previous # 上一次崩溃的日志! # 4. Service 不通 → 端点列表空? kubectl get endpoints demo-web # NONE = selector/label 不匹配 kubectl exec -it xxx -- curl localhost:8080 # 容器内自测 # 5. OOM / 重启循环 kubectl get pod xxx -o jsonpath=\u0026#39;{.status.containerStatuses[0].restartCount}\u0026#39; kubectl describe pod xxx | grep -B2 -A5 \u0026#34;State\u0026#34; # 6. 进入容器 \u0026amp; 看资源 kubectl exec -it xxx -- sh kubectl top pod xxx Events 永远是第一现场——90% 的部署问题，describe 最后那段 Events 直接给了答案。\n七、常用字段速查 kubectl get all -n default # 一屏看全 kubectl get pod -o wide # 带节点/IP kubectl get pod -w # watch 模式 kubectl explain deploy.spec.strategy # 现场查字段文档（神器） kubectl diff -f web.yaml # apply 前看差异 kubectl delete -f web.yaml # 删除 kubectl run debug --rm -it --image=busybox -- sh # 临时调试 Pod 小结 需求 配置 不接坏流量 readinessProbe 假死自愈 livenessProbe 零中断发布 maxUnavailable=0 + 探针 回滚 rollout undo \u0026ndash;to-revision 资源 requests 调度 / limits 封顶 / 内存超限 OOM 排障 describe 的 Events → logs \u0026ndash;previous → endpoints 本文是「云原生」系列第 6 篇。\n","permalink":"https://antidebug.cn/posts/2024/07/k8s-%E9%83%A8%E7%BD%B2%E5%BA%94%E7%94%A8pod-%E4%B8%8E-deployment-%E5%AE%9E%E6%88%98/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e概念篇过了理论，这次动手：把一个带健康检查、资源限额、优雅升级能力的\u003cstrong\u003e生产级 Deployment\u003c/strong\u003e 从零部署起来。环境用 kind（本地单机 K8s）。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 5 分钟准备实验环境\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003einstall ./kind /usr/local/bin/kind\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ekind create cluster --name lab\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ekubectl cluster-info\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"一完整的生产级清单\"\u003e一、完整的生产级清单\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eweb.yaml\u003c/code\u003e——每一块都是实战标配：\u003c/p\u003e","title":"K8s 部署应用：Pod 与 Deployment 实战"},{"content":"前言 Python \u0026ldquo;自带电池\u0026rdquo;——标准库覆盖了运维脚本 80% 的需求。这篇盘点我使用频率最高的那些，每个都给实战片段。\n一、os / sys / pathlib：系统交互 import os, sys from pathlib import Path # 环境与退出 os.environ.get(\u0026#34;HOME\u0026#34;, \u0026#34;/root\u0026#34;) # 读环境变量（带默认值） os.getpid(), os.getcwd() sys.exit(1) # 带状态码退出（脚本规范） # pathlib 三板斧（上篇讲过, 这里补充） for f in Path(\u0026#34;/var/log\u0026#34;).rglob(\u0026#34;*.log\u0026#34;): # 递归找 print(f, f.stat().st_size) # 执行外部命令的正确姿势见 subprocess（别用 os.system） 二、collections：数据结构增强 from collections import Counter, defaultdict, deque, OrderedDict # Counter: 计数器之王 c = Counter([\u0026#34;go\u0026#34;, \u0026#34;py\u0026#34;, \u0026#34;go\u0026#34;, \u0026#34;sh\u0026#34;, \u0026#34;go\u0026#34;]) c.most_common(2) # [(\u0026#39;go\u0026#39;, 3), (\u0026#39;py\u0026#39;, 1)] c[\u0026#34;go\u0026#34;] # 3（不存在的 key 返回 0, 不报错） # defaultdict: 消灭 if key not in groups = defaultdict(list) for name, dept in [(\u0026#34;a\u0026#34;, \u0026#34;ops\u0026#34;), (\u0026#34;b\u0026#34;, \u0026#34;dev\u0026#34;), (\u0026#34;c\u0026#34;, \u0026#34;ops\u0026#34;)]: groups[dept].append(name) # {\u0026#39;ops\u0026#39;: [\u0026#39;a\u0026#39;, \u0026#39;c\u0026#39;], \u0026#39;dev\u0026#39;: [\u0026#39;b\u0026#39;]} logs = defaultdict(int) # 默认 0: 直接 += 1 不会 KeyError logs[\u0026#34;error\u0026#34;] += 1 # deque: 双端队列（队列/栈/滑动窗口） q = deque(maxlen=5) for i in range(10): q.append(i) # 自动挤掉最老的 # deque([5, 6, 7, 8, 9]) q.appendleft(0) # O(1) 头部插入, list 是 O(n) 三、itertools：迭代器魔法 from itertools import chain, islice, groupby, product, combinations # chain: 拼接多个可迭代对象 list(chain([1, 2], [3], range(4, 6))) # [1, 2, 3, 4, 5] # islice: 切迭代器（不能下标的对象） list(islice(range(100), 5, 10)) # [5, 6, 7, 8, 9] # product: 笛卡尔积（替代多层 for） for env, svc in product([\u0026#34;prod\u0026#34;, \u0026#34;staging\u0026#34;], [\u0026#34;web\u0026#34;, \u0026#34;api\u0026#34;]): print(f\u0026#34;{env}-{svc}\u0026#34;) # groupby: 分组（注意: 先排序再分组!） data = sorted([(\u0026#34;a\u0026#34;, 1), (\u0026#34;b\u0026#34;, 2), (\u0026#34;a\u0026#34;, 3)], key=lambda x: x[0]) for k, g in groupby(data, key=lambda x: x[0]): print(k, list(g)) # a [(\u0026#39;a\u0026#39;,1),(\u0026#39;a\u0026#39;,3)] / b [(\u0026#39;b\u0026#39;,2)] 四、datetime：时间处理 from datetime import datetime, timedelta, timezone now = datetime.now() utc = datetime.now(timezone.utc) # 时区 aware（推荐!） # 格式化与解析 now.strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;) # \u0026#39;2024-06-15 10:30:00\u0026#39; datetime.strptime(\u0026#34;2024-06-15\u0026#34;, \u0026#34;%Y-%m-%d\u0026#34;) # 字符串 -\u0026gt; datetime datetime.fromisoformat(\u0026#34;2024-06-15T10:00\u0026#34;) # ISO 格式直解 # 运算 yesterday = now - timedelta(days=1) (now - yesterday).days # 1 now.timestamp() # -\u0026gt; Unix 时间戳 datetime.fromtimestamp(1718419200) # \u0026lt;- Unix 时间戳 # 时区转换 tz_sh = timezone(timedelta(hours=8)) utc.astimezone(tz_sh) # UTC -\u0026gt; 北京时间 五、subprocess：执行命令（重点） import subprocess # ✅ 推荐写法: run + capture（3.5+） r = subprocess.run( [\u0026#34;df\u0026#34;, \u0026#34;-h\u0026#34;], # 列表形式（不经 shell, 更安全） capture_output=True, text=True, timeout=10, ) print(r.returncode) # 0 print(r.stdout) # 带 shell 管道时才用 shell=True（注意注入风险!） r = subprocess.run(\u0026#34;df -h | grep data\u0026#34;, shell=True, capture_output=True, text=True) # 失败抛异常（写脚本报错即停） r = subprocess.run([\u0026#34;ls\u0026#34;, \u0026#34;/nope\u0026#34;], check=True, capture_output=True, text=True) # 实战: 检查服务是否存活 def service_active(name: str) -\u0026gt; bool: r = subprocess.run([\u0026#34;systemctl\u0026#34;, \u0026#34;is-active\u0026#34;, name], capture_output=True, text=True) return r.stdout.strip() == \u0026#34;active\u0026#34; 六、argparse：像样的命令行参数 import argparse p = argparse.ArgumentParser(description=\u0026#34;主机巡检工具\u0026#34;) p.add_argument(\u0026#34;hosts\u0026#34;, nargs=\u0026#34;+\u0026#34;, help=\u0026#34;目标主机列表\u0026#34;) p.add_argument(\u0026#34;-p\u0026#34;, \u0026#34;--port\u0026#34;, type=int, default=22) p.add_argument(\u0026#34;-v\u0026#34;, \u0026#34;--verbose\u0026#34;, action=\u0026#34;store_true\u0026#34;) args = p.parse_args() # python check.py web01 web02 -p 2222 -v # args.hosts=[\u0026#39;web01\u0026#39;,\u0026#39;web02\u0026#39;] args.port=2222 args.verbose=True 七、json / re / hashlib / secrets：日常刚需 import json, re, hashlib, secrets # json（上篇详述） json.dumps({\u0026#34;k\u0026#34;: \u0026#34;值\u0026#34;}, ensure_ascii=False, indent=2) # re: 三个够用的函数 re.search(r\u0026#34;(\\d+\\.\\d+\\.\\d+\\.\\d+)\u0026#34;, text) # 找第一处（返回 match） re.findall(r\u0026#34;ERROR.*\u0026#34;, log) # 找全部 re.sub(r\u0026#34;\\s+\u0026#34;, \u0026#34; \u0026#34;, s) # 替换 # 预编译（循环里高频使用时） IP = re.compile(r\u0026#34;^(?:\\d{1,3}\\.){3}\\d{1,3}$\u0026#34;) IP.match(\u0026#34;10.0.0.1\u0026#34;) # hashlib: 校验文件 h = hashlib.sha256() for chunk in iter(lambda: open(\u0026#34;app.tar.gz\u0026#34;,\u0026#34;rb\u0026#34;).read(8192), b\u0026#34;\u0026#34;): h.update(chunk) print(h.hexdigest()) # secrets: 生成安全随机串（密码/Token, 别用 random!） secrets.token_urlsafe(32) 八、logging：别再 print 了 import logging logging.basicConfig( level=logging.INFO, format=\u0026#34;%(asctime)s [%(levelname)s] %(message)s\u0026#34;, datefmt=\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, ) log = logging.getLogger(__name__) log.info(\u0026#34;开始巡检 %s\u0026#34;, \u0026#34;web01\u0026#34;) # 惰性格式化, 占位符写法 try: 1 / 0 except ZeroDivisionError: log.exception(\u0026#34;计算失败\u0026#34;) # 自动带堆栈 九、综合实战：一行式巡检脚本 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;快速体检: 磁盘/内存/负载\u0026#34;\u0026#34;\u0026#34; import subprocess, shutil, os def sh(cmd: str) -\u0026gt; str: r = subprocess.run(cmd, shell=True, capture_output=True, text=True) return r.stdout.strip() def check_disk(threshold=80): for line in sh(\u0026#34;df -h --output=pcent,target\u0026#34;).splitlines()[1:]: pct, mount = line.split() if int(pct.rstrip(\u0026#34;%\u0026#34;)) \u0026gt; threshold: print(f\u0026#34;[告警] {mount} 使用率 {pct}\u0026#34;) def check_load(): cores = os.cpu_count() load = float(sh(\u0026#34;cat /proc/loadavg\u0026#34;).split()[0]) status = \u0026#34;告警\u0026#34; if load \u0026gt; cores else \u0026#34;正常\u0026#34; print(f\u0026#34;[{status}] load {load} / {cores} cores\u0026#34;) def check_mem(): total, _, free, _, _, avail = sh(\u0026#34;free -b\u0026#34;).splitlines()[1].split()[1:] print(f\u0026#34;[信息] 可用内存 {int(avail)/2**30:.1f}G\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: check_disk(); check_load(); check_mem() 零第三方依赖，拷到任何有 Python3 的机器就能跑。\n小结 库 一句话 pathlib / os 文件与系统 collections Counter 计数、defaultdict 分组 itertools chain/product/islice subprocess run + 列表参数 + timeout argparse 脚本参数 logging 替代 print 本文是「Python」系列第 6 篇。\n","permalink":"https://antidebug.cn/posts/2024/06/python-%E5%B8%B8%E7%94%A8%E6%A0%87%E5%87%86%E5%BA%93%E7%9B%98%E7%82%B9/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003ePython \u0026ldquo;自带电池\u0026rdquo;——标准库覆盖了运维脚本 80% 的需求。这篇盘点我使用频率最高的那些，每个都给实战片段。\u003c/p\u003e\n\u003ch2 id=\"一os--sys--pathlib系统交互\"\u003e一、os / sys / pathlib：系统交互\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"nn\"\u003esys\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003epathlib\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ePath\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 环境与退出\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;HOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/root\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e     \u003cspan class=\"c1\"\u003e# 读环境变量（带默认值）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetpid\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetcwd\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esys\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eexit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                          \u003cspan class=\"c1\"\u003e# 带状态码退出（脚本规范）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# pathlib 三板斧（上篇讲过, 这里补充）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ePath\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/var/log\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erglob\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;*.log\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e     \u003cspan class=\"c1\"\u003e# 递归找\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estat\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003est_size\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 执行外部命令的正确姿势见 subprocess（别用 os.system）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"二collections数据结构增强\"\u003e二、collections：数据结构增强\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003ecollections\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eCounter\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edefaultdict\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOrderedDict\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Counter: 计数器之王\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eCounter\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;go\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;py\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;go\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;sh\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;go\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emost_common\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                  \u003cspan class=\"c1\"\u003e# [(\u0026#39;go\u0026#39;, 3), (\u0026#39;py\u0026#39;, 1)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;go\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e                           \u003cspan class=\"c1\"\u003e# 3（不存在的 key 返回 0, 不报错）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# defaultdict: 消灭 if key not in\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003egroups\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edefaultdict\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edept\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"p\"\u003e[(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ops\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;dev\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;c\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ops\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)]:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003egroups\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003edept\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# {\u0026#39;ops\u0026#39;: [\u0026#39;a\u0026#39;, \u0026#39;c\u0026#39;], \u0026#39;dev\u0026#39;: [\u0026#39;b\u0026#39;]}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogs\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edefaultdict\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e           \u003cspan class=\"c1\"\u003e# 默认 0: 直接 += 1 不会 KeyError\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogs\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# deque: 双端队列（队列/栈/滑动窗口）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emaxlen\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003erange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                   \u003cspan class=\"c1\"\u003e# 自动挤掉最老的\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# deque([5, 6, 7, 8, 9])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappendleft\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                   \u003cspan class=\"c1\"\u003e# O(1) 头部插入, list 是 O(n)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"三itertools迭代器魔法\"\u003e三、itertools：迭代器魔法\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eitertools\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003echain\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eislice\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003egroupby\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eproduct\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecombinations\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# chain: 拼接多个可迭代对象\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003echain\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"nb\"\u003erange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e)))\u003c/span\u003e      \u003cspan class=\"c1\"\u003e# [1, 2, 3, 4, 5]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# islice: 切迭代器（不能下标的对象）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eislice\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003erange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e            \u003cspan class=\"c1\"\u003e# [5, 6, 7, 8, 9]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# product: 笛卡尔积（替代多层 for）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esvc\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eproduct\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;prod\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;staging\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;web\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;api\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eenv\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e-\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003esvc\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# groupby: 分组（注意: 先排序再分组!）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003esorted\u003c/span\u003e\u003cspan class=\"p\"\u003e([(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)],\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eg\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003egroupby\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eg\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e            \u003cspan class=\"c1\"\u003e# a [(\u0026#39;a\u0026#39;,1),(\u0026#39;a\u0026#39;,3)] / b [(\u0026#39;b\u0026#39;,2)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"四datetime时间处理\"\u003e四、datetime：时间处理\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003edatetime\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimedelta\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimezone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eutc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etimezone\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eutc\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e             \u003cspan class=\"c1\"\u003e# 时区 aware（推荐!）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 格式化与解析\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrftime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan class=\"si\"\u003e%d\u003c/span\u003e\u003cspan class=\"s2\"\u003e %H:%M:%S\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e            \u003cspan class=\"c1\"\u003e# \u0026#39;2024-06-15 10:30:00\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrptime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;2024-06-15\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan class=\"si\"\u003e%d\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# 字符串 -\u0026gt; datetime\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efromisoformat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;2024-06-15T10:00\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e   \u003cspan class=\"c1\"\u003e# ISO 格式直解\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 运算\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eyesterday\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"n\"\u003etimedelta\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edays\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"n\"\u003eyesterday\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edays\u003c/span\u003e                       \u003cspan class=\"c1\"\u003e# 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etimestamp\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e                              \u003cspan class=\"c1\"\u003e# -\u0026gt; Unix 时间戳\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edatetime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efromtimestamp\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1718419200\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e           \u003cspan class=\"c1\"\u003e# \u0026lt;- Unix 时间戳\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 时区转换\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etz_sh\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etimezone\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etimedelta\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehours\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eutc\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eastimezone\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etz_sh\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                        \u003cspan class=\"c1\"\u003e# UTC -\u0026gt; 北京时间\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"五subprocess执行命令重点\"\u003e五、subprocess：执行命令（重点）\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003esubprocess\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# ✅ 推荐写法: run + capture（3.5+）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esubprocess\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;df\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;-h\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e                         \u003cspan class=\"c1\"\u003e# 列表形式（不经 shell, 更安全）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ecapture_output\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereturncode\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e        \u003cspan class=\"c1\"\u003e# 0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estdout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 带 shell 管道时才用 shell=True（注意注入风险!）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esubprocess\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;df -h | grep data\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eshell\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                   \u003cspan class=\"n\"\u003ecapture_output\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 失败抛异常（写脚本报错即停）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esubprocess\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ls\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/nope\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003echeck\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecapture_output\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 实战: 检查服务是否存活\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eservice_active\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esubprocess\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;systemctl\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;is-active\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                       \u003cspan class=\"n\"\u003ecapture_output\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estdout\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrip\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;active\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"六argparse像样的命令行参数\"\u003e六、argparse：像样的命令行参数\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eargparse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eargparse\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eArgumentParser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edescription\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;主机巡检工具\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd_argument\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;hosts\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enargs\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;+\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehelp\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;目标主机列表\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd_argument\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;-p\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;--port\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e22\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd_argument\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;-v\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;--verbose\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaction\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;store_true\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ep\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eparse_args\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# python check.py web01 web02 -p 2222 -v\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# args.hosts=[\u0026#39;web01\u0026#39;,\u0026#39;web02\u0026#39;] args.port=2222 args.verbose=True\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"七json--re--hashlib--secrets日常刚需\"\u003e七、json / re / hashlib / secrets：日常刚需\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"nn\"\u003ere\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"nn\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"nn\"\u003esecrets\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# json（上篇详述）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edumps\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;k\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;值\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e \u003cspan class=\"n\"\u003eensure_ascii\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eindent\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# re: 三个够用的函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ere\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esearch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003er\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;(\\d+\\.\\d+\\.\\d+\\.\\d+)\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e        \u003cspan class=\"c1\"\u003e# 找第一处（返回 match）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ere\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efindall\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003er\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ERROR.*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                       \u003cspan class=\"c1\"\u003e# 找全部\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ere\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esub\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003er\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\\s+\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                            \u003cspan class=\"c1\"\u003e# 替换\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 预编译（循环里高频使用时）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eIP\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ere\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecompile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003er\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;^(?:\\d{1,3}\\.)\u003c/span\u003e\u003cspan class=\"si\"\u003e{3}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\\d{1,3}$\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eIP\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003ematch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;10.0.0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# hashlib: 校验文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eh\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esha256\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003echunk\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003eiter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003elambda\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;app.tar.gz\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;rb\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eread\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e8192\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"sa\"\u003eb\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eh\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eupdate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003echunk\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eh\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehexdigest\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# secrets: 生成安全随机串（密码/Token, 别用 random!）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esecrets\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etoken_urlsafe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e32\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"八logging别再-print-了\"\u003e八、logging：别再 print 了\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003elogging\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebasicConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elevel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eINFO\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eformat\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e%(asctime)s\u003c/span\u003e\u003cspan class=\"s2\"\u003e [\u003c/span\u003e\u003cspan class=\"si\"\u003e%(levelname)s\u003c/span\u003e\u003cspan class=\"s2\"\u003e] \u003c/span\u003e\u003cspan class=\"si\"\u003e%(message)s\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edatefmt\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan class=\"si\"\u003e%d\u003c/span\u003e\u003cspan class=\"s2\"\u003e %H:%M:%S\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elog\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"vm\"\u003e__name__\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;开始巡检 \u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;web01\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e       \u003cspan class=\"c1\"\u003e# 惰性格式化, 占位符写法\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eZeroDivisionError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eexception\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;计算失败\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e          \u003cspan class=\"c1\"\u003e# 自动带堆栈\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"九综合实战一行式巡检脚本\"\u003e九、综合实战：一行式巡检脚本\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"ch\"\u003e#!/usr/bin/env python3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u0026#34;快速体检: 磁盘/内存/负载\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003esubprocess\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"nn\"\u003eshutil\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"nn\"\u003eos\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003esh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecmd\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003er\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esubprocess\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecmd\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eshell\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecapture_output\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estdout\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrip\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003echeck_disk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ethreshold\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e80\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eline\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003esh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;df -h --output=pcent,target\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esplitlines\u003c/span\u003e\u003cspan class=\"p\"\u003e()[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e:]:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003epct\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emount\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eline\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epct\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erstrip\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;%\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003ethreshold\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;[告警] \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003emount\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e 使用率 \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epct\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003echeck_load\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ecores\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecpu_count\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003efloat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;cat /proc/loadavg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e()[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003estatus\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;告警\u0026#34;\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eload\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003ecores\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;正常\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;[\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e] load \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eload\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e / \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ecores\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e cores\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003echeck_mem\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003etotal\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efree\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eavail\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;free -b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esplitlines\u003c/span\u003e\u003cspan class=\"p\"\u003e()[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e()[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e:]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;[信息] 可用内存 \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eavail\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e**\u003c/span\u003e\u003cspan class=\"mi\"\u003e30\u003c/span\u003e\u003cspan class=\"si\"\u003e:\u003c/span\u003e\u003cspan class=\"s2\"\u003e.1f\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003eG\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"vm\"\u003e__name__\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;__main__\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003echeck_disk\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e \u003cspan class=\"n\"\u003echeck_load\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e \u003cspan class=\"n\"\u003echeck_mem\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e零第三方依赖，拷到任何有 Python3 的机器就能跑。\u003c/p\u003e","title":"Python 常用标准库盘点"},{"content":"前言 容器一多，问题就从\u0026quot;跑起来\u0026quot;变成\u0026quot;怎么连\u0026quot;：容器之间怎么互访？端口映射的原理是什么？为什么生产推荐自定义网络？这篇把 Docker 网络讲透。\n一、五种网络模式总览 docker network ls # NETWORK ID NAME DRIVER # xxx bridge bridge # 默认网桥 docker0 # xxx host host # 共享宿主机网络栈 # xxx none null # 无网络 模式 命令 特点 场景 bridge（默认） --network bridge 独立网络栈 + NAT 绝大多数场景 host --network host 直接用宿主机网络，无隔离 追求网络性能 container --network container:xx 复用另一容器的网络栈 K8s Pod 的雏形 none --network none 只有 lo，完全隔离 安全敏感/离线任务 自定义 bridge docker network create 支持 DNS、隔离性好 生产推荐 二、bridge：默认模式与端口映射原理 docker run -d --name web -p 8080:80 nginx 内部发生了什么：\n宿主机 容器 web ┌────────────────────┐ ┌──────────────┐ │ eth0: 10.0.4.13 │ │ eth0: │ │ │ │ │ 172.17.0.2 │ │ docker0: 172.17.0.1│◄──────►│ (veth pair) │ │ │ │ │ gw=172.17.0.1│ │ iptables DNAT │ └──────────────┘ │ :8080 → 172.17.0.2:80 └────────────────────┘ 安装 Docker 时创建了虚拟网桥 docker0（172.17.0.0/16） 每个容器拿到一张虚拟网卡（veth pair 一端在容器、一端挂网桥） -p 8080:80 本质是一条 iptables DNAT 规则： iptables -t nat -L DOCKER -n # DNAT tcp dpt:8080 to:172.17.0.2:80 容器出网靠 SNAT/MASQUERADE，源地址被改写成宿主机 IP # 在容器里验证 docker exec web ip addr # eth0 172.17.0.2/16 docker exec web ip route # default via 172.17.0.1 IP 不固定——容器重建后 IP 会变，这就是要靠自定义网络 DNS 的原因。\n三、host 模式：零开销，全暴露 docker run -d --network host nginx:alpine 容器直接用宿主机的 eth0/lo，没有 NAT、没有端口映射（-p 会被忽略） 性能 = 裸机进程（高吞吐网络应用收益明显） 代价：端口冲突风险——宿主机 80 被占，容器 nginx 起不来 排障口子：ss -tlnp 看到宿主机端口，实际是容器在听 # 典型用法：监控 agent、需要高性能转发的组件 docker run -d --network host --name node-exporter prom/node-exporter 四、container 模式与 none # container 模式: 共享另一个容器的网络命名空间（IP/端口/hosts 全共享） docker run -d --name app myapp docker run -d --name sidecar --network container:app busybox sleep 3600 # sidecar 里 localhost:8080 就是 app 的 8080 —— K8s Pod 内多容器互通的原理 # none 模式: 只有 lo docker run --rm --network none busybox ip addr # 1: lo ... （没有 eth0） 五、自定义网络：生产标配 默认 docker0 网络的痛点：容器间只能靠 IP 互访（没有 DNS），且不同网络完全隔离。\n# 创建 docker network create --driver bridge --subnet 172.20.0.0/16 mynet # 挂到网络上跑 docker run -d --name db --network mynet -e MYSQL_ROOT_PASSWORD=pass mysql:8.0 docker run -d --name app --network mynet myapp 自定义网络自带 DNS：\ndocker exec app ping -c2 db # 直接用容器名! docker exec app getent hosts db # 172.20.0.2 # 应用配置里写 db:3306 即可, 不用管 IP 多网络隔离：\ndocker network create frontend docker network create backend docker run -d --name web --network frontend nginx docker run -d --name api --network frontend --network backend myapi docker run -d --name db --network backend mysql:8.0 # web ↔ api 通, api ↔ db 通, web ↔ db 不通（按需连通） docker network inspect mynet # 网络详情: 容器列表/IP/网关 docker network connect mynet web # 给运行中的容器再接一个网络 docker network rm mynet # 删除（需先断开所有容器） 六、容器与外界通信全景 容器 → 外网 ✔ SNAT 改写源地址（bridge 模式开箱即用） 外网 → 容器 ✔ -p 端口映射（DNAT） 容器 → 宿主机 ✔ 网关地址: docker0 的 IP（172.17.0.1）或 host.docker.internal 宿主机 → 容器 ✔ 直接访问容器 IP（bridge 模式同宿主机可直连 172.17.0.2） 容器 ↔ 容器 ✔ 自定义网络 + DNS 容器名互访 一个容易被忽略的点——容器访问宿主机上的服务：\n# 宿主机服务必须监听 0.0.0.0 或 docker0 IP, 只听 127.0.0.1 则容器连不上 docker exec app curl http://172.17.0.1:9090/metrics 七、常见坑速查 现象 原因 容器间用容器名连不上 在默认 docker0 网络上（无 DNS），换自定义网络 -p 映射后外部不通 安全组/防火墙；或宿主机端口被占 容器重启后 IP 变了 正常现象，永远用 DNS 名不用 IP host 模式容器起不来 端口与宿主机冲突 172.17 网段与公司内网冲突 --subnet 自定义网段，或改 daemon.json 的 bip 删不掉网络 还有容器连着，docker network disconnect 小结 需求 方案 日常单容器对外服务 bridge + -p 多容器互通 自定义网络 + 容器名 DNS 网络性能敏感 host 模式（注意端口冲突） 按需隔离分组 多网络 + 按容器挂载 理解底层 veth pair + docker0 + iptables NAT 本文是「云原生」系列第 5 篇。\n","permalink":"https://antidebug.cn/posts/2024/06/docker-%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e容器一多，问题就从\u0026quot;跑起来\u0026quot;变成\u0026quot;怎么连\u0026quot;：容器之间怎么互访？端口映射的原理是什么？为什么生产推荐自定义网络？这篇把 Docker 网络讲透。\u003c/p\u003e","title":"Docker 网络模式详解"},{"content":"前言 Linux 防火墙经历了 iptables → nftables → firewalld（前端）的演进。现代 RHEL 系默认 firewalld（底层实为 nftables），但老机器和容器网络里 iptables 依旧无处不在。两个都要会。\n一、先搞清三层\u0026quot;墙\u0026quot; 外部流量 │ ▼ ① 云安全组（腾讯云/阿里云控制台, 机器之外） │ ▼ ② 系统防火墙（firewalld/iptables, 机器之内） │ ▼ ③ 应用自身监听（0.0.0.0 vs 127.0.0.1） 排查口子问题：从外到内逐层验证——安全组放了吗 → 系统防火墙放了吗 → 进程监听对吗。\n二、firewalld：zone 思维 firewalld 把规则按 zone（区域） 组织，每个网卡绑定一个 zone：\nfirewall-cmd --get-default-zone # 默认 public firewall-cmd --get-active-zones # 网卡与 zone 绑定关系 firewall-cmd --list-all # 当前 zone 全部规则 firewall-cmd --get-zones # 所有可用 zone zone 语义 public 公共区域，只放行指定服务（服务器默认） trusted 全放行 internal/drop/block 内网/全拒/拒绝带响应 服务与端口操作：\n# 放行标准服务（本质是 /usr/lib/firewalld/services/*.xml 里定义的端口组） firewall-cmd --add-service=http --permanent firewall-cmd --add-service=https --permanent # 放行自定义端口 firewall-cmd --add-port=8080/tcp --permanent firewall-cmd --add-port=60000-61000/udp --permanent # 端口转发（本机 80 -\u0026gt; 8080） firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080 --permanent # 任何 --permanent 的修改都要 reload 生效 firewall-cmd --reload # 注意: 不加 --permanent 只对运行时生效（重启丢失）; 调试时可先运行时放行 富规则：精细控制\n# 只允许特定网段访问 SSH firewall-cmd --permanent --add-rich-rule=\u0026#39; rule family=ipv4 source address=10.0.0.0/24 port port=22 protocol=tcp accept\u0026#39; # 封禁一个恶意 IP firewall-cmd --permanent --add-rich-rule=\u0026#39; rule family=ipv4 source address=1.2.3.4 reject\u0026#39; # 允许 ping firewall-cmd --permanent --add-rich-rule=\u0026#39;rule protocol value=icmp accept\u0026#39; # 端口转发到另一台机器（需配 masquerade） firewall-cmd --permanent --add-masquerade firewall-cmd --permanent --add-rich-rule=\u0026#39; rule family=ipv4 forward-port port=443 protocol=tcp to-addr=10.0.1.20\u0026#39; firewall-cmd --reload 三、iptables：表与链的世界 四表（处理优先级从高到低）：raw → mangle → nat → filter\n五链（规则挂载点）：\nPREROUTING ──► FORWARD ──► POSTROUTING （转发路径, nat 表主场） │ ▼ INPUT ──► [本机进程] ──► OUTPUT （本机进出, filter 表主场） 链 时机 PREROUTING 刚进网卡（DNAT 在这） INPUT 发给本机进程 FORWARD 经过本机转发（路由器/网关场景） OUTPUT 本机进程发出 POSTROUTING 即将发出网卡（SNAT 在这） 规则 = 匹配条件 + 动作：\n# 查看（-n 不解析域名, -v 带计数） iptables -L INPUT -n -v --line-numbers # 放行 80 iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 只允许内网访问 22 iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j DROP # 其余全拒（规则从上到下匹配!） # 封 IP iptables -I INPUT -s 1.2.3.4 -j DROP # -I 插到最前（优先级最高） # 删除第 3 条规则 iptables -D INPUT 3 # 已建立连接的回包放行（状态防火墙精髓） iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 允许本机回环 iptables -A INPUT -i lo -j ACCEPT 动作速记：ACCEPT 放行 / DROP 丢弃（无响应）/ REJECT 拒绝（回拒绝报文）。\nNAT 实战（做一台小路由器/网关）：\n# 开启转发 echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward # SNAT: 内网 10.0.0.0/24 出公网（网卡 eth0 的 IP） iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE # DNAT: 公网访问本机 8443 转给内网机器 443 iptables -t nat -A PREROUTING -p tcp --dport 8443 -j DNAT --to-destination 10.0.1.20:443 MASQUERADE 是动态版 SNAT（出口 IP 会变时用，如拨号/DHCP）。\n四、保存与持久化 # iptables 规则默认重启即失 iptables-save \u0026gt; /etc/sysconfig/iptables # RHEL 系保存 iptables-restore \u0026lt; /etc/sysconfig/iptables # 恢复 # firewalld 自动持久（--permanent 写入 /etc/firewalld/zones/） ls /etc/firewalld/zones/ 五、firewalld vs iptables vs nftables firewalld iptables nftables 定位 管理前端（规则易读易管理） 经典工具 内核新框架（iptables 继任者） 规则修改 增量（reload 不断连接） 全量替换 增量 适合 日常服务器运维 老系统/精确控制/K8s 网络底层 新项目/高性能场景 Docker/K8s（kube-proxy iptables 模式）会直接操作 iptables 规则——这就是装了 Docker 后 iptables -L 多出一堆链的原因，别手删。\n六、排障速查 # 端口到底谁没放行? # 1. 计数器证据: 每条规则有 pkts 计数, 看是否命中 iptables -L INPUT -nv | grep 8080 # 2. 抓包确认包到了没 tcpdump -i any -nn port 8080 -c 10 # 3. firewalld panic 模式（全断! 误开排查） firewall-cmd --query-panic # 4. 规则对了还不通: 检查内核转发与安全组 sysctl net.ipv4.ip_forward 小结 任务 命令 放行端口 firewall-cmd --add-port=8080/tcp --permanent \u0026amp;\u0026amp; firewall-cmd --reload 白名单 rich rule + source address= 封 IP rich rule reject 或 iptables -I INPUT -s x -j DROP NAT 上网 MASQUERADE + ip_forward 排障 规则计数器 + tcpdump + 别忘安全组 本文是「Linux 运维」系列第 7 篇。\n","permalink":"https://antidebug.cn/posts/2024/05/linux-%E9%98%B2%E7%81%AB%E5%A2%99firewalld-%E4%B8%8E-iptables/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eLinux 防火墙经历了 iptables → nftables → firewalld（前端）的演进。现代 RHEL 系默认 \u003cstrong\u003efirewalld\u003c/strong\u003e（底层实为 nftables），但老机器和容器网络里 iptables 依旧无处不在。两个都要会。\u003c/p\u003e","title":"Linux 防火墙：firewalld 与 iptables"},{"content":"前言 Go 从语言层面为并发而生：go 关键字一行起\u0026quot;线程\u0026quot;，channel 让协程间安全通信。口号是——不要通过共享内存来通信，而要通过通信来共享内存。\n一、goroutine：廉价的并发单元 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func say(msg string) { for i := 0; i \u0026lt; 3; i++ { fmt.Println(msg, i) time.Sleep(100 * time.Millisecond) } } func main() { go say(\u0026#34;world\u0026#34;) // go 关键字: 并发执行 say(\u0026#34;hello\u0026#34;) // 当前协程继续跑 } goroutine 的\u0026quot;轻\u0026quot;：\n维度 OS 线程 goroutine 栈初始大小 1~8 MB 固定 2KB 起步、按需增长 创建/切换成本 系统调用，微秒级 用户态调度，纳秒级 单机数量级 数千 数十万~百万 调度由 Go 运行时的 GMP 模型完成（G=goroutine，M=内核线程，P=逻辑处理器），此处知道存在即可。\n⚠️ main 退出 = 所有 goroutine 立即死亡（不管跑没跑完），所以需要等待机制 → WaitGroup。\n二、WaitGroup：等所有协程干完 var wg sync.WaitGroup for _, url := range urls { wg.Add(1) // 计数 +1（必须在 go 之前!） go func(u string) { // 注意: 参数传值, 避免闭包变量陷阱 defer wg.Done() // 干完 -1 fetch(u) }(url) } wg.Wait() // 阻塞到计数归零 经典闭包陷阱（Go 1.22 前 for 循环变量复用）：\nfor _, url := range urls { go func() { fmt.Println(url) // ❌ 老版本: 全打印最后一个! }() } // 解法1: url := url 影子变量 // 解法2: 像上面那样通过参数传入 // Go 1.22+ 循环变量已是每轮独立, 但老代码里这坑遍地 三、channel：协程间的高速公路 // 无缓冲: 发送阻塞直到有人接收（同步, 交接班） ch := make(chan int) // 有缓冲: 装满才阻塞（异步, 邮筒） ch := make(chan int, 10) ch \u0026lt;- 42 // 发送 v := \u0026lt;-ch // 接收 close(ch) // 关闭（发送方关!） 生产者-消费者：\nfunc producer(ch chan\u0026lt;- int) { // chan\u0026lt;- 只写 for i := 0; i \u0026lt; 5; i++ { ch \u0026lt;- i } close(ch) // 没有更多数据了 } func consumer(ch \u0026lt;-chan int) { // \u0026lt;-chan 只读 for v := range ch { // 循环接收, channel 关闭且排空后自动退出 fmt.Println(\u0026#34;got\u0026#34;, v) } } func main() { ch := make(chan int, 3) go producer(ch) consumer(ch) } 关闭规则：\n由发送方关闭，接收方永远不要 close 关闭后：接收先拿完剩余值，然后得到零值 v, ok := \u0026lt;-ch：ok == false 表示已关闭且取空 向已关闭的 channel 发送 → panic 关闭 nil channel → panic 四、select：多路复用 select { case v := \u0026lt;-chA: fmt.Println(\u0026#34;A:\u0026#34;, v) case chB \u0026lt;- 42: fmt.Println(\u0026#34;发送到 B 成功\u0026#34;) case \u0026lt;-time.After(3 * time.Second): // 超时控制（高频用法!） fmt.Println(\u0026#34;超时\u0026#34;) return } 多个 case 同时就绪 → 随机选一个（防饿死） default 分支：都 不就绪时立即走（非阻塞收发） // 非阻塞发送 select { case ch \u0026lt;- msg: default: // 满了就丢弃, 不卡住 log.Println(\u0026#34;丢弃\u0026#34;, msg) } 超时控制实战：\nfunc fetchWithTimeout(url string) error { done := make(chan error, 1) // 缓冲1: goroutine 不泄漏 go func() { done \u0026lt;- realFetch(url) }() select { case err := \u0026lt;-done: return err case \u0026lt;-time.After(2 * time.Second): return fmt.Errorf(\u0026#34;timeout: %s\u0026#34;, url) } } 五、sync 包三剑客 // Mutex: 保护共享数据（计数器、map 等） var ( mu sync.Mutex counter int ) func inc() { mu.Lock() defer mu.Unlock() // defer 保证解锁, 死锁保险 counter++ } // RWMutex: 读多写少场景（读锁共享, 写锁独占） var rwmu sync.RWMutex func readConfig() string { rwmu.RLock() defer rwmu.RUnlock() return conf } // Once: 只执行一次（单例、初始化） var once sync.Once var conn *Conn func getConn() *Conn { once.Do(func() { conn = dial() // 无论并发调用多少次, 只 dial 一次 }) return conn } 原则：能用 channel 表达的用 channel（传递数据所有权），需要保护共享状态时用 Mutex。两者不是对立而是互补。\n六、常见死锁与泄漏 死锁现场一：无缓冲 channel 自发自收\nch := make(chan int) ch \u0026lt;- 1 // ❌ 永久阻塞: 没有接收者, fatal error: all goroutines are asleep 死锁现场二：互斥锁循环等待\n// goroutine1: mu1.Lock() -\u0026gt; 要 mu2 // goroutine2: mu2.Lock() -\u0026gt; 要 mu1 💀 // 预防: 多把锁永远按相同顺序加锁 泄漏现场：goroutine 阻塞在发送，无人接收\nfunc leak() { ch := make(chan int) // ❌ 无缓冲 go func() { ch \u0026lt;- 42 }() // 若没人收, 这个 goroutine 永远卡着 // ... 提前 return 了 } // ✅ 解法: make(chan int, 1) 或用 context 取消（后续专篇） 排查工具：\ngo run -race main.go # 竞态检测器, CI 必开 go test -race ./... 七、实战：并发抓取器 func fetchAll(urls []string) map[string]string { var ( mu sync.Mutex wg sync.WaitGroup results = make(map[string]string, len(urls)) ) for _, u := range urls { wg.Add(1) go func(u string) { defer wg.Done() body := fetchOne(u) // 返回摘要 mu.Lock() results[u] = body mu.Unlock() }(u) } wg.Wait() return results } 10 个 URL 并行抓取，总耗时 ≈ 最慢的那个，而不是总和。\n小结 组件 用途 go f() 起协程 WaitGroup 等一组协程结束 channel 协程间传数据，关闭由发送方做 select 多路等待 + 超时/非阻塞 Mutex/RWMutex 保护共享内存 -race 上线前必跑 本文是「Golang」系列第 5 篇。\n","permalink":"https://antidebug.cn/posts/2024/05/golang-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8Bgoroutine-%E4%B8%8E-channel/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eGo 从语言层面为并发而生：\u003ccode\u003ego\u003c/code\u003e 关键字一行起\u0026quot;线程\u0026quot;，channel 让协程间安全通信。口号是——\u003cstrong\u003e不要通过共享内存来通信，而要通过通信来共享内存\u003c/strong\u003e。\u003c/p\u003e","title":"Golang 并发编程：goroutine 与 channel"},{"content":"前言 程序出错是常态，文件处理是日常。这两块写优雅了，脚本的质量直接上一个档次。\n一、异常基础 try: result = 10 / int(user_input) except ValueError as e: # 精确捕获, 别裸 except! print(f\u0026#34;输入不是数字: {e}\u0026#34;) except ZeroDivisionError: print(\u0026#34;除数不能为零\u0026#34;) else: print(f\u0026#34;结果: {result}\u0026#34;) # 无异常才执行（放成功逻辑, try 块保持精简） finally: print(\u0026#34;总是执行\u0026#34;) # 清理逻辑（关连接等） 异常层次结构（理解它就不会乱捕获）：\nBaseException ├── SystemExit # sys.exit() ├── KeyboardInterrupt # Ctrl+C └── Exception # ← 日常异常的共同祖先 ├── ValueError ├── TypeError ├── KeyError / IndexError ├── FileNotFoundError / PermissionError (OSError 子类) ├── ConnectionError / TimeoutError (OSError 子类) └── ... 二、反模式与正确姿势 # ❌ 反模式1: 裸 except 吞掉一切（连 Ctrl+C 都吞） try: main() except: pass # ❌ 反模式2: 捕获过宽 try: parse(cfg) except Exception: ... # ✅ 姿势: 捕获你预期的、能处理的 try: parse(cfg) except (ValueError, KeyError) as e: logger.error(\u0026#34;配置格式错误: %s\u0026#34;, e) raise # 处理不了就继续往上抛 raise 的三种用法：\nraise # 重新抛出当前异常（保留现场） raise RuntimeError(\u0026#34;bad state\u0026#34;) from e # 异常链: 换类型但保留根因 raise RuntimeError(f\u0026#34;port {p} 非法\u0026#34;) # 抛新异常 三、自定义异常 class AppError(Exception): \u0026#34;\u0026#34;\u0026#34;应用基础异常\u0026#34;\u0026#34;\u0026#34; class ConfigError(AppError): def __init__(self, file: str, reason: str): super().__init__(f\u0026#34;config {file}: {reason}\u0026#34;) self.file, self.reason = file, reason def load_config(path): if not os.path.exists(path): raise ConfigError(path, \u0026#34;文件不存在\u0026#34;) # 调用方可以按层次精确处理 try: load_config(\u0026#34;/etc/myapp/app.conf\u0026#34;) except ConfigError as e: print(e, e.file) # 额外属性携带上下文 经验法则：写库/框架时定义异常基类；写脚本时 Exception 信息带足上下文即可。\n四、文件操作：with 是唯一正确姿势 # ❌ 老写法: 忘了 close 或中途异常泄漏句柄 f = open(\u0026#34;data.txt\u0026#34;) data = f.read() f.close() # ✅ with: 无论是否异常都自动关闭 with open(\u0026#34;data.txt\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: data = f.read() 常用模式：\n# 读: 一次 vs 逐行（大文件必逐行!） with open(\u0026#34;access.log\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: content = f.read() # 小文件 with open(\u0026#34;access.log\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: for line in f: # 流式读取, 内存友好 if \u0026#34;ERROR\u0026#34; in line: ... # 写: 覆盖 / 追加 with open(\u0026#34;out.txt\u0026#34;, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: f.write(\u0026#34;hello\\n\u0026#34;) with open(\u0026#34;out.txt\u0026#34;, \u0026#34;a\u0026#34;) as f: f.write(\u0026#34;more\\n\u0026#34;) # 编码永远显式声明! Windows 默认 GBK, 跨机器就乱码 五、pathlib：路径操作现代化 from pathlib import Path p = Path(\u0026#34;/var/log/myapp/app.log\u0026#34;) p.name # \u0026#39;app.log\u0026#39; p.stem # \u0026#39;app\u0026#39; p.suffix # \u0026#39;.log\u0026#39; p.parent # Path(\u0026#39;/var/log/myapp\u0026#39;) p.exists(), p.is_file(), p.is_dir() # 拼接（用 / 运算符, 跨平台） conf = Path(\u0026#34;/etc\u0026#34;) / \u0026#34;myapp\u0026#34; / \u0026#34;app.conf\u0026#34; # 遍历 for f in Path(\u0026#34;/var/log\u0026#34;).glob(\u0026#34;*.log\u0026#34;): print(f) # 建目录 / 读写文本（pathlib 自带快捷方法!） Path(\u0026#34;/opt/app/data\u0026#34;).mkdir(parents=True, exist_ok=True) cfg = conf.read_text(encoding=\u0026#34;utf-8\u0026#34;) conf.write_text(\u0026#34;debug=false\\n\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) # 安全删除 import shutil shutil.rmtree(\u0026#34;/tmp/build\u0026#34;) # 删目录树 六、结构化数据：JSON 与 CSV import json from pathlib import Path # 读写 JSON conf = {\u0026#34;host\u0026#34;: \u0026#34;0.0.0.0\u0026#34;, \u0026#34;port\u0026#34;: 8080, \u0026#34;debug\u0026#34;: False} Path(\u0026#34;conf.json\u0026#34;).write_text( json.dumps(conf, indent=2, ensure_ascii=False), # 中文不转义 encoding=\u0026#34;utf-8\u0026#34;, ) loaded = json.loads(Path(\u0026#34;conf.json\u0026#34;).read_text(encoding=\u0026#34;utf-8\u0026#34;)) import csv # 读写 CSV with open(\u0026#34;hosts.csv\u0026#34;, newline=\u0026#34;\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: rows = list(csv.DictReader(f)) # [{name:..., ip:...}, ...] with open(\u0026#34;out.csv\u0026#34;, \u0026#34;w\u0026#34;, newline=\u0026#34;\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: w = csv.DictWriter(f, fieldnames=[\u0026#34;name\u0026#34;, \u0026#34;ip\u0026#34;]) w.writeheader() w.writerows([{\u0026#34;name\u0026#34;: \u0026#34;web01\u0026#34;, \u0026#34;ip\u0026#34;: \u0026#34;10.0.1.11\u0026#34;}]) 七、实战：健壮的配置加载器 综合运用本篇知识点：\nimport json from pathlib import Path class ConfigError(Exception): pass def load_config(path: str) -\u0026gt; dict: p = Path(path) try: raw = p.read_text(encoding=\u0026#34;utf-8\u0026#34;) except FileNotFoundError: raise ConfigError(f\u0026#34;配置文件不存在: {p}\u0026#34;) from None except PermissionError: raise ConfigError(f\u0026#34;无权读取: {p}\u0026#34;) from None try: conf = json.loads(raw) except json.JSONDecodeError as e: raise ConfigError(f\u0026#34;JSON 语法错误 (行{e.lineno}): {e.msg}\u0026#34;) from None # 必填字段校验 for key in (\u0026#34;host\u0026#34;, \u0026#34;port\u0026#34;): if key not in conf: raise ConfigError(f\u0026#34;缺少必填字段: {key}\u0026#34;) return conf if __name__ == \u0026#34;__main__\u0026#34;: import logging try: print(load_config(\u0026#34;/etc/myapp/app.json\u0026#34;)) except ConfigError as e: logging.error(\u0026#34;启动失败: %s\u0026#34;, e) raise SystemExit(1) 小结 场景 姿势 捕获 精确类型 + as e，处理不了就 raise 清理 finally 或更好的 with 文件 永远 with open(..., encoding=\u0026quot;utf-8\u0026quot;) 路径 pathlib.Path + / 拼接 大文件 逐行迭代，别 read() 全量 自定义异常 继承 Exception，信息带上下文 本文是「Python」系列第 5 篇。\n","permalink":"https://antidebug.cn/posts/2024/04/python-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E4%B8%8E%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e程序出错是常态，文件处理是日常。这两块写优雅了，脚本的质量直接上一个档次。\u003c/p\u003e\n\u003ch2 id=\"一异常基础\"\u003e一、异常基础\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eresult\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser_input\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e            \u003cspan class=\"c1\"\u003e# 精确捕获, 别裸 except!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;输入不是数字: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eZeroDivisionError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;除数不能为零\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;结果: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e       \u003cspan class=\"c1\"\u003e# 无异常才执行（放成功逻辑, try 块保持精简）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efinally\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;总是执行\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e               \u003cspan class=\"c1\"\u003e# 清理逻辑（关连接等）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e异常层次结构\u003c/strong\u003e（理解它就不会乱捕获）：\u003c/p\u003e","title":"Python 异常处理与文件操作"},{"content":"前言 单机 Docker 跑十几个容器还行，成百上千个跨几十台机器呢？谁挂了谁来重启？怎么滚动升级不中断服务？Kubernetes（K8s）就是容器世界的\u0026quot;操作系统\u0026quot;，负责调度、自愈、扩缩容。\n一、为什么需要 K8s 痛点 K8s 的答案 容器挂了没人管 自愈：自动重新拉起（重启/换节点） 手动挑机器部署 调度：按资源请求自动选择节点 发布中断服务 滚动更新：新旧副本渐进切换，随时回滚 流量打到哪个容器 服务发现与负载均衡（Service） 配置改了要重启 配置中心（ConfigMap/Secret 热更新） 高峰扩容、低谷缩容 HPA 自动扩缩容 二、架构：控制面 + 工作节点 ┌─────────────── 控制平面 (Control Plane) ───────────────┐ │ API Server ── 唯一入口, 所有增删改查走 REST/kubectl │ │ etcd ──────── 集群大脑: 存储全部期望状态(键值库) │ │ Scheduler ─── 决定新 Pod 落在哪个节点 │ │ Controller ── 控制循环: 不断对齐\u0026#34;期望状态 vs 实际状态\u0026#34; │ └───────────────────────────────────────────────────────┘ │ 监听/上报 ┌───────────▼─────────────── 工作节点 (Node) ──────────────┐ │ kubelet ──── 节点管家: 管理本节点容器生命周期 │ │ kube-proxy ─ 网络规则: 让 Service 的 VIP 能转发到 Pod │ │ 容器运行时 ── containerd（实际跑容器的进程） │ └────────────────────────────────────────────────────────┘ 核心思想：声明式 API + 控制循环\n你只声明\u0026quot;我要 3 个副本的 nginx\u0026quot;（写 YAML），控制器持续对比现状并修复差距——挂一个补一个。这与传统\u0026quot;一步步执行命令\u0026quot;的命令式运维是范式级差异。\nkubectl get pods # 万物皆可 get kubectl describe pod xxx # 排障第一命令 kubectl logs -f xxx # 看日志 kubectl exec -it xxx -- sh # 进容器 三、核心对象（由小到大） Pod：最小调度单元 apiVersion: v1 kind: Pod metadata: name: web spec: containers: - name: nginx image: nginx:1.25 resources: requests: {cpu: 100m, memory: 128Mi} # 调度依据 limits: {cpu: 500m, memory: 256Mi} # 上限(超限可能被 OOM 杀) 一个 Pod = 一组同生共死、共享网络/存储的容器（多数 Pod 只有一个主容器） Pod 是易逝的（牛，不是宠物）——IP 会变，所以不能直接对外 Deployment：管理 Pod 的副本与升级 apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 3 # 期望副本数 selector: matchLabels: {app: web} template: # Pod 模板 metadata: labels: {app: web} # 打标签, selector 靠它认领 spec: containers: - name: web image: reg.local/myapp:v1.2.0 ports: [{containerPort: 8080}] livenessProbe: # 存活检查: 失败则重启容器 httpGet: {path: /healthz, port: 8080} readinessProbe: # 就绪检查: 未就绪不接流量 httpGet: {path: /ready, port: 8080} Deployment 能力：副本控制、滚动更新、回滚、暂停发布。\nkubectl rollout status deploy/web kubectl rollout undo deploy/web # 一键回滚 kubectl scale deploy/web --replicas=5 # 手动扩容 kubectl set image deploy/web web=reg.local/myapp:v1.3.0 Service：稳定的访问入口 Pod IP 随时会变，Service 提供固定虚拟 IP（VIP）+ 域名，把流量负载均衡到后端 Pod：\napiVersion: v1 kind: Service metadata: name: web spec: selector: {app: web} # 认领带此标签的 Pod ports: - port: 80 # Service 端口 targetPort: 8080 # 转发到容器的端口 三种类型: ClusterIP 集群内访问（默认）: http://web.default.svc.cluster.local NodePort 每个节点开一个高位端口: 节点IP:30080 LoadBalancer 云厂商分配公网 LB（付费但省心） Ingress：七层入口（域名/路由/TLS） apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: blog spec: rules: - host: antidebug.cn http: paths: - path: / pathType: Prefix backend: service: name: web port: {number: 80} 集群只需一个 LoadBalancer 暴露 Ingress Controller，内部靠域名路由到不同 Service。\nConfigMap / Secret：配置与敏感信息 apiVersion: v1 kind: ConfigMap metadata: name: app-conf data: APP_ENV: \u0026#34;production\u0026#34; nginx.conf: | server { listen 80; } --- # Secret 同理, 值是 base64（编码≠加密, 生产用外部密钥管理） apiVersion: v1 kind: Secret metadata: name: db-cred stringData: password: \u0026#34;s3cret\u0026#34; # 挂进 Pod: 环境变量 或 文件 env: - name: APP_ENV valueFrom: {configMapKeyRef: {name: app-conf, key: APP_ENV}} volumeMounts: [{name: conf, mountPath: /etc/app}] volumes: - name: conf configMap: {name: app-conf} 命名空间：逻辑分区 kubectl get ns kubectl -n prod get pods # 生产命名空间下的 Pod 按环境或团队划分，配 ResourceQuota 限额。\n四、一个应用的完整上云之路 # 1. 镜像先行（上篇的 Dockerfile 产物） docker build -t reg.local/myapp:v1.0 . \u0026amp;\u0026amp; docker push reg.local/myapp:v1.0 # 2. 写 YAML（Deployment + Service + ConfigMap） kubectl apply -f app.yaml # 3. 观察 kubectl get pods -w kubectl rollout status deploy/web # 4. 升级 kubectl set image deploy/web web=reg.local/myapp:v1.1 kubectl rollout undo deploy/web # 有问题秒回滚 # 5. 对外暴露 kubectl apply -f ingress.yaml curl http://antidebug.cn/ 五、学习建议 先用 kind/minikube 起个单机集群练手：kind create cluster 把上面每个对象各写一遍 YAML，describe 看事件 别背概念，盯住一条流量路径：Ingress → Service → Pod → 容器端口 排障三板斧：describe（Events 段）、logs、exec 小结 对象 一句话 Pod 最小调度单元，易逝 Deployment 副本管理 + 滚动升级 Service 稳定 VIP + 负载均衡 Ingress 域名/路由/TLS 的七层入口 ConfigMap/Secret 配置与密钥 声明式 说\u0026quot;要什么\u0026quot;，控制器负责\u0026quot;怎么做\u0026quot; 本文是「云原生」系列第 4 篇，下篇动手把应用部署进 K8s。\n","permalink":"https://antidebug.cn/posts/2024/04/kubernetes-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E5%85%A5%E9%97%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e单机 Docker 跑十几个容器还行，成百上千个跨几十台机器呢？谁挂了谁来重启？怎么滚动升级不中断服务？\u003cstrong\u003eKubernetes（K8s）就是容器世界的\u0026quot;操作系统\u0026quot;\u003c/strong\u003e，负责调度、自愈、扩缩容。\u003c/p\u003e","title":"Kubernetes 核心概念入门"},{"content":"前言 \u0026ldquo;服务器好卡\u0026quot;是运维最常收到的一句话。定位性能瓶颈的标准流程：先看全局（top），再分维度下钻（CPU/内存/IO/网络）。这篇讲前三者的经典工具。\n一、top：全局快照 top # 按 P 按 CPU 排序, M 按内存, 1 展开各核心, c 完整命令 第一行关键指标：\nload average: 4.20, 3.10, 2.50 1/5/15 分钟平均负载。经验值：load 持续 \u0026gt; CPU 核数 = 过载。4 核机器 load 4.2，说明已经排队。\nCPU 行（%Cpu(s)）：\n指标 含义 异常解读 us 用户态 CPU 高 = 应用在烧 CPU（正常业务 or 死循环） sy 内核态 CPU 高 = 系统调用/上下文切换频繁（IO、锁竞争） id 空闲 低 = CPU 不够用 wa IO 等待 高 = 磁盘是瓶颈（CPU 在等 IO） st 被宿主机偷走 云主机独有：宿主机超卖，别的 VM 抢资源 内存行（KiB Mem）：\ntotal 4G, free 200M, buff/cache 2G, avail 3.2G 看内存够不够，看 avail 而不是 free——buff/cache 是文件缓存，应用需要时秒让出 swap used 持续增长 = 物理内存真不够了 进程列表关注：%CPU、%MEM、RES（实际物理内存）、S（状态，大量 D 态查 IO）。\n二、vmstat：CPU 与 IO 的动态趋势 vmstat 1 5 # 每 1 秒采样, 共 5 次（一定要看趋势, 单次无意义） procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 0 0 200000 300000 2000000 0 0 10 20 500 800 15 5 80 0 0 8 2 0 150000 300000 1900000 0 0 8000 200 9000 12000 60 20 15 5 0 procs：\nr：等待 CPU 的进程数（run queue）——r 持续大于核数 = CPU 瓶颈 b：阻塞在不可中断 IO 的进程数——b 大 = IO 瓶颈 swap：si/so（换入/换出）——非零 = 内存不足，开始拿磁盘当内存用，性能断崖。\nio：bi/bo（块设备读/写，单位 块/s ≈ KB/s）——数值大且 wa 高，就是磁盘忙。\nsystem：in（中断/s）、cs（上下文切换/s）——几万级 cs + sy 高，常见于频繁系统调用或线程风暴。\n黄金十字：r 高 → CPU 瓶颈；b 高 + wa 高 → IO 瓶颈；si/so 非零 → 内存瓶颈。\n三、iostat：磁盘维度 dnf install -y sysstat iostat -x 1 3 # 扩展视图, 每 1 秒一次 Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await vda 50.0 200.0 800 3200 0.0 10.0 95.20 12.50 指标 含义 解读 r/s w/s 每秒读/写次数（IOPS） 与盘的规格上限对比 rkB/s wkB/s 吞吐量 顺序大文件看这个 await 每次 IO 平均耗时(ms) SSD \u0026gt;5ms、机械 \u0026gt;15ms 就算高 %util 设备繁忙度 接近 100% = 盘打满（NVMe 并发高时 util 100% 不一定饱和，结合 await 看） # 找出哪个进程在狂写盘（iotop 交互查看） dnf install -y iotop iotop -oP # 只显示有 IO 的进程 # 或用 pidstat 按进程看 IO/CPU/内存（低侵入, 适合采集） pidstat -d 1 3 # 磁盘 pidstat -u 1 3 # CPU pidstat -r 1 3 # 内存 四、内存：free 的正确读法 free -h total used free shared buff/cache available Mem: 3.8Gi 1.2Gi 200Mi 10Mi 2.4Gi 2.3Gi Swap: 2.0Gi 0Bi 2.0Gi available 才是\u0026quot;还能给应用多少\u0026rdquo; buff/cache 高是好事（Linux 拿空闲内存做缓存加速） 判断内存压力：sar -B 看 majflt/s（缺页），或 vmstat 的 si/so 五、综合实战：一次\u0026quot;卡顿\u0026quot;排查 # 1. 全局看一眼 top # → load 8, %wa 40, r=2, b=6 指向 IO # 2. vmstat 确认趋势 vmstat 1 5 # → b 列持续 \u0026gt;0, bo 写入 30MB/s, si/so=0（内存没事） # 3. iostat 锁定磁盘 iostat -x 1 3 # → vda %util 98, await 25ms 磁盘打满 # 4. 找凶手进程 iotop -oP # → mysqld 占 80% 写入 # 5. 下钻应用层 # → 慢查询? 一次性 update 亿级大表? binlog 刷盘策略? 五分钟定位方向：top 定维度 → vmstat 定方向 → iostat/iotop 定设备与进程 → 应用层定根因。\n六、工具地图（USE 方法） 资源 × 三视角（Utilization 使用率 / Saturation 饱和度 / Errors 错误）：\n资源 首选工具 CPU top vmstat pidstat -u mpstat -P ALL 内存 free -h vmstat(si/so) sar -B 磁盘 iostat -x iotop pidstat -d 网络 ss iftop nethogs（后续网络篇展开） 全能 sar（历史数据回放）dstat 小结 load \u0026gt; 核数 = 过载；wa 高查磁盘；st 高是宿主机超卖 vmstat 看趋势：r 高 CPU 瓶颈、b 高 IO 瓶颈、si/so 非零内存告急 iostat：await 看延迟、%util 看饱和 free 看 available，不是 free 排障路径：top → vmstat → 定向工具 → 应用层 本文是「Linux 运维」系列第 6 篇。\n","permalink":"https://antidebug.cn/posts/2024/03/linux-%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E5%85%A5%E9%97%A8topvmstatiostat/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;服务器好卡\u0026quot;是运维最常收到的一句话。定位性能瓶颈的标准流程：\u003cstrong\u003e先看全局（top），再分维度下钻（CPU/内存/IO/网络）\u003c/strong\u003e。这篇讲前三者的经典工具。\u003c/p\u003e","title":"Linux 性能分析入门：top、vmstat、iostat"},{"content":"前言 docker run 只是开始，把应用做成自己可控的镜像才算入门容器化。同样的应用，Dockerfile 写法不同，镜像可以从 1GB 到 20MB——这篇讲怎么写出又小又快又安全的镜像。\n一、基础指令速览 FROM python:3.11-slim # 基础镜像（一切的地基） WORKDIR /app # 工作目录（不存在会自动创建） COPY app.py requirements.txt ./ # 拷贝文件（比 ADD 更纯粹, 推荐） RUN pip install -r requirements.txt # 构建期执行命令 ENV TZ=Asia/Shanghai # 环境变量 EXPOSE 8000 # 声明端口（文档性质） CMD [\u0026#34;python\u0026#34;, \u0026#34;app.py\u0026#34;] # 容器启动命令（可被 run 参数覆盖） ENTRYPOINT [\u0026#34;./server\u0026#34;] # 入口（不易被覆盖, 与 CMD 配合） CMD vs ENTRYPOINT：\nENTRYPOINT [\u0026#34;python\u0026#34;, \u0026#34;app.py\u0026#34;] CMD [\u0026#34;--port\u0026#34;, \u0026#34;8000\u0026#34;] # CMD 变成传给 ENTRYPOINT 的默认参数 # docker run myimg -\u0026gt; python app.py --port 8000 # docker run myimg --port 9000 -\u0026gt; python app.py --port 9000 COPY vs ADD：日常一律 COPY；只有需要自动解压 tar（ADD app.tar.gz /opt/）或加远程 URL（不推荐）时才用 ADD。\nshell 格式 vs exec 格式：\nCMD python app.py # shell 格式: 实际执行 /bin/sh -c \u0026#34;python app.py\u0026#34;, PID1 是 sh CMD [\u0026#34;python\u0026#34;, \u0026#34;app.py\u0026#34;] # exec 格式: python 直接是 PID1 ✅（信号能正确传递） 二、分层缓存：指令顺序的艺术 每条指令生成一层，层有缓存；某层失效，其后所有层全部重建。\n# ❌ 错误示范：改一行代码, 依赖重装一遍 COPY . /app RUN pip install -r /app/requirements.txt # ✅ 正确：先拷依赖清单装依赖（很少变, 缓存命中）, 再拷代码 COPY requirements.txt . RUN pip install -r requirements.txt COPY . . 从上到下按变动频率从低到高排列：基础镜像 → 系统依赖 → 语言依赖 → 源代码。\n三、多阶段构建：瘦身的王牌 编译期需要的工具链（JDK、Go 工具链、node_modules）运行期完全不需要。多阶段构建只把产物带走：\nGo 应用（1GB+ → 20MB 以内） # ---------- 阶段1: 构建 ---------- FROM golang:1.22 AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download # 依赖层缓存 COPY . . RUN CGO_ENABLED=0 go build -ldflags=\u0026#34;-s -w\u0026#34; -o /out/server . # ---------- 阶段2: 运行 ---------- FROM alpine:3.19 RUN adduser -D -u 10001 app \u0026amp;\u0026amp; apk add --no-cache tzdata ca-certificates COPY --from=builder /out/server /usr/local/bin/server USER app ENTRYPOINT [\u0026#34;server\u0026#34;] 甚至可以 FROM scratch（空镜像），Go 静态编译的程序直接跑，最终镜像 ≈ 二进制本身大小。\nPython 应用 FROM python:3.11-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 FROM base AS builder WORKDIR /wheels COPY requirements.txt . RUN pip wheel --no-cache-dir -w . -r requirements.txt FROM base WORKDIR /app COPY --from=builder /wheels /wheels RUN pip install --no-cache-dir /wheels/*.whl \u0026amp;\u0026amp; rm -rf /wheels COPY . . RUN useradd -m appuser USER appuser EXPOSE 8000 CMD [\u0026#34;gunicorn\u0026#34;, \u0026#34;app:app\u0026#34;, \u0026#34;-b\u0026#34;, \u0026#34;0.0.0.0:8000\u0026#34;] 四、最佳实践清单 镜像体积 选 slim/alpine 基础镜像（python:3.11-slim 而非 python:3.11） apt install 后清理：rm -rf /var/lib/apt/lists/* 多阶段构建，不带构建工具链 .dockerignore 排除无关文件（.git、node_modules、.env、测试数据） # .dockerignore .git .venv __pycache__ *.pyc .env tests/ docs/ *.md 可复现性 基础镜像锁 digest：FROM python@sha256:abc123...（安全级别要求时） 不用 latest 标签 依赖清单锁版本（requirements.txt 带 ==、go.sum） 安全 非 root 运行（上面示例的 adduser/useradd） 不在镜像里放密钥：COPY .env . 是重大事故源头 最小化安装：不需要的包不装（减少攻击面） 镜像扫描：docker scout / Trivy # Trivy 扫描漏洞 trivy image myapp:v1.2.0 运行健康 日志输出到 stdout/stderr（交给 docker logs / 收集器） 优雅退出：程序处理 SIGTERM，配合 docker stop -t 30 定义 HEALTHCHECK（编排系统会更早发现问题） HEALTHCHECK --interval=30s --timeout=3s --retries=3 \\ CMD wget -qO- http://127.0.0.1:8000/health || exit 1 五、构建与推送 # 构建（注意最后的点 = 构建上下文路径） docker build -t myapp:v1.2.0 . # 看构建过程与缓存命中 docker build --progress=plain -t myapp:v1.2.0 . # 对比优化前后体积 docker images | grep myapp # 推送私有仓库（如 Harbor） docker tag myapp:v1.2.0 reg.local/team/myapp:v1.2.0 docker push reg.local/team/myapp:v1.2.0 六、常见坑 现象 原因 改代码构建依然用缓存 COPY 上下文没变化 / .dockerignore 排除了文件 容器时区不对 加 ENV TZ=Asia/Shanghai + 装 tzdata docker stop 要等 10s PID1 是 sh 没转发信号，用 exec 格式 CMD 镜像越滚越大 历史层里有垃圾，用多阶段构建根除 容器里连不上外网 检查 DNS：docker run --rm busybox nslookup baidu.com 小结 原则 手段 快 分层缓存 + 指令按变动频率排序 小 多阶段构建 + slim 基础镜像 + .dockerignore 安全 非 root + 不放密钥 + 锁版本 + 扫描 可靠 stdout 日志 + 信号处理 + HEALTHCHECK 本文是「云原生」系列第 3 篇。\n","permalink":"https://antidebug.cn/posts/2024/03/dockerfile-%E7%BC%96%E5%86%99%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003edocker run\u003c/code\u003e 只是开始，把应用做成\u003cstrong\u003e自己可控的镜像\u003c/strong\u003e才算入门容器化。同样的应用，Dockerfile 写法不同，镜像可以从 1GB 到 20MB——这篇讲怎么写出又小又快又安全的镜像。\u003c/p\u003e","title":"Dockerfile 编写最佳实践"},{"content":"前言 Python 里\u0026quot;一切皆对象\u0026quot;，但很多同学写脚本一直用不上自定义类。这篇把 OOP 核心概念讲到位，同时告诉你 Pythonic 的取舍——不为了面向对象而面向对象。\n一、类与实例 class Server: \u0026#34;\u0026#34;\u0026#34;表示一台服务器的类\u0026#34;\u0026#34;\u0026#34; # docstring def __init__(self, host, port=80): # 构造方法 self.host = host # 实例属性 self.port = port self._status = \u0026#34;unknown\u0026#34; # _前缀 = 约定私有 self.__secret = \u0026#34;x\u0026#34; # __双下划线 = 名称改写(name mangling) def ping(self): # 实例方法 return f\u0026#34;ping {self.host}:{self.port} -\u0026gt; {self._status}\u0026#34; def set_status(self, status): # 实例方法: 修改状态 self._status = status web = Server(\u0026#34;10.0.0.1\u0026#34;, 8080) # 实例化（不需要 new） web.set_status(\u0026#34;up\u0026#34;) print(web.ping()) # ping 10.0.0.1:8080 -\u0026gt; up db = Server(\u0026#34;10.0.0.2\u0026#34;, 5432) # 每个实例状态独立 print(web.port, db.port) # 8080 5432 要点：\nself 是实例自身的引用，实例方法第一个参数永远是它（调用时不用传） Python 没有 true private，_x / __x 只是约定和名称改写 类属性 vs 实例属性：\nclass Server: kind = \u0026#34;linux\u0026#34; # 类属性, 所有实例共享 def __init__(self, host): self.host = host # 实例属性, 每个实例独立 a, b = Server(\u0026#34;s1\u0026#34;), Server(\u0026#34;s2\u0026#34;) print(a.kind) # linux（实例找不到就找类） Server.kind = \u0026#34;bsd\u0026#34; # 改类属性, 全体生效 a.kind = \u0026#34;windows\u0026#34; # 给 a 创建了实例属性遮蔽类属性 print(b.kind) # bsd —— b 不受影响 二、继承与 super() class BaseNotifier: def __init__(self, name): self.name = name def send(self, msg): raise NotImplementedError # 接口约定: 子类必须实现 def info(self): return f\u0026#34;notifier: {self.name}\u0026#34; class DingtalkNotifier(BaseNotifier): def __init__(self, name, webhook): super().__init__(name) # 复用父类初始化 self.webhook = webhook def send(self, msg): # 方法重写(override) print(f\u0026#34;[{self.name}] POST {self.webhook} body={msg}\u0026#34;) class EmailNotifier(BaseNotifier): def __init__(self, name, smtp): super().__init__(name) self.smtp = smtp def send(self, msg): print(f\u0026#34;[{self.name}] SMTP {self.smtp} body={msg}\u0026#34;) # 多态: 同一接口, 不同行为 notifiers = [ DingtalkNotifier(\u0026#34;ding\u0026#34;, \u0026#34;https://oapi.dingtalk.com/robot/send?token=x\u0026#34;), EmailNotifier(\u0026#34;mail\u0026#34;, \u0026#34;smtp.corp.local\u0026#34;), ] for n in notifiers: n.send(\u0026#34;CPU usage high!\u0026#34;) # 各自的 send 被调用 isinstance / issubclass / 鸭子类型：\nisinstance(n, BaseNotifier) # True issubclass(DingtalkNotifier, BaseNotifier) # True # 但 Python 更推崇鸭子类型: \u0026#34;会 send 就行, 管你是谁生的\u0026#34; class Webhook: # 没继承任何类 def send(self, msg): ... for n in [DingtalkNotifier(...), Webhook()]: n.send(\u0026#34;hi\u0026#34;) # ✅ 都能跑 三、魔术方法：让对象更 Pythonic class Money: def __init__(self, yuan): self.yuan = yuan def __repr__(self): # 调试/打印显示 return f\u0026#34;Money({self.yuan}¥)\u0026#34; def __eq__(self, other): # == return self.yuan == other.yuan def __lt__(self, other): # \u0026lt; (配合 sorted) return self.yuan \u0026lt; other.yuan def __add__(self, other): # + return Money(self.yuan + other.yuan) def __len__(self): # len() return self.yuan a, b = Money(100), Money(50) print(a) # Money(100¥) print(a + b) # Money(150¥) print(a \u0026gt; b) # True sorted([b, a]) # [Money(50¥), Money(100¥)] 常用魔术方法速查：\n方法 触发场景 __init__ 实例化 __repr__ / __str__ 打印、调试 __eq__ __lt__ 比较、排序 __len__ __getitem__ len()、下标访问 __enter__ __exit__ with 上下文管理 __iter__ __next__ for 迭代 四、property：优雅的读写控制 class AppConfig: def __init__(self): self._port = 80 @property def port(self): # 读 port 时触发 return self._port @port.setter def port(self, value): # 写 port 时触发校验 if not (1 \u0026lt;= value \u0026lt;= 65535): raise ValueError(f\u0026#34;非法端口: {value}\u0026#34;) self._port = value conf = AppConfig() conf.port = 8080 # 走 setter 校验 print(conf.port) # 走 getter # conf.port = 99999 # ValueError 好处：调用方代码不变（conf.port = x），校验逻辑却收进类里。\n五、dataclass：消灭样板代码 Python 3.7+ 的 @dataclass 让\u0026quot;数据类\u0026quot;一行搞定：\nfrom dataclasses import dataclass, field @dataclass class Host: name: str ip: str port: int = 22 tags: list[str] = field(default_factory=list) # 可变默认值的正确姿势 # 自动获得: __init__ / __repr__ / __eq__ h1 = Host(\u0026#34;web01\u0026#34;, \u0026#34;10.0.1.11\u0026#34;, tags=[\u0026#34;web\u0026#34;]) h2 = Host(\u0026#34;web01\u0026#34;, \u0026#34;10.0.1.11\u0026#34;, tags=[\u0026#34;web\u0026#34;]) print(h1) # Host(name=\u0026#39;web01\u0026#39;, ip=\u0026#39;10.0.1.11\u0026#39;, port=22, tags=[\u0026#39;web\u0026#39;]) print(h1 == h2) # True 再加一个方法就是完整业务对象：\n@dataclass class Host: ... @property def addr(self) -\u0026gt; str: return f\u0026#34;{self.ip}:{self.port}\u0026#34; 六、什么时候不需要类 # ❌ 过度设计: 一个脚本也要 AbstractFactorySingleton # ✅ 这些场景用函数/字典就够了: # 场景1: 只是存数据 -\u0026gt; dict / dataclass server = {\u0026#34;host\u0026#34;: \u0026#34;a\u0026#34;, \u0026#34;port\u0026#34;: 80} # 场景2: 一组相关函数 -\u0026gt; 模块本身就够 # deploy.py: install(), start(), stop() # 场景3: 简单分发 -\u0026gt; 字典比类层次好 handlers = {\u0026#34;start\u0026#34;: do_start, \u0026#34;stop\u0026#34;: do_stop} handlers[action]() 判断标准：有状态要封装、有多态要抽象、样板代码太多——满足其一才上类。\n小结 概念 关键点 __init__ + self 实例状态初始化 继承 + super() 复用父类；Python 更爱组合与鸭子类型 魔术方法 让自定义对象融入语言原生操作 property 无感校验读写 dataclass 数据类首选，一行顶十行 克制 脚本级代码用函数 + dict 就够 本文是「Python」系列第 4 篇。\n","permalink":"https://antidebug.cn/posts/2024/02/python-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003ePython 里\u0026quot;一切皆对象\u0026quot;，但很多同学写脚本一直用不上自定义类。这篇把 OOP 核心概念讲到位，同时告诉你 Pythonic 的取舍——\u003cstrong\u003e不为了面向对象而面向对象\u003c/strong\u003e。\u003c/p\u003e","title":"Python 面向对象编程基础"},{"content":"前言 一台公网服务器上线几小时，/var/log/secure 里就会堆满爆破记录。SSH 是大门，这篇是一份可以直接照抄的加固清单。\n一、先看看有多少人在撬门 # 今天的爆破尝试 grep \u0026#34;Failed password\u0026#34; /var/log/secure | wc -l # 谁在撬（来源 IP Top10） grep \u0026#34;Failed password\u0026#34; /var/log/secure \\ | grep -oE \u0026#34;from [0-9.]+\u0026#34; | sort | uniq -c | sort -rn | head # 用哪些用户名试 grep \u0026#34;Failed password\u0026#34; /var/log/secure | grep -oE \u0026#34;invalid user \\S+\u0026#34; | sort | uniq -c | sort -rn | head 看完这些数字，你会立刻想做下面所有事。\n二、第一优先级：密钥登录，禁用密码 1. 生成密钥对（在自己电脑上） ssh-keygen -t ed25519 -C \u0026#34;zy@laptop\u0026#34; # 一路回车（ passphrase 可留空, 更安全是设一个） # 生成: ~/.ssh/id_ed25519（私钥, 绝不外传） + id_ed25519.pub（公钥） 2. 公钥上服务器 ssh-copy-id -i ~/.ssh/id_ed25519.pub root@1.2.3.4 # 或手动: 把公钥内容追加到服务器 ~/.ssh/authorized_keys 3. 验证密钥能登录后，再关闭密码 vim /etc/ssh/sshd_config 三、sshd_config 加固清单 # ===== /etc/ssh/sshd_config 关键项 ===== # 1. 禁用 root 直接登录（先建好普通用户 + sudo!） PermitRootLogin no # 2. 禁止密码认证（前提: 密钥已验证可用!） PasswordAuthentication no ChallengeResponseAuthentication no PubkeyAuthentication yes # 3. 只允许特定用户/组登录 AllowUsers zy deploy # AllowGroups ops # 4. 改默认端口（挡掉无脑扫描, 防不住针对性攻击） Port 2222 # 5. 空闲超时自动踢下线 ClientAliveInterval 300 ClientAliveCountMax 2 # 6. 限制登录尝试与认证时限 MaxAuthTries 3 LoginGraceTime 30 # 7. 不用到的认证方式全关掉 GSSAPIAuthentication no UseDNS no # 还能加快登录速度 # 生效前先验证语法！ sshd -t # 用另一个终端保持现有连接不动, 新开连接测试（保命操作!） systemctl restart sshd ⚠️ 顺序就是安全：先建普通用户并配好 sudo → 部署密钥 → 验证密钥登录 → 才能改 sshd_config 重启。反着来就是把自己锁在门外。\n# 建用户 + sudo（wheel 组） useradd -m -G wheel zy passwd zy 改了端口后记得放行防火墙和安全组：\nfirewall-cmd --add-port=2222/tcp --permanent \u0026amp;\u0026amp; firewall-cmd --reload 四、fail2ban：自动拉黑爆破者 dnf install -y epel-release \u0026amp;\u0026amp; dnf install -y fail2ban cat \u0026gt; /etc/fail2ban/jail.local \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; [DEFAULT] bantime = 3600 # 封禁 1 小时 findtime = 600 # 10 分钟窗口内 maxretry = 5 # 失败 5 次就封 banaction = firewallcmd-rich-rules [sshd] enabled = true port = 2222 # 与 sshd 端口一致 EOF systemctl enable --now fail2ban # 查看战果 fail2ban-client status sshd # 目前封禁列表、总攻击次数一目了然 fail2ban-client set sshd unbanip 1.2.3.4 # 手动解封 五、进阶招式 只允许证书 + 双因素（如 Google Authenticator） dnf install -y google-authenticator google-authenticator # 扫码绑定, 生成 ~/.google_authenticator # sshd_config 追加: # AuthenticationMethods publickey,keyboard-interactive # ChallengeResponseAuthentication yes 跳板机模式（团队标准姿势） 所有服务器只监听内网/白名单 IP，外部统一走一台跳板机：\n# 服务器 sshd_config ListenAddress 10.0.0.5 # 只听内网 # 或者防火墙层面只放跳板机 IP firewall-cmd --add-rich-rule=\u0026#39;rule family=ipv4 source address=10.0.0.10 port port=2222 protocol=tcp accept\u0026#39; --permanent # 客户端 ~/.ssh/config 简化跳板 Host web01 HostName 10.0.1.11 User zy Port 2222 ProxyJump bastion.example.com # 一条命令直达: ssh web01 免密自动化（CI/CD 部署用） # 服务器侧限制自动化密钥的能力（只能跑特定命令） # ~/.ssh/authorized_keys: command=\u0026#34;/opt/deploy/pull.sh\u0026#34;,no-pty,no-port-forwarding ssh-ed25519 AAAA... deploy@ci 六、加固核对清单 普通用户 + sudo 已就绪，不再日常使用 root ed25519 密钥登录验证通过 PasswordAuthentication no PermitRootLogin no AllowUsers 白名单 改默认端口（同步防火墙 + 安全组） sshd -t 通过后才 restart，且保留旧会话保底 fail2ban 运行中，status sshd 有数据 客户端 ~/.ssh/config 配好 ProxyJump 小结 安全是分层洋葱：密钥 \u0026gt; 禁密码 \u0026gt; 白名单 \u0026gt; 改端口 \u0026gt; fail2ban \u0026gt; 跳板机。每一层都在提高攻击成本。而加固的铁律是：每改一步，先验证，再进行下一步——把自己锁在门外是新手最贵的一课。\n本文是「安全」系列第 1 篇。\n","permalink":"https://antidebug.cn/posts/2024/02/ssh-%E5%AE%89%E5%85%A8%E5%8A%A0%E5%9B%BA%E5%AE%9E%E8%B7%B5/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e一台公网服务器上线几小时，\u003ccode\u003e/var/log/secure\u003c/code\u003e 里就会堆满爆破记录。SSH 是大门，这篇是一份可以直接照抄的加固清单。\u003c/p\u003e\n\u003ch2 id=\"一先看看有多少人在撬门\"\u003e一、先看看有多少人在撬门\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 今天的爆破尝试\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep \u003cspan class=\"s2\"\u003e\u0026#34;Failed password\u0026#34;\u003c/span\u003e /var/log/secure \u003cspan class=\"p\"\u003e|\u003c/span\u003e wc -l\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 谁在撬（来源 IP Top10）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep \u003cspan class=\"s2\"\u003e\u0026#34;Failed password\u0026#34;\u003c/span\u003e /var/log/secure \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e grep -oE \u003cspan class=\"s2\"\u003e\u0026#34;from [0-9.]+\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e sort \u003cspan class=\"p\"\u003e|\u003c/span\u003e uniq -c \u003cspan class=\"p\"\u003e|\u003c/span\u003e sort -rn \u003cspan class=\"p\"\u003e|\u003c/span\u003e head\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 用哪些用户名试\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep \u003cspan class=\"s2\"\u003e\u0026#34;Failed password\u0026#34;\u003c/span\u003e /var/log/secure \u003cspan class=\"p\"\u003e|\u003c/span\u003e grep -oE \u003cspan class=\"s2\"\u003e\u0026#34;invalid user \\S+\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e sort \u003cspan class=\"p\"\u003e|\u003c/span\u003e uniq -c \u003cspan class=\"p\"\u003e|\u003c/span\u003e sort -rn \u003cspan class=\"p\"\u003e|\u003c/span\u003e head\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e看完这些数字，你会立刻想做下面所有事。\u003c/p\u003e","title":"SSH 安全加固实践"},{"content":"前言 Go 没有 class、没有继承，但通过结构体 + 方法 + 接口实现了更轻量的面向对象。核心哲学一句话：组合优于继承，隐式优于显式。\n一、结构体 type Server struct { Host string // 大写开头 = 导出（公开） Port int // 小写 = 包内私有 Tags []string metadata map[string]string } // 字面量初始化（推荐: 带字段名, 可读性好且不怕字段调整） s := Server{ Host: \u0026#34;10.0.0.1\u0026#34;, Port: 8080, Tags: []string{\u0026#34;prod\u0026#34;, \u0026#34;web\u0026#34;}, } // 零值可用（Go 设计哲学!） var s2 Server // Host=\u0026#34;\u0026#34; Port=0 Tags=nil, 直接用不会崩 s2.Port = 9090 // 结构体是值类型：赋值即拷贝 s3 := s2 s3.Port = 1000 fmt.Println(s2.Port) // 9090, 不受影响 构造函数惯例（Go 没有 constructor，用普通函数模拟）：\nfunc NewServer(host string, port int) *Server { return \u0026amp;Server{ Host: host, Port: port, } } 二、方法与接收者 // 值接收者 func (s Server) Addr() string { return fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, s.Host, s.Port) } // 指针接收者：方法内能修改原对象 func (s *Server) SetPort(port int) { s.Port = port } 怎么选？ 记住两条规则：\n需要修改接收者 → 必须指针接收者 结构体较大或包含 mutex 等字段 → 指针接收者（避免拷贝开销/拷贝导致锁失效） 同一结构体的方法保持统一：要有一个用指针，全用指针 s := NewServer(\u0026#34;10.0.0.1\u0026#34;, 80) s.SetPort(8080) // 语法糖: Go 自动 (\u0026amp;s).SetPort(8080) fmt.Println(s.Addr()) // 10.0.0.1:8080 三、组合与嵌入（Go 的\u0026quot;继承\u0026quot;） type BaseLogger struct { Prefix string } func (l BaseLogger) Log(msg string) { fmt.Println(l.Prefix, msg) } // 嵌入（不是继承!）: 直接把类型写进来 type App struct { BaseLogger // 匿名嵌入 Name string } app := App{BaseLogger: BaseLogger{Prefix: \u0026#34;[app]\u0026#34;}, Name: \u0026#34;web\u0026#34;} app.Log(\u0026#34;started\u0026#34;) // 方法被\u0026#34;提升\u0026#34;, 像自己的方法一样调用 嵌入 vs 继承的区别：没有父类子类的多态链条，只是能力复用。需要覆盖时显式重定义：\nfunc (a App) Log(msg string) { // 覆盖提升的方法 a.BaseLogger.Log(a.Name + \u0026#34; \u0026#34; + msg) // 显式调用内层的 } 四、接口：隐式实现 Go 接口最独特之处：不需要 implements 声明，只要实现了接口要求的方法集，就自动满足该接口：\ntype Describer interface { Describe() string } func (s Server) Describe() string { // Server 实现了 Describer return \u0026#34;server at \u0026#34; + s.Addr() } func Print(d Describer) { // 面向接口编程 fmt.Println(d.Describe()) } Print(s) // ✅ 编译器自动确认 Server 满足 Describer 接口是\u0026quot;能力清单\u0026quot;，惯例是小而专：\n// 标准库的例子 type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { // 接口组合 Reader Writer } 接口值 = 动态类型 + 动态值：\nvar d Describer fmt.Println(d == nil) // true d = s fmt.Printf(\u0026#34;%T\\n\u0026#34;, d) // main.Server —— 动态类型 // 类型断言与 type switch v, ok := d.(Server) // 断言成具体类型 if ok { fmt.Println(v.Host) } switch x := d.(type) { case Server: fmt.Println(\u0026#34;是 Server\u0026#34;, x.Host) default: fmt.Println(\u0026#34;未知类型\u0026#34;) } ⚠️ 经典坑——nil 接口 ≠ 装着 nil 指针的接口：\nvar p *Server // p == nil var d Describer = p // 接口里装了 (类型=*Server, 值=nil) fmt.Println(d == nil) // false! 返回 error 时如果 var e *MyErr; return e，调用方 if err != nil 会误判——返回明确的 nil。\n五、两个必会接口 Stringer：自定义打印 func (s Server) String() string { return fmt.Sprintf(\u0026#34;Server{%s:%d}\u0026#34;, s.Host, s.Port) } fmt.Println(s) // Server{10.0.0.1:8080} —— %v 自动调用 error：标准错误接口 type error interface { Error() string } // 自定义错误类型（比 fmt.Errorf 多了类型信息, 可断言分类处理） type ConfigError struct { File string Err error } func (e *ConfigError) Error() string { return fmt.Sprintf(\u0026#34;config %s: %v\u0026#34;, e.File, e.Err) } 六、实战：用接口解耦 type Notifier interface { Notify(title, body string) error } type DingtalkNotifier struct{ Webhook string } func (d DingtalkNotifier) Notify(t, b string) error { /* ... */ return nil } type EmailNotifier struct{ SMTP string } func (e EmailNotifier) Notify(t, b string) error { /* ... */ return nil } // 告警系统只依赖接口, 不关心具体渠道 type Alerter struct{ N Notifier } func (a Alerter) Fire(title string) { _ = a.N.Notify(title, time.Now().String()) } // 切换渠道只改这一行 alert := Alerter{N: DingtalkNotifier{Webhook: \u0026#34;https://...\u0026#34;}} alert.Fire(\u0026#34;CPU high\u0026#34;) 这就是 Go 风格的多态：小接口 + 组合 + 依赖注入。\n小结 概念 Go 的做法 类 struct + 方法 继承 嵌入组合（无多态链） 接口 隐式实现、小接口、面向能力 多态 接口变量持有动态类型 陷阱 nil 接口、值/指针接收者混用 本文是「Golang」系列第 4 篇。\n","permalink":"https://antidebug.cn/posts/2024/01/golang-%E7%BB%93%E6%9E%84%E4%BD%93%E6%96%B9%E6%B3%95%E4%B8%8E%E6%8E%A5%E5%8F%A3/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eGo 没有 class、没有继承，但通过\u003cstrong\u003e结构体 + 方法 + 接口\u003c/strong\u003e实现了更轻量的面向对象。核心哲学一句话：\u003cstrong\u003e组合优于继承，隐式优于显式\u003c/strong\u003e。\u003c/p\u003e\n\u003ch2 id=\"一结构体\"\u003e一、结构体\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eServer\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e     \u003cspan class=\"kt\"\u003estring\u003c/span\u003e        \u003cspan class=\"c1\"\u003e// 大写开头 = 导出（公开）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e     \u003cspan class=\"kt\"\u003eint\u003c/span\u003e           \u003cspan class=\"c1\"\u003e// 小写 = 包内私有\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eTags\u003c/span\u003e     \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003emetadata\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 字面量初始化（推荐: 带字段名, 可读性好且不怕字段调整）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eServer\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;10.0.0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e8080\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eTags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;prod\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;web\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 零值可用（Go 设计哲学!）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003es2\u003c/span\u003e \u003cspan class=\"nx\"\u003eServer\u003c/span\u003e               \u003cspan class=\"c1\"\u003e// Host=\u0026#34;\u0026#34; Port=0 Tags=nil, 直接用不会崩\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003es2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e9090\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 结构体是值类型：赋值即拷贝\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003es3\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003es2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003es3\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1000\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e        \u003cspan class=\"c1\"\u003e// 9090, 不受影响\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e构造函数惯例\u003c/strong\u003e（Go 没有 constructor，用普通函数模拟）：\u003c/p\u003e","title":"Golang 结构体、方法与接口"},{"content":"前言 写代码不用 Git 等于走钢丝不系保险绳。这篇把 Git 的核心心智模型和日常高频操作整理成册，附\u0026quot;事故急救\u0026quot;章节。\n一、心智模型：三个区 工作区 (Working Directory) │ git add ▼ 暂存区 (Staging / Index) │ git commit ▼ 本地仓库 (Repository) │ git push ▼ 远程仓库 (Remote: GitHub/Gitee/自建) 记住这个流向，所有命令都能推导出来。\n二、基础操作流 # 初始化 / 克隆 git init git clone https://gitee.com/user/repo.git # 身份配置（第一次必做） git config --global user.name \u0026#34;zy\u0026#34; git config --global user.email \u0026#34;zy@example.com\u0026#34; # 日常四连 git status # 看状态（先看再动, 好习惯） git add file.txt # 加入暂存区; git add . 全部 git commit -m \u0026#34;feat: add login\u0026#34; # 提交到本地仓库 git push origin main # 推到远程 # 拉取更新 git pull = git fetch + git merge 写好 commit message：推荐约定式提交——feat: 新功能、fix: 修 bug、docs: 文档、refactor: 重构、chore: 杂务。\n三、查看历史与差异 git log --oneline --graph --all # 一图流看历史 git diff # 工作区 vs 暂存区 git diff --staged # 暂存区 vs 最近提交 git diff HEAD~1 # 与上一次提交比 git show 3f2a9c1 # 看某次提交改了什么 git blame file.py # 这行代码是谁写的（甩锅神器） 四、分支操作 git branch # 列分支 git switch -c feature/login # 创建并切换新分支（新命令） git switch main # 切回主分支 git merge feature/login # 把 feature 合进当前分支 git branch -d feature/login # 删除已合并分支 # 远程分支 git fetch origin git switch -c hotfix origin/hotfix # 基于远程分支建本地分支 git push origin --delete old-branch # 删远程分支 merge vs rebase：\n# merge: 保留分叉历史, 产生合并提交 git merge main # rebase: 把我的提交\u0026#34;嫁接\u0026#34;到 main 顶端, 历史成一条直线（整洁但改写历史） git rebase main 团队铁律：公共分支禁 rebase（会改写别人的历史），个人 feature 分支 rebase main 保持最新是优雅实践。\n五、远程仓库 git remote -v # 查看远程 git remote add origin \u0026lt;url\u0026gt; # 关联远程 git push -u origin main # 首推并建立跟踪 git remote set-url origin \u0026lt;new-url\u0026gt; # 换远程地址 # 多远程（公司 + 个人） git remote add gitee https://gitee.com/zy/repo.git git push gitee main 国内访问 GitHub 慢，可以 Gitee/GitCode 做镜像，或用 SSH 协议 + 代理。\nSSH 免密：\nssh-keygen -t ed25519 -C \u0026#34;zy@example.com\u0026#34; cat ~/.ssh/id_ed25519.pub # 贴到平台的 SSH Keys 设置里 git remote set-url origin git@github.com:zy/repo.git 六、.gitignore # .gitignore 示例 node_modules/ *.pyc __pycache__/ .venv/ .env # 密钥文件千万别进仓库! *.log .idea/ dist/ 已被跟踪的文件加入 ignore 后需：\ngit rm --cached .env \u0026amp;\u0026amp; git commit -m \u0026#34;remove env\u0026#34; 七、事故急救手册 事故 急救命令 改乱了工作区，想放弃修改 git restore file add 错了想撤出暂存区 git restore --staged file commit 信息写错 git commit --amend 少提交了文件 补 add 后 git commit --amend --no-edit 想撤销最近一次提交（保留改动） git reset --soft HEAD~1 想彻底回滚到某个提交 git reset --hard 3f2a9c1（⚠️ 丢弃之后的改动） 已经 push 的错误提交 git revert 3f2a9c1（生成反向提交, 安全） 误删分支 git reflog 找到提交号 → git switch -c rescued 3f2a9c1 stash 暂存现场 git stash / git stash pop reset 三模式：\ngit reset --soft HEAD~1 # 只回退 commit, 改动留在暂存区 git reset --mixed HEAD~1 # 回退 commit+add, 改动留在工作区（默认） git reset --hard HEAD~1 # 全部丢弃, 回到过去 八、进阶技巧 # 交互式暂存：一块一块挑着提交 git add -p # 挑 cherry-pick：把某个提交\u0026#34;复制\u0026#34;到当前分支 git cherry-pick 3f2a9c1 # 二分查找引入 bug 的提交（超好用） git bisect start git bisect bad # 当前是坏的 git bisect good v1.2 # 这个版本是好的 # Git 自动切换中间版本, 测试后标记 good/bad, 定位凶手 git bisect reset # 储藏现场再切分支 git stash push -m \u0026#34;wip: login\u0026#34; git stash list git stash pop 小结 层级 命令 提交流 status → add → commit → push 分支 switch -c / merge / 个人分支 rebase 撤销 工作区 restore、提交 reset --soft、已推送 revert 救命 reflog 永远有后悔药 本文是「工具」系列第 1 篇。\n","permalink":"https://antidebug.cn/posts/2024/01/git-%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e写代码不用 Git 等于走钢丝不系保险绳。这篇把 Git 的核心心智模型和日常高频操作整理成册，附\u0026quot;事故急救\u0026quot;章节。\u003c/p\u003e\n\u003ch2 id=\"一心智模型三个区\"\u003e一、心智模型：三个区\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e工作区 (Working Directory)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  git add\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e暂存区 (Staging / Index)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  git commit\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e本地仓库 (Repository)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  git push\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e远程仓库 (Remote: GitHub/Gitee/自建)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e记住这个流向，所有命令都能推导出来。\u003c/strong\u003e\u003c/p\u003e","title":"Git 版本控制快速上手"},{"content":"前言 云厂商把物理机\u0026quot;切\u0026quot;成一台台云主机卖给你，用的核心武器就是 KVM 虚拟化。理解它，就理解了 IaaS 的地基，也理解了容器的宿主环境。\n一、虚拟化要解决什么 一台物理机上跑多个\u0026quot;完整电脑\u0026quot;，核心需求：\nCPU 虚拟化：让多个客户机（Guest）安全地共享物理 CPU 内存虚拟化：每个 Guest 拥有连续的\u0026quot;假物理内存\u0026quot; 设备虚拟化：网卡、磁盘都是模拟出来的 隔离与调度：Hypervisor（VMM）负责资源分配 二、KVM 与 QEMU 的分工 这对搭档经常一起出现，但职责完全不同：\n组件 形态 职责 KVM Linux 内核模块（kvm.ko, kvm_intel.ko） 提供 CPU/内存虚拟化（依赖硬件 VT-x/AMD-V），只管\u0026quot;核心\u0026quot; QEMU 用户态程序 模拟整套硬件（BIOS、网卡、显卡、磁盘控制器），提供设备 IO libvirt 守护进程 + 工具集 管理 API（定义/启动/迁移虚拟机），virsh 是它的 CLI 一句话：KVM 是发动机，QEMU 是车身和方向盘，libvirt 是仪表台。\n# 检查 CPU 是否支持虚拟化 grep -cE \u0026#34;(vmx|svm)\u0026#34; /proc/cpuinfo # \u0026gt;0 即支持 lsmod | grep kvm # 模块是否加载 # 安装全家桶（RHEL 系） dnf install -y qemu-kvm libvirt virt-install virt-top systemctl enable --now libvirtd 三、CPU 虚拟化怎么工作（概念级） 全虚拟化：Guest 不知道自己被虚拟化，敏感指令陷入 KVM 由内核处理（硬件辅助，VT-x 的 root/non-root 模式） 半虚拟化（PV）：Guest 装了特殊驱动（virtio），知道自己在虚拟机里，IO 走高效通道 直通（passthrough）：把物理设备直接分配给 Guest（VFIO/SR-IOV），性能接近裸机 四、virtio：性能的关键 模拟真实硬件（e1000 网卡、IDE 磁盘）兼容性好但慢；virtio 是\u0026quot;为虚拟化而生\u0026quot;的设备族，Guest 装上 virtio 驱动后走前后端共享内存通道：\n设备 模拟设备（慢） virtio（快） 网卡 e1000 virtio-net 磁盘 IDE/SATA virtio-blk / virtio-scsi 队列 — virtio-queue（多队列） 云主机的网卡之所以叫 eth0 而性能还不错，就是 virtio-net 在工作。\n五、动手：命令行创建一台 KVM 虚拟机 # 1. 准备磁盘镜像（qcow2: 支持精简置备+快照） qemu-img create -f qcow2 /var/lib/libvirt/images/test.qcow2 20G # 2. 安装系统（文本/串口方式, 适合无图形界面服务器） virt-install \\ --name test-vm \\ --vcpus 2 --memory 2048 \\ --disk path=/var/lib/libvirt/images/test.qcow2,format=qcow2,bus=virtio \\ --network network=default,model=virtio \\ --os-variant centos-stream9 \\ --location /opt/CentOS-Stream-9-latest-x86_64-dvd1.iso \\ --graphics none --console pty,target_type=serial \\ --extra-args \u0026#34;console=ttyS0\u0026#34; virsh 日常管理：\nvirsh list --all # 所有虚拟机 virsh start test-vm # 开机 virsh shutdown test-vm # 优雅关机 virsh destroy test-vm # 拔电源（强制） virsh console test-vm # 串口控制台（退出: Ctrl+]） virsh dominfo test-vm # 详细信息 virsh autostart test-vm # 宿主机开机自启 virsh snapshot-create-as test-vm snap1 # 快照 virsh dumpxml test-vm \u0026gt; vm.xml # 导出定义 六、镜像格式：qcow2 vs raw 特性 qcow2 raw 空间占用 精简（用多少占多少） 预分配全量 快照 ✅ 内置支持 需要外部方案（LVM） 性能 略有开销 最优 压缩/加密 ✅ ❌ qemu-img info test.qcow2 # 查看实际占用 vs 虚拟大小 qemu-img convert -O raw a.qcow2 a.raw # 格式转换 qemu-img snapshot -l test.qcow2 # 列出快照 云主机启动的秘密：云厂商的系统镜像 + 你的数据盘，本质就是一套 qcow2/raw 镜像加一层调度系统（OpenStack/自研），底层还是这套 KVM 技术。\n七、嵌套虚拟化与常见问题 # 在虚拟机里再跑虚拟机（测试 K8s 集群常用） modprobe kvm_intel nested=1 cat /sys/module/kvm_intel/parameters/nested # Y 即开启 # 虚拟机网络不通排查 virsh net-list # default 网络是否 active virsh domifaddr test-vm # 拿到虚拟机 IP 小结 KVM（内核）+ QEMU（设备模拟）+ libvirt（管理） 三层架构是 IaaS 地基 硬件辅助虚拟化（VT-x）让全虚拟化性能可用，virtio 让 IO 高效 qcow2 精简+快照适合日常，raw 性能极致 virt-install + virsh 就能在命令行管理完整虚拟机生命周期 下一篇回到代码：Git 版本控制。\n本文是「云计算」系列第 2 篇。\n","permalink":"https://antidebug.cn/posts/2023/12/%E4%BA%91%E8%AE%A1%E7%AE%97%E8%99%9A%E6%8B%9F%E5%8C%96%E5%9F%BA%E7%A1%80kvm-%E4%B8%8E-qemu/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e云厂商把物理机\u0026quot;切\u0026quot;成一台台云主机卖给你，用的核心武器就是 \u003cstrong\u003eKVM 虚拟化\u003c/strong\u003e。理解它，就理解了 IaaS 的地基，也理解了容器的宿主环境。\u003c/p\u003e\n\u003ch2 id=\"一虚拟化要解决什么\"\u003e一、虚拟化要解决什么\u003c/h2\u003e\n\u003cp\u003e一台物理机上跑多个\u0026quot;完整电脑\u0026quot;，核心需求：\u003c/p\u003e","title":"云计算虚拟化基础：KVM 与 QEMU"},{"content":"前言 \u0026ldquo;昨晚服务为什么挂了？\u0026quot;——答案 90% 藏在日志里。现代 Linux 有两套并行的日志体系：传统 rsyslog（写文件）与 systemd-journald（二进制数据库）。搞懂它们，排障效率翻倍。\n一、日志全景图 应用程序 ──┬─► /dev/log ──► systemd-journald ──► /run/log/journal（二进制, 易失） │ │ │ └──► 转发 ──► rsyslog ──► /var/log/*.log（文本文件） │ └─► 自己直接写文件（如 nginx access.log） journald：systemd 全家桶，统一收集，按索引查询，默认重启丢失（可开持久化） rsyslog：老牌守护进程，按规则分类写入文本文件，方便 grep/tail 二、journald 使用 journalctl # 全部日志（分页） journalctl -u nginx # 某服务 journalctl -u nginx -f # 实时跟踪 journalctl -n 100 # 最近 100 行 journalctl -b # 本次开机以来 journalctl -b -1 -e # 上次开机（跳到末尾）—— 查\u0026#34;为什么重启\u0026#34;神器 journalctl --since \u0026#34;2023-12-01\u0026#34; --until \u0026#34;2023-12-02 03:00\u0026#34; journalctl -p err..alert # 按严重级别过滤 journalctl _PID=8642 # 按进程 journalctl -k # 仅内核日志 journalctl --disk-usage # 占用 journalctl --vacuum-size=500M # 收缩 开启持久化（默认存内存，重启即丢）：\nmkdir -p /var/log/journal systemctl restart systemd-journald # 之后重启, journalctl -b -1 就能查上次开机的日志了 三、rsyslog 与经典日志文件 rsyslog 规则格式：facility.severity 动作\nfacility（来源）：kern、auth、cron、daemon、mail、local0-7（自定义）…… severity（级别）：debug \u0026lt; info \u0026lt; notice \u0026lt; warning \u0026lt; err \u0026lt; crit \u0026lt; alert \u0026lt; emerg grep -v \u0026#34;^#\u0026#34; /etc/rsyslog.conf | grep -v \u0026#34;^$\u0026#34; # 看生效规则 tail -f /var/log/messages # 通用消息（RHEL 系主战场） tail -f /var/log/secure # 认证相关: SSH 登录成功/失败都在这 grep \u0026#34;Accepted\u0026#34; /var/log/secure # 谁成功登录过 grep \u0026#34;Failed password\u0026#34; /var/log/secure | wc -l # 被爆破次数 tail -f /var/log/cron # 定时任务执行记录 dmesg -T # 内核环形缓冲（硬件、OOM 杀进程） 自定义应用日志接入（写个 local facility）：\n# /etc/rsyslog.d/myapp.conf local6.* /var/log/myapp.log systemctl restart rsyslog # 应用里: logger -p local6.info \u0026#34;app started\u0026#34; 四、logrotate：日志轮转 日志不管理迟早撑爆磁盘。logrotate 按策略切割/压缩/删除：\ncat /etc/logrotate.conf # 主配置 ls /etc/logrotate.d/ # 各软件的子配置 给自家应用写一个 /etc/logrotate.d/myapp：\n/var/log/myapp/*.log { daily # 每天轮转 rotate 14 # 保留 14 份历史 missingok # 文件不存在不报错 notifempty # 空文件不轮转 compress # 压缩旧日志 delaycompress # 延迟一天压缩（最新的还能直接看） dateext # 文件名带日期: app.log-20231202.gz copytruncate # 复制后截断原文件（应用无需重开文件句柄） sharedscripts postrotate [ -f /var/run/myapp.pid ] \u0026amp;\u0026amp; kill -USR1 $(cat /var/run/myapp.pid) endscript } copytruncate vs postrotate 发信号：前者简单粗暴但可能丢极少量日志；后者优雅但要求应用支持重开日志文件（如 nginx 的 USR1）。\nlogrotate -d /etc/logrotate.d/myapp # dry-run 演练, 不真动文件 logrotate -f /etc/logrotate.d/myapp # 强制立即执行一次 五、排障套路：从日志找线索 # 1. 时间线: 出问题的时间点前后, 系统层有什么动静 journalctl --since \u0026#34;10:00\u0026#34; --until \u0026#34;10:30\u0026#34; -p warning # 2. 资源杀手: OOM 会在内核日志留痕 dmesg -T | grep -i -E \u0026#34;oom|killed process\u0026#34; journalctl -k | grep -i oom # 3. 登录审计: 半夜有没有人动过机器 last | head # 登录历史 lastb | head # 失败的登录 grep \u0026#34;session opened\u0026#34; /var/log/secure # 4. 磁盘: 是不是日志自己把盘写满了 du -sh /var/log find /var/log -size +100M -exec ls -lh {} \\; 六、实践建议 做法 说明 journald 开持久化 /var/log/journal 目录一建就行 应用日志统一目录 /var/log/appname/，别散落在家目录 每个应用配 logrotate 用 copytruncate 起步最省心 cron 任务输出重定向 否则邮件堆爆 /var/spool/mail 集中收集 单机方案之上，后续上 ELK/Loki（后面写文章） 小结 journald 管 systemd 生态（journalctl -u 一把梭），rsyslog 管文本文件（好 grep 好归档） 排障三板斧：journalctl --since、dmesg -T、/var/log/secure 日志三防：轮转（logrotate）、限额（journald vacuum）、集中（ELK） 本文是「Linux 运维」系列第 5 篇。\n","permalink":"https://antidebug.cn/posts/2023/12/linux-%E6%97%A5%E5%BF%97%E7%AE%A1%E7%90%86rsyslog-%E4%B8%8E-journald/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;昨晚服务为什么挂了？\u0026quot;——答案 90% 藏在日志里。现代 Linux 有两套并行的日志体系：传统 \u003cstrong\u003ersyslog\u003c/strong\u003e（写文件）与 \u003cstrong\u003esystemd-journald\u003c/strong\u003e（二进制数据库）。搞懂它们，排障效率翻倍。\u003c/p\u003e","title":"Linux 日志管理：rsyslog 与 journald"},{"content":"前言 Go 的日常编码，八成时间在和 slice 与 map 打交道。这篇把它们的核心语义和著名\u0026quot;陷阱\u0026quot;讲透。\n一、数组：定长的值类型 // 长度是类型的一部分: [3]int 和 [4]int 是不同类型! var a [3]int // {0, 0, 0}（零值） b := [3]int{1, 2, 3} c := [...]int{1, 2, 3} // 编译器数长度, 仍是 [3]int // 数组是值类型: 赋值和传参都是完整拷贝! d := b d[0] = 99 fmt.Println(b[0]) // 1 —— b 不受影响 实际开发中很少直接用数组（定长 + 值拷贝太死板），主力是切片。\n二、切片：数组的\u0026quot;动态视图\u0026quot; s := []int{1, 2, 3} // 注意: 没有⻓度, 这是切片 s = append(s, 4) // 追加: [1 2 3 4] s = append(s, 5, 6) // 可以一次追加多个 s2 := append(s, []int{7, 8}...) // 追加另一个切片 sub := s[1:4] // 切片截取: [2 3 4] len(s), cap(s) // 长度 与 容量 // make 初始化 buf := make([]int, 0, 16) // len=0, cap=16（预分配, 性能友好） matrix := make([][]int, 3) // 二维 底层结构（理解陷阱的关键） 切片是一个三元组结构体：\nslice ──► [指针 ptr]──► 底层数组 [长度 len] [容量 cap] 由此衍生三大陷阱：\n陷阱 1：切片是引用语义（共享底层数组）\na := []int{1, 2, 3, 4, 5} b := a[1:3] // [2 3] b[0] = 99 fmt.Println(a[1]) // 99 —— 改 b 动到了 a 的底层数组! 陷阱 2：append 可能换数组\na := make([]int, 1, 2) // len=1 cap=2 b := append(a, 10) // cap 够, 复用数组 b[0] = 99 fmt.Println(a) // [99] —— a 也变了 c := append(a, 1, 2, 3) // 超过 cap, 分配新数组 c[0] = 100 fmt.Println(a) // [99] —— a 不再受影响 想彻底独立拷贝？\ndst := make([]int, len(src)) copy(dst, src) // 或 dst2 := append([]int(nil), src...) 陷阱 3：从大切片截小片导致内存泄漏\ndata := make([]byte, 100\u0026lt;\u0026lt;20) // 100MB small := data[:10] // 只用 10 字节 // 只要 small 活着, 100MB 底层数组就无法回收! // 正确: 拷贝出需要的部分 smallSafe := make([]byte, 10) copy(smallSafe, data[:10]) 三、常用操作速查 // 遍历 for i, v := range s { ... } // 查找 idx := slices.Index(s, 3) // 标准库 slices（go1.21+） // 排序 sort.Ints(s) slices.Sort(s) sort.Slice(peoples, func(i, j int) bool { return peoples[i].Age \u0026lt; peoples[j].Age // 按字段排序 }) // 删除下标 i 的元素 s = append(s[:i], s[i+1:]...) // 去重（借助 map） seen := map[string]bool{} for _, v := range items { if !seen[v] { seen[v] = true uniq = append(uniq, v) } } // 判断相等（go1.21+ 前不能直接 ==） slices.Equal(a, b) 四、映射 map // 创建 m := map[string]int{\u0026#34;go\u0026#34;: 1, \u0026#34;py\u0026#34;: 2} m2 := make(map[string][]string) // 值是切片 // 增删改查 m[\u0026#34;sh\u0026#34;] = 3 delete(m, \u0026#34;py\u0026#34;) v := m[\u0026#34;go\u0026#34;] // 不存在时返回零值: 0 // 判断 key 是否存在（comma ok 惯用法） if v, ok := m[\u0026#34;rust\u0026#34;]; ok { fmt.Println(\u0026#34;存在\u0026#34;, v) } else { fmt.Println(\u0026#34;不存在\u0026#34;) // ← 走这里 } // 遍历（注意: 顺序是故意随机化的!） for k, v := range m { fmt.Println(k, v) } map 的值是不可寻址的：\ntype Counter map[string]int c := Counter{} c[\u0026#34;a\u0026#34;]++ // ✅ 这样可以 type User struct{ Name string } users := map[string]User{} users[\u0026#34;zy\u0026#34;].Name = \u0026#34;x\u0026#34; // ❌ 编译错误: cannot assign to struct field users[\u0026#34;zy\u0026#34;] = User{Name: \u0026#34;x\u0026#34;} // ✅ 整体替换 // 或者值用指针 users2 := map[string]*User{} users2[\u0026#34;zy\u0026#34;].Name = \u0026#34;x\u0026#34; // ✅ map 不是并发安全的，并发读写直接 panic：\nvar mu sync.Mutex mu.Lock() m[k] = v mu.Unlock() // 或用标准库并发安全版 var sm sync.Map // 读多写少场景 五、函数传参：Go 全是值传递 func modify(s []int) { s[0] = 99 // 改的是\u0026#34;拷贝的切片头\u0026#34;指向的同一数组 → 外部可见 s = append(s, 100) // 但重新赋值只影响局部变量 → 外部不可见 } func main() { s := []int{1, 2, 3} modify(s) fmt.Println(s) // [99 2 3] } 切片头（ptr/len/cap 结构体）被拷贝，但指向的数组是共享的——这就是\u0026quot;值传递却像引用\u0026quot;的真相。\n小结 要点 一句话 数组 值类型、定长，基本不用 切片 三元组；截取共享底层数组 append 超 cap 换新数组，改老的不影响 copy 想要真拷贝 map comma-ok 判存在；遍历随机；非并发安全 传参 永远是值传递，切片共享底层数组 本文是「Golang」系列第 3 篇。\n","permalink":"https://antidebug.cn/posts/2023/11/golang-%E5%A4%8D%E5%90%88%E7%B1%BB%E5%9E%8B%E6%95%B0%E7%BB%84%E5%88%87%E7%89%87%E4%B8%8E%E6%98%A0%E5%B0%84/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eGo 的日常编码，八成时间在和 \u003ccode\u003eslice\u003c/code\u003e 与 \u003ccode\u003emap\u003c/code\u003e 打交道。这篇把它们的核心语义和著名\u0026quot;陷阱\u0026quot;讲透。\u003c/p\u003e\n\u003ch2 id=\"一数组定长的值类型\"\u003e一、数组：定长的值类型\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 长度是类型的一部分: [3]int 和 [4]int 是不同类型!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003ea\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e              \u003cspan class=\"c1\"\u003e// {0, 0, 0}（零值）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eb\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ec\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e    \u003cspan class=\"c1\"\u003e// 编译器数长度, 仍是 [3]int\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 数组是值类型: 赋值和传参都是完整拷贝!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ed\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ed\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e99\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePrintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e         \u003cspan class=\"c1\"\u003e// 1 —— b 不受影响\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e实际开发中\u003cstrong\u003e很少直接用数组\u003c/strong\u003e（定长 + 值拷贝太死板），主力是切片。\u003c/p\u003e","title":"Golang 复合类型：数组、切片与映射"},{"content":"前言 备份、日志切割、证书续期、数据同步……运维一半的自动化都靠定时任务。这篇把 cron 用法与环境陷阱一次讲清。\n一、crontab 基础 crontab -e # 编辑当前用户的定时任务 crontab -l # 列出 crontab -r # 删除全部（危险! 慎用） ls /var/spool/cron/ # 各用户的任务文件 五段表达式：\n┌──────── 分钟 (0-59) │ ┌────── 小时 (0-23) │ │ ┌──── 日 (1-31) │ │ │ ┌── 月 (1-12) │ │ │ │ ┌ 星期 (0-7, 0和7都是周日) │ │ │ │ │ * * * * * 要执行的命令 # 常用示例 */5 * * * * /opt/scripts/health.sh # 每 5 分钟 0 2 * * * /opt/scripts/backup.sh # 每天凌晨 2 点 0 3 * * 1 /opt/scripts/clean.sh # 每周一 3 点 0 0 1 * * /opt/scripts/report.sh # 每月 1 号 30 8 * * 1-5 /opt/scripts/workday.sh # 工作日早 8:30 0 */2 * * * /opt/scripts/sync.sh # 每两小时 二、系统级 /etc/crontab 与 cron.d 区别于用户 crontab，系统任务多一个用户字段：\n# 文件: /etc/crontab SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin # ← 可以定义 PATH! # 分 时 日 月 周 用户 命令 0 2 * * * root /opt/scripts/backup.sh 软件包和运维脚本推荐放 /etc/cron.d/xxx（一个服务一个文件，便于管理与卸载）：\ncat \u0026gt; /etc/cron.d/backup \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; SHELL=/bin/bash PATH=/usr/local/bin:/usr/bin:/bin 0 2 * * * root /opt/scripts/backup.sh \u0026gt;\u0026gt; /var/log/backup.log 2\u0026gt;\u0026amp;1 EOF chmod 644 /etc/cron.d/backup # 权限必须是 644, 否则被忽略 三、两大经典陷阱 陷阱一：PATH 不一致 cron 执行环境不是登录 shell，PATH 通常只有 /usr/bin:/bin。所以你手动跑得好好的脚本，cron 里报 command not found。\n解法（任选其一）：\n# 1. 脚本里全部用绝对路径 /usr/local/bin/rsync -a ... # 2. 脚本开头自己定义 PATH export PATH=/usr/local/bin:/usr/bin:/bin:$PATH # 3. crontab 文件顶部写 PATH=... 陷阱二：% 是特殊字符 cron 里 % 会被解释成换行，date +%F 直接废掉：\n# ❌ 错误 0 2 * * * /opt/backup.sh \u0026gt; /backup-$(date +%F).log # ✅ 正确: % 用反斜杠转义 0 2 * * * /opt/backup.sh \u0026gt; /backup-$(date +\\%F).log 排查方法 # 看 cron 是否执行过你的任务 grep CRON /var/log/cron | tail # 手动模拟 cron 环境 env -i /bin/sh -c \u0026#39;/opt/scripts/backup.sh\u0026#39; # 干净环境跑一遍 四、任务必须\u0026quot;幂等 + 有日志\u0026quot; 好的定时任务脚本三要素：\n#!/usr/bin/env bash set -euo pipefail LOG=/var/log/backup.log exec \u0026gt;\u0026gt;\u0026#34;$LOG\u0026#34; 2\u0026gt;\u0026amp;1 # 所有输出进日志 echo \u0026#34;=== $(date \u0026#39;+%F %T\u0026#39;) 开始备份 ===\u0026#34; # 防重复执行: 拿不到锁就退出 exec 9\u0026gt;/var/run/backup.lock flock -n 9 || { echo \u0026#34;上一次任务还没跑完, 跳过\u0026#34;; exit 0; } # ... 备份逻辑 echo \u0026#34;=== 备份完成 ===\u0026#34; 五、常用模板 每日数据库备份（保留 7 天）：\n0 2 * * * root mysqldump --single-transaction mydb | gzip \u0026gt; /backup/mydb-$(date +\\%F).sql.gz \u0026amp;\u0026amp; find /backup -name \u0026#34;mydb-*.sql.gz\u0026#34; -mtime +7 -delete 日志切割（其实更推荐 logrotate，这里给个 cron 思路）：\n0 0 * * * root find /var/log/myapp -name \u0026#34;*.log\u0026#34; -size +100M -exec gzip {} \\; 证书到期监控（剩余 30 天告警）：\n0 9 * * 1 root for d in /etc/nginx/certs/*.crt; do end=$(openssl x509 -enddate -noout -in \u0026#34;$d\u0026#34; | cut -d= -f2); left=$(( ($(date -d \u0026#34;$end\u0026#34; +%s) - $(date +%s)) / 86400 )); [ \u0026#34;$left\u0026#34; -lt 30 ] \u0026amp;\u0026amp; echo \u0026#34;$d 将在 ${left} 天后过期\u0026#34;; done 六、anacron 与 systemd timer 特性 cron anacron systemd timer 粒度 分钟 天 秒级 错过的任务 跳过 开机补跑 可配置 依赖管理 无 无 可依赖服务/网络 日志 /var/log/cron 自带 journalctl -u xx.timer 服务器 7×24 在线用 cron；笔记本/会关机的机器用 anacron；现代发行版中，logrotate、man-db 等已迁到 systemd timer：\nsystemctl list-timers | head journalctl -u myapp.timer # 看 timer 日志 小结 记住五段表达式 + */n 步进即可覆盖 90% 场景 绝对路径、转义 %、输出重定向到日志是保命三件套 grep CRON /var/log/cron 是排查第一现场 系统级任务放 /etc/cron.d/，一个场景一个文件 本文是「Linux 运维」系列第 4 篇。\n","permalink":"https://antidebug.cn/posts/2023/11/linux-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1-cron-%E5%AE%9E%E6%88%98/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e备份、日志切割、证书续期、数据同步……运维一半的自动化都靠定时任务。这篇把 cron 用法与环境陷阱一次讲清。\u003c/p\u003e\n\u003ch2 id=\"一crontab-基础\"\u003e一、crontab 基础\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecrontab -e      \u003cspan class=\"c1\"\u003e# 编辑当前用户的定时任务\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecrontab -l      \u003cspan class=\"c1\"\u003e# 列出\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecrontab -r      \u003cspan class=\"c1\"\u003e# 删除全部（危险! 慎用）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003els /var/spool/cron/       \u003cspan class=\"c1\"\u003e# 各用户的任务文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e五段表达式\u003c/strong\u003e：\u003c/p\u003e","title":"Linux 定时任务 cron 实战"},{"content":"前言 函数是 Python 的乐高积木。参数传递的几种形态、作用域查找规则、闭包——这三块搞明白，写函数式风格的代码就通了。\n一、参数的四种形态 def connect(host, port=5432, *, timeout=3, **extra): print(f\u0026#34;{host}:{port} timeout={timeout} extra={extra}\u0026#34;) # 1. 位置参数：按顺序传 connect(\u0026#34;db.local\u0026#34;) # 2. 关键字参数：指名道姓传（推荐, 可读性好） connect(host=\u0026#34;db.local\u0026#34;, port=5433) # 3. 仅关键字参数（* 之后的必须用关键字） connect(\u0026#34;db.local\u0026#34;, 5433, timeout=10) # 4. 可变参数 def add(*args, **kwargs): # args 收集多余位置参数为 tuple, kwargs 收集关键字为 dict print(args, kwargs) add(1, 2, 3, debug=True) # (1, 2, 3) {\u0026#39;debug\u0026#39;: True} # 反向解包：调用时把序列/字典摊开 args = (\u0026#34;db.local\u0026#34;, 5433) connect(*args) ⚠️ 默认参数的头号大坑——可变默认值：\ndef bad(item, lst=[]): # ❌ 默认列表在函数定义时只创建一次! lst.append(item) return lst bad(1) # [1] bad(2) # [1, 2] —— 惊不惊喜? # 正确姿势 def good(item, lst=None): if lst is None: lst = [] lst.append(item) return lst 二、作用域：LEGB 规则 Python 查找变量的顺序：Local → Enclosing → Global → Built-in。\nx = \u0026#34;global\u0026#34; def outer(): x = \u0026#34;enclosing\u0026#34; def inner(): x = \u0026#34;local\u0026#34; print(x) # local（L 优先） inner() outer() 想在函数内修改外层变量，必须声明：\ncount = 0 def bump(): global count # 改全局需要 global count += 1 def make_counter(): n = 0 def counter(): nonlocal n # 改闭包变量需要 nonlocal n += 1 return n return counter 良好实践：少用 global——函数应通过参数进、返回值出，保持纯粹。\n三、闭包：函数记住它的出生地 闭包 = 内层函数 + 它引用的外层变量。函数返回后，被引用的环境被\u0026quot;记住\u0026quot;了：\ndef multiplier(factor): def multiply(x): return x * factor # 引用了外层的 factor return multiply double = multiplier(2) triple = multiplier(3) double(10) # 20 triple(10) # 30 print(double.__closure__[0].cell_contents) # 2 —— factor 被闭包持有 闭包的典型应用是工厂函数和装饰器（装饰器本质就是闭包，后面单独写一篇）。\n四、lambda 与高阶函数 # lambda: 一个表达式的小函数 square = lambda x: x * x key_fn = lambda item: item[1] # 高阶函数: 接收/返回函数的函数 nums = [3, 1, 4, 1, 5, 9, 2, 6] list(map(lambda x: x * 2, nums)) # [6, 2, 8, ...] list(filter(lambda x: x \u0026gt; 3, nums)) # [4, 5, 9, 6] # sorted 的 key 用法（日常最高频） words = [\u0026#34;banana\u0026#34;, \u0026#34;apple\u0026#34;, \u0026#34;cherry\u0026#34;] sorted(words) # 按字母 sorted(words, key=len) # 按长度 sorted(words, key=lambda w: w[::-1]) # 按反转拼写（演示而已） students = [(\u0026#34;zy\u0026#34;, 88), (\u0026#34;ab\u0026#34;, 95), (\u0026#34;cd\u0026#34;, 72)] sorted(students, key=lambda s: s[1], reverse=True) # 按分数降序 # max/min 也吃 key max(students, key=lambda s: s[1]) # (\u0026#39;ab\u0026#39;, 95) 其实大多数 map/filter 场景，推导式可读性更好：\n[x * 2 for x in nums] [x for x in nums if x \u0026gt; 3] 五、实战：一个通用重试函数 把\u0026quot;参数默认值 + 闭包 + 高阶函数\u0026quot;揉在一起：\nimport time from functools import wraps def retry(times=3, delay=1): \u0026#34;\u0026#34;\u0026#34;装饰器工厂: 生成一个重试装饰器\u0026#34;\u0026#34;\u0026#34; def decorator(func): @wraps(func) # 保留原函数元信息 def wrapper(*args, **kwargs): for attempt in range(1, times + 1): try: return func(*args, **kwargs) except Exception as e: print(f\u0026#34;第{attempt}次失败: {e}\u0026#34;) if attempt == times: raise # 最后一次失败就抛出 time.sleep(delay) return wrapper return decorator @retry(times=5, delay=2) def fetch(url): import urllib.request return urllib.request.urlopen(url, timeout=3).status 这段代码先用着，原理在《装饰器与生成器》一篇展开。\n六、函数是一等公民 # 函数可以存进数据结构、当参数传、当返回值 ops = {\u0026#34;add\u0026#34;: lambda a, b: a + b, \u0026#34;sub\u0026#34;: lambda a, b: a - b} ops[\u0026#34;add\u0026#34;](3, 4) # 7 def apply(f, data): return [f(x) for x in data] apply(str.upper, [\u0026#34;go\u0026#34;, \u0026#34;py\u0026#34;]) # [\u0026#39;GO\u0026#39;, \u0026#39;PY\u0026#39;] 小结 知识点 要点 可变默认参数 用 None 哨兵值 作用域 LEGB；改外层用 global/nonlocal 闭包 内层函数持有外层变量 lambda 配 sorted/map/filter 的 key 用 高阶函数 函数可以当值传递 本文是「Python」系列第 3 篇。\n","permalink":"https://antidebug.cn/posts/2023/10/python-%E5%87%BD%E6%95%B0%E8%BF%9B%E9%98%B6%E5%8F%82%E6%95%B0%E4%BD%9C%E7%94%A8%E5%9F%9F%E4%B8%8E%E9%97%AD%E5%8C%85/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e函数是 Python 的乐高积木。参数传递的几种形态、作用域查找规则、闭包——这三块搞明白，写函数式风格的代码就通了。\u003c/p\u003e\n\u003ch2 id=\"一参数的四种形态\"\u003e一、参数的四种形态\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e5432\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e\u003cspan class=\"n\"\u003eextra\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e:\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e timeout=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e extra=\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eextra\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 1. 位置参数：按顺序传\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;db.local\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 2. 关键字参数：指名道姓传（推荐, 可读性好）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;db.local\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e5433\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 3. 仅关键字参数（* 之后的必须用关键字）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;db.local\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e5433\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 4. 可变参数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e\u003cspan class=\"n\"\u003ekwargs\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e        \u003cspan class=\"c1\"\u003e# args 收集多余位置参数为 tuple, kwargs 收集关键字为 dict\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekwargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edebug\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e         \u003cspan class=\"c1\"\u003e# (1, 2, 3) {\u0026#39;debug\u0026#39;: True}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 反向解包：调用时把序列/字典摊开\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;db.local\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e5433\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e⚠️ \u003cstrong\u003e默认参数的头号大坑——可变默认值\u003c/strong\u003e：\u003c/p\u003e","title":"Python 函数进阶：参数、作用域与闭包"},{"content":"前言 概念懂了，开始动手装 Docker。目标：装好、跑起第一个容器、掌握镜像与容器的日常操作。\n一、安装与加速 # 1. 卸载旧版本 dnf remove -y docker docker-client docker-common # 2. 配置国内 yum 源（阿里云） dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 3. 安装 dnf install -y docker-ce docker-ce-cli containerd.io # 4. 配置镜像加速器（国内拉 Docker Hub 很慢） mkdir -p /etc/docker cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; { \u0026#34;registry-mirrors\u0026#34;: [\u0026#34;https://docker.m.daocloud.io\u0026#34;], \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: {\u0026#34;max-size\u0026#34;: \u0026#34;50m\u0026#34;, \u0026#34;max-file\u0026#34;: \u0026#34;3\u0026#34;} } EOF # 5. 启动 systemctl enable --now docker docker version # Client + Server 都有输出即成功 daemon.json 里顺手配了日志上限——容器 stdout 日志不加限制，迟早把磁盘写爆（血泪教训）。\n二、第一个容器 # 跑个 Nginx（本地没有镜像会自动 pull） docker run -d --name web -p 8080:80 nginx:alpine # 参数拆解: # -d 后台运行 # --name web 容器命名 # -p 8080:80 端口映射: 宿主机8080 -\u0026gt; 容器80 # nginx:alpine 精简版镜像 curl -I http://127.0.0.1:8080 # 200 OK 🎉 数据持久化——数据卷挂载：\ndocker run -d --name web \\ -p 8080:80 \\ -v /opt/www:/usr/share/nginx/html:ro \\ nginx:alpine # -v 宿主机路径:容器路径[:ro] # 容器删了, /opt/www 的文件还在 —— 数据不进容器是黄金法则 三、镜像操作 docker pull python:3.11-slim # 拉取（不写 tag 默认 latest, 生产禁用） docker images # 本地镜像列表 docker rmi python:3.11-slim # 删除镜像 docker tag nginx:alpine reg.local/mynginx:v1 # 打标签 docker push reg.local/mynginx:v1 # 推到私有仓库 docker save -o nginx.tar nginx:alpine # 导出离线包 docker load -i nginx.tar # 导入 docker history nginx:alpine # 看镜像分层 docker inspect nginx:alpine # 镜像详情（JSON） 镜像分层：镜像是只读层的堆叠（每条 Dockerfile 指令一层），多个镜像共享基础层，省空间、拉取快。\n四、容器生命周期 docker ps # 运行中的容器 docker ps -a # 包括已停止的 docker stop web \u0026amp;\u0026amp; docker start web docker restart web docker rm web # 删除（停止的）容器 docker rm -f web # 强删（运行中的） docker container prune # 清理所有停止的容器 # 进入容器排查（比 attach 安全, 退出不带走容器） docker exec -it web sh # 看日志 docker logs -f --tail 100 web # 拷文件（双向） docker cp web:/etc/nginx/nginx.conf ./ docker cp ./app.conf web:/etc/app/ # 查看资源占用 docker stats --no-stream docker top web # 容器内进程 五、容器 ↔ 进程的对应关系 # 容器本质是宿主机上的进程 docker inspect -f \u0026#39;{{.State.Pid}}\u0026#39; web # 拿到容器主进程 PID, 假设 9876 ps -p 9876 # 宿主机上能看到 ls /proc/9876/ns/ # namespace 隔离证据 六、动手练习：跑一个 Python 应用 mkdir -p /opt/pyapp \u0026amp;\u0026amp; cd /opt/pyapp cat \u0026gt; app.py \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; from flask import Flask app = Flask(__name__) @app.route(\u0026#34;/\u0026#34;) def index(): return \u0026#34;hello from container\\n\u0026#34; app.run(host=\u0026#34;0.0.0.0\u0026#34;, port=5000) EOF docker run -d --name pyapp \\ -v /opt/pyapp:/app -w /app \\ -p 5000:5000 \\ python:3.11-slim \\ sh -c \u0026#34;pip install -q flask -i https://mirrors.aliyun.com/pypi/simple/ \u0026amp;\u0026amp; python app.py\u0026#34; curl http://127.0.0.1:5000 这个例子把代码挂载、工作目录、端口映射、启动命令四个高频点全串起来了。\n七、常见坑速查 现象 原因与解法 拉镜像超时 没配 registry-mirrors，或镜像源挂了换一个 端口映射后外部不通 云安全组没放行；或容器内服务只监听 127.0.0.1 容器一启动就退出 前台没有常驻进程；看 docker logs 磁盘被吃满 /var/lib/docker 膨胀，docker system prune + 日志限额 exec 进不去 精简镜像（alpine/scratch）没有 bash，用 sh # 大扫除（谨慎: 会删掉未使用的镜像/容器/网络） docker system df # 先看占用 docker system prune -a 小结 操作 命令 跑容器 docker run -d --name x -p 宿:容器 -v 宿:容器 镜像 进容器 docker exec -it x sh 看日志 docker logs -f x 清理 docker rm / docker rmi / system prune 牢记两条黄金法则：数据不进容器（用 -v）、生产不用 latest（锁版本）。下一篇写 Dockerfile，把\u0026quot;怎么做出自己的镜像\u0026quot;补上。\n本文是「云原生」系列第 2 篇。\n","permalink":"https://antidebug.cn/posts/2023/10/docker-%E5%AE%89%E8%A3%85%E4%B8%8E%E9%95%9C%E5%83%8F%E5%9F%BA%E7%A1%80%E6%93%8D%E4%BD%9C/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e概念懂了，开始动手装 Docker。目标：装好、跑起第一个容器、掌握镜像与容器的日常操作。\u003c/p\u003e\n\u003ch2 id=\"一安装与加速\"\u003e一、安装与加速\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 1. 卸载旧版本\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ednf remove -y docker docker-client docker-common\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 2. 配置国内 yum 源（阿里云）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ednf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 3. 安装\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ednf install -y docker-ce docker-ce-cli containerd.io\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 4. 配置镜像加速器（国内拉 Docker Hub 很慢）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emkdir -p /etc/docker\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecat \u0026gt; /etc/docker/daemon.json \u003cspan class=\"s\"\u003e\u0026lt;\u0026lt;\u0026#39;EOF\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  \u0026#34;registry-mirrors\u0026#34;: [\u0026#34;https://docker.m.daocloud.io\u0026#34;],\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  \u0026#34;log-opts\u0026#34;: {\u0026#34;max-size\u0026#34;: \u0026#34;50m\u0026#34;, \u0026#34;max-file\u0026#34;: \u0026#34;3\u0026#34;}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eEOF\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 5. 启动\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl \u003cspan class=\"nb\"\u003eenable\u003c/span\u003e --now docker\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker version          \u003cspan class=\"c1\"\u003e# Client + Server 都有输出即成功\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003edaemon.json\u003c/code\u003e 里顺手配了日志上限——容器 stdout 日志不加限制，迟早把磁盘写爆（血泪教训）。\u003c/p\u003e","title":"Docker 安装与镜像基础操作"},{"content":"前言 这两年\u0026quot;云原生\u0026quot;（Cloud Native）铺天盖地，但很多同学只是\u0026quot;用着 Docker\u0026quot;，却说不出容器到底解决了什么问题。这篇把概念的来龙去脉捋清楚。\n一、部署方式的演进 阶段 时代 部署单元 启动速度 资源开销 物理机 ~2005 整台服务器 小时/天 100% 独占 虚拟机 2005~2013 VM 分钟级 GB 级、每个 VM 一个完整 OS 容器 2013~ 容器 秒级 MB 级、共享宿主内核 Serverless 现在 函数 毫秒级 按调用计费 每一跃迁的本质都是：部署单元更轻、边界更细、弹性更快。\n二、虚拟机 vs 容器 两者都是\u0026quot;隔离环境\u0026quot;，但隔离的层级完全不同：\n┌─────────────────────────────┐ ┌─────────────────────────────┐ │ VM 架构 │ │ 容器架构 │ │ │ │ │ │ App A │ App B │ │ App A │ App B │ │ Guest OS │ Guest OS │ │ Bins/Libs │ Bins/Libs │ │─────────┴──────────────────│ │───────────┴─────────────────│ │ Hypervisor │ │ Container Engine │ │ （KVM/ESXi） │ │ （Docker） │ │ Host OS │ │ Host OS │ │ 硬件 │ │ 硬件 │ └─────────────────────────────┘ └─────────────────────────────┘ 维度 虚拟机 容器 隔离级别 硬件级（独立内核） 进程级（共享内核） 体积 几 GB~几十 GB 几 MB~几百 MB 启动 分钟 秒 密度 一台物理机几十个 一台宿主机成百上千个 安全隔离 强 相对弱（内核漏洞影响面大） 典型用途 多租户、异构 OS 应用交付、微服务、CI/CD 一句话总结：虚拟机隔离\u0026quot;机器\u0026quot;，容器隔离\u0026quot;应用\u0026quot;。\n三、容器到底解决了什么问题 1. \u0026ldquo;在我机器上是好的\u0026rdquo; 传统交付：源码/二进制 + 一份部署文档（装哪些依赖、改哪些配置）。环境差异是无穷无尽的坑。\n容器把应用 + 依赖 + 运行时打包成镜像，走到哪都一样——镜像即交付物。\n2. 环境搭建成本 # 传统方式装一个 MySQL：加源、装包、初始化、配权限……半小时 # 容器方式： docker run -d -e MYSQL_ROOT_PASSWORD=pass mysql:8.0 # 30 秒后你有一个干净的 MySQL 3. 资源利用率 VM 每个都要跑完整 OS，闲置时内存/CPU 浪费严重；容器共享内核、按需分配，同样硬件能承载多得多的 workload。\n4. 弹性与标准化 镜像不可变（Immutable）→ 扩容就是\u0026quot;多跑几个副本\u0026quot;；OCI 标准化后，镜像可以在任何符合规范的引擎上运行——这是 Kubernetes 得以统治编排层的前提。\n四、容器靠什么实现（预告） 容器不是\u0026quot;轻量虚拟机\u0026quot;，而是被内核特性\u0026quot;圈起来的进程\u0026quot;：\nNamespace：视图隔离（pid、net、mnt、uts、ipc、user），让进程以为自己独占系统 Cgroup：资源限额（CPU、内存、IO），防止互相挤兑 联合文件系统（overlayfs）：分层镜像，多层只读层 + 顶层可写层 这三板斧的细节后续单独开一篇（详见《容器底层原理》）。\n五、云原生技术版图 CNCF（云原生计算基金会）的景观图有几百个项目，抓大放小记这几块：\n领域 代表项目 作用 容器运行时 Docker/containerd 跑容器 编排调度 Kubernetes 容器的\u0026quot;操作系统\u0026quot;，事实标准 服务发现/网格 CoreDNS、Istio 微服务通信治理 可观测性 Prometheus、Grafana、ELK 指标、日志、链路 CI/CD GitLab CI、ArgoCD 自动化构建发布 镜像仓库 Harbor 私有镜像存储 IaC Terraform 声明式管理基础设施 云原生的核心思想：应用从设计之初就为云环境而生——弹性、可观测、松耦合、可自动化。\n六、学习路线建议 1. Docker → 会打包镜像、compose 起多容器（当前优先级最高） 2. K8s 核心对象 → Pod/Deployment/Service 3. 配置与存储 → ConfigMap/Secret/PV/PVC 4. 生态工具 → Helm、Prometheus、日志方案 5. 进阶 → 网络原理、调度策略、服务网格 小结 容器 = 进程级隔离 + 镜像化交付，解决环境一致性与资源效率 虚拟机隔离强、容器密度高，生产中常常组合使用（K8s 节点本身就是 VM） 云原生 = 容器 + 编排 + DevOps + 微服务的方法论集合 下一篇开始动手：Docker 安装与镜像操作。\n本文是「云原生」系列第 1 篇。\n","permalink":"https://antidebug.cn/posts/2023/09/%E4%BA%91%E5%8E%9F%E7%94%9F%E5%85%A5%E9%97%A8%E4%BB%8E%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%B0%E5%AE%B9%E5%99%A8/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e这两年\u0026quot;云原生\u0026quot;（Cloud Native）铺天盖地，但很多同学只是\u0026quot;用着 Docker\u0026quot;，却说不出容器到底解决了什么问题。这篇把概念的来龙去脉捋清楚。\u003c/p\u003e","title":"云原生入门：从虚拟机到容器"},{"content":"前言 现代 Linux（CentOS 7+/Debian 8+）的 1 号进程都是 systemd，服务管理早已是它的天下。systemctl 是每个运维每天都要敲的命令，这篇把常用姿势和进阶玩法一次讲透。\n一、基础操作 systemctl start nginx # 启动 systemctl stop nginx # 停止 systemctl restart nginx # 重启 systemctl reload nginx # 重载配置（不断连接, 服务需支持） systemctl status nginx # 状态（最常用, 出问题先看它） systemctl enable nginx # 开机自启 systemctl disable nginx # 取消自启 systemctl enable --now nginx # 自启 + 立即启动（一步到位） systemctl is-active nginx # 是否运行中 systemctl is-enabled nginx # 是否自启 看懂 status 输出：\n● nginx.service - The nginx HTTP and reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled) Active: active (running) since Sat 2023-09-09 10:00:00 CST; 3 days ago Main PID: 1234 (nginx) Tasks: 3 (limit: 4915) Memory: 12.5M CPU: 1.204s CGroup: /system.slice/nginx.service ├─1234 nginx: master process └─1235 nginx: worker process Sep 09 10:00:00 web01 systemd[1]: Started nginx... 重点关注：Active 状态（active (running) / failed）、enabled 自启与否、Main PID、最近的日志行。failed 状态时把最后几行日志读完，八成原因就在里面。\n二、Unit 文件结构 服务定义文件位于 /usr/lib/systemd/system/（软件包安装）或 /etc/systemd/system/（自定义，优先级更高）。\n# /etc/systemd/system/myapp.service [Unit] Description=My Go Application # 描述 After=network-online.target # 在网络就绪后再启动 Wants=network-online.target # 弱依赖: 拉起它, 失败不阻塞 [Service] Type=simple # 启动类型 ExecStart=/opt/myapp/bin/server --port 8080 ExecReload=/bin/kill -HUP $MAINPID # reload 时执行的命令 Restart=on-failure # 异常退出自动拉起 RestartSec=5s # 重启间隔 User=app # 运行用户（最小权限!） WorkingDirectory=/opt/myapp Environment=GIN_MODE=release # 环境变量 LimitNOFILE=65536 # 文件描述符上限 [Install] WantedBy=multi-user.target # 挂到哪个启动级别 Type 四种取值：\nType 含义 适用 simple ExecStart 启动的进程就是主进程 前台程序（默认） forking 进程会 fork 到后台，父进程退出 传统守护进程 notify 进程主动发信号告知就绪 支持 sd_notify 的程序 oneshot 跑完就退出 初始化脚本 修改 unit 后必须：\nsystemctl daemon-reload # 重载 unit 定义 systemctl restart myapp 三、依赖与启动顺序 After= / Before=：只控制顺序，不拉起对方 Wants=：弱依赖，拉起失败也继续 Requires=：强依赖，失败则自己跟着失败 BindsTo=：绑定命运，对方停了自己也停 常见组合：数据库服务写 After=network-online.target + Wants=network-online.target，避免网络没好就启动连库失败。\n四、journalctl：日志的正确打开方式 journalctl -u nginx # 某服务的全部日志 journalctl -u nginx -f # 实时跟踪（像 tail -f） journalctl -u nginx -n 50 # 最近 50 行 journalctl -u nginx --since today journalctl --since \u0026#34;2023-09-09 10:00\u0026#34; --until \u0026#34;2023-09-09 12:00\u0026#34; journalctl -p err -b # 本次开机以来的 error 级日志 journalctl -b -1 # 上一次开机的日志（排查重启原因神器） journalctl --disk-usage # 日志占用 journalctl --vacuum-size=500M # 收缩到 500M 五、实战：把 Go 程序做成系统服务 # 1. 二进制就位 install -m 755 /root/server /opt/myapp/bin/server # 2. 写 unit（用上面 myapp.service 的模板） vim /etc/systemd/system/myapp.service # 3. 启用 systemctl daemon-reload systemctl enable --now myapp systemctl status myapp # active (running) 即成功 # 4. 验证开机自启与崩溃自愈 systemctl is-enabled myapp # enabled kill -9 $(systemctl show -p MainPID --value myapp) sleep 6 \u0026amp;\u0026amp; systemctl is-active myapp # active —— Restart=on-failure 生效 六、其他常用单位类型 systemctl list-units --type=service --state=running # 正在跑的服务 systemctl list-unit-files --type=service # 全部服务及自启状态 systemctl list-timers # 定时任务（下篇细讲） systemctl reboot / poweroff / suspend # 电源管理 systemctl isolate multi-user.target # 切到无图形界面 常见坑 现象 原因 改了 unit 不生效 忘了 daemon-reload start 显示 active 但秒变 failed 用 status 看退出码 + journalctl -u xx 看报错 程序跑了几小时被杀 没配 Restart；或内存超限被 OOM（dmesg | grep -i kill） 中文日志乱码 journalctl 按 locale 显示，export LANG=en_US.UTF-8 试试 服务以 root 跑被安全扫描告警 unit 里加 User= 切普通用户 小结 日常三板斧：status → journalctl -u → restart 自定义服务记住模板：[Unit] 描述依赖、[Service] 命令与自愈、[Install] 挂载级别 enable --now、daemon-reload、-b -1 是三个高频却总被忘的命令 本文是「Linux 运维」系列第 3 篇。\n","permalink":"https://antidebug.cn/posts/2023/09/systemd-%E6%9C%8D%E5%8A%A1%E7%AE%A1%E7%90%86%E8%AF%A6%E8%A7%A3/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e现代 Linux（CentOS 7+/Debian 8+）的 1 号进程都是 systemd，服务管理早已是它的天下。\u003ccode\u003esystemctl\u003c/code\u003e 是每个运维每天都要敲的命令，这篇把常用姿势和进阶玩法一次讲透。\u003c/p\u003e","title":"Systemd 服务管理详解"},{"content":"前言 Python 写得好不好，一半看数据结构选得对不对。内置的四大金刚——列表、元组、字典、集合——覆盖了日常 95% 的场景。\n一、列表：有序可变序列 # 创建 nums = [1, 2, 3] mixed = [1, \u0026#34;hello\u0026#34;, True, [4, 5]] # 可以混装类型 # 索引与切片 nums[0] # 1 nums[-1] # 3（负数从尾部数） nums[0:2] # [1, 2] nums[::-1] # [3, 2, 1] 反转 # 增删改 nums.append(4) # 尾部追加: [1,2,3,4] nums.insert(0, 0) # 指定位置插入 nums.extend([5, 6]) # 拼接另一个列表 nums.pop() # 弹出尾部元素 nums.pop(0) # 弹出指定下标 nums.remove(3) # 按值删除（只删第一个匹配） del nums[0] # 按下标删除 # 常用操作 len(nums) # 长度 sorted(nums, reverse=True) max(nums), min(nums), sum(nums) 1 in nums # 成员判断（列表是 O(n)） nums.index(2) # 查下标 nums.count(2) # 计数 ⚠️ 经典坑：b = a 只是引用同一个列表，改 b 会影响 a。要复制用 a.copy() 或 a[:]。\n二、元组：不可变序列 point = (3, 5) x, y = point # 解包（很常用!） a, b = b, a # 交换变量 # 单元素元组必须带逗号 t = (42,) # ✅ 元组 t = (42) # ❌ 这只是个 int # 不可变 = 可哈希 = 能当字典的 key、能进集合 locations = {(30.5, 114.3): \u0026#34;wuhan\u0026#34;} 函数返回多个值，本质就是返回元组：\ndef min_max(nums): return min(nums), max(nums) lo, hi = min_max([3, 1, 4, 1, 5]) 三、字典：键值对映射 user = {\u0026#34;name\u0026#34;: \u0026#34;zy\u0026#34;, \u0026#34;level\u0026#34;: 5, \u0026#34;tags\u0026#34;: [\u0026#34;linux\u0026#34;, \u0026#34;go\u0026#34;]} # 读写 user[\u0026#34;name\u0026#34;] # \u0026#39;zy\u0026#39; user.get(\u0026#34;email\u0026#34;) # None（不抛 KeyError） user.get(\u0026#34;email\u0026#34;, \u0026#34;未填写\u0026#34;) # 带默认值 user[\u0026#34;email\u0026#34;] = \u0026#34;zy@x.cn\u0026#34; # 新增/修改 del user[\u0026#34;email\u0026#34;] # 删除 # 安全访问嵌套结构 conf = {\u0026#34;server\u0026#34;: {\u0026#34;port\u0026#34;: 8080}} port = conf.get(\u0026#34;server\u0026#34;, {}).get(\u0026#34;port\u0026#34;, 80) # 遍历 for k, v in user.items(): print(f\u0026#34;{k} = {v}\u0026#34;) # 合并（3.9+） defaults = {\u0026#34;debug\u0026#34;: False, \u0026#34;port\u0026#34;: 80} config = defaults | {\u0026#34;port\u0026#34;: 8080} # {\u0026#39;debug\u0026#39;: False, \u0026#39;port\u0026#39;: 8080} 字典的查找是 O(1)（哈希表），这是它与列表最大的性能差异，也是\u0026quot;用空间换时间\u0026quot;的经典设计。\n实用技巧——计数与分组：\nfrom collections import Counter, defaultdict words = [\u0026#34;go\u0026#34;, \u0026#34;py\u0026#34;, \u0026#34;go\u0026#34;, \u0026#34;sh\u0026#34;, \u0026#34;go\u0026#34;] Counter(words).most_common(2) # [(\u0026#39;go\u0026#39;, 3), (\u0026#39;py\u0026#39;, 1)] groups = defaultdict(list) for name, dept in [(\u0026#34;a\u0026#34;, \u0026#34;ops\u0026#34;), (\u0026#34;b\u0026#34;, \u0026#34;dev\u0026#34;), (\u0026#34;c\u0026#34;, \u0026#34;ops\u0026#34;)]: groups[dept].append(name) # {\u0026#39;ops\u0026#39;: [\u0026#39;a\u0026#39;, \u0026#39;c\u0026#39;], \u0026#39;dev\u0026#39;: [\u0026#39;b\u0026#39;]} 四、集合：无序不重复 a = {1, 2, 3} b = {3, 4, 5} a | b # 并集 {1,2,3,4,5} a \u0026amp; b # 交集 {3} a - b # 差集 {1,2} a ^ b # 对称差 {1,2,4,5} # 最常见用途: 去重 dup = [1, 3, 2, 3, 1] unique = list(set(dup)) # [1, 2, 3]（顺序不保证） # 成员判断 O(1), 大列表判存在性时先转 set valid = {\u0026#34;prod\u0026#34;, \u0026#34;staging\u0026#34;} if env in valid: ... 五、推导式：Pythonic 的灵魂 # 列表推导式 squares = [x * x for x in range(10)] evens = [x for x in range(20) if x % 2 == 0] # 带条件表达式 labels = [\u0026#34;偶\u0026#34; if x % 2 == 0 else \u0026#34;奇\u0026#34; for x in range(5)] # 字典推导式 word_len = {w: len(w) for w in [\u0026#34;go\u0026#34;, \u0026#34;python\u0026#34;, \u0026#34;shell\u0026#34;]} # {\u0026#39;go\u0026#39;: 2, \u0026#39;python\u0026#39;: 6, \u0026#39;shell\u0026#39;: 5} # 集合推导式 first_chars = {w[0] for w in [\u0026#34;apple\u0026#34;, \u0026#34;avocado\u0026#34;, \u0026#34;banana\u0026#34;]} # 生成器表达式: 惰性求值, 省内存 total = sum(x * x for x in range(1_000_000)) 六、怎么选？ 需求 选择 原因 有序、会增删 list 通用序列 固定不变的配对/坐标 tuple 不可变、可哈希 按 key 快速查找 dict O(1) 查找 去重、交并差 set O(1) 成员判断 一次性大数据流 生成器 不占内存 实战小例子：日志状态统计 import re from collections import Counter stats = Counter() with open(\u0026#34;access.log\u0026#34;) as f: for line in f: m = re.search(r\u0026#39;\u0026#34; (\\d{3}) \u0026#39;, line) if m: stats[m.group(1)] += 1 for code, n in stats.most_common(): print(f\u0026#34;{code}: {n}\u0026#34;) 字典 + Counter + 推导式，几行代码顶半页 if-else。\n小结 四大结构一句话：列表管顺序、元组管不变、字典管映射、集合管唯一。加上推导式，就是 Python 数据处理的全部地基。\n本文是「Python」系列第 2 篇。\n","permalink":"https://antidebug.cn/posts/2023/08/python-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%88%97%E8%A1%A8%E5%AD%97%E5%85%B8%E4%B8%8E%E9%9B%86%E5%90%88/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003ePython 写得好不好，一半看数据结构选得对不对。内置的四大金刚——\u003cstrong\u003e列表、元组、字典、集合\u003c/strong\u003e——覆盖了日常 95% 的场景。\u003c/p\u003e\n\u003ch2 id=\"一列表有序可变序列\"\u003e一、列表：有序可变序列\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 创建\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003emixed\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;hello\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e]]\u003c/span\u003e     \u003cspan class=\"c1\"\u003e# 可以混装类型\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 索引与切片\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e          \u003cspan class=\"c1\"\u003e# 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e         \u003cspan class=\"c1\"\u003e# 3（负数从尾部数）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e        \u003cspan class=\"c1\"\u003e# [1, 2]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e[::\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e       \u003cspan class=\"c1\"\u003e# [3, 2, 1] 反转\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 增删改\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e           \u003cspan class=\"c1\"\u003e# 尾部追加: [1,2,3,4]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einsert\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e        \u003cspan class=\"c1\"\u003e# 指定位置插入\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eextend\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e      \u003cspan class=\"c1\"\u003e# 拼接另一个列表\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e               \u003cspan class=\"c1\"\u003e# 弹出尾部元素\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epop\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e              \u003cspan class=\"c1\"\u003e# 弹出指定下标\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eremove\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e           \u003cspan class=\"c1\"\u003e# 按值删除（只删第一个匹配）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edel\u003c/span\u003e \u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e              \u003cspan class=\"c1\"\u003e# 按下标删除\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 常用操作\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                \u003cspan class=\"c1\"\u003e# 长度\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003esorted\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ereverse\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003emax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nb\"\u003emin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nb\"\u003esum\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003enums\u003c/span\u003e                \u003cspan class=\"c1\"\u003e# 成员判断（列表是 O(n)）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e            \u003cspan class=\"c1\"\u003e# 查下标\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003enums\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e            \u003cspan class=\"c1\"\u003e# 计数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e⚠️ 经典坑：\u003ccode\u003eb = a\u003c/code\u003e 只是\u003cstrong\u003e引用同一个列表\u003c/strong\u003e，改 b 会影响 a。要复制用 \u003ccode\u003ea.copy()\u003c/code\u003e 或 \u003ccode\u003ea[:]\u003c/code\u003e。\u003c/p\u003e","title":"Python 数据结构：列表、字典与集合"},{"content":"前言 \u0026ldquo;服务起不来\u0026rdquo;、\u0026ldquo;连不上数据库\u0026rdquo;、\u0026ldquo;网页打不开\u0026rdquo;——90% 的线上问题最后都指向网络。这篇整理 Linux 网络排查的命令工具链和标准流程。\n一、看懂网络接口与 IP 老的 ifconfig/netstat 已逐步被 ip/ss 取代（iproute2 工具集）：\n# 查看网卡与 IP ip addr # 简写 ip a ip -br addr # 精简模式, 一行一块网卡（推荐!） # 输出示例: # lo UNKNOWN 127.0.0.1/8 # eth0 UP 10.0.4.13/24 关键信息：网卡名（eth0/ens5）、状态（UP）、IP 与掩码（/24 = 255.255.255.0）。\n# 查看路由表（下一跳在哪里） ip route # default via 10.0.4.1 dev eth0 # 查看网卡统计（丢包、错误） ip -s link show eth0 临时改 IP（重启失效，永久改用 nmcli 或写配置文件）：\nip addr add 192.168.1.100/24 dev eth0 ip link set eth0 up/down RHEL 系永久配置用 NetworkManager：\nnmcli con show # 看连接名 nmcli con mod \u0026#34;System eth0\u0026#34; ipv4.addresses 10.0.4.13/24 nmcli con up \u0026#34;System eth0\u0026#34; 二、端口与连接：ss # 监听中的端口（-t tcp, -u udp, -l listen, -n 数字, -p 进程） ss -tlnp # 所有 TCP 连接（含状态） ss -tan # 谁连着我的 3306 ss -tnp state established \u0026#39;( dport = :3306 )\u0026#39; # 统计各状态连接数（TIME_WAIT 堆积?） ss -tan | awk \u0026#39;{print $1}\u0026#39; | sort | uniq -c TCP 状态速查（排查必背）：\n状态 含义 大量出现说明 LISTEN 服务在等连接 正常 ESTAB 连接建立中 正常（活跃连接） TIME_WAIT 主动关闭后的等待期 短连接太频繁，考虑连接池/长连接 CLOSE_WAIT 对端关了，本端还没关 代码没调 close()，查应用 bug SYN_RECV 半连接 可能是 SYN 攻击 三、连通性测试 # ICMP 连通性（防火墙可能禁 ping, 不通不代表挂了） ping -c 4 8.8.8.8 # 端口级探测（比 ping 更贴近业务） curl -v telnet://10.0.4.5:3306 # 试试 3306 通不通 nc -zv 10.0.4.5 3306 # -z 只测端口 timeout 2 bash -c \u0026#39;\u0026lt;/dev/tcp/10.0.4.5/3306\u0026#39; \u0026amp;\u0026amp; echo OK # 路径追踪: 看卡在哪一跳 traceroute -n 8.8.8.8 mtr 8.8.8.8 # traceroute + ping 持续统计（更好用） # DNS 检查 dig +short antidebug.cn # 解析出 IP 即正常 dig @114.114.114.114 antidebug.cn # 指定 DNS 服务器 nslookup antidebug.cn 四、防火墙 # firewalld（RHEL 系默认） systemctl is-active firewalld firewall-cmd --list-all # 当前开放情况 firewall-cmd --add-service=http --permanent # 放行 80 firewall-cmd --add-port=8080/tcp --permanent # 放行自定义端口 firewall-cmd --reload # 重载生效 # nftables / iptables 底层规则 nft list ruleset | head -30 iptables -L -n | head -20 ⚠️ 云服务器还有第三层\u0026quot;墙\u0026quot;：云平台安全组。本机防火墙全关也可能因安全组没放行而连不上——排查时先看安全组。\n五、标准排查流程：端口不通的六步法 以\u0026quot;外部访问 http://服务器IP:8080 不通\u0026quot;为例：\n# 1. 进程在吗? 端口在听吗? ss -tlnp | grep 8080 # 2. 本机能通吗?（排除应用自身问题） curl -I http://127.0.0.1:8080 # 3. 本机防火墙放行了吗? firewall-cmd --list-all | grep 8080 # 4. 云安全组放行了吗?（控制台看, 这步最常被忽略） # 5. 从外部探测 nc -zv 服务器IP 8080 # 6. 抓包看真相（终极手段） tcpdump -i any -nn port 8080 -c 50 九成问题在第 1、3、4 步就现形：要么进程没起来/只监听了 127.0.0.1，要么是墙没放行。\n六、容易踩的坑 现象 原因 服务本机 curl 通、外部不通 进程只监听 127.0.0.1，要改成 0.0.0.0 ping 通但端口不通 ICMP 与 TCP 是两回事，端口有防火墙/安全组拦截 改了 DNS 不生效 systemd-resolved 有缓存；resolvectl flush-caches TIME_WAIT 几万个 短连接风暴，应用侧上连接池或内核调参 小结 排查网络问题，按\u0026quot;进程 → 本机 → 防火墙 → 安全组 → 外部 → 抓包\u0026ldquo;六步走，基本无死角。ip -br a、ss -tlnp、curl -v、tcpdump 是必须形成肌肉记忆的四个命令。\n本文是「Linux 基础」系列第 5 篇。\n","permalink":"https://antidebug.cn/posts/2023/08/linux-%E7%BD%91%E7%BB%9C%E9%85%8D%E7%BD%AE%E4%B8%8E%E6%8E%92%E6%9F%A5%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e\u0026ldquo;服务起不来\u0026rdquo;、\u0026ldquo;连不上数据库\u0026rdquo;、\u0026ldquo;网页打不开\u0026rdquo;——90% 的线上问题最后都指向网络。这篇整理 Linux 网络排查的命令工具链和标准流程。\u003c/p\u003e","title":"Linux 网络配置与排查常用命令"},{"content":"前言 环境搭好（见上篇），开始正式修炼 Go 语法。Go 的设计哲学是 少即是多——关键字只有 25 个，没有继承、没有异常、没有三元运算符，但每个特性都精打细磨。\n一、变量与常量 package main import \u0026#34;fmt\u0026#34; func main() { // 三种声明方式 var a int = 10 // 完整声明 var b = 3.14 // 类型推断 c := \u0026#34;hello\u0026#34; // 短声明（只能用在函数内, 最常用） // 多变量 x, y := 1, 2 x, y = y, x // 交换变量, 不需要中间变量! // 常量（编译期确定, 不可修改） const Pi = 3.14159 const ( StatusOK = 200 StatusErr = 500 ) // iota: 自动递增的常量生成器 const ( Mon = iota + 1 // 1 Tue // 2 Wed // 3 ) fmt.Println(a, b, c, x, y, Pi, Mon, Tue, Wed) } ⚠️ Go 没有\u0026quot;未使用警告\u0026quot;那么温柔——声明了不用的变量直接编译报错，逼你保持代码干净。\n二、基本类型与零值 类别 类型 零值 整型 int, int8/16/32/64, uint... 0 浮点 float32, float64 0.0 布尔 bool false 字符串 string \u0026quot;\u0026quot;（空串, 不是 nil） 指针/切片/map/接口/函数/channel *T, []T, map[K]V\u0026hellip; nil Go 没有隐式类型转换，必须显式：\nvar i int = 42 var f float64 = float64(i) // 必须显式转 var u uint = uint(f) 字符串常用操作：\ns := \u0026#34;hello, 世界\u0026#34; len(s) // 字节数: 13（UTF-8 下汉字 3 字节） utf8.RuneCountInString(s) // 字符数: 9 strings.Contains(s, \u0026#34;世界\u0026#34;) // true strings.Split(\u0026#34;a,b,c\u0026#34;, \u0026#34;,\u0026#34;) // [a b c] fmt.Sprintf(\u0026#34;%s=%d\u0026#34;, \u0026#34;n\u0026#34;, 5) // 格式化字符串 三、指针（比 C 简单） func main() { x := 10 p := \u0026amp;x // p 是指向 x 的指针 fmt.Println(*p) // 解引用: 10 *p = 20 fmt.Println(x) // 20 } Go 指针不能做算术运算（不能 p++），避免了 C 的一整类内存错误。\n四、函数：多返回值是杀手锏 // 多返回值 + 命名返回值 func divide(a, b int) (result int, err error) { if b == 0 { err = fmt.Errorf(\u0026#34;除数不能为零\u0026#34;) return // 裸返回 } result = a / b return } // 可变参数 func sum(nums ...int) int { total := 0 for _, n := range nums { total += n } return total } // 函数是一等公民: 可以赋值、传参 f := func(x int) int { return x * x } fmt.Println(f(5)) 调用侧的惯用法（后面错误处理那篇会展开）：\nr, err := divide(10, 0) if err != nil { fmt.Println(\u0026#34;出错了:\u0026#34;, err) } 五、控制流：只有 for Go 把所有循环统一成了 for：\n// 经典三段式 for i := 0; i \u0026lt; 5; i++ { ... } // 相当于 while n := 0 for n \u0026lt; 10 { n++ } // 死循环 for { break } // 遍历切片 nums := []int{1, 2, 3} for i, v := range nums { fmt.Println(i, v) } // 遍历 map m := map[string]int{\u0026#34;a\u0026#34;: 1} for k, v := range m { fmt.Println(k, v) } // 不需要下标时用 _ 丢弃 for _, v := range nums { ... } if 的特殊语法——可以带初始化语句（限定了变量作用域）：\nif err := doSomething(); err != nil { fmt.Println(\u0026#34;failed:\u0026#34;, err) } // 这里已经访问不到 err 了 switch 更强大——默认 break，一个 case 可多值：\nswitch day := 3; day { case 1, 2, 3, 4, 5: fmt.Println(\u0026#34;工作日\u0026#34;) case 6, 7: fmt.Println(\u0026#34;周末\u0026#34;) default: fmt.Println(\u0026#34;非法\u0026#34;) } // 无表达式 switch, 替代长串 if-else switch { case score \u0026gt;= 90: grade = \u0026#34;A\u0026#34; case score \u0026gt;= 60: grade = \u0026#34;B\u0026#34; default: grade = \u0026#34;C\u0026#34; } 六、defer：延迟执行的清理利器 func readFile(path string) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() // 函数返回前必定执行, 不怕中间 return // ... 读文件逻辑 return nil } 要点：\n多个 defer 后进先出（栈）：defer a(); defer b() → 执行顺序 b → a defer 的参数在声明时求值： i := 0 defer fmt.Println(i) // 打印 0, 不是 1 i = 1 典型用途：关文件、解锁 mu.Unlock()、recover 捕获 panic 小结 特性 一句话 := 函数内短声明，最常用 零值 没初始化也不会是\u0026quot;随机垃圾值\u0026quot; 多返回值 (result, err) 惯例的根基 for 唯一的循环关键字 switch 默认 break，case 可多值 defer 资源清理标配，LIFO 语法过关后，下一站是复合类型：数组、切片与映射——Go 数据结构的主战场。\n本文是「Golang」系列第 2 篇。\n","permalink":"https://antidebug.cn/posts/2023/07/golang-%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95%E7%B1%BB%E5%9E%8B%E5%87%BD%E6%95%B0%E4%B8%8E%E6%8E%A7%E5%88%B6%E6%B5%81/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e环境搭好（见上篇），开始正式修炼 Go 语法。Go 的设计哲学是 \u003cstrong\u003e少即是多\u003c/strong\u003e——关键字只有 25 个，没有继承、没有异常、没有三元运算符，但每个特性都精打细磨。\u003c/p\u003e","title":"Golang 基础语法：类型、函数与控制流"},{"content":"前言 Nginx 是运维接触最多的软件之一。这篇从零开始，把一个静态网站（比如本博客）部署到 Nginx 上的完整流程走一遍。\n一、安装 # RHEL 系 dnf install -y nginx # 确认版本并启动 nginx -v systemctl enable --now nginx curl -I http://127.0.0.1/ # 出现 Welcome to nginx 即成功 二、配置文件结构 /etc/nginx/ ├── nginx.conf # 主配置: 全局 + events + http 块 ├── conf.d/ # 独立站点配置（推荐放这里） │ └── *.conf └── mime.types # 扩展名 -\u0026gt; Content-Type 映射 nginx.conf 的 http 块末尾有一行关键配置：\ninclude /etc/nginx/conf.d/*.conf; # 我们加站点就是往这里丢 .conf 三、部署第一个静态站点 1. 准备站点文件：\nmkdir -p /var/www/demo echo \u0026#39;\u0026lt;h1\u0026gt;Hello, Nginx!\u0026lt;/h1\u0026gt;\u0026#39; \u0026gt; /var/www/demo/index.html 2. 写站点配置 /etc/nginx/conf.d/demo.conf：\nserver { listen 80; # 监听端口 server_name demo.example.com; # 匹配请求 Host 头（域名） root /var/www/demo; # 站点根目录 index index.html index.htm; location / { try_files $uri $uri/ =404; # 依次尝试: 文件 -\u0026gt; 目录 -\u0026gt; 404 } } 3. 检查并生效：\nnginx -t # 语法检查, 永远先跑这一步 systemctl reload nginx # 平滑重载, 不断连接 curl -H \u0026#34;Host: demo.example.com\u0026#34; http://127.0.0.1/ # \u0026lt;h1\u0026gt;Hello, Nginx!\u0026lt;/h1\u0026gt; 四、location 匹配规则 这是 Nginx 配置的灵魂，优先级从高到低：\n写法 类型 示例 = 精确匹配 location = /health ^~ 前缀匹配（命中后不再查正则） location ^~ /static/ ~ / ~* 正则（区分/不区分大小写） location ~ \\.php$ 无符号 普通前缀 location / # 静态资源走长缓存（文件名带 hash 时可激进些） location ~* \\.(css|js|png|jpg|svg|woff2)$ { expires 7d; add_header Cache-Control \u0026#34;public\u0026#34;; } # 精确的健康检查, 给负载均衡器探测用 location = /health { access_log off; return 200 \u0026#34;ok\\n\u0026#34;; } 五、必备优化项 gzip 压缩 gzip on; gzip_min_length 1k; # 小于 1k 不压（不划算） gzip_comp_level 5; gzip_types text/css application/javascript application/json image/svg+xml text/plain; 文本类资源通常能压到原来的 1/3，立竿见影。\n隐藏版本号 server_tokens off; # 不暴露 nginx 版本, 减少被针对性攻击的信息 日志 默认在 /var/log/nginx/access.log。自定义日志格式（加响应耗时，方便排查慢请求）：\nlogformat main \u0026#39;$remote_addr - [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; rt=$request_time\u0026#39;; server { ... access_log /var/log/nginx/demo.access.log main; } 六、一台 Nginx 托管多个站点 靠 server_name 区分（虚拟主机）。再建一个 blog.conf：\nserver { listen 80; server_name blog.example.com; # 不同域名 -\u0026gt; 不同 root root /var/www/blog; ... } Nginx 匹配逻辑：请求的 Host 头与哪个 server_name 一致就进哪个 server 块；都不匹配则进默认 server。这也是一台服务器绑定多个域名的原理——本博客的 antidebug.cn 和 www.antidebug.cn 就是写在一个 server 块里共用一份站点文件。\n七、常用运维命令 命令 作用 nginx -t 测试配置语法（reload 前必跑） systemctl reload nginx 平滑重载配置 nginx -s stop / nginx -s quit 立即停止 / 优雅退出 tail -f /var/log/nginx/error.log 看错误日志排障 403 排查思路：root 目录无权限（chmod +x 各级目录）→ 无 index 文件 → SELinux 上下文（restorecon -R）。\n502 排查思路：反代后端挂了/端口不对；curl 后端端口验证进程存活。\n小结 静态站点部署三件套：文件放好 → server 块写好 → nginx -t 后 reload。再记住 location 优先级、gzip、缓存三招，一个能上生产的基础配置就成型了。反向代理与负载均衡，下下篇见。\n本文是「Linux 运维」系列第 2 篇。\n","permalink":"https://antidebug.cn/posts/2023/07/nginx-%E9%9D%99%E6%80%81%E7%AB%99%E7%82%B9%E9%83%A8%E7%BD%B2%E5%AE%9E%E6%88%98/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eNginx 是运维接触最多的软件之一。这篇从零开始，把一个静态网站（比如本博客）部署到 Nginx 上的完整流程走一遍。\u003c/p\u003e\n\u003ch2 id=\"一安装\"\u003e一、安装\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# RHEL 系\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ednf install -y nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 确认版本并启动\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enginx -v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl \u003cspan class=\"nb\"\u003eenable\u003c/span\u003e --now nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -I http://127.0.0.1/        \u003cspan class=\"c1\"\u003e# 出现 Welcome to nginx 即成功\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"二配置文件结构\"\u003e二、配置文件结构\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e/etc/nginx/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── nginx.conf                 # 主配置: 全局 + events + http 块\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── conf.d/                    # 独立站点配置（推荐放这里）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   └── *.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e└── mime.types                 # 扩展名 -\u0026gt; Content-Type 映射\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003enginx.conf\u003c/code\u003e 的 http 块末尾有一行关键配置：\u003c/p\u003e","title":"Nginx 静态站点部署实战"},{"content":"前言 入行运维绕不开\u0026quot;云\u0026quot;。面试常问：IaaS、PaaS、SaaS 到底啥区别？这篇用最经典的比喻 + 实际场景把云的基础概念一网打尽。\n一、三层服务模型：披萨比喻 业界流传最广的解释是 RedHat 的\u0026quot;披萨即服务\u0026quot;：\n模式 比喻 你管什么 自建机房 在家自己做披萨（和面、烤炉全都自己来） 一切：机房、硬件、系统、应用 IaaS 厨房外卖：给你炉子和面团（基础设施即服务） 系统及以上：OS、中间件、应用 PaaS 披萨坯送到家：你只管加料烤（平台即服务） 只管应用和数据 SaaS 直接点外卖披萨（软件即服务） 什么都不用管，用就行 用一张\u0026quot;责任分层表\u0026quot;看得更清楚：\n分层 自建 IaaS PaaS SaaS 应用 🙋你 🙋你 🙋你 ☁️云 数据 🙋你 🙋你 🙋你 ☁️云 运行时/中间件 🙋你 🙋你 ☁️云 ☁️云 操作系统 🙋你 🙋你 ☁️云 ☁️云 虚拟化 🙋你 ☁️云 ☁️云 ☁️云 服务器/存储/网络 🙋你 ☁️云 ☁️云 ☁️云 二、每层对应的产品与使用场景 IaaS：云上的\u0026quot;毛坯房\u0026quot; 代表：阿里云 ECS、腾讯云 CVM、AWS EC2。\n你拿到的是一台虚拟机：自己装系统补丁、装软件、做安全 适合：自定义程度高的业务、传统架构迁移上云 PaaS：拎包入住的\u0026quot;精装房\u0026quot; 代表：数据库 RDS、容器服务、函数计算、对象存储（部分归入 PaaS 层能力）。\n平台帮你管运行时、扩缩容、高可用，你只管代码和数据 适合：专注业务开发、不想碰运维细节的团队 代价：被平台绑定（Vendor Lock-in）的风险 SaaS：直接\u0026quot;住酒店\u0026quot; 代表：钉钉、飞书、GitHub、腾讯文档。\n打开浏览器就能用，按订阅/席位付费 适合：通用型业务需求（CRM、协同办公、客服系统） 三、部署模型：公有云、私有云、混合云 类型 特点 典型场景 公有云 资源多租户共享，弹性最好，按量付费 互联网业务、创业公司 私有云 企业自建（OpenStack/vSphere），数据完全自控 金融、政务、强合规行业 混合云 敏感数据放私有云，弹性业务放公有云 大促时\u0026quot;云爆发\u0026quot;扩容 四、云计算的核心技术关键词 虚拟化 一台物理机切成多台虚拟机（VM），是 IaaS 的地基。核心组件 Hypervisor：\nKVM：Linux 内核原生虚拟化，开源云的事实标准 QEMU：配套的用户态设备模拟 vSphere/ESXi：VMware 商业方案 弹性伸缩（Auto Scaling） 按负载自动增减实例：流量高峰自动扩容，低谷自动缩容省钱。依赖镜像 + 启动模板 + 监控指标（CPU/QPS）。\n计费模型 模式 说明 适合 包年包月 固定配置预付费，单价最低 长期稳定业务 按量付费 秒级计费，随开随关 临时测试、爆发流量 竞价实例 低价竞拍闲置资源，可能被回收 无状态、可中断任务 对象存储 / 块存储 / 文件存储 块存储（云硬盘）：给虚拟机当系统盘/数据盘 对象存储（OSS/COS/S3）：存图片、备份、静态资源，按容量+请求计费，可挂 CDN 文件存储（NAS）：多机共享挂载 详细对比后续会单独写一篇。\n五、从运维视角看云带来的变化 传统运维 云时代运维 采购服务器等几周 API/控制台分钟级开机器 手动装系统 镜像 + 自动化初始化 机房巡检 控制台监控告警 容量靠拍脑袋 弹性伸缩自动调节 硬件故障大动干戈 换一台实例即恢复 运维的战场从\u0026quot;物理硬件\u0026quot;转向\u0026quot;稳定性架构 + 自动化 + 成本优化\u0026quot;。这也是为什么后面要学容器、K8s 和 IaC——它们都是云时代的运维语言。\n小结 IaaS 管到虚拟化，PaaS 管到运行时，SaaS 全托管 公有云吃弹性、私有云吃合规、混合云两头兼顾 虚拟化是地基，弹性伸缩是灵魂，按量付费是杠杆 本文是「云计算」系列第 1 篇，下篇预告：虚拟化技术基础 KVM 与 QEMU。\n","permalink":"https://antidebug.cn/posts/2023/06/%E4%BA%91%E8%AE%A1%E7%AE%97%E5%85%A5%E9%97%A8iaaspaassaas-%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e入行运维绕不开\u0026quot;云\u0026quot;。面试常问：IaaS、PaaS、SaaS 到底啥区别？这篇用最经典的比喻 + 实际场景把云的基础概念一网打尽。\u003c/p\u003e","title":"云计算入门：IaaS、PaaS、SaaS 有什么区别"},{"content":"前言 云服务器买了数据盘，登上去却找不到？新增磁盘从\u0026quot;裸设备\u0026quot;到\u0026quot;能用的目录\u0026quot;要经历三步：分区 → 格式化 → 挂载。这篇把全流程和日常排查一次讲清。\n一、看清磁盘现场 # 列出所有块设备（最常用, 一目了然） lsblk # NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT # vda 252:0 0 40G 0 disk # └─vda1 252:1 0 40G 0 part / # 查看磁盘与分区详情 fdisk -l # 已挂载文件系统的容量与使用率 df -h # 目录实际占用大小 du -sh /var/log 云主机系统盘一般是 vda（virtio）或 sda；TYPE=disk 是整盘，part 是分区，lvm 是逻辑卷。\n二、分区：fdisk 与 parted 小于 2TB 用 fdisk（MBR），大于 2TB 必须 parted（GPT）。\n# fdisk 交互式操作（以新盘 /dev/vdb 为例） fdisk /dev/vdb # 输入 n (新建) -\u0026gt; p (主分区) -\u0026gt; 回车默认起始 -\u0026gt; 回车默认结束 -\u0026gt; w (写入) parted 方式（可脚本化，支持大容量）：\nparted /dev/vdb --script mklabel gpt parted /dev/vdb --script mkpart primary ext4 0% 100% # 让内核重读分区表 partprobe lsblk # 已能看到 vdb1 三、格式化 # ext4: 兼容性最好 mkfs.ext4 /dev/vdb1 # xfs: RHEL 系默认, 大文件高性能, 但不能缩容 mkfs.xfs /dev/vdb1 # 带卷标 mkfs.ext4 -L data /dev/vdb1 选择建议：通用场景 ext4，大文件/高吞吐场景 xfs。\n四、挂载与开机自动挂载 # 临时挂载（重启失效） mkdir -p /data mount /dev/vdb1 /data df -h | grep data # 卸载（注意不能在挂载点目录内执行） umount /data # 查看挂载详情与 UUID blkid /dev/vdb1 永久挂载写入 /etc/fstab（推荐用 UUID，设备名可能漂移）：\n# \u0026lt;设备/UUID\u0026gt; \u0026lt;挂载点\u0026gt; \u0026lt;类型\u0026gt; \u0026lt;选项\u0026gt; \u0026lt;dump\u0026gt; \u0026lt;fsck顺序\u0026gt; UUID=xxxx-xxxx-xxxx /data ext4 defaults,noatime 0 2 # 写完必须验证语法, 错误的 fstab 可能导致重启起不来! mount -a \u0026amp;\u0026amp; echo \u0026#34;fstab OK\u0026#34; 五、LVM：弹性的磁盘管理 固定分区不够用时，LVM（Logical Volume Manager）可以把多块盘\u0026quot;Pool\u0026quot;起来动态扩容。\n核心三层结构：\n物理盘 PV (Physical Volume) ↓ 组成 卷组 VG (Volume Group) ← 资源池 ↓ 划分 逻辑卷 LV (Logical Volume) ← 格式化挂载用这个 # 1. 创建 PV pvcreate /dev/vdb /dev/vdc # 2. 创建 VG（名字 data-vg） vgcreate data-vg /dev/vdb /dev/vdc # 3. 创建 LV（用掉全部空间） lvcreate -l 100%FREE -n data-lv data-vg # 4. 格式化并挂载 mkfs.xfs /dev/data-vg/data-lv mount /dev/data-vg/data-lv /data 不停机在线扩容（云盘扩容后）：\n# 假设在云控制台把磁盘从 100G 扩到 200G echo 1 \u0026gt; /sys/class/block/vdb/device/rescan # 重读磁盘容量 pvresize /dev/vdb lvextend -l +100%FREE /dev/data-vg/data-lv xfs_growfs /data # xfs 在线扩文件系统 # 若是 ext4: resize2fs /dev/data-vg/data-lv df -h /data 六、日常排查：磁盘满了怎么办 # 1. 确认哪个分区满 df -h # 2. 定位大目录（逐层下钻） du -hx --max-depth=1 / 2\u0026gt;/dev/null | sort -rh | head du -hx --max-depth=1 /var | sort -rh | head # 3. 常见\u0026#34;重灾区\u0026#34; du -sh /var/log/* | sort -rh | head journalctl --vacuum-size=500M # 收缩系统日志 find / -size +500M -type f 2\u0026gt;/dev/null | xargs ls -lh # 4. 注意: 删除了文件但空间没释放? # 说明文件仍被某进程打开着 lsof +L4 | grep deleted # 处理: 重启或 reload 对应进程（如 logrotate 后 nginx -s reload） 小结 步骤 命令 查盘 lsblk / fdisk -l 分区 fdisk（\u0026lt;2T）/ parted（\u0026gt;2T, GPT） 格式化 mkfs.ext4 / mkfs.xfs 挂载 mount + /etc/fstab（用 UUID） 弹性管理 pvcreate → vgcreate → lvcreate 空间排查 df -h → du -hx → lsof +L4 本文是「Linux 运维」系列第 1 篇，下篇预告：云计算入门。\n","permalink":"https://antidebug.cn/posts/2023/06/linux-%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86%E5%88%86%E5%8C%BA%E6%A0%BC%E5%BC%8F%E5%8C%96%E4%B8%8E%E6%8C%82%E8%BD%BD/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e云服务器买了数据盘，登上去却找不到？新增磁盘从\u0026quot;裸设备\u0026quot;到\u0026quot;能用的目录\u0026quot;要经历三步：\u003cstrong\u003e分区 → 格式化 → 挂载\u003c/strong\u003e。这篇把全流程和日常排查一次讲清。\u003c/p\u003e","title":"Linux 磁盘管理：分区、格式化与挂载"},{"content":"前言 决定把 Golang 加入技能树——云原生生态（Docker、Kubernetes、Prometheus）几乎全是 Go 写的，学它性价比极高。第一步，把环境搭顺。\n一、安装 Go 方式一：官方二进制包（推荐） # 下载（国内可用 https://mirrors.aliyun.com/golang/ 加速） cd /tmp curl -LO https://go.dev/dl/go1.22.3.linux-amd64.tar.gz # 解压到 /usr/local rm -rf /usr/local/go \u0026amp;\u0026amp; tar -C /usr/local -xzf go1.22.3.linux-amd64.tar.gz # 配置 PATH（写入 /etc/profile.d/go.sh 对所有用户生效） cat \u0026gt; /etc/profile.d/go.sh \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; export PATH=$PATH:/usr/local/go/bin EOF source /etc/profile.d/go.sh go version # go version go1.22.3 linux/amd64 方式二：包管理器 dnf install -y golang # 版本可能偏旧, 看发行版 二、理解环境变量 go env # 查看全部配置 go env GOROOT GOPATH GOPROXY 变量 含义 GOROOT Go 安装目录（/usr/local/go） GOPATH 旧模式的工作区；模块模式下主要放 go install 的二进制（默认 ~/go） GOPROXY 模块下载代理 GOBIN go install 安装目录（默认 $GOPATH/bin） 国内必做：设置模块代理，否则拉依赖会超时：\ngo env -w GOPROXY=https://goproxy.cn,direct go env -w GO111MODULE=on 三、第一个程序 mkdir -p ~/dev/hello \u0026amp;\u0026amp; cd ~/dev/hello go mod init hello # 初始化模块, 生成 go.mod main.go：\npackage main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;Hello, Golang! 👋\u0026#34;) } # 直接运行 go run main.go # 编译成二进制（静态编译, 部署超方便） go build -o hello . ./hello # 交叉编译: 在 Linux 上编译 Windows 可执行文件 GOOS=windows GOARCH=amd64 go build -o hello.exe . 💡 Go 编译产物是静态链接的，不依赖运行时库，扔到裸机容器（FROM scratch）里就能跑——这是它适合容器化和云原生的原因之一。\n四、第二个程序：Web 版 Hello World package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello, %s! 时间: %s\\n\u0026#34;, r.URL.Path[1:], time.Now().Format(\u0026#34;15:04:05\u0026#34;)) } func main() { http.HandleFunc(\u0026#34;/\u0026#34;, handler) log.Println(\u0026#34;listening on :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } go run main.go \u0026amp; curl http://localhost:8080/zy # Hello, zy! 时间: 20:15:33 标准库里一个 net/http 就能起服务，不需要任何框架——这就是 Go 的风格。\n五、开发工具速配 # 安装 gofmt 官方格式化不用装, 自带。装个常用开发工具集 go install golang.org/x/tools/cmd/goimports@latest # 确保 ~/go/bin 在 PATH echo \u0026#39;export PATH=$PATH:$(go env GOPATH)/bin\u0026#39; \u0026gt;\u0026gt; ~/.bashrc VS Code 装 Go 插件（Go Team 出品），打开 .go 文件按提示装全 gopls 等工具即可。\n六、常见坑 现象 原因/解决 go: command not found PATH 未包含 /usr/local/go/bin，source 一下 拉依赖超时 没设 GOPROXY，go env -w GOPROXY=https://goproxy.cn,direct cannot find main module 目录下没有 go.mod，先 go mod init xxx 旧 GOPATH 教程看不懂 现代开发全走 Modules，不用关心 GOPATH/src 那套 小结 环境三步走：装二进制 → 配 GOPROXY → go mod init。接下来就可以正式开始语法学习了：变量、类型、函数与控制流。\n本文是「Golang」系列第 1 篇，下篇预告：Golang 基础语法。\n","permalink":"https://antidebug.cn/posts/2023/05/golang-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E4%B8%8E%E7%AC%AC%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e决定把 Golang 加入技能树——云原生生态（Docker、Kubernetes、Prometheus）几乎全是 Go 写的，学它性价比极高。第一步，把环境搭顺。\u003c/p\u003e","title":"Golang 开发环境搭建与第一个程序"},{"content":"前言 服务卡了、CPU 飙了、进程僵了——处理这些问题的第一步永远是\u0026quot;看清进程现场\u0026quot;。这篇梳理进程查看、信号机制和后台任务管理的核心知识。\n一、查看进程：ps # 查看当前终端的进程 ps # 查看系统全部进程（两种风格等价） ps aux # BSD 风格 ps -ef # System V 风格 ps aux 各列含义：\nUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.3 175364 13568 ? Ss 10:00 0:05 /usr/lib/systemd/systemd 列 含义 PID 进程 ID（PPID 为父进程） %CPU / %MEM CPU、内存占用 RSS 实际占用物理内存 STAT 进程状态 TIME 累计 CPU 时间 STAT 常见值：\n符号 含义 R 运行中 / 等待 CPU S 可中断睡眠（等待事件，大多数服务都处于此态） D 不可中断睡眠（通常在等 IO，大量 D 态要警惕磁盘问题） Z 僵尸进程（已退出但未被父进程回收） T 已停止 s 会话首进程；l 多线程；\u0026lt; 高优先级；N 低优先级 实用组合：\n# 查看某服务的进程树 ps -ef --forest | grep -A5 nginx # 按内存排序取前 10 ps aux --sort=-%mem | head -11 # 找出 CPU 占用前 10 ps aux --sort=-%cpu | head -11 # 查看进程的启动目录和完整命令 ls -l /proc/\u0026lt;PID\u0026gt;/cwd cat /proc/\u0026lt;PID\u0026gt;/cmdline | tr \u0026#39;\\0\u0026#39; \u0026#39; \u0026#39; 二、动态监控：top 与 htop top 关键快捷键（进入 top 后按）：\n按键 作用 P 按 CPU 排序 M 按内存排序 1 展开每个 CPU 核心的使用率 c 显示完整命令行 k 杀进程（输入 PID） q 退出 load average 三个数字分别是 1/5/15 分钟的平均负载。经验法则：负载持续超过 CPU 核心数说明机器忙不过来了。\n# 一次性的负载快照 uptime nproc # CPU 核心数 # 更好用的 htop（有颜色、可交互、支持鼠标） dnf install -y htop 三、信号与 kill kill 不是\u0026quot;杀死\u0026quot;那么简单，它本质是给进程发信号。\n# 默认发 SIGTERM(15): 优雅终止, 进程可以捕获并做清理 kill 1234 # 强制杀死: SIGKILL(9), 进程无法捕获, 直接蒸发(可能留下残留资源) kill -9 1234 # 发送自定义信号 kill -HUP 1234 # 很多服务用它重载配置(如 nginx) kill -USR1 1234 # 按名字杀 pkill -f \u0026#34;python app.py\u0026#34; killall nginx # 列出所有信号 kill -l 常用信号速查：\n信号 编号 含义 SIGTERM 15 优雅终止（默认） SIGKILL 9 强制终止（不可捕获） SIGHUP 1 挂断；常被复用为\u0026quot;重载配置\u0026quot; SIGINT 2 Ctrl+C 中断 SIGSTOP/T 19/20 暂停进程 SIGCONT 18 恢复运行 SIGUSR1/2 10/12 用户自定义信号 ⚠️ 生产环境优先 kill（TERM），给进程优雅退出的机会；kill -9 是最后手段——它不给应用任何清理资源的机会，数据库类进程慎用。\n四、前后台任务管理 # 把前台命令放后台运行（加 \u0026amp;） ./server \u0026amp; # Ctrl+Z 暂停前台任务后, 转入后台继续 bg # 查看当前终端的后台任务 jobs # 调回前台 fg %1 # 让进程脱离终端会话, 关掉 SSH 也继续活着 nohup ./server \u0026gt;server.log 2\u0026gt;\u0026amp;1 \u0026amp; disown -h %1 # 补救: 已启动的任务去掉 SIGHUP 关联 五、实战：排查一个 100% CPU 的进程 # 1. 找到元凶 top # 按 P 排序, 记下 PID, 假设是 8642 # 2. 看它到底在跑什么 cat /proc/8642/cmdline | tr \u0026#39;\\0\u0026#39; \u0026#39; \u0026#39;; echo ls -l /proc/8642/cwd # 3. 如果是自己的程序, 看看线程级热点 top -H -p 8642 # -H 显示线程 # 4. 确认无误后处理 kill 8642 # 先礼后兵 kill -9 8642 # 仍不退出再强杀 小结 ps aux --sort=-%cpu / top 是定位资源问题第一现场 STAT 里 D 堆积查磁盘、Z 堆积查父进程 kill（TERM）优先，kill -9 兜底 长任务记得 nohup ... \u0026amp; 脱离终端 本文是「Linux 基础」系列第 4 篇，下篇预告：Golang 开发环境搭建。\n","permalink":"https://antidebug.cn/posts/2023/05/linux-%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86pstop-%E4%B8%8E%E4%BF%A1%E5%8F%B7%E6%9C%BA%E5%88%B6/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e服务卡了、CPU 飙了、进程僵了——处理这些问题的第一步永远是\u0026quot;看清进程现场\u0026quot;。这篇梳理进程查看、信号机制和后台任务管理的核心知识。\u003c/p\u003e","title":"Linux 进程管理：ps、top 与信号机制"},{"content":"前言 Linux 下一切皆文件，而日志、配置、命令输出全是文本。grep 找、sed 改、awk 算——这三把剑练熟，日常 80% 的文本处理需求都能搞定。\n一、grep：文本搜索 # 基本搜索（支持管道） grep \u0026#34;ERROR\u0026#34; app.log journalctl -u nginx | grep -i \u0026#34;fail\u0026#34; # -i 忽略大小写 # 常用选项 grep -n \u0026#34;root\u0026#34; /etc/passwd # 显示行号 grep -c \u0026#34;ERROR\u0026#34; app.log # 只统计匹配行数 grep -v \u0026#34;127.0.0.1\u0026#34; access.log # 反向: 排除匹配的行 grep -r \u0026#34;bind 0.0.0.0\u0026#34; /etc/ # 递归搜目录 grep -E \u0026#34;ERROR|FATAL\u0026#34; app.log # 扩展正则, 多模式 grep -A 3 -B 1 \u0026#34;panic\u0026#34; app.log # 显示匹配行的后3行、前1行 grep -w \u0026#34;error\u0026#34; app.log # 全词匹配 基础正则速查：\n符号 含义 示例 ^ 行首 grep \u0026quot;^#\u0026quot; 注释行 $ 行尾 grep \u0026quot;:$\u0026quot; 以冒号结尾 . 任意单字符 grep \u0026quot;r.ot\u0026quot; * 前一字符 0+ 次 grep \u0026quot;go*gle\u0026quot; [] 字符集合 grep \u0026quot;^[abc]\u0026quot; [^] 取反 grep \u0026quot;^[^#]\u0026quot; 非注释行 实战：统计 Nginx 日志里出现最多的 10 个 IP：\nawk \u0026#39;{print $1}\u0026#39; access.log | sort | uniq -c | sort -rn | head 二、sed：流编辑器 sed 逐行读取文本，对每行执行编辑命令后输出。核心场景是替换。\n# 替换每行第一个匹配 sed \u0026#39;s/http/https/\u0026#39; conf.txt # 替换所有匹配（全局 g 标志） sed \u0026#39;s/http/https/g\u0026#39; conf.txt # 直接修改文件（-i）, 先备份为 .bak sed -i.bak \u0026#39;s/8080/9090/g\u0026#39; app.conf # 只处理第 2~5 行 sed \u0026#39;2,5s/8080/9090/g\u0026#39; app.conf # 删除注释行和空行 sed -e \u0026#39;/^#/d\u0026#39; -e \u0026#39;/^$/d\u0026#39; sshd_config # 删除第 3 行 sed \u0026#39;3d\u0026#39; file.txt # 在第 2 行后插入一行 sed \u0026#39;2a ServerName antidebug.cn\u0026#39; httpd.conf # 打印 10~20 行（当 sed 用） sed -n \u0026#39;10,20p\u0026#39; big.log ⚠️ 生产环境改配置前，先 sed 预览输出，确认无误后再加 -i；重要文件务必 -i.bak 留备份。\n三、awk：文本计算器 awk 按**字段（列）**处理文本，默认以空格/Tab 分隔，$1 是第一列，$0 是整行。\n# 打印第 1、3 列 awk \u0026#39;{print $1, $3}\u0026#39; data.txt # 指定分隔符 awk -F: \u0026#39;{print $1, $7}\u0026#39; /etc/passwd # 条件过滤: 磁盘使用率超过 80% 的分区 df -h | awk \u0026#39;$5+0 \u0026gt; 80 {print $6, $5}\u0026#39; # 内置变量 awk -F: \u0026#39;{print NR\u0026#34;: \u0026#34;$1}\u0026#39; /etc/passwd # NR 当前行号 awk \u0026#39;{print NF}\u0026#39; file # NF 当前行字段数 # 使用分隔符美化输出 awk -F: -v OFS=\u0026#39; | \u0026#39; \u0026#39;{print $1, $3, $7}\u0026#39; /etc/passwd BEGIN / END 块做统计 # 统计第 1 列的总和与平均值 awk \u0026#39;BEGIN {sum=0} {sum+=$1} END {print \u0026#34;sum=\u0026#34;sum, \u0026#34;avg=\u0026#34;sum/NR}\u0026#39; nums.txt # 统计每种 HTTP 状态码出现次数 awk \u0026#39;{count[$9]++} END {for (s in count) print s, count[s]}\u0026#39; access.log 从日志里提取信息 # 提取 500 错误的 URI（第 9 列是状态码） awk \u0026#39;$9 == 500 {print $7}\u0026#39; access.log | sort | uniq -c | sort -rn # 提取响应时间超过 1s 的请求（假设 $NF 是耗时） awk \u0026#39;$NF \u0026gt; 1.0 {print $4, $7, $NF}\u0026#39; access.log | head 四、三剑合璧：一条经典流水线 统计访问日志中 昨天 每小时 PV，并输出 Top 时段：\ngrep \u0026#34;$(date -d yesterday \u0026#39;+%d/%b/%Y\u0026#39;)\u0026#34; access.log \\ | awk \u0026#39;{print substr($4,14,2)}\u0026#39; \\ | sort | uniq -c \\ | sort -rn | head -5 每一环的分工：\ngrep → 框定目标行（按日期） awk → 抽取小时字段 sort → 排序（uniq 统计前必须先排序） uniq -c → 相邻去重并计数 sort -rn → 按数字倒序取 Top 五、速查表 需求 命令 找匹配行及上下文 grep -A3 -B1 \u0026quot;err\u0026quot; log 全局替换并备份 sed -i.bak 's/a/b/g' f 删除注释和空行 sed -e '/^#/d' -e '/^$/d' f 打印某列 awk '{print $3}' f 按条件过滤行 awk '$3 \u0026gt; 100' f 分组计数 awk '{c[$1]++} END{for(k in c) print k,c[k]}' Top N sort | uniq -c | sort -rn | head 小结 三剑客的价值在于组合：管道把简单工具串成流水线，每一步只做一件事。日志分析、配置批量修改、数据提取统计，都离不开它们。建议把本文几个实战命令在自己的机器上敲一遍，肌肉记忆比收藏有用。\n本文是「Linux 基础」系列第 3 篇，下篇预告：Linux 进程管理。\n","permalink":"https://antidebug.cn/posts/2023/04/linux-%E6%96%87%E6%9C%AC%E5%A4%84%E7%90%86%E4%B8%89%E5%89%91%E5%AE%A2grepsedawk/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003eLinux 下一切皆文件，而日志、配置、命令输出全是文本。\u003cstrong\u003egrep 找、sed 改、awk 算\u003c/strong\u003e——这三把剑练熟，日常 80% 的文本处理需求都能搞定。\u003c/p\u003e\n\u003ch2 id=\"一grep文本搜索\"\u003e一、grep：文本搜索\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 基本搜索（支持管道）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep \u003cspan class=\"s2\"\u003e\u0026#34;ERROR\u0026#34;\u003c/span\u003e app.log\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ejournalctl -u nginx \u003cspan class=\"p\"\u003e|\u003c/span\u003e grep -i \u003cspan class=\"s2\"\u003e\u0026#34;fail\u0026#34;\u003c/span\u003e        \u003cspan class=\"c1\"\u003e# -i 忽略大小写\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 常用选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep -n \u003cspan class=\"s2\"\u003e\u0026#34;root\u0026#34;\u003c/span\u003e /etc/passwd                   \u003cspan class=\"c1\"\u003e# 显示行号\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep -c \u003cspan class=\"s2\"\u003e\u0026#34;ERROR\u0026#34;\u003c/span\u003e app.log                      \u003cspan class=\"c1\"\u003e# 只统计匹配行数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep -v \u003cspan class=\"s2\"\u003e\u0026#34;127.0.0.1\u0026#34;\u003c/span\u003e access.log              \u003cspan class=\"c1\"\u003e# 反向: 排除匹配的行\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep -r \u003cspan class=\"s2\"\u003e\u0026#34;bind 0.0.0.0\u0026#34;\u003c/span\u003e /etc/                 \u003cspan class=\"c1\"\u003e# 递归搜目录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep -E \u003cspan class=\"s2\"\u003e\u0026#34;ERROR|FATAL\u0026#34;\u003c/span\u003e app.log                \u003cspan class=\"c1\"\u003e# 扩展正则, 多模式\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep -A \u003cspan class=\"m\"\u003e3\u003c/span\u003e -B \u003cspan class=\"m\"\u003e1\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;panic\u0026#34;\u003c/span\u003e app.log               \u003cspan class=\"c1\"\u003e# 显示匹配行的后3行、前1行\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egrep -w \u003cspan class=\"s2\"\u003e\u0026#34;error\u0026#34;\u003c/span\u003e app.log                      \u003cspan class=\"c1\"\u003e# 全词匹配\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e基础正则速查\u003c/strong\u003e：\u003c/p\u003e","title":"Linux 文本处理三剑客：grep、sed、awk"},{"content":"前言 会写 Bash 脚本，是运维和后端开发日常效率的分水岭。这篇把最核心的语法要素串一遍，看完能写出规范、能跑的脚本。\n一、脚本骨架 从一个标准骨架开始：\n#!/usr/bin/env bash # 描述: 演示脚本骨架 set -euo pipefail # 出错即停 + 未定义变量报错 + 管道错误传递 LOG_FILE=\u0026#34;/var/log/myapp.log\u0026#34; log() { echo \u0026#34;[$(date \u0026#39;+%F %T\u0026#39;)] $*\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } main() { log \u0026#34;脚本开始\u0026#34; # ... 业务逻辑 log \u0026#34;脚本结束\u0026#34; } main \u0026#34;$@\u0026#34; 三件套说明：\nset -e：任何命令返回非零就立即退出 set -u：使用了未定义变量直接报错 set -o pipefail：管道中任何一个命令失败，整条管道算失败 二、变量与引号 name=\u0026#34;world\u0026#34; # 等号两边不能有空格！ echo \u0026#34;hello, $name\u0026#34; # 双引号: 会展开变量 echo \u0026#39;hello, $name\u0026#39; # 单引号: 原样输出 echo \u0026#34;文件数: $(ls | wc -l)\u0026#34; # 命令替换 echo \u0026#34;当前目录: $(pwd)\u0026#34; 引号三原则：\n变量引用一律加双引号：\u0026quot;$name\u0026quot;，防止空格和通配符展开把脚本搞崩 原样字符串用单引号 命令替换用 $() 而不是反引号 ` `（可读性、可嵌套性更好） 特殊变量速查：\n变量 含义 $0 脚本名 $1~$9 第 1~9 个参数 $# 参数个数 $@ 所有参数（每个独立） $? 上一条命令的退出码 $$ 当前脚本 PID 三、算术运算 count=10 count=$((count + 1)) echo \u0026#34;$count\u0026#34; # 11 # 自增 ((count++)) # 浮点运算 bash 原生不支持, 借助 bc echo \u0026#34;scale=2; 10 / 3\u0026#34; | bc # 3.33 四、条件判断 if 语法（注意分号和空格） if [ -f /etc/passwd ]; then echo \u0026#34;文件存在\u0026#34; elif [ -d /etc ]; then echo \u0026#34;这是目录\u0026#34; else echo \u0026#34;都不满足\u0026#34; fi 常用测试条件：\n写法 含义 -f file 是普通文件 -d dir 是目录 -e path 存在 -z \u0026quot;$s\u0026quot; 字符串为空 -n \u0026quot;$s\u0026quot; 字符串非空 \u0026quot;$a\u0026quot; = \u0026quot;$b\u0026quot; 字符串相等 -eq / -ne / -gt / -lt 数值 比较 -r / -w / -x 可读/可写/可执行 数值比较示例：\nload=$(uptime | awk -F\u0026#39;load average:\u0026#39; \u0026#39;{print int($2)}\u0026#39;) if [ \u0026#34;$load\u0026#34; -gt 4 ]; then echo \u0026#34;负载偏高: $load\u0026#34; fi case 语句 case \u0026#34;$1\u0026#34; in start) echo \u0026#34;starting...\u0026#34;;; stop) echo \u0026#34;stopping...\u0026#34;;; restart) echo \u0026#34;restarting...\u0026#34;;; *) echo \u0026#34;用法: $0 {start|stop|restart}\u0026#34;; exit 1;; esac 五、循环 for 循环 # 遍历列表 for svc in nginx redis mysql; do systemctl is-active \u0026#34;$svc\u0026#34; \u0026amp;\u0026amp; echo \u0026#34;$svc ✓\u0026#34; done # 遍历数字范围 for i in {1..5}; do echo \u0026#34;第 $i 次执行\u0026#34; done # 遍历命令输出（逐行） for line in $(cat /etc/passwd); do echo \u0026#34;$line\u0026#34; done # 更安全的逐行读取（保留空格） while IFS= read -r line; do echo \u0026#34;$line\u0026#34; done \u0026lt; /etc/hosts while + break/continue retry=0 while true; do if curl -sf http://localhost:8080/health \u0026gt;/dev/null; then echo \u0026#34;服务健康\u0026#34; break fi ((retry++)) [ \u0026#34;$retry\u0026#34; -ge 10 ] \u0026amp;\u0026amp; { echo \u0026#34;重试超限\u0026#34;; exit 1; } sleep 3 done 六、实战小例子：批量检查主机存活 #!/usr/bin/env bash set -euo pipefail HOSTS=(192.168.1.10 192.168.1.11 192.168.1.12) for ip in \u0026#34;${HOSTS[@]}\u0026#34;; do if ping -c1 -W1 \u0026#34;$ip\u0026#34; \u0026amp;\u0026gt;/dev/null; then echo \u0026#34;[UP] $ip\u0026#34; else echo \u0026#34;[DOWN] $ip\u0026#34; fi done 小结 骨架 + set -euo pipefail 是规范脚本的起点 变量永远记得 \u0026quot;$var\u0026quot; 双引号 数值判断用 -eq/-gt，字符串判断用 = 复杂逐行处理用 while IFS= read -r Bash 适合胶水和短脚本；逻辑超过两三百行时，就该考虑 Python 了。\n本文是「Linux 基础」系列第 2 篇，下篇预告：Linux 文本处理三剑客 grep、sed、awk。\n","permalink":"https://antidebug.cn/posts/2023/04/bash-%E8%84%9A%E6%9C%AC%E5%85%A5%E9%97%A8%E5%8F%98%E9%87%8F%E6%9D%A1%E4%BB%B6%E4%B8%8E%E5%BE%AA%E7%8E%AF/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e会写 Bash 脚本，是运维和后端开发日常效率的分水岭。这篇把最核心的语法要素串一遍，看完能写出规范、能跑的脚本。\u003c/p\u003e\n\u003ch2 id=\"一脚本骨架\"\u003e一、脚本骨架\u003c/h2\u003e\n\u003cp\u003e从一个标准骨架开始：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#!/usr/bin/env bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e# 描述: 演示脚本骨架\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eset\u003c/span\u003e -euo pipefail          \u003cspan class=\"c1\"\u003e# 出错即停 + 未定义变量报错 + 管道错误传递\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eLOG_FILE\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/var/log/myapp.log\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003elog\u003cspan class=\"o\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;[\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003edate \u003cspan class=\"s1\"\u003e\u0026#39;+%F %T\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"s2\"\u003e] \u003c/span\u003e\u003cspan class=\"nv\"\u003e$*\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e tee -a \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$LOG_FILE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emain\u003cspan class=\"o\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    log \u003cspan class=\"s2\"\u003e\u0026#34;脚本开始\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# ... 业务逻辑\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    log \u003cspan class=\"s2\"\u003e\u0026#34;脚本结束\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emain \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$@\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e三件套说明：\u003c/p\u003e","title":"Bash 脚本入门：变量、条件与循环"},{"content":"为什么需要虚拟环境 新手最常见的事故现场：系统 Python 里装了一堆包，某天升级一个库，另一个项目直接崩了。**虚拟环境（Virtual Environment）**就是给每个项目一个独立、干净的 Python 运行空间，互不干扰。\n一、安装 Python（多版本共存） CentOS / OpenCloudOS 系：\n# 查看自带的 python3 python3 --version # 安装开发工具与依赖（编译源码时需要） dnf groupinstall -y \u0026#34;Development Tools\u0026#34; dnf install -y openssl-devel bzip2-devel libffi-devel zlib-devel readline-devel sqlite-devel # 编译安装另一个版本（以 3.11 为例），用 make altinstall 避免覆盖系统 python3 cd /tmp curl -LO https://mirrors.huaweicloud.com/python/3.11.4/Python-3.11.4.tgz tar -xzf Python-3.11.4.tgz \u0026amp;\u0026amp; cd Python-3.11.4 ./configure --prefix=/usr/local/python311 --enable-optimizations make -j$(nproc) \u0026amp;\u0026amp; make altinstall ⚠️ 一定要用 make altinstall 而不是 make install，否则会覆盖系统的 /usr/bin/python3，导致 yum/dnf 等工具罢工。\n二、venv：内置的虚拟环境方案 Python 3.3+ 自带 venv 模块，无需额外安装：\n# 创建虚拟环境（习惯放在项目下的 .venv 目录） python3 -m venv .venv # 激活 source .venv/bin/activate # 激活后提示符前面会出现 (.venv)，此时 pip 安装的包只进这个环境 (.venv) $ pip install requests # 退出虚拟环境 deactivate 激活虚拟环境后发生了什么？本质是把 .venv/bin 插到了 PATH 最前面：\n(.venv) $ which python /opt/myproject/.venv/bin/python 三、pip 使用与国内源 pip install requests # 安装 pip install \u0026#34;flask==2.3.2\u0026#34; # 指定版本 pip install -U pip # 升级自己 pip list # 已安装列表 pip show requests # 查看某个包详情 pip uninstall requests # 卸载 国内服务器直连 PyPI 很慢，换成清华源（一劳永逸的写法）：\npip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 也可以只对单次安装生效：\npip install -i https://mirrors.aliyun.com/pypi/simple/ requests 四、依赖管理：requirements.txt # 导出当前环境全部依赖 pip freeze \u0026gt; requirements.txt # 在新环境一键还原 pip install -r requirements.txt 一个实用的 requirements.txt 长这样（建议锁版本）：\nrequests==2.31.0 flask==2.3.2 gunicorn==21.2.0 五、常用组合技 场景 命令 给每个项目独立环境 python3 -m venv .venv \u0026amp;\u0026amp; source .venv/bin/activate 优雅删除环境 直接 rm -rf .venv（它只是一个目录） 迁移项目 新机器 python3 -m venv .venv + pip install -r requirements.txt 生产部署 .venv/bin/gunicorn app:app（无需激活即可调用） 常见坑 python 命令不存在：老系统只有 python3，可 ln -s /usr/bin/python3 /usr/local/bin/python 或用 alternatives 管理 误改系统 Python：RHEL 系的 yum 依赖系统 Python，动它之前先想清楚 requirements.txt 越滚越大：pip freeze 会把间接依赖也导出来，团队协作时可考虑 pip-tools 生成两份文件（requirements.in → requirements.txt） 小结 虚拟环境是 Python 工程化的第一课：一个项目一个环境，依赖全部锁版本，换机一条命令还原。后面写爬虫、写 FastAPI 服务，都基于这套环境体系。\n本文是「Python」系列第 1 篇，下篇预告：Python 数据结构：列表、字典与集合。\n","permalink":"https://antidebug.cn/posts/2023/03/python-%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E4%B8%8E%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83%E7%AE%A1%E7%90%86/","summary":"\u003ch2 id=\"为什么需要虚拟环境\"\u003e为什么需要虚拟环境\u003c/h2\u003e\n\u003cp\u003e新手最常见的事故现场：系统 Python 里装了一堆包，某天升级一个库，另一个项目直接崩了。**虚拟环境（Virtual Environment）**就是给每个项目一个独立、干净的 Python 运行空间，互不干扰。\u003c/p\u003e","title":"Python 环境搭建与虚拟环境管理"},{"content":"前言 刚接触 Linux 的同学，第一次看到 ls -l 输出前面那串 drwxr-xr-x 大概率是一脸懵的。这篇文章把 Linux 文件权限这套模型掰开揉碎讲清楚，包括很多人用了很多年也没完全理解的特殊权限位。\n一、基础权限：rwx 三元组 ls -l 看一个文件：\n-rw-r--r-- 1 root root 1024 Mar 5 10:00 app.conf 第一个字符之后的 9 个字符，每 3 个一组，分别对应：\n位置 含义 英文 1-3 所有者权限 owner / user 4-6 所属组权限 group 7-9 其他人权限 others 每个三元组里三种权限的含义：\nr（read，读）：文件可读内容；目录可列出里面的条目 w（write，写）：文件可修改；目录可在其中创建/删除条目 x（execute，执行）：文件可执行；目录可进入（cd） ⚠️ 注意一个反直觉的点：对目录而言，x 权限比 r 更关键。没有 x 你连 cd 都进不去，即使有 r 也只能看到文件名列表。\n二、数字表示法 三种权限对应二进制位：\nr = 4 (100) w = 2 (010) x = 1 (001) 所以 rwxr-xr-- = 4+2+1 / 4+0+1 / 4+0+0 = 754。\n常见组合速查：\n数字 字母 含义 755 rwxr-xr-x 目录、可执行程序的标准权限 644 rw-r\u0026ndash;r\u0026ndash; 普通文件的标准权限 600 rw\u0026mdash;\u0026mdash;- 私密文件（如 ~/.ssh/id_rsa） 777 rwxrwxrwx 全开（生产环境慎用！） 修改权限：\n# 数字法 chmod 755 deploy.sh # 符号法：给其他人加执行权限 chmod o+x deploy.sh # 递归修改目录 chmod -R 755 /var/www/html 三、所有权：chown 与 chgrp # 同时修改所有者和组 chown nginx:nginx /var/www/html/favicon.ico # 只改组 chgrp docker /var/run/docker.sock # 递归 chown -R www-data:www-data /opt/app 四、特殊权限位 前面 ls -l 的第一个字符如果出现 s 或 t，就说明特殊权限登场了。\n4.1 SUID（Set UID） 出现在所有者的 x 位上（-rwsr-xr-x）。作用：普通用户执行该文件时，临时获得文件所有者的身份。\n最典型的例子：\n$ ls -l /usr/bin/passwd -rwsr-xr-x 1 root root ... /usr/bin/passwd 普通用户执行 passwd 修改自己的密码，但 /etc/shadow 只有 root 能写——靠的就是 SUID 让进程临时持有 root 权限。\n# 设置/去除 SUID chmod u+s file chmod 4755 file # 4 开头表示 SUID chmod u-s file 4.2 SGID（Set GID） 用在可执行文件上：执行者临时获得文件所属组的身份。\n用在目录上更常见：目录下新建的文件，自动继承目录的属组（而不是创建者的主组）。团队共享目录的经典配置：\nmkdir /data/share chgrp devops /data/share chmod 2775 /data/share # 2 开头表示 SGID 4.3 Sticky Bit 出现在其他人的 x 位上（drwxrwxrwt）。作用：目录内的文件，只有所有者（和 root）能删除，即使目录对所有人可写。\n最经典的就是 /tmp：\n$ ls -ld /tmp drwxrwxrwt 15 root root ... /tmp 所有人都能往里写，但 A 用户删不掉 B 用户的文件。\n五、一个综合练习 需求：搭建一个团队共享目录 /data/project，要求——\n属于 dev 组 组内成员可读写，其他人无权限 新建文件自动归入 dev 组 groupadd dev mkdir -p /data/project chgrp dev /data/project chmod 2770 /data/project 验证：\n# 用两个组内用户分别创建文件 sudo -u alice touch /data/project/a.txt sudo -u bob touch /data/project/b.txt ls -l /data/project # 两个文件属组都是 dev, 且互相可编辑 六、安全提醒 生产环境禁止无脑 chmod 777，这是很多安全扫描的第一条告警 定期排查系统里的 SUID 文件，防止被利用提权： find / -perm -4000 -type f 2\u0026gt;/dev/null 敏感私钥文件务必 600，目录 700： chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 小结 权限 数字前缀 字母位置 核心作用 SUID 4 owner 的 x 位 执行时获得所有者身份 SGID 2 group 的 x 位 目录内文件继承属组 Sticky 1 others 的 x 位 仅所有者可删除文件 权限模型是 Linux 一切安全体系的基石，把这篇文章的内容吃透，后续学容器、学 K8s 里的 runAsUser、fsGroup 时会发现全是同一套思想的延伸。\n本文是「Linux 基础」系列第 1 篇，下一篇：Python 环境搭建与虚拟环境管理。\n","permalink":"https://antidebug.cn/posts/2023/03/linux-%E6%96%87%E4%BB%B6%E6%9D%83%E9%99%90%E4%B8%8E%E7%89%B9%E6%AE%8A%E6%9D%83%E9%99%90%E4%BD%8D%E8%AF%A6%E8%A7%A3/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e刚接触 Linux 的同学，第一次看到 \u003ccode\u003els -l\u003c/code\u003e 输出前面那串 \u003ccode\u003edrwxr-xr-x\u003c/code\u003e 大概率是一脸懵的。这篇文章把 Linux 文件权限这套模型掰开揉碎讲清楚，包括很多人用了很多年也没完全理解的特殊权限位。\u003c/p\u003e","title":"Linux 文件权限与特殊权限位详解"},{"content":"👋 Hello, World 我是 zy，一名长期混迹于 Linux 运维与云原生领域的基础设施工程师。\n这个博客名字叫 练级成长之路——因为技术这条路真的就像打游戏：\n每解决一个线上故障 = 击败一个小怪 🐛 每啃完一个新概念 = 学会一个新技能 📖 每次复盘事故 = 打完 BOSS 后捡装备 💎 🛠️ 技能面板 方向 熟练度 Linux 基础与运维 ★★★★☆ Nginx / 中间件 ★★★☆☆ 云计算（虚拟化/存储/网络） ★★★☆☆ 云原生 ★★★★☆ Python ★★★☆☆ Golang ★★★☆☆ 📮 联系方式 博客域名：antidebug.cn —— 反调试，调试人生 😄 RSS 订阅：点这里 本站已备案：粤ICP备2021032804号-1\n","permalink":"https://antidebug.cn/about/","summary":"\u003ch2 id=\"-hello-world\"\u003e👋 Hello, World\u003c/h2\u003e\n\u003cp\u003e我是 \u003cstrong\u003ezy\u003c/strong\u003e，一名长期混迹于 Linux 运维与云原生领域的基础设施工程师。\u003c/p\u003e\n\u003cp\u003e这个博客名字叫 \u003cstrong\u003e练级成长之路\u003c/strong\u003e——因为技术这条路真的就像打游戏：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e每解决一个线上故障 = 击败一个小怪 🐛\u003c/li\u003e\n\u003cli\u003e每啃完一个新概念 = 学会一个新技能 📖\u003c/li\u003e\n\u003cli\u003e每次复盘事故 = 打完 BOSS 后捡装备 💎\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"-技能面板\"\u003e🛠️ 技能面板\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e方向\u003c/th\u003e\n          \u003cth\u003e熟练度\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eLinux 基础与运维\u003c/td\u003e\n          \u003ctd\u003e★★★★☆\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eNginx / 中间件\u003c/td\u003e\n          \u003ctd\u003e★★★☆☆\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e云计算（虚拟化/存储/网络）\u003c/td\u003e\n          \u003ctd\u003e★★★☆☆\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e云原生\u003c/td\u003e\n          \u003ctd\u003e★★★★☆\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ePython\u003c/td\u003e\n          \u003ctd\u003e★★★☆☆\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eGolang\u003c/td\u003e\n          \u003ctd\u003e★★★☆☆\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"-联系方式\"\u003e📮 联系方式\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e博客域名：\u003ccode\u003eantidebug.cn\u003c/code\u003e —— 反调试，调试人生 😄\u003c/li\u003e\n\u003cli\u003eRSS 订阅：\u003ca href=\"/index.xml\"\u003e点这里\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e本站已备案：\u003ca href=\"https://beian.miit.gov.cn/\"\u003e粤ICP备2021032804号-1\u003c/a\u003e\u003c/p\u003e","title":"关于我"}]