躬行笔记
type: channel
link: https://www.fuzhoupyy.work/
description: 古人学问无遗力，少壮工夫老始成。 纸上得来终觉浅，绝知此事要躬行。
items: 蓝绿部署实战指南：从零到一搭建生产级无缝发布系统
items: 从零开始搞懂DDoS和CC攻击：运维人员必备的防护攻略
items: Pingora实战：从零搭建比Nginx还快的反向代理，手把手教你玩转Cloudflare开源神器
items: Linux 内存泄漏排查全攻略：从现象到根因，一文讲透生产环境实战经验
items: K8s持久化存储深度解析：PV、PVC、StorageClass三剑客的生产实战
items: Docker Swarm 搞定高可用集群，生产环境再也不怕服务挂掉了
items: 还在手动点控制台建资源？Terraform和CloudFormation入门实战，这篇够你喝一壶了
items: 开源协议有哪些你知道吗？
items: 深度拆解Tomcat：从架构设计到生产实践，这些年踩过的坑都在这里了
items: 生产环境 Nginx 代理 WebSocket 总断连？这几个隐藏配置坑了多少人

蓝绿部署实战指南：从零到一搭建生产级无缝发布系统
type: item
date: 2026-04-16T15:06:11+00:00
description: 蓝绿部署实战指南：从零到一搭建生产级无缝发布系统我在公司做运维这几年，最怕的就是发布新版本的时候出问题。记得有一次周五下午发版本，结果服务挂了两个小时，用户投诉电话打爆了，我在机房里忙活到晚上11点才搞定。从那以后，我就开始研究各种发布策略，蓝绿部署就是其中最实用的一种。今天就把我这几年摸索出来的蓝绿部署经验分享给大家，全是生产环境踩过的坑和总结出的干货。什么是蓝绿部署蓝绿部署说白了就是准备两套一模一样的生产环境，一套叫蓝环境，一套叫绿环境。当前用户访问的是蓝环境，我要发新版本的时候，就把新版本部署到绿环境上，测试没问题了再把流量切过去。这样做的好处是什么？如果新版本有问题，我立马就能切回到旧版本，用户基本感受不到服务中断。我之前用滚动更新的方式，每次更新都要重启服务，虽然时间很短，但还是会有那么几秒钟的服务不可用。用了蓝绿部署之后，这个问题彻底解决了。蓝绿部署的核心原理整个流程其实很简单：当前蓝环境在线提供服务在绿环境部署新版本应用绿环境测试通过后，负载均衡器流量从蓝环境切换到绿环境绿环境成为新的生产环境，蓝环境变成备用环境关键点就是这个流量切换，必须要做到瞬间完成，不能有中间状态。实现蓝绿部署的几种方式方式一：基于Nginx的蓝绿部署这是我最常用的方式，成本低，配置简单。先准备两台服务器，192.168.1.10（蓝）和192.168.1.11（绿），前面放一台Nginx做负载均衡。Nginx配置文件关键部分：upstream blue_pool { server 192.168.1.10:8080 weight=1 max_fails=3 fail_timeout=30s; } upstream green_pool { server 192.168.1.11:8080 weight=1 max_fails=3 fail_timeout=30s; } # 当前生产环境指向蓝环境 upstream production { server 192.168.1.10:8080; } server { listen 80; server_name api.example.com; location / { proxy_pass http://production; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }切换的时候，写个脚本：#!/bin/bash # switch.sh NGINX_CONF="/etc/nginx/conf.d/app.conf" CURRENT_ENV=$(grep -A1 "upstream production" $NGINX_CONF | grep server | awk '{print $2}' | cut -d: -f1) if [ "$CURRENT_ENV" = "192.168.1.10" ]; then # 当前是蓝环境，切换到绿环境 sed -i 's/server 192.168.1.10:8080;/server 192.168.1.11:8080;/' $NGINX_CONF echo "已切换到绿环境" else # 当前是绿环境，切换到蓝环境 sed -i 's/server 192.168.1.11:8080;/server 192.168.1.10:8080;/' $NGINX_CONF echo "已切换到蓝环境" fi # 重新加载Nginx配置 nginx -s reload这个方案的优点是简单直接，缺点是需要手动执行切换脚本。不过对于中小型项目来说已经够用了。方式二：基于HAProxy的蓝绿部署HAProxy在流量切换方面比Nginx更灵活，支持动态权重调整。HAProxy配置：global daemon maxconn 4096 defaults mode http timeout connect 5000ms timeout client 50000ms timeout server 50000ms frontend web_frontend bind *:80 default_backend web_servers backend web_servers balance roundrobin # 初始状态：蓝环境权重100，绿环境权重0 server blue 192.168.1.10:8080 weight 100 check server green 192.168.1.11:8080 weight 0 check # 管理接口 …

从零开始搞懂DDoS和CC攻击：运维人员必备的防护攻略
type: item
date: 2026-04-15T14:45:00+00:00
description: 作为一个在运维岗位摸爬滚打好几年的人，我想和大家聊聊DDoS和CC攻击这个话题。这两种攻击方式虽然都能让你的服务器瞬间趴窝，但原理和防护方法却大不相同。今天就把我这些年踩过的坑和积累的经验分享给大家。DDoS攻击到底是个什么鬼DDoS全称是Distributed Denial of Service，翻译过来就是分布式拒绝服务攻击。听起来很高大上，其实原理挺简单的。你可以想象一下，平时你家门口最多同时来3-5个客人，你能应付得过来。但如果突然来了100个人，都要进你家坐坐，你家的门、客厅、厕所...肯定就瘫痪了。DDoS攻击就是这个道理，通过控制大量的"肉鸡"（被感染的计算机）同时向目标服务器发送请求，把服务器的带宽、CPU、内存等资源耗尽。DDoS攻击主要分几种类型：流量型攻击（Volume-based attacks）这种最直接，就是用大量的垃圾流量把你的带宽塞满。常见的有UDP洪水攻击、ICMP洪水攻击等。攻击者会发送大量的UDP包或者ping包，把你的网络带宽占满，正常用户就访问不了了。协议型攻击（Protocol attacks）这种攻击利用网络协议的缺陷，比如SYN洪水攻击。TCP三次握手大家都知道吧，客户端发SYN，服务器回SYN-ACK，然后等客户端的ACK。攻击者只发SYN不发ACK，服务器就一直等着，连接池很快就满了。应用层攻击（Application layer attacks）这种攻击针对应用程序，看起来像正常的请求，但会消耗大量的服务器资源。比如不断请求复杂的数据库查询，或者上传大文件等。CC攻击又是怎么回事CC攻击的全称是Challenge Collapsar，中文叫挑战黑洞。听这名字就知道不是什么好东西。CC攻击和DDoS攻击的区别在于，CC攻击更加"精准"。它不需要很大的流量，而是模拟正常用户的行为，不断地请求那些消耗资源比较大的页面或接口。CC攻击的特点是：流量不大，但请求频繁看起来像正常用户行为针对消耗资源较大的功能难以通过简单的流量限制来防护两种攻击的本质区别说到区别，我觉得可以用一个比喻来解释。DDoS攻击就像是在你家门口堵了一大群人，把路都堵死了，正常客人根本进不来。这种攻击比较"粗暴"，流量大，容易发现。CC攻击则像是派了几个人，每个人都很有礼貌地敲门，但一进来就要你做很复杂的事情，比如帮忙搬家具、修电器...虽然人不多，但你很快就累趴下了。从技术角度来说：DDoS主要消耗带宽和网络资源CC主要消耗服务器的CPU、内存和数据库资源DDoS流量特征明显，CC攻击更隐蔽DDoS防护相对简单粗暴，CC防护需要更精细的策略防护策略：不只是买个高防就完事很多人以为防DDoS和CC攻击就是买个高防服务器或者CDN，其实远没有这么简单。我这些年总结下来，防护需要多层次的策略。基础防护措施首先是服务器层面的优化。我一般会调整一些系统参数，比如增加TCP连接池大小，调整超时时间等。在nginx配置里也会做一些限制：# 限制单个IP的连接数 limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m; limit_conn conn_limit_per_ip 20; # 限制请求频率 limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s; limit_req zone=req_limit_per_ip burst=20 nodelay;这些配置能在一定程度上缓解攻击，但面对大规模DDoS还是不够的。应用层防护对于CC攻击，我会在应用层做一些防护。比如给那些消耗资源大的接口加上缓存，限制单个用户的请求频率，对异常请求进行识别和拦截。我记得有次优化一个电商网站的搜索功能，发现搜索结果页面的数据库查询特别复杂。于是我们加了Redis缓存，把热门搜索词的结果缓存起来，这样即使遇到CC攻击，数据库压力也不会太大。CDN和云防护现在各大云厂商的防护产品确实很强大。我用过阿里云的DDoS高防，腾讯云的大禹，华为云的Anti-DDoS等，效果都不错。这些产品的原理基本相似：在你的服务器前面部署一层防护节点，所有流量先经过这些节点进行清洗，正常流量才会转发到你的服务器。流量清洗的那些门道流量清洗听起来很神秘，其实原理并不复杂。就是在海量的网络流量中，把攻击流量识别出来并丢弃，让正常流量通过。特征识别最基础的方法是基于特征识别。比如DDoS攻击的流量往往有一些明显特征：源IP分布异常（大量来自同一地区或AS）请求模式单一（相同的User-Agent、请求路径等）流量突增（短时间内流量暴涨几十倍）我见过一些简单的攻击，所有请求的User-Agent都一样，这种很容易识别。但…

Pingora实战：从零搭建比Nginx还快的反向代理，手把手教你玩转Cloudflare开源神器
type: item
date: 2026-04-14T15:55:05+00:00
description: 前两天看到Cloudflare开源了Pingora这个项目，说是用Rust写的反向代理框架，性能比Nginx还要快，我就坐不住了。咱们做运维的，对新东西总是要保持好奇心，何况这玩意儿号称每秒能处理百万级请求，这不得赶紧试试？说实话，刚开始看到这个消息的时候我还挺怀疑的。Nginx可是咱们运维圈的老伙计了，用了这么多年，稳定可靠，你说一个新出来的框架能比它快？不过转念一想，Cloudflare可是全球最大的CDN服务商之一，他们每天处理的流量那都是天文数字，既然敢开源出来，肯定是有两把刷子的。抱着试试看的心态，我在测试环境折腾了几天，把整个安装配置流程都跑了一遍。不得不说，这东西确实有点东西，今天就把我的实践经验分享给大家，希望能帮到想尝鲜的朋友们。Pingora是个啥？咱们先聊聊Pingora到底是个什么东西。简单来说，它是Cloudflare用Rust语言开发的一个高性能反向代理框架[1]。Cloudflare大家应该都不陌生吧，全球最大的CDN服务商之一，每天处理的HTTP请求那是数以万亿计的。以前Cloudflare也是用Nginx，但是随着业务规模越来越大，Nginx的一些局限性就开始显现出来了。比如说，Nginx的配置文件虽然灵活，但是要做一些复杂的逻辑处理就比较麻烦；还有就是性能瓶颈，在某些极端场景下已经满足不了需求了。于是Cloudflare就开始自己造轮子，用Rust写了这个Pingora。根据官方的说法，Pingora在性能、安全性、可扩展性方面都有很大的提升[2]。特别是在高并发场景下，表现相当亮眼。我看过一些性能测试的对比数据，Pingora在某些场景下确实比Nginx快不少[3]。当然，这个"快"也要看具体场景，不是说什么情况下都快的。咱们做技术的，不能盲目迷信，得自己动手测试才知道。环境准备好了，说了这么多，咱们开始动手吧。不过在安装Pingora之前，得先把环境准备好。Pingora是用Rust写的，所以首先你得有Rust的环境。要是你系统里还没有Rust，那就得先装一下。我这边测试用的是debain13，其他Linux发行版应该也差不多。安装Rust其实挺简单的，官方提供了一个脚本：curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh运行这个脚本，一路回车就好。装完之后记得重启一下终端，或者手动source一下环境变量：source $HOME/.cargo/env然后验证一下安装是否成功：rustc --version cargo --version看到版本号输出就说明OK了。对了，还得提醒一下，Pingora对Rust的版本有要求，最好是1.70以上的版本。要是版本太老，可能编译的时候会报错。我一开始就是用的系统自带的旧版本，结果折腾半天编译不过，后来升级了Rust版本才解决。除了Rust，你还得有一些基础的编译工具，比如gcc、make之类的。debain13的话可以这样装：apt-get install -y gcc make openssl-devel pkg-config这些工具在编译Pingora的时候会用到，少了哪个都会报错，到时候再装就麻烦了。创建项目环境准备好了，接下来就是创建项目。Pingora不是一个独立的可执行程序，而是一个框架，你需要基于它来开发自己的代理服务。这个跟Nginx不太一样，Nginx是直接装好就能用的，Pingora更像是一个开发框架。用Cargo创建一个新项目：cargo new my_pingora_proxy cd my_pingora_proxy创建后的目录结构：然后编辑Cargo.toml文件，添加Pingora的依赖：[package] name = "my_pingora_proxy" version = "0.1.0" edition = "2024" [dependencies] pingora = { version = "0.1", features = ["proxy"] } async-trait = "0.1"这里要注意一下，Pingora的版本号可能会更新，具体可以去crates.io上看最新的版本。我写这篇文章的时候是0.1版本，现在可能已经更新了。添加依赖的时候，那个features = ["proxy"]不能少，不然编译的时候会提示找不到某些模块。我第一次搞的时候就忘了这个，折腾了好久才发现。写第一个代理程序依赖配好了，接下来就是写代码了。说实话，第一次看到Pingora的代码的时候，我还挺懵的。跟Nginx那种配置文件的方式完全不一样，Pingora是用代码来定义代理逻辑的。不过仔细看看，其实也不难理解。咱们先写一个最简单的反向代理，把请求转发到后…

Linux 内存泄漏排查全攻略：从现象到根因，一文讲透生产环境实战经验
type: item
date: 2026-04-13T13:50:40+00:00
description: 做运维这么多年，内存泄漏这事儿真是没少碰。有些时候程序跑着跑着内存就蹭蹭往上涨，重启一下好了，过两天又不行了。这种问题最烦人，不像CPU高那样容易定位，也不像磁盘满了那样好处理。内存泄漏就像个隐形杀手，藏在代码深处，等你发现的时候往往已经酿成事故了。今天咱们不聊虚的，直接上干货，把Linux下排查内存泄漏的方法系统梳理一遍。从基础的命令行工具到高级的调试技巧，能用的都给大家整理出来。内存泄漏到底是咋回事说白了就是申请了内存用完不释放。程序向系统申请了一块内存空间，用完了却没告诉系统"我不用了"，这块内存就被标记为占用状态。时间一长，这种"僵尸内存"越来越多，可用内存越来越少，最后系统扛不住了，触发OOM Killer把进程干掉，或者整个系统卡死。Linux的内存管理机制本身是没问题的，问题出在应用程序层面。C/C++这种需要手动管理内存的语言是重灾区，Java、Go这种有垃圾回收的语言理论上好点，但也不代表不会泄漏。比如Java里的对象引用没释放，或者堆外内存用多了，照样会出问题。内存泄漏有个特点：渐进性。刚启动的时候没事，运行几天甚至几周才开始显现。所以排查起来特别费劲，你得知道是哪个时间点开始泄漏的，做了什么操作触发的。怎么发现内存泄漏监控是第一道防线。我们生产环境用的是Prometheus + Grafana，内存使用率曲线一目了然。正常的内存使用应该是波动的，有升有降，垃圾回收或者缓存清理后内存会下来。如果看到一条斜向上的直线，那八成有问题。命令行下最常用的是free命令：free -h total used free shared buff/cache available Mem: 15Gi 8.5Gi 2.1Gi 256Mi 4.9Gi 6.2Gi Swap: 8.0Gi 1.2Gi 6.8Gi重点看available这列，这才是真正可用的内存。free那列看起来少，但很多是缓存，系统需要的时候可以回收。如果available持续下降，那就要警惕了。top命令更直观，能看到每个进程的内存占用：top - 14:30:25 up 10 days, 3:45, 2 users, load average: 0.52, 0.58, 0.59 Tasks: 156 total, 1 running, 155 sleeping, 0 stopped, 0 zombie %Cpu(s): 3.2 us, 1.5 sy, 0.0 ni, 94.8 id, 0.5 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 16384.0 total, 2150.5 free, 8720.3 used, 5513.2 buff/cache MiB Swap: 8192.0 total, 6800.2 free, 1391.8 used. 6352.1 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 12345 app 20 0 4.2g 3.5g 123m S 3.2 22.1 12:34.56 java按M可以按内存排序。VIRT是虚拟内存，RES是实际物理内存，SHR是共享内存。排查内存泄漏主要看RES，这个才是真正占用的物理内存。还有个命令vmstat，能看内存变化趋势：vmstat 1 10 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 1425400 2150352 451232 5060992 0 0 2 15 12 15 3 1 95 0 0 0 0 1425400 2148320 451240 5061124 0 0 0 0 1234 2156 3 1 95 0 0看free列是不是在持续减少。si和so是swap in和swap out，如果这两个值很大，说明系统在频繁换页，内存已经紧张了。定位问题进程发现系统内存不够了，下一步就是找出是谁在搞鬼。top命令能看到进程级别的内存占用，但有时候进程下面有很多线程，想知道具体是哪个线程占的多，可以用：top -H -p <pid>这样能显示进程下所有线程的资源占用情况。还有一个很好用的命令是ps，可以按内存排序：ps aux --sort=-%mem | head -10这会列出内存占用最高的10个进程。有时候进程名都一样，比如Java程序都叫java，怎么区分？看命令行参数：ps aux | grep java或者看/proc/<pid>…

K8s持久化存储深度解析：PV、PVC、StorageClass三剑客的生产实战
type: item
date: 2026-04-12T15:02:37+00:00
description: 做了这么多年运维，K8s的存储这块一直是个让人又爱又恨的东西。说它复杂吧，确实概念挺多的，PV、PVC、StorageClass这些名词听起来就头大；说它简单吧，一旦搞明白了原理，用起来还是很顺手的。今天我就把自己这些年在生产环境中踩过的坑、总结的经验分享给大家，让你们少走点弯路。为什么需要持久化存储容器本身是无状态的，这个大家都知道。Pod一重启，里面的数据就没了，这对于数据库、文件存储这些有状态应用来说简直是灾难。我记得刚开始用K8s的时候，有次MySQL的Pod重启了，结果所有数据都丢了，那个心情...简直想死的心都有。从那以后我就深刻认识到，持久化存储在K8s中有多重要。K8s的持久化存储主要解决这么几个问题：数据持久性：Pod重启、迁移后数据不丢失数据共享：多个Pod可以访问同一份数据存储抽象：屏蔽底层存储实现细节PV、PVC、StorageClass的关系这三个概念的关系，我用一个比较接地气的比喻来解释：PV（PersistentVolume）就像是一个仓库，管理员提前准备好的存储空间。PVC（PersistentVolumeClaim）就像是租仓库的申请单，用户说我要多大的空间、什么类型的。StorageClass就像是仓库的分类标准，比如高性能仓库、普通仓库、便宜仓库等。具体来说：PV（持久卷）PV是集群级别的资源，由管理员创建，代表集群中的一块存储。它包含存储实现的具体细节，比如NFS、iSCSI、云存储等。apiVersion: v1 kind: PersistentVolume metadata: name: my-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain nfs: server: 192.168.1.100 path: /data/nfs这个配置我在生产环境用过很多次，需要注意几个点：capacity定义存储大小accessModes定义访问模式，RWO（ReadWriteOnce）表示只能被一个节点挂载persistentVolumeReclaimPolicy定义回收策略PVC（持久卷声明）PVC是用户对存储的请求，它会去寻找合适的PV进行绑定。apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi这里有个坑要注意，PVC请求的存储大小必须小于等于PV提供的大小，而且访问模式也要匹配。StorageClass（存储类）StorageClass提供了动态创建PV的方式，不需要管理员手动创建PV。apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: kubernetes.io/aws-ebs parameters: type: gp2 zone: us-west-2a静态供应 vs 动态供应静态供应静态供应就是管理员提前创建好PV，然后用户创建PVC去绑定。这种方式比较传统，适合小规模或者对存储有特殊要求的场景。我在一个项目中就是用的静态供应，因为客户要求数据必须存储在指定的NFS服务器上，所以只能手动创建PV。动态供应动态供应通过StorageClass自动创建PV，用户只需要在PVC中指定StorageClass即可。apiVersion: v1 kind: PersistentVolumeClaim metadata: name: dynamic-pvc spec: accessModes: - ReadWriteOnce storageClassName: fast-ssd resources: requests: storage: 20Gi这种方式更灵活，特别适合云环境。我现在基本都用动态供应，省事多了。生命周期管理PV和PVC的生命周期有几个关键阶段：Available（可用）PV已创建，但还没有被PVC绑定。Bound（已绑定）PV已经被PVC绑定，正在使用中。Released（已释放）PVC被删除，但PV还没有被回收。Failed（失败）自动回收失败。这里要特别说一下回收策略，有三种：Retain：保留数据，需要手动清理Delete：自动删除PV和底层存储Recycle：已废弃，不建议使用我在生产环境中一般用Retain策略，因为数据安全第一，宁可手动清理也不能误删数据。访问模式详解K8s支持三种访问模式：…

Docker Swarm 搞定高可用集群，生产环境再也不怕服务挂掉了
type: item
date: 2026-04-11T14:09:37+00:00
description: Docker Swarm是什么？Docker Swarm 是 Docker 官方推出的容器集群管理工具，基于 Go 语言实现。代码开源在：https://github.com/docker/swarm 使用它可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机，快速打造一套容器云平台。Docker Swarm 是生产环境中运行 Docker 应用程序最简单的方法。作为容器集群管理器，Swarm 最大的优势之一就是 100% 支持标准的 Docker API。各种基于标准 API 的工具比如 Compose、docker-py、各种管理软件，甚至 Docker 本身等都可以很容易的与 Swarm 进行集成。大大方便了用户将原先基于单节点的系统移植到 Swarm 上，同时 Swarm 内置了对 Docker 网络插件的支持，用户可以很容易地部署跨主机的容器集群服务。Docker Swarm 和 Docker Compose 一样，都是 Docker 官方容器编排工具，但不同的是，Docker Compose 是一个在单个服务器或主机上创建多个容器的工具，而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务，对于微服务的部署，显然 Docker Swarm 会更加适合。为什么选 Docker Swarm 而不是 K8s说实话，Kubernetes 确实强大，但对我们这种中小团队来说，K8s 的学习成本和维护成本都太高了。光是搭建一套高可用的 K8s 集群就得折腾好几天，更别说日常维护了。Docker Swarm 就不一样了，它是 Docker 原生自带的编排工具，学过 Docker 的基本上一个小时就能上手。而且功能足够强大，服务发现、负载均衡、滚动更新、健康检查这些该有的都有，完全能满足大部分生产场景的需求。客户现在的业务规模大概有 30 多个微服务，每天访问量在百万级别，用 Docker Swarm 完全够用。如果你的规模跟我差不多，真心建议别一上来就搞 K8s，Swarm 真的够用了。环境准备5 台服务器，配置如下：3 台 Manager 节点：16G 内存，4 核 CPU2 台 Worker 节点：8G 内存，2 核 CPU为什么 Manager 要 3 台？这个后面会详细说。系统用的是 CentOS 7.9，Docker 版本是 20.10.14。在开始之前，有几件事得先做好：1. 主机名和 hosts 配置每台机器都要设置不同的主机名，不然等会儿集群建起来都叫 localhost，看着就头疼。# 在 manager1 上执行 hostnamectl set-hostname manager1 # 其他机器类似，改成 manager2、manager3、worker1、worker2然后修改 /etc/hosts 文件，把所有节点的 IP 和主机名都写进去：192.168.1.101 manager1 192.168.1.102 manager2 192.168.1.103 manager3 192.168.1.104 worker1 192.168.1.105 worker22. 关闭防火墙或者开放端口Docker Swarm 需要几个端口：2377/tcp：集群管理通信7946/tcp+udp：节点间通信4789/udp：覆盖网络流量生产环境建议还是开防火墙，只开放这些端口。测试环境图省事直接关了也行：systemctl stop firewalld systemctl disable firewalld3. 时间同步这个特别重要！如果节点时间不同步，集群选举会出问题，服务调度也会乱套。我们用 chrony 做时间同步：yum install -y chrony systemctl start chronyd systemctl enable chronyd初始化 Swarm 集群准备工作做完了，现在开始正式搭建集群。在 manager1 上执行初始化命令：docker swarm init --advertise-addr 192.168.1.101执行完会输出一串命令，大概是这样的：Swarm initialized: current node (dxn1zf6l61qsb1josjja83ngz) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssm7h7g6w7br5d7vwlh 192.…

还在手动点控制台建资源？Terraform和CloudFormation入门实战，这篇够你喝一壶了
type: item
date: 2026-04-11T14:09:17+00:00
description: 提到搭建环境，很多兄弟可能还停留在“手工匠人”的模式。打开AWS控制台，点那个EC2，选镜像，配安全组，搞子网，再加个负载均衡……这一套下来，脑子都大了。最要命的是，你根本记不住上次那个安全组的入站规则到底是开了80端口还是8080端口。这种“雪花服务器”，配置漂移，环境不一致，简直是运维人的噩梦。这就是为什么我们今天必须得聊聊基础设施即代码这档子事儿。这玩意儿不是什么新概念，但真正能落地用好的，真能救命。咱们今天挑两个最重量级的选手——Terraform和CloudFormation，来一场从入门到实战的“保姆级”教学。别被那些英文单词吓到了，咱们用大白话把它拆开了揉碎了讲。咱们先说说这俩到底是啥如果你还没接触过IaC，可能觉得这俩词儿挺高大上。其实吧，说白了，就是把你的服务器、网络、数据库这些基础设施，像写代码一样定义出来。以前我们要建个仓库，得找施工队，拿图纸，一砖一瓦砌墙。现在有了IaC，就像是给了你一个3D打印机，你写好配置文件，告诉它我要几层楼、几个窗户、门朝哪开，一按按钮，房子自己就长出来了。CloudFormation，这可是AWS的亲儿子。它是AWS自家出的模板服务，专门用来管理AWS的资源。你想想，亲儿子肯定最了解家里的情况，所以它对AWS新特性的支持是最快的，而且跟AWS的控制台集成得非常深。你写个JSON或者YAML文件，往S3桶里一扔，CloudFormation引擎一读，哗啦啦给你创建一堆资源。如果中间出错了，它还能自动回滚，就像撤销键一样，这点特别稳。Terraform，则是HashiCorp公司搞的一个开源工具。这家公司简直是运维界的瑞士军刀厂，Vagrant、Consul都是他们家的。Terraform最大的特点就是“花心”，它不绑定任何一家云厂商。它支持AWS、Azure、Google Cloud，甚至还能管理本地的VMware、Kubernetes。这就好比你找了个全能装修队，既能给你家盖别墅，也能给隔壁老王家盖洋房，用的还是同一套图纸。这对于多云架构来说，简直是神器。那到底选哪个？如果你公司只死磕AWS一家，CloudFormation确实稳，毕竟是亲生的。但如果你不想被一家云厂商绑定死，或者将来有上多云的打算，那我强烈建议你从Terraform入手。咱们今天也主要拿Terraform来举例，顺便把CloudFormation的坑也给你填上。工欲善其事，必先装个“破”软件咱们别光说不练，先把环境搭起来。如果你用的是Mac，那简单，打开终端，如果你装了Homebrew，直接敲：brew install terraform要是Linux或者Windows，去官网下个二进制包，解压后把路径加到环境变量里就行。这没什么技术含量，我就不贴那些流水账似的教程了。装好后，敲个 terraform version，只要能蹦出版本号，那就说明成了。CloudFormation这玩意儿不需要你单独装软件，它就在AWS的控制台里，或者说它本身就是AWS的一部分。你只要有个AWS账号，有AK/SK密钥，用AWS CLI或者直接在网页上点都行。不过为了显得咱们专业点，咱们还是推荐用CLI操作，毕竟天天点鼠标那是文员干的事儿。Terraform入门：从Hello World开始咱们先来搞定Terraform。这玩意儿的核心就是那个 .tf 文件。别看到代码就头大，Terraform用的语言叫HCL（HashiCorp Configuration Language），这语言设计得特别人性化，基本上你只要会点英语，就能猜个八九不离十。咱们先建个文件夹，叫 terraform_demo，进去之后，先建个 main.tf 文件。这就像是咱们写文章的大纲，所有资源都在这儿定义。第一步，咱们得告诉Terraform，我们要用哪个云厂商的服务。这叫Provider。terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 4.0" } } } provider "aws" { region = "ap-northeast-1" # 咱们选东京区域，速度还行 # 这里默认会读取你环境变量里的 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY }你看，这段代码多直白。terraform 块里声明了我们要用 hashicorp/aws 这个provider，版本大概是4.0以上的。然后 provider "aws" 里指定了区域。这就好比你去饭店，先告诉服务员我要点川菜，然后找个位置坐下。接下来，咱们来点实际的，建个最简单的EC2服务器（虚拟机）。resource "aws_instance" "my…

开源协议有哪些你知道吗？
type: item
date: 2026-04-09T14:22:15+00:00
description: 前两天有个读者在后台问我，说他们公司用了某个开源组件做项目，结果收到律师函了，说违反了开源协议。我当时心里一紧，这种事其实挺常见的，很多开发者压根不知道自己用的那些库到底有什么限制。想起来我刚开始做运维那会儿，也是一脸懵。那时候领导让我搭建个内部代码仓库，我就直接把公司代码往上一推，后来才知道有些依赖包是不能随便用的。好险没出大问题。今天就聊聊开源协议这事儿，说实话，很多人干了几年开发运维，对这个还是一知半解。特别是那个让无数SaaS公司闻风丧胆的AGPL，后面我会专门讲到。开源协议到底是啥玩意说白了，开源协议就是一套规则，告诉你这个代码你能怎么用、能怎么改、能不能商用。就像你去别人家做客，主人得跟你说清楚哪些房间能进，哪些东西能碰。很多人有个误区，觉得开源就是免费的，想咋用咋用。这想法挺危险的。开源不等于免费，也不等于没有约束。代码开源了，作者还是拥有著作权的，只是给了你一些使用权而已。我记得有次跟同事争论，他说开源代码随便用，公司项目里直接复制粘贴都行。我当时就提醒他，你看看那个项目的 LICENSE 文件写的啥。结果一看，GPL 协议，这下尴尬了。为啥要有这东西这事儿得从上世纪 80 年代说起。那时候有个叫 Richard Stallman 的程序员，他在 MIT 人工智能实验室工作。有次打印机坏了，他想修改打印机的驱动程序，结果发现厂商不给他源代码。这事儿让他很不爽，他觉得软件应该自由，用户应该有权利修改自己用的软件。于是 1983 年，他发起了 GNU 项目，1985 年成立了自由软件基金会（FSF）。这个人挺有意思，为了理想，放弃了不少东西。他创造了一个概念叫 copyleft，跟 copyright（版权）对着干。说到这儿，我得插一句。很多人分不清自由软件和开源软件。其实这俩不完全一样。自由软件强调的是用户的自由权利，开源软件更强调开发模式的开放性。不过日常交流中，大家经常混着用。主流开源协议大盘点现在主流的开源协议有好几种，每种都有自己的脾气。我一个个说。MIT 协议这个最宽松，基本上就是"你想咋用咋用，只要保留我的版权声明就行"。具体来说，你可以：随便用，商业用途也行随便改，改完可以闭源随便分发限制就一条：保留原作者的版权声明和许可声明。我见过不少项目用 MIT，比如 React、Vue.js 这些。选择 MIT 的好处是传播广，坏处是别人拿你的代码去赚钱，你也没话说。Apache 协议 2.0这个比 MIT 稍微复杂点，但也很友好。主要特点：可以商用，可以闭源修改后需要说明修改了什么保留版权声明如果有专利，自动授予专利许可这个专利授权挺重要的。有些公司开源项目，可能会涉及专利问题。Apache 2.0 就明确说了，我开源这代码，专利权也给你用了，不能以后拿专利来告你。Android 系统就是用的 Apache 2.0，这也是为什么那么多厂商能基于 Android 做自己的系统。BSD 协议这个有好几个版本：BSD 2-Clause、BSD 3-Clause 等。BSD 2-Clause 跟 MIT 差不多，就是允许自由使用，保留版权声明就行。BSD 3-Clause 多了一条：不能用原作者的名义来做广告。这条挺实用的，避免有人打着原作者的旗号搞事情。GPL 协议这个是开源协议里的刺头，也是争议最大的。GPL 的核心思想是传染性。什么意思呢？如果你用了 GPL 协议的代码，那你的项目也得开源，而且得用 GPL 协议。这就像病毒一样，会传染。所以很多商业公司对 GPL 很警惕，生怕一不小心就中招了。GPL 有两个主要版本：GPL v2 和 GPL v3。GPL v2 是最经典的，Linux 内核就是用的这个。Linus Torvalds 到现在还坚持用 v2，不用 v3。GPL v3 更严格，加了一些反 DRM 的条款。简单说就是，如果你的设备上运行的是 GPL v3 的软件，你得让用户能修改这个软件。这主要是针对 TiVo 这种设备的，TiVo 用了 Linux，但不让用户修改，所以 FSF 在 v3 里专门加了这条款。LGPL 协议这个是 GPL 的弱化版，主要给库文件用的。如果你把 LGPL 的库链接到你的程序，你的程序不需要开源。但如果你修改了这个库本身，那修改的部分得开源。很多底层库用 LGPL，比如 glibc。这样商业软件也能用，不用担心被传染。MPL 协议Mozilla Public License，Mozilla 基金会搞的。这个比较折中，文件级别的 copyleft。就是说，你修改了某个文件，这个文件得开源。但你新增的文件不需要开源。Firefox 就是用的这个协议。AGPL 3.0 - SaaS 公司的噩梦这个协议我得专门拿出来说，因为这几年太重要了，也是很多公司容易踩坑的地方。AGPL …

深度拆解Tomcat：从架构设计到生产实践，这些年踩过的坑都在这里了
type: item
date: 2026-04-08T14:09:46+00:00
description: 说起Tomcat，估计每个搞Java开发和运维的同学都不陌生。但是真要问你Tomcat的内部工作原理，能完整说清楚的人其实不多。我在生产环境摸爬滚打这些年，从最开始的小白到现在能独当一面，Tomcat这个"老朋友"没少让我吃苦头。今天就把我这些年对Tomcat的理解和实战经验分享给大家，不是那种照本宣科的理论，而是真正在生产环境中能用得上的干货。为什么要深入了解Tomcat前段时间公司新来了个小伙子，部署应用的时候直接用默认配置就上线了。结果呢？服务刚上线没几天，用户量一上来就开始各种502、504错误。领导找过来问情况，那个场面...想想都尴尬。这种事情其实很常见，很多人觉得Tomcat嘛，下载下来启动就能用，有什么难的？但实际上，默认配置的Tomcat在生产环境基本上就是个定时炸弹。Tomcat的核心架构到底是怎么回事我记得刚开始接触Tomcat的时候，各种概念搞得我云里雾里的。什么Connector、Container、Engine、Host...听起来就头大。后来慢慢摸索，发现其实Tomcat的架构设计还是很有意思的。Server - 最顶层的管理者Server就是整个Tomcat实例，一个JVM进程里只有一个Server。你可以把它理解成一个大管家，负责管理下面所有的组件。在server.xml里面，最外层的<Server>标签就是它。我之前遇到过一个问题，就是在同一台机器上部署多个Tomcat实例的时候，Server的shutdown端口冲突了。默认是8005端口，如果不改的话，第二个实例启动就会报错。这种小细节在生产环境特别容易被忽略。Service - 服务的组织者Service在Server下面，主要作用是把Connector和Engine组织在一起。通常情况下，一个Server里只有一个Service，叫做"Catalina"。Connector - 网络通信的门面Connector是Tomcat对外提供服务的入口，负责监听端口、接收请求。这里面有两个重要的组件：Coyote - 这是Tomcat的HTTP连接器，专门处理HTTP协议。它支持多种I/O模型，比如BIO、NIO、NIO2、APR。我在生产环境一般都用NIO，性能和稳定性都不错。记得有一次线上出现了大量的TIME_WAIT连接，排查了半天发现是BIO模式下的连接没有及时释放。后来切换到NIO模式，问题就解决了。Jasper - 这是JSP引擎，负责把JSP页面编译成Servlet。虽然现在前后端分离比较多，JSP用得少了，但在一些老项目里还是经常能见到。Container - 业务处理的核心Container是一个层次结构，从外到内分别是Engine、Host、Context、Wrapper。Engine - 整个Servlet引擎，一个Service只有一个Engine。它负责处理所有的请求，并将请求分发给合适的Host。Host - 代表一个虚拟主机，可以配置多个域名指向同一个Tomcat实例。比如你可以让www.example.com和api.example.com都指向同一个Tomcat，但处理不同的应用。Context - 代表一个Web应用，也就是我们部署的WAR包或者目录。每个Context有自己独立的类加载器，这样不同应用之间就不会互相影响。Wrapper - 代表一个Servlet，是最小的处理单元。请求处理的完整流程理解了架构，我们来看看一个HTTP请求在Tomcat中是怎么被处理的。当用户在浏览器输入网址访问我们的应用时，请求会这样流转：Connector接收请求 - Coyote监听到8080端口有请求进来，创建Request和Response对象协议解析 - 解析HTTP协议，提取请求头、请求体等信息请求分发 - Engine根据Host头信息找到对应的虚拟主机应用定位 - Host根据URL路径找到对应的Context（Web应用）Servlet匹配 - Context根据URL模式匹配到具体的Servlet业务处理 - Wrapper调用Servlet的service方法处理业务逻辑响应返回 - 处理结果通过Connector返回给客户端这个过程看起来复杂，但实际上Tomcat内部有很多优化。比如对象池、连接池等技术，避免频繁创建销毁对象。生产环境的配置优化说了这么多理论，现在来点实际的。我把这些年在生产环境踩过的坑和总结的经验分享一下。JVM参数调优这是最基础也是最重要的。我见过太多因为JVM参数配置不当导致的生产事故。export JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewRatio=1 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRem…

生产环境 Nginx 代理 WebSocket 总断连？这几个隐藏配置坑了多少人
type: item
date: 2026-04-07T14:26:54+00:00
description: 前两天半夜正准备关机睡觉，微信突然响个不停。消息连着发了好几条，全是感叹号，说他们新上线的即时通讯服务炸了，客户端连两分钟都撑不住就掉线，重启服务好了一小会儿，马上又不行了。我看了一眼他发来的 Nginx 配置截图，再看了看报错日志，心里大概就有数了。这情况太典型了，很多兄弟在搞 WebSocket 代理的时候，都是照着网上的教程一顿抄，proxy_pass 一配，upstream 一挂，测试环境通了一下就觉得完事了。结果一上生产，并发上来，时间一长，各种稀奇古怪的问题全冒出来。WebSocket 这玩意儿，看着简单，不就是 HTTP 协议升个级嘛，但在 Nginx 这一层面，如果不把它的脾气摸透，真的能把人折腾死。今天咱们就借着这个实际案例，把 Nginx 代理 WebSocket 这件事彻底掰开了揉碎了讲讲，特别是那些没人告诉你、但生产环境一定会遇到的坑。咱们先得搞清楚，WebSocket 到底是个啥，为什么 Nginx 默认的 HTTP 代理配置搞不定它。平时咱们浏览网页，大多是 HTTP 协议。这玩意儿是个“哑巴”模式，客户端发个请求，服务器回个响应，这就完事了，连接断开。就像你去食堂打饭，你喊一句“阿姨来份红烧肉”，阿姨给你打一勺，交易结束，你俩就没关系了。下次想要米饭，得重新排队、重新喊。但是 WebSocket 不一样。它是一次握手，长久通话。就像你跟对象打电话，拨通那一刻（握手），双方的话筒（连接）就一直通着。你想说话就说，对方想回话就回，不用每次都重新拨号。这就是所谓的全双工通信。对于即时通讯、在线游戏、股票行情这种需要服务器主动推数据给客户端的场景，简直是神器。那问题来了，Nginx 作为一个反向代理，默认是处理 HTTP 请求的。它就像一个中间商，负责把客户的请求转给后端的服务器。对于普通的 HTTP 请求，Nginx 转发完就等着后端回包，然后发给客户，任务结束。但 WebSocket 来了，Nginx 就有点懵了。WebSocket 的建立过程，首先是发一个 HTTP 请求，但是这个请求头里带了点“私货”：Upgrade: websocket Connection: Upgrade这两句话的意思就是告诉服务器：“哥们，我不发普通网页请求了，咱们把协议升个级，换成 WebSocket 玩玩吧。”如果 Nginx 不做特殊配置，它看到这个请求，会把它当成普通 HTTP 请求转给后端。后端服务器（比如 Node.js、Go 或者 Java）收到请求，一看头信息，知道要升级协议，就会返回一个 101 Switching Protocols 的状态码。这时候，如果 Nginx 还傻傻地等着后端返回 HTTP 200 或者 404，那连接就断了。因为 101 状态码意味着协议切换，之后的数据流不再是标准的 HTTP 报文，而是 TCP 数据流了。所以，要让 Nginx 成功代理 WebSocket，核心就是得让它“睁一只眼闭一只眼”，允许协议升级，并且把这个升级的请求头原封不动地传给后端，同时还要保持这条连接长时间不断开。咱们来看看小张发给我的那个“有问题”的配置，估计很多人也是这么配的：upstream ws_backend { server 192.168.1.100:8080; } server { listen 80; server_name ws.example.com; location /ws { proxy_pass http://ws_backend; } }这配置看着挺干净，没毛病吧？测试环境测一下，发个消息，能收到。然后大家就以为搞定了。实际上，这配置在生产环境就是个“定时炸弹”。第一个坑，也是最直接的坑，就是握手失败。很多时候你会发现，浏览器控制台报 WebSocket connection failed，状态码 400 或者 502。这是因为 Nginx 默认转发请求的时候，可能会修改或者丢弃 Upgrade 和 Connection 这两个头。后端收不到这两个头，就不知道你要升级协议，自然就按普通 HTTP 请求处理了，结果就是握手失败。所以，咱们得明确告诉 Nginx，把这两个头给我传过去。标准配置得这么写：location /ws { proxy_pass http://ws_backend; # 关键配置：必须使用 HTTP 1.1 版本 proxy_http_version 1.1; # 透传 Upgrade 头 proxy_set_header Upgrade $http_upgrade; # 设置 Connection 头 proxy_set_header Connection "Upgrade"; }这里有个细节，proxy_http_version 1.1; 这一行千万…