古人学问无遗力，少壮工夫老始成。 纸上得来终觉浅，绝知此事要躬行。
告别命令行配置地狱！这个可视化反向代理神器让运维工作轻松10倍
最近在公司搭建测试环境的时候，又遇到了老问题——需要给十几个不同的服务配置反向代理。按照以前的做法，就是在nginx配置文件里一个个写server块，改完还得测试语法，重载服务...光是想想就头疼。正好前段时间在GitHub上看到了Nginx Proxy Manager这个项目，说是可以通过Web界面管理nginx反向代理配置。我想着反正也要重新搭环境，不如试试这个工具到底好不好用。用了一段时间后，我只能说——真香！今天就来跟大家分享一下这个让我彻底告别nginx配置文件的神器。什么是Nginx Proxy Manager官网地址：https://nginxproxymanager.com/简单来说，Nginx Proxy Manager就是给nginx套了个漂亮的Web管理界面。你不用再去编辑那些复杂的配置文件，所有的反向代理、SSL证书、访问控制都可以在浏览器里点点鼠标就搞定。这个项目是基于nginx的，但是把所有复杂的配置都封装起来了。你只需要在Web界面里填写源地址、目标地址，它就会自动生成对应的nginx配置。而且还支持自动申请Let's Encrypt证书，这个功能真的太实用了。我记得以前每次配置SSL证书都要折腾半天，现在点几下鼠标就能搞定，效率提升不是一点半点。在Debian 13上安装部署我这次用的是Debian 13系统，整个安装过程还算顺利。不过中间也踩了几个坑，一会儿会详细说。准备工作首先确保系统是最新的：sudo apt update && sudo apt upgrade -y然后安装Docker和Docker Compose，因为Nginx Proxy Manager官方推荐用Docker部署：# 安装必要的包 sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release -y # 添加Docker官方GPG密钥 curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 添加Docker仓库 echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io -y # 安装Docker Compose sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose安装完成后记得把当前用户加到docker组里，这样就不用每次都sudo了：sudo usermod -aG docker $USER这里需要重新登录一下才能生效。创建Docker Compose配置我在/opt目录下创建了一个专门的文件夹来存放配置：sudo mkdir -p /opt/nginx-proxy-manager cd /opt/nginx-proxy-manager然后创建docker-compose.yml文件：cat <<eof>docker-compose.yml services: app: image: 'jc21/nginx-proxy-manager:latest' restart: unless-stopped ports: - '80:80' - '81:81' - '443:443' volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt eof这个配置文件里有几个要注意的地方。端口81是管理界面的端口，80和443是实际提供服务的端口。数据库我用的是MariaDB，密码什么的在生产环境记得改成复杂一点的。启动服务配置文件准备好后就可以启动了：sudo docker-compose up -d第一次启动会下载镜像，可能需要等一会儿。启动完成后可以检查一下容器状态：sudo docker-compose ps如果看到容器都是Up状态就说明启动成功了。初始配置和基本使用服务启动后，打开浏览器访问 http://你的服务器IP:81 就能看到登录界面了。默认的管理员账号是：邮箱：admin@example.com密码：changeme第一次登录会强制要求修改邮箱和密码，这个安全措施还是很贴心的。界面介绍登录后的界面还是很清爽的，主要分为几个部分：Dashboard：总览页面，能看到所有代理主机的状态Proxy Hosts：反向代理配置Redirection Hosts：重定向配置Streams：TCP/UDP流代理404 Hosts：404页面配置Access Lists：访问控制列表SSL Certificates：SSL证书管理最常用的就是Proxy Hosts这个功能了。创建第一个反向代理我先拿公司的一个内部系统来测试。这个系统跑在8080端口，我想通过域名访问，并且自动配置HTTPS。点击"Proxy Hosts"，然后点"Add Proxy Host"，会弹出配置窗口：Details标签页：Domain Names：填写你的域名，比如 app.example.comScheme：选择http或https，这里是指后端服务的协议Forward Hostname/IP：后端服务的地址，可以是IP或域名Forward Port：后端服务端口Cache Assets：是否缓存静态资源Block Common Exploits：是否阻止常见攻击Websockets Support：是否支持WebSocketSSL标签页：SSL Certificate：可以选择已有证书或者申请新的Let's Encrypt证书Force SSL：强制HTTPSHTTP/2 Support：启用HTTP/2HSTS Enabled：启用HSTS安全头我这里选择了"Request a new SSL Certificate"，填写邮箱地址，勾选同意条款，点击保存。等了大概30秒，证书就自动申请好了！这个速度比我手动用certbot快多了。实际使用场景和技巧多环境管理我们公司有开发、测试、预发布几个环境，每个环境都有十几个微服务。以前都是在nginx配置文件里写一大堆server块，维护起来特别麻烦。现在用Nginx Proxy Manager，每个服务就是一个Proxy Host，需要修改的时候直接在界面上改就行了。而且还能很直观地看到每个服务的状态，哪个服务挂了一眼就能看出来。SSL证书管理这个功能真的是解决了我的一个大痛点。以前管理SSL证书要记住每个证书的过期时间，定期手动续期。现在Let's Encrypt证书会自动续期，再也不用担心证书过期导致服务不可用了。而且支持通配符证书，一个*.example.com的证书就能覆盖所有子域名。访问控制Access Lists功能可以设置IP白名单或黑名单，对于一些内部系统的访问控制很有用。我给测试环境设置了IP白名单，只允许公司内网访问。配置也很简单，创建一个Access List，添加允许的IP段，然后在Proxy Host里关联就行了。流量代理除了HTTP代理，还支持TCP和UDP的流量代理。我用这个功能代理了MySQL和Redis的连接，效果还不错。不过这个功能相对简单一些，没有HTTP代理那么多高级选项。踩过的坑和解决方案使用过程中也遇到了一些问题，记录一下避免大家重复踩坑。端口冲突问题刚开始部署的时候，发现80端口启动失败。检查后发现系统里已经有apache在跑了。解决方法就是先停掉apache：sudo systemctl stop apache2 sudo systemctl disable apache2或者修改docker-compose.yml里的端口映射，比如改成8080:80。证书申请失败有几次Let's Encrypt证书申请失败，错误信息显示域名验证不通过。这个问题通常是DNS解析的问题。Let's Encrypt需要通过HTTP-01或DNS-01方式验证域名所有权，如果域名还没有解析到服务器IP，验证就会失败。解决方法是先确保域名正确解析，然后再申请证书。与传统nginx配置的对比用了这么长时间，我觉得Nginx Proxy Manager相比传统的nginx配置有明显的优势。优势：可视化管理，不需要记忆复杂的nginx语法自动SSL证书申请和续期，省心省力配置修改实时生效，不需要手动reload内置访问控制和安全防护支持配置备份和恢复劣势：灵活性相对较低，一些复杂的nginx配置可能无法实现多了一层抽象，出问题时排查可能更复杂资源占用相对较高（因为包含了数据库和Web界面）总的来说，对于大部分常见的反向代理需求，Nginx Proxy Manager都能很好地满足。只有在一些特别复杂的场景下，可能还是需要直接配置nginx。安全注意事项虽然Nginx Proxy Manager简化了配置，但安全方面还是不能马虎。管理界面安全管理界面默认在81端口，建议：修改默认端口，避免被扫描到设置复杂的管理员密码如果可能，只允许内网访问管理界面定期备份配置数据SSL配置虽然可以自动申请证书，但还是要注意：启用HSTS，防止降级攻击禁用不安全的SSL协议版本定期检查证书状态，虽然会自动续期但偶尔也会失败对于敏感应用，考虑使用EV证书而不是DV证书访问控制合理使用Access Lists功能：# 可以在Advanced配置中添加更细粒度的控制 allow 192.168.1.0/24; allow 10.0.0.0/8; deny all;我在生产环境中给管理后台都加了IP白名单，只允许办公网络访问。日志监控Nginx Proxy Manager的日志可以通过docker logs查看：sudo docker-compose logs -f app建议配置日志轮转，避免日志文件过大：# 在docker-compose.yml中添加日志配置 logging: driver: "json-file" options: max-size: "10m" max-file: "3"扩展和集成Nginx Proxy Manager还可以和其他工具集成，实现更强大的功能。与Docker Swarm集成如果你用Docker Swarm管理容器集群，可以配置Nginx Proxy Manager自动发现新的服务：version: '3.8' services: nginx-proxy-manager: image: jc21/nginx-proxy-manager:latest ports: - "80:80" - "443:443" - "81:81" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro deploy: placement: constraints: [node.role == manager]API自动化Nginx Proxy Manager提供了REST API，可以通过脚本自动化管理：# 获取访问token TOKEN=$(curl -s -X POST http://localhost:81/api/tokens \ -H "Content-Type: application/json" \ -d '{"identity": "admin@example.com", "password": "your_password"}' | jq -r '.token') # 创建新的代理主机 curl -X POST http://localhost:81/api/nginx/proxy-hosts \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "domain_names": ["api.example.com"], "forward_host": "192.168.1.100", "forward_port": 8080, "forward_scheme": "http", "certificate_id": 1, "ssl_forced": true }'这个功能在CI/CD流程中特别有用，可以在部署新服务时自动配置反向代理。与Kubernetes集成虽然Kubernetes有自己的Ingress Controller，但在一些混合环境中，Nginx Proxy Manager也能发挥作用。我在一个项目中就是用Nginx Proxy Manager作为外部负载均衡器，将流量分发到不同的Kubernetes集群。未来发展和替代方案Nginx Proxy Manager虽然好用，但也不是万能的。在一些场景下可能需要考虑其他方案。替代方案Traefik： 更适合容器化环境，支持自动服务发现HAProxy： 性能更好，但配置相对复杂Cloudflare Tunnel： 适合需要穿透NAT的场景Kong： 功能更强大的API网关选择建议如果主要是简单的HTTP反向代理，Nginx Proxy Manager是很好的选择如果需要复杂的负载均衡算法，考虑HAProxy如果是微服务架构，Traefik可能更合适如果需要API管理功能，Kong是不错的选择我觉得工具没有绝对的好坏，关键是要适合自己的使用场景。写在最后Nginx Proxy Manager整体感受还是很不错的。特别是对于中小型团队来说，能够显著提升运维效率。最大的优点就是简单易用，基本上不需要学习成本，会用浏览器就能配置反向代理。自动SSL证书申请这个功能真的是太实用了，再也不用担心证书过期的问题。当然也有一些不足，比如在一些复杂场景下灵活性不够，但对于大部分常见需求都能很好地满足。如果你也在为nginx配置文件头疼，不妨试试这个工具。相信我，用过之后你就回不去了。最后提醒一下，虽然工具简化了操作，但安全意识不能放松。该做的备份、监控、访问控制一样都不能少。好了，今天的分享就到这里。如果你在使用过程中遇到什么问题，欢迎在评论区交流讨论。觉得这篇文章对你有帮助的话，别忘了点赞转发支持一下！如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
Windows服务器出问题？别慌！教你几招日志排查绝技，让故障无处遁形
最近客户遇到windows服务器出问题了一时半会不知道怎么办？出问题最好的办法就是看日志，但是我平常分享的都是linux系统的排查，今天就来盘盘windows服务器的！我记得刚入行那会儿，遇到服务器问题就是一顿乱操作，重启、重装，能折腾的都折腾一遍（重启能解决90%的问题）。后来被师傅骂了好几次才明白，日志才是解决问题的关键。今天就跟大家分享一下Windows Server环境下如何查看和排查日志，这些都是我这些年踩坑总结出来的经验。事件查看器 - 你的第一站Windows的事件查看器就像是服务器的"黑匣子"，记录着系统运行的方方面面。打开方式很简单，Win+R输入eventvwr.msc就行，或者在服务器管理器里也能找到。进去之后你会看到左边有个树状结构，主要分为几个大类：Windows日志这块是重点关注的地方。应用程序日志记录的是各种软件的运行情况，我经常在这里找到数据库连接失败、Web服务异常之类的问题。系统日志更多是硬件和系统级别的事件，比如磁盘错误、内存问题等等。安全日志这个就比较有意思了，记录着登录失败、权限变更这些敏感操作。有一次我们服务器被人暴力破解密码，就是通过安全日志发现的，里面密密麻麻全是登录失败的记录，IP地址还都是国外的。应用程序和服务日志这里面的内容就更丰富了，各种角色和功能都有自己的日志。比如你装了IIS，就会有IIS相关的日志；装了SQL Server，也会有对应的日志分类。看日志的时候要注意几个关键信息：时间、事件ID、来源、级别。级别分为信息、警告、错误、严重错误几种，一般我们重点关注警告和错误级别的事件。事件ID这个东西特别有用，每种错误都有固定的ID，你可以直接拿这个ID去微软官网搜索，基本都能找到详细的解释和解决方案。比如事件ID 1074表示系统重启，6005表示事件日志服务启动，这些都是有规律可循的。还可以通过事件id来查日志信息：Windows 服务器常见事件 ID 列表类别事件 ID描述系统事件1系统时间已更改 6系统启动 7系统关闭 12操作系统启动 13操作系统关闭 41系统崩溃重启 1074系统关机/重启安全事件4624账户成功登录 4625账户登录失败 4634账户注销 4648显式凭据登录 4672使用特权登录 4720创建用户账户 4724重置密码尝试 4725禁用用户账户 4728将成员添加到安全组 4732将成员添加到本地安全组 4740用户账户被锁定 4776域控制器尝试验证账户凭据应用程序事件1000应用程序错误 1001Windows 错误报告 1002应用程序挂起 1005应用程序终止服务事件7000服务无法启动 7009服务超时 7022服务正常启动 7023服务异常终止 7024服务无法启动 7031服务尝试重启 7034服务意外终止 7035服务发送控制 7036服务状态改变Active Directory 事件1644目录服务无法分配相对标识符 2886目录服务暂停 2887目录服务已恢复 5137目录服务对象创建 5141目录服务对象删除DNS 服务器事件4013DNS 服务器启动 4015DNS 服务器暂停 6702DNS 事件文件复制服务13508文件复制服务无法与主域控制器建立会话 13509文件复制服务成功与主域控制器建立会话磁盘和存储事件51磁盘错误 55文件系统结构损坏 7设备驱动程序错误PowerShell - 命令行的威力虽然图形界面看起来直观，但有时候用PowerShell查日志效率更高，特别是需要筛选大量数据的时候。Get-EventLog这个命令是基础中的基础：Get-EventLog -LogName System -Newest 100这样就能看到系统日志的最新100条记录。如果你想看特定时间段的日志：Get-EventLog -LogName Application -After "2024-01-01" -Before "2024-01-02"更强大的是Get-WinEvent命令，功能比Get-EventLog丰富多了：Get-WinEvent -FilterHashtable @{LogName='System'; Level=2,3}这里Level=2表示错误，Level=3表示警告，这样就只显示有问题的事件，不用在一堆信息级别的日志里翻来翻去。有时候我需要查找包含特定关键词的日志：Get-WinEvent -FilterHashtable @{LogName='Application'} | Where-Object {$_.Message -like "*SQL*"}这样就能找到所有消息中包含"SQL"的事件，对于定位特定应用的问题很有帮助。IIS日志 - Web服务的秘密如果你的服务器跑着Web服务（一般用windows服务器都会跑这个服务吧！），IIS日志绝对是排查问题的重要工具。默认情况下，IIS日志存放在C:\inetpub\logs\LogFiles目录下。IIS日志的格式一般是W3C扩展格式，看起来像这样：2024-01-15 10:30:25 192.168.1.100 GET /api/users - 80 - 192.168.1.200 Mozilla/5.0... 200 0 0 1250这里面包含了访问时间、客户端IP、请求方法、URL、状态码、响应时间等信息。状态码是重点关注的，200表示正常，404是找不到页面，500是服务器内部错误。我经常用一些工具来分析IIS日志，比如Log Parser Studio，可以用SQL语句来查询日志：SELECT TOP 10 cs-uri-stem, COUNT(*) as hits FROM [logfile] WHERE sc-status >= 400 GROUP BY cs-uri-stem ORDER BY hits DESC这样就能找出错误最多的页面，对于定位问题很有帮助。当然，如果你喜欢用PowerShell，也可以这样分析：Import-Csv "C:\inetpub\logs\LogFiles\W3SVC1\u_ex240115.log" -Delimiter " " | Where-Object {$_."sc-status" -ge 400} | Group-Object "cs-uri-stem" | Sort-Object Count -Descending性能监视器 - 系统健康的体检报告除了事件日志，性能监视器也是个好东西。虽然它主要是监控性能指标，但也会记录一些日志信息。打开perfmon.msc，你可以看到实时的性能数据，也可以创建数据收集器集来长期监控系统状态。我一般会关注几个关键指标：CPU使用率持续过高可能表示有程序占用过多资源；内存使用率如果接近100%，系统就会开始使用虚拟内存，性能会明显下降；磁盘队列长度如果经常大于2，说明磁盘IO成为瓶颈了。有一次我们的Web服务响应特别慢，通过性能监视器发现磁盘队列长度经常超过10，后来查到是数据库日志文件和数据文件放在同一个磁盘上，IO冲突导致的。应用程序特定日志不同的应用程序都有自己的日志记录方式，这些也不能忽视。SQL Server的日志一般在SQL Server Management Studio里查看，也可以直接看文件，通常在SQL Server安装目录的Log文件夹下。SQL Server的错误日志记录着数据库启动、连接、查询错误等信息，对于数据库问题排查很重要。.NET应用程序如果没有特殊配置，一般会把日志写到事件查看器的应用程序日志中。但很多应用会使用NLog、Log4Net这些日志框架，把日志写到自定义的文件里。自定义应用程序的日志位置就五花八门了，有的放在程序目录下，有的放在ProgramData文件夹，还有的直接写数据库。这就需要你了解具体应用的日志配置了。日志分析的一些技巧看日志不是简单的从头看到尾，需要一些技巧。时间关联是最重要的，当用户报告某个时间点出现问题时，你要在各个日志中找到对应时间段的记录，看看系统、应用、网络各个层面发生了什么。关键词搜索也很有用，比如"error"、"exception"、"failed"、"timeout"这些词汇经常出现在问题相关的日志中。模式识别需要一定经验，比如看到大量的登录失败记录，可能是暴力破解攻击；看到内存不足的警告后紧跟着应用程序崩溃，基本可以确定是内存问题。有时候问题不是单一原因造成的，需要综合分析多个日志源。我遇到过一次奇怪的问题，Web应用偶尔会卡死，单看应用日志没发现异常，后来结合系统日志发现是网卡驱动有问题，偶尔会丢包，导致数据库连接超时。日志级别的理解也很关键。信息级别的日志通常记录正常操作，比如服务启动、用户登录成功等；警告级别表示有潜在问题但不影响正常运行；错误级别就是明确的问题了，需要重点关注；严重错误通常意味着系统或服务即将崩溃。我习惯先看错误和严重错误，再看警告，最后才看信息。这样能快速定位到问题的核心。常见问题的日志特征经过这么多年的运维经验，我总结了一些常见问题在日志中的表现特征。内存不足通常会在系统日志中看到事件ID 2004（资源不足）或者1001（Windows错误报告），应用程序日志中可能会有OutOfMemoryException相关的错误。磁盘空间不足会产生事件ID 2013（磁盘空间不足警告）或者55（文件系统错误），这时候应用程序可能会出现各种奇怪的问题。网络连接问题在系统日志中可能看到事件ID 4201（网络适配器连接丢失）或者DNS解析失败的错误，应用程序日志中会有连接超时、无法连接到远程服务器之类的错误。权限问题通常在安全日志中有记录，事件ID 4625表示登录失败，4648表示使用显式凭据登录，这些都可能与权限相关。服务启动失败会在系统日志中产生事件ID 7000系列的错误，具体的错误信息通常会指出是什么原因导致服务无法启动。故障排查的实战案例说了这么多理论，来个实际案例吧。前段时间我们有个客户的Web应用突然变得很慢，用户投诉不断。我按照惯例开始查日志：首先看IIS日志，发现响应时间确实比平时长很多，而且有不少500错误。然后看应用程序日志，发现大量的数据库连接超时错误。接着查看SQL Server日志，发现数据库本身没什么异常，但是有一些死锁的记录。再看系统日志，发现磁盘IO等待时间异常高。最后通过性能监视器确认，磁盘队列长度经常超过10，平均响应时间也比正常情况高出好几倍。问题定位到了磁盘IO，进一步检查发现是数据库的事务日志文件增长过快，而且没有定期备份和截断，导致磁盘空间紧张，IO性能下降。解决方案就是立即备份事务日志，释放空间，然后调整数据库的备份策略，避免类似问题再次发生。整个排查过程用了不到一个小时，如果没有日志的帮助，可能要花好几倍的时间才能找到根本原因。监控和告警的重要性被动地查看日志效率不高，更好的做法是建立主动的监控和告警机制。Windows Performance Toolkit中的工具可以帮助建立性能基线，当系统偏离正常范围时及时告警。SCOM（System Center Operations Manager）是微软的企业级监控解决方案，可以监控整个Windows环境，包括服务器、应用程序、网络设备等。对于中小型环境，PRTG或者Zabbix这些开源工具也是不错的选择。我之前用过Zabbix监控Windows服务器，配置相对简单，而且免费。关键是要设置合理的告警阈值。我见过有些环境告警设置得太敏感，一天能收到几十条告警邮件，最后管理员都麻木了，真正的问题反而被忽略。比如CPU使用率告警，我一般设置为连续5分钟超过80%才告警，偶尔的峰值是正常的。内存使用率可以设置为超过90%告警，磁盘空间剩余不足10%时告警。写在最后Windows服务器的日志排查是个系统性工程，需要掌握多种工具和技巧。从基础的事件查看器到高级的PowerShell命令，从单机日志分析到分布式日志管理，每个层面都有其价值。关键是要养成良好的习惯：遇到问题先看日志，定期检查系统状态，建立合理的监控告警机制。日志不会说谎，每个问题都会在某个地方留下痕迹，耐心分析总能找到答案。现在的IT环境越来越复杂，微服务、容器、云原生这些新技术带来了新的挑战，但日志分析的基本思路是不变的：收集、存储、分析、告警。掌握了这些基础技能，不管技术怎么发展，你都能游刃有余。记住，成为日志分析专家不是一朝一夕的事情，需要在实践中不断积累经验。每次排查问题后都要总结，建立自己的知识库，这样遇到类似问题时就能快速解决。希望这篇文章对大家有帮助，如果你觉得有用的话，别忘了点个赞转发一下。有什么问题或者想法，也欢迎在评论区交流讨论！如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
CICD是什么CI/CD这个词听起来很高大上，其实说白了就是让代码从开发到上线这个过程变得自动化。CI是Continuous Integration的缩写，中文叫持续集成。简单理解就是，开发人员每次提交代码后，系统会自动把所有人的代码合并在一起，然后跑一遍测试，看看有没有冲突或者bug。就像搭积木一样，每加一块都要检查一下整体结构是不是还稳固。CD有两个意思，一个是Continuous Delivery（持续交付），另一个是Continuous Deployment（持续部署）。持续交付是说代码随时都处于可发布状态，就像工厂流水线上的产品，每个环节都质检过了，随时可以出厂。持续部署更进一步，直接自动发布到生产环境，完全不需要人工干预。举个例子，以前我们部署一个网站，可能需要手动上传文件、重启服务、清缓存等等，一套流程下来半小时，还容易出错。有了CI/CD之后，开发人员只要把代码推送到Git仓库，剩下的事情全部自动完成：自动测试、自动打包、自动部署、自动重启服务，几分钟就搞定了。CI/CD的核心思想就是"小步快跑，快速反馈"。与其攒一堆功能一次性发布（风险大），不如每个小功能都快速上线，出问题也能快速定位和修复。这样既提高了开发效率，又降低了上线风险，是现代软件开发的标配。为什么选择Jenkins官方文档：https://www.jenkins.io/zh/doc/book/installing/说实话，CI/CD工具现在选择挺多的，GitLab CI、GitHub Actions、Azure DevOps什么的都不错。但是我最终还是选择了Jenkins，主要原因有几个：Jenkins的插件生态真的很丰富，基本上你能想到的功能都有对应的插件。而且它是开源的，不用担心费用问题。最重要的是，Jenkins对PHP项目的支持很好，配置起来也比较灵活。当然了，Jenkins也有它的缺点，比如界面看起来有点老旧，配置相对复杂一些。不过习惯了就好，功能强大才是硬道理。环境准备和Jenkins安装我们的服务器环境是debain13。Jenkins可以用Docker来部署，这样比较干净，也方便管理。docker run \ -u root \ --rm \ -d \ -p 8080:8080 \ -p 50000:50000 \ -v jenkins-data:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ jenkinsci/blueocean开始下载我镜像一直下载失败，换了直接用软件包安装（记得安装java环境），文档地址：https://www.jenkins.io/zh/doc/book/installing/#war%E6%96%87%E4%BB%B6wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add - sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list' sudo apt-get update sudo apt-get install jenkins启动后访问http://your-server:8080，按照提示完成初始化配置。初始密码在容器日志里能找到：必要插件安装Jenkins装好后，需要安装一些必要的插件。进入"系统管理" -> "插件管理"，安装以下插件（这些插件记得装！！！）：Git plugin（代码拉取）Pipeline（流水线支持）Blue Ocean（更好看的界面）Publish Over SSH（SSH部署）Build Timestamp（构建时间戳）Email Extension（邮件通知）插件安装完成后记得重启Jenkins。有时候插件之间会有依赖关系，Jenkins会自动帮你安装依赖的插件，这点还是很贴心的。配置Git仓库连接我们的代码托管在GitLab上，需要配置Jenkins能够拉取代码。在"系统管理" -> "全局工具配置"中配置Git路径，一般Jenkins会自动检测到。然后在"系统管理" -> "系统配置"中添加Git凭据。我建议使用SSH密钥的方式，比较安全：# 在Jenkins服务器上生成SSH密钥 ssh-keygen -t rsa -b 4096 -C "jenkins@yourcompany.com" # 将公钥添加到GitLab的Deploy Keys中 cat ~/.ssh/id_rsa.pub把私钥内容添加到Jenkins的凭据管理中，类型选择"SSH Username with private key"。创建第一个Pipeline任务选择测试hello world点击立即构建，构建完成写在最后jenkins的搭建就讲完了，没什么技术含量。照着文档做就行了，要注意的是软件包和网络环境的问题。目前遇到的问题丢给ai都可以很好的解决！（学会用ai很重要！！！），下一期来讲讲jenkins如何来实现php环境的cicd（主要是我博客用的是php哈哈哈）好了，今天的分享就到这里。如果这篇文章对你有帮助，别忘了点个赞、转发给需要的朋友。想了解更多运维实战经验，记得关注@运维躬行录，我会持续分享一线的技术实践和踩坑经验。让我们一起在运维的路上越走越远！个人博客：https://www.fuzhoupyy.work
这个开源知识库太香了！PandaWiki搭建全过程，踩坑经验全分享
最近公司要搭建一个内部知识库，领导让我调研几个方案。说实话，市面上的知识库产品真的是五花八门，什么Confluence、Notion、语雀啥的，但要么收费贵得离谱，要么功能不够用。偶然间发现了PandaWiki这个开源项目，试用了一下发现还真不错。界面简洁，功能够用，关键是免费！今天就把我搭建的整个过程分享给大家，希望能帮到有需要的朋友。什么是PandaWiki项目地址：https://github.com/chaitin/PandaWiki演示体验demo：https://192.168.198.133:2443/doc/editor/019a131f-12df-759c-a1a9-194d3c5ae47cPandaWiki是一个基于Go语言（好多项目都是go开发的，看来确实得学go了！！！）开发的轻量级知识库系统，支持Markdown编辑，有着不错的用户体验。相比其他重量级的知识库系统，它的部署和维护都相对简单。我之前用过MediaWiki，那个配置起来真的是要命，光是数据库配置就能折腾半天。PandaWiki就不一样了，基本上几个命令就能跑起来。环境准备在开始之前，我们需要准备一台Linux服务器。我这里用的是Debian 13（老演员了！），配置是2核4G，对于小团队使用完全够了。首先更新一下系统：apt update && apt upgrade -y然后安装一些必要的工具：apt install -y wget curl git vim开始部署PandaWiki一条命令搞定安装！！舒服！bash -c "$(curl -fsSLk https://release.baizhi.cloud/panda-wiki/manager.sh)"选择安装还是升级！会让你确认安装目录，无脑回车就行，也是通过docker部署(最喜欢看这种安装界面了哈哈哈哈)还有交流群二维码！！哈哈哈初始化配置打开浏览器访问 http://你的服务器IP:8080，应该能看到PandaWiki的初始化页面。第一次访问会要求你创建管理员账户。我建议用户名不要用admin这种太常见的，密码也要设置复杂一点。初始化完成后，你就能看到一个干净的知识库界面了。界面还是挺清爽的，左侧是目录树，右侧是内容区域。让你配置大模型很丰富注册靠谱白嫖一个（不是广告！）基本使用新手文档：https://192.168.198.133:2443/doc/editor/019a131f-12da-76b3-bafe-3cfbe1069e69创建文档点击"新建文档"按钮，可以选择创建普通文档或者文件夹。PandaWiki支持Markdown语法，写起来还是很顺手的。我试着创建了几个测试文档，发现它的Markdown渲染效果还不错，代码高亮、表格、图片都支持得挺好。我这里直接通过rss来导入自己博客的文档！通过rss订阅可以直接拉取很多地方的操作文档！这是拉取到的博客文章：去发布导入本地文本这就是财富！！！发布！！！使用统计！常用配置使用体验用了一段时间下来，感觉PandaWiki还是挺不错的。界面简洁，功能够用，性能也还可以。我们团队大概20多个人在用，响应速度还是很快的。当然也有一些不足的地方，比如权限管理相对简单，插件生态也不够丰富。但对于大多数中小团队来说，这些功能已经够用了。最重要的是它是开源免费的，不用担心突然收费或者服务停止。而且代码托管在GitHub上，如果有需要还可以自己修改。我感觉很nice！！会给出具体的引用，我准备用这个来替代我的博客搜索了哈哈哈写在最后PandaWiki作为一个轻量级的知识库解决方案，确实有它的优势。部署简单，维护方便，功能够用，关键是免费开源。虽然在一些高级功能上可能不如商业产品，但对于大多数团队来说已经足够了。特别是对于预算有限的小团队，这绝对是个不错的选择。如果你也在寻找知识库解决方案，不妨试试PandaWiki。反正是免费的，试试又不会怀孕。部署过程中如果遇到问题，可以查看官方文档或者GitHub上的issue。社区虽然不大，但还是比较活跃的。好了，今天的分享就到这里。如果这篇文章对你有帮助，记得点个赞转发一下。有问题的话也可以在评论区留言，我看到会回复的。如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
网络攻击套路大揭秘！这些年我见过的各种"妖魔鬼怪"
做运维这么多年，我算是见识过各种各样的网络攻击了。从最初的懵懵懂懂被攻击搞得焦头烂额，到现在能够淡定应对各种攻击手段，这中间踩过的坑真的是数不胜数。前几天有个粉丝朋友问我，现在网络攻击都有哪些类型，怎么防护？我想了想，确实有必要系统地梳理一下这些年遇到过的各种攻击手段。毕竟知己知彼，才能百战不殆嘛。今天就跟大家聊聊我这些年见过的各种网络攻击，从技术原理到防护方法，希望能帮到正在被攻击困扰的兄弟们。SQL注入攻击 - 数据库的噩梦说到网络攻击，SQL注入绝对是我遇到最多的一种。这玩意儿简直就是Web应用的头号杀手。攻击原理SQL注入的原理其实不复杂，就是利用应用程序对用户输入过滤不严，在数据库查询中插入恶意SQL代码。我记得刚工作那会儿，维护过一个老系统，登录页面的代码是这样写的：SELECT * FROM users WHERE username='$username' AND password='$password'看起来没问题对吧？但如果用户在用户名框里输入：admin' OR '1'='1' --那么实际执行的SQL就变成了：SELECT * FROM users WHERE username='admin' OR '1'='1' --' AND password='xxx'由于'1'='1'永远为真，而--会注释掉后面的密码验证，攻击者就能直接以admin身份登录了。常见的注入类型这些年我见过的SQL注入攻击大概有这几种：联合查询注入：通过UNION语句获取其他表的数据。我处理过一个案例，攻击者通过这种方式把整个用户表都下载走了，包括密码哈希。布尔盲注：通过构造真假条件来逐个猜测数据库内容。这种攻击比较隐蔽，日志里看起来都是正常请求，但实际上在一点点窃取数据。时间盲注：利用数据库的延时函数，通过响应时间来判断条件真假。我见过攻击者用这种方法花了几个小时把管理员密码给猜出来的。报错注入：故意触发数据库错误，从错误信息中获取敏感数据。有些开发为了调试方便，会把详细的错误信息显示给用户，这就给了攻击者可乘之机。防护措施防护SQL注入其实也不难，关键是要养成好习惯：参数化查询：这是最有效的防护方法。使用预编译语句，把SQL代码和数据分离。现在主流的开发框架都支持这个功能。输入验证：对所有用户输入进行严格验证，过滤掉危险字符。不过这个方法容易被绕过，不能作为唯一的防护手段。最小权限原则：数据库用户只给必要的权限，不要用root或sa这种超级用户来连接数据库。WAF防护：部署Web应用防火墙，可以识别和阻断大部分SQL注入攻击。我现在维护的系统都强制使用ORM框架，基本杜绝了SQL注入的可能性。虽然性能会有一点影响，但安全性大大提升了。XSS攻击 - 浏览器里的陷阱跨站脚本攻击（XSS）是另一个让我头疼的问题，特别是在处理用户生成内容的网站时。攻击原理XSS攻击的核心是在网页中插入恶意脚本，当其他用户访问时，这些脚本就会在他们的浏览器中执行。我印象最深的一次XSS攻击发生在一个论坛网站。攻击者在帖子里插入了这样的代码： 结果所有查看这个帖子的用户，他们的Cookie都被发送到了攻击者的服务器上。攻击者拿到Cookie后，就能冒充这些用户登录网站了。XSS的分类根据我的经验，XSS攻击主要分为三种：反射型XSS：恶意脚本通过URL参数传递，服务器直接把参数内容输出到页面上。这种攻击需要诱骗用户点击特制的链接。存储型XSS：恶意脚本被保存在服务器上（比如数据库），每次有用户访问相关页面时都会执行。这种危害最大，因为影响面广。DOM型XSS：攻击发生在客户端，通过修改页面的DOM结构来执行恶意脚本。这种攻击比较隐蔽，服务器端很难检测到。实际案例我处理过一个比较典型的存储型XSS案例。那是一个电商网站的商品评论功能，攻击者在评论中插入了恶意脚本： 这段代码看起来像是一个破损的图片，但实际上会执行JavaScript代码，把用户的Cookie编码后发送给攻击者。更可怕的是，攻击者还在脚本中加入了蠕虫功能，被感染的用户会自动发布包含恶意脚本的评论，导致攻击快速传播。防护策略防护XSS攻击需要多管齐下：输出编码：对所有输出到HTML页面的内容进行编码，把<、>、"等特殊字符转换成HTML实体。输入过滤：对用户输入进行过滤，移除或转义危险的HTML标签和JavaScript代码。CSP策略：配置内容安全策略（Content Security Policy），限制页面可以加载的资源来源。HttpOnly Cookie：设置Cookie的HttpOnly属性，防止JavaScript访问Cookie。我现在管理的网站都部署了严格的XSS防护策略，用户输入的任何内容都会经过多层过滤和编码。虽然偶尔会误杀一些正常内容，但总比被攻击强。CSRF攻击 - 身份冒用的艺术跨站请求伪造（CSRF）是一种比较隐蔽的攻击方式，很多开发者都容易忽视这个问题。攻击机制CSRF攻击利用用户已经登录的身份，诱骗用户在不知情的情况下执行某些操作。我遇到过一个经典的CSRF攻击案例。攻击者制作了一个看似无害的网页，里面包含了这样的代码： 当已经登录网银的用户访问这个页面时，浏览器会自动发送转账请求，而银行服务器看到的是一个"正常"的已认证用户发起的请求。攻击场景CSRF攻击在以下场景特别危险：金融操作：转账、支付、修改密码等敏感操作账户管理：修改个人信息、删除数据等社交功能：发帖、评论、关注等操作我见过最狠的一次CSRF攻击，攻击者通过邮件发送了一个"中奖通知"页面，里面暗藏了修改用户邮箱地址的请求。很多用户在不知情的情况下把邮箱改成了攻击者的，然后攻击者就能通过"忘记密码"功能重置用户密码了。防护方法防护CSRF攻击有几种有效方法：CSRF Token：在表单中加入随机生成的token，服务器验证token的有效性。这是最常用也是最有效的方法。验证Referer：检查请求的来源页面，拒绝来自外部网站的请求。不过这个方法不够可靠，因为Referer可能被伪造或者被代理服务器过滤掉。双重Cookie验证：在Cookie和请求参数中都包含相同的随机值，服务器验证两者是否一致。验证码：对于敏感操作，要求用户输入验证码。这个方法最安全，但用户体验不好。我现在负责的系统对所有状态改变的操作都使用了CSRF Token保护，同时对转账、修改密码等高风险操作还加了短信验证码。文件上传漏洞 - 服务器的后门文件上传功能几乎每个网站都有，但如果处理不当，就可能成为攻击者的突破口。攻击原理文件上传漏洞的核心是攻击者上传恶意文件到服务器，然后通过访问这些文件来执行恶意代码。我处理过一个典型的案例，网站有个头像上传功能，只是简单地检查了文件扩展名。攻击者上传了一个名为avatar.php的文件，内容是：<?php if(isset($_GET['cmd'])){ system($_GET['cmd']); } ?>然后通过访问http://website.com/uploads/avatar.php?cmd=ls就能执行系统命令了。更可怕的是，攻击者还能通过这个后门上传更多的恶意文件，完全控制服务器。绕过技巧攻击者为了绕过文件上传限制，会使用各种技巧：双重扩展名：上传shell.php.jpg这样的文件，某些服务器配置下会按照.php来解析。大小写绕过：上传shell.PHP或shell.Php，利用大小写不敏感的特性。空字节截断：在文件名中插入空字节，如shell.php%00.jpg，某些语言会在空字节处截断文件名。MIME类型伪造：修改HTTP请求中的Content-Type，让恶意文件看起来像图片。文件内容伪造：在PHP代码前面加上GIF文件头，绕过文件内容检测。防护措施防护文件上传漏洞需要多层验证：白名单验证：只允许特定类型的文件上传，比如只允许jpg、png、gif等图片格式。文件内容检测：不仅检查扩展名，还要检查文件的实际内容和文件头。重命名文件：上传后重新命名文件，使用随机生成的文件名，避免攻击者直接访问。隔离执行：把上传的文件存储在无法执行脚本的目录中，或者使用独立的文件服务器。大小限制：限制上传文件的大小，防止攻击者上传大文件消耗服务器资源。我现在管理的系统对所有上传文件都会进行病毒扫描，同时使用云存储服务来隔离文件，基本杜绝了通过文件上传攻击服务器的可能性。暴力破解攻击 - 最原始但有效的方法虽然暴力破解听起来很原始，但在实际攻击中仍然非常常见和有效。攻击方式暴力破解主要有几种形式：密码暴力破解：使用字典或者穷举法尝试各种密码组合。我见过攻击者用常见密码字典攻击SSH服务，成功率还挺高的。验证码暴力破解：对于数字验证码，攻击者可能会尝试所有可能的组合。API接口暴力破解：针对登录接口、重置密码接口等进行大量尝试。实际案例我印象最深的一次暴力破解攻击发生在一个企业内网。攻击者通过某种方式获得了内网访问权限，然后对所有服务器的SSH端口进行暴力破解。那次攻击持续了好几天，攻击者使用了一个包含几万个常见密码的字典，对每台服务器都尝试了admin、root、test等常见用户名。最终成功破解了几台使用弱密码的服务器。更可怕的是，攻击者还会根据企业信息定制密码字典，比如使用公司名称、员工姓名、重要日期等作为密码尝试。这种针对性的攻击成功率往往更高。我还遇到过一种比较狡猾的暴力破解方式，攻击者会先收集目标网站的用户名列表，然后使用分布式的方式进行攻击。每个IP只尝试少量密码，避免触发账户锁定机制，但整体攻击规模很大。防护策略防护暴力破解有很多成熟的方法：账户锁定策略：连续失败几次后锁定账户一段时间。不过要注意设置合理的锁定时间，避免被攻击者利用来进行拒绝服务攻击。验证码机制：在登录页面加入图形验证码或者滑动验证码，增加自动化攻击的成本。IP限制：限制单个IP的登录尝试频率，超过阈值就临时封禁。我一般设置为每分钟最多5次尝试。强密码策略：强制用户使用复杂密码，包含大小写字母、数字和特殊字符。双因素认证：即使密码被破解，攻击者也需要第二个认证因子才能登录。我现在管理的所有系统都部署了fail2ban，会自动分析日志文件，发现暴力破解行为就立即封禁攻击IP。效果还不错，基本上能阻止90%以上的暴力破解攻击。钓鱼攻击 - 社会工程学的经典应用钓鱼攻击虽然技术含量不高，但在实际攻击中的成功率却很高，因为它直接针对人性的弱点。攻击手法钓鱼攻击的形式多种多样：邮件钓鱼：伪造银行、电商平台等官方邮件，诱导用户点击恶意链接。短信钓鱼：通过短信发送虚假中奖、账户异常等信息。网站钓鱼：制作高仿真的假网站，骗取用户账号密码。社交媒体钓鱼：在微信、QQ等平台上冒充熟人进行诈骗。真实案例我处理过一个比较典型的钓鱼攻击事件。攻击者注册了一个和某知名银行非常相似的域名，比如把icbc.com.cn改成icbc-online.com，然后完全仿制了银行的官方网站。攻击者通过邮件群发"安全升级通知"，声称用户需要重新验证身份信息，否则账户会被冻结。邮件制作得非常精美，包含了银行的logo、官方语调，甚至连邮件签名都一模一样。很多用户没有仔细看域名就点击了链接，在假网站上输入了银行卡号、密码、手机验证码等信息。更狡猾的是，假网站在收集完信息后还会跳转到真正的银行网站，让用户以为只是网络延迟，不会产生怀疑。我还见过针对企业的定向钓鱼攻击。攻击者会先收集企业员工的邮箱地址和社交信息，然后冒充CEO或者HR发送邮件，要求员工提供某些敏感信息或者点击恶意链接。这种攻击的成功率特别高，因为员工很难怀疑"领导"的邮件。识别技巧识别钓鱼攻击有几个关键点：仔细检查域名：正规网站的域名都是固定的，要注意拼写错误或者多余字符。查看SSL证书：点击浏览器地址栏的锁图标，查看证书信息。验证发送者身份：对于可疑邮件，可以通过其他渠道联系发送方确认。不要点击邮件中的链接：对于敏感操作，最好直接在浏览器中输入官方网址。防护建议防护钓鱼攻击主要靠提高安全意识：员工培训：定期进行安全意识培训，让大家了解钓鱼攻击的常见套路。邮件安全网关：部署邮件过滤系统，自动识别和拦截钓鱼邮件。DNS防护：使用安全DNS服务，阻止访问已知的钓鱼网站。多因素认证：即使密码被盗，攻击者也难以完成登录。我们公司现在每个季度都会进行钓鱼攻击演练，给员工发送模拟的钓鱼邮件，测试大家的安全意识。刚开始点击率有30%多，现在已经降到5%以下了。中间人攻击 - 通信链路上的窃听者中间人攻击（MITM）是一种比较高级的攻击手法，攻击者悄悄插入到通信双方之间，窃听甚至篡改通信内容。攻击原理中间人攻击的核心是让攻击者成为通信链路中的一环：ARP欺骗：在局域网中伪造ARP响应，让数据包经过攻击者的机器。DNS劫持：篡改DNS响应，让用户访问攻击者控制的服务器。恶意WiFi热点：搭建假的WiFi热点，诱导用户连接。SSL剥离：强制用户使用HTTP连接，去除加密保护。实际案例我遇到过一个在咖啡厅发生的中间人攻击。攻击者搭建了一个名为"Free_WiFi"的热点，信号很强，很多顾客都连接了这个网络。攻击者在这个网络中部署了中间人攻击工具，能够看到所有用户的网络流量。对于使用HTTP的网站，攻击者甚至能看到用户输入的账号密码。更可怕的是，攻击者还会在用户访问的网页中注入广告或者恶意脚本。我还处理过一个企业内网的ARP欺骗案例。攻击者首先通过钓鱼邮件感染了一台内网机器，然后利用这台机器对整个网段进行ARP欺骗，窃听其他机器的网络通信。通过这种方式，攻击者获得了大量内部邮件和文档。防护措施防护中间人攻击需要从多个层面入手：使用HTTPS：确保敏感通信都使用SSL/TLS加密，即使被窃听也无法解密内容。证书验证：不要忽略浏览器的证书警告，遇到证书异常要格外小心。VPN保护：在不可信网络中使用VPN，为通信增加一层加密保护。网络监控：部署网络监控工具，及时发现ARP欺骗等异常行为。静态ARP绑定：在关键服务器上配置静态ARP表，防止ARP欺骗。我现在出差时从来不连接公共WiFi进行敏感操作，都是用手机热点或者公司VPN。虽然流量费贵一点，但安全性大大提升了。内网渗透 - 从外到内的攻击链内网渗透是APT攻击的典型手法，攻击者通过各种方式获得内网立足点后，逐步扩大攻击范围。攻击流程典型的内网渗透包括几个阶段：初始入侵：通过钓鱼邮件、漏洞利用等方式获得第一个立足点。信息收集：扫描内网结构，收集用户信息、系统配置等。权限提升：利用系统漏洞或配置错误获得更高权限。横向移动：在内网中寻找其他可攻击目标，扩大控制范围。持久化：建立多个持久访问通道，确保不被轻易清除。目标达成：窃取敏感数据或进行其他恶意活动。常用技术内网渗透中经常用到的技术：凭据窃取：从内存、注册表、浏览器等位置窃取用户凭据。Pass-the-Hash：使用窃取的密码哈希直接进行身份认证。Kerberos攻击：利用域认证协议的设计缺陷，比如黄金票据攻击。WMI滥用：使用Windows管理工具进行远程命令执行。PowerShell攻击：利用系统自带工具进行无文件攻击，难以被检测。云安全威胁 - 新环境的挑战云计算普及带来便利，也带来了新的安全挑战。主要威胁云环境面临的威胁包括：配置错误：云服务配置不当导致数据泄露，这是最常见的问题。身份管理：云环境下的认证和权限管理更复杂。数据泄露：云存储中的敏感数据可能被未授权访问。API安全：云服务API的漏洞可能被利用。供应链风险：云服务商的问题可能影响所有用户。我见过很多因为云存储配置错误导致的数据泄露，比如把AWS S3设置为公开访问，结果用户信息全部泄露。防护策略云安全防护需要：正确配置：仔细配置云服务安全设置。访问控制：实施严格的身份认证和权限控制。数据加密：对云端敏感数据进行加密。持续监控：实时监控云环境安全状态。合规管理：确保符合相关法规要求。最后网络安全是一个持续对抗的过程，攻击手段在不断演进，防护技术也要跟上节奏。作为运维人员，我们需要保持学习的心态，及时了解新的威胁和防护方法。最重要的是要建立安全意识，很多攻击其实都是可以预防的，关键在于是否重视和是否采取了正确的防护措施。希望这篇文章能帮助大家更好地了解网络攻击，做好安全防护工作。网络安全没有银弹，需要我们持续投入和改进。只有这样，才能在这个充满威胁的数字世界中保护好我们的系统和数据。如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
Python sh库：让运维脚本告别subprocess的痛苦，一行代码搞定系统命令
最近在整理服务器监控脚本的时候，发现自己写的代码里到处都是subprocess.Popen()，看着就头疼。想起刚入行那会儿，为了执行个简单的ls命令，愣是写了十几行代码处理各种异常和输出，现在想想真是太蠢了。直到遇到了Python的sh库，我才发现原来执行系统命令可以这么优雅。今天就来聊聊这个让我爱不释手的库，相信看完之后你也会和我一样，再也不想碰subprocess了。为什么我会选择sh库说实话，Python标准库里的subprocess功能很强大，但用起来真的很繁琐。每次想执行个简单命令都要考虑一堆问题：怎么获取输出？怎么处理错误？怎么传参数？import subprocess # 传统的subprocess写法，光看着就累 result = subprocess.run(['ls', '-la'], capture_output=True, text=True, check=True) print(result.stdout)而用sh库呢？import sh print(sh.ls('-la'))就这么简单！一行代码搞定，看着就舒服多了。sh库的安装和基本使用安装很简单，pip一下就行：pip install sh基本使用更简单，直接把系统命令当成Python函数调用就可以了：import sh # 执行ls命令 print(sh.ls()) # 带参数的命令 print(sh.ls('-la', '/home')) # 管道操作也很简单 print(sh.grep(sh.ps('aux'), 'python'))我第一次看到这种写法的时候，真的被震撼到了。这不就是我一直想要的那种简洁的方式吗？实际项目中的应用场景在我日常运维中，sh库真的帮了大忙。比如说服务器健康检查脚本：import sh from datetime import datetime def check_server_status(): # 检查磁盘使用情况 disk_usage = sh.df('-h') print(f"磁盘使用情况：\n{disk_usage}") # 检查内存使用 memory_info = sh.free('-h') print(f"内存使用情况：\n{memory_info}") # 检查CPU负载 load_avg = sh.uptime() print(f"系统负载：{load_avg}") # 检查特定进程 try: nginx_processes = sh.pgrep('nginx') print(f"Nginx进程数：{len(nginx_processes.strip().split())}") except sh.ErrorReturnCode_1: print("警告：Nginx进程未运行！")这种写法比subprocess清爽太多了，而且代码可读性特别好。处理命令输出和错误sh库在处理命令输出方面也很贴心：import sh # 获取命令输出 output = sh.ls('/home') print(type(output)) # <class 'sh.RunningCommand'> print(str(output)) # 转换为字符串 # 实时处理输出 def process_line(line): print(f"处理行: {line.strip()}") sh.tail('-f', '/var/log/syslog', _out=process_line)错误处理也很直观：import sh try: # 执行可能失败的命令 result = sh.ls('/nonexistent') except sh.ErrorReturnCode_2 as e: print(f"命令执行失败: {e}") print(f"错误输出: {e.stderr}") print(f"标准输出: {e.stdout}") print(f"退出码: {e.exit_code}")高级功能：管道和重定向sh库对管道的支持让我印象深刻，写起来就像在shell里一样自然：import sh # 管道操作 result = sh.ps('aux') | sh.grep('python') | sh.wc('-l') print(f"Python进程数量: {result}") # 更复杂的管道 log_errors = sh.tail('-n', '1000', '/var/log/app.log') | sh.grep('ERROR') print(log_errors) # 重定向到文件 sh.dmesg(_out='/tmp/dmesg.log') # 从文件读取输入 sh.sort(_in='/tmp/unsorted.txt', _out='/tmp/sorted.txt')这种写法真的很接近shell脚本的感觉，但又保持了Python的灵活性。在自动化部署中的应用我们团队的自动化部署脚本大量使用了sh库，效果很不错：import sh import os def deploy_application(app_name, version): print(f"开始部署 {app_name} 版本 {version}") # 停止服务 try: sh.systemctl('stop', app_name) print("服务已停止") except sh.ErrorReturnCode: print("服务停止失败，继续部署...") # 备份当前版本 backup_dir = f"/backup/{app_name}-{datetime.now().strftime('%Y%m%d_%H%M%S')}" sh.mkdir('-p', backup_dir) sh.cp('-r', f'/opt/{app_name}', backup_dir) print(f"已备份到 {backup_dir}") # 下载新版本 download_url = f"https://releases.example.com/{app_name}-{version}.tar.gz" sh.wget(download_url, '-O', f'/tmp/{app_name}-{version}.tar.gz') # 解压和部署 sh.tar('-xzf', f'/tmp/{app_name}-{version}.tar.gz', '-C', '/opt/') # 更新配置文件权限 sh.chown('-R', 'app:app', f'/opt/{app_name}') sh.chmod('-R', '755', f'/opt/{app_name}') # 启动服务 sh.systemctl('start', app_name) sh.systemctl('enable', app_name) # 验证部署 try: status = sh.systemctl('is-active', app_name) if 'active' in str(status): print("部署成功！") else: print("部署可能有问题，请检查服务状态") except sh.ErrorReturnCode: print("服务启动失败！")日志分析的实际应用在日志分析方面，sh库也表现得很出色（看看就行，都用python了，谁还这样分析呀！！！）：import sh from collections import defaultdict import re def analyze_nginx_logs(log_file): # 分析访问最多的IP top_ips = sh.awk('{print $1}', log_file) | sh.sort() | sh.uniq('-c') | sh.sort('-nr') | sh.head('-10') print("访问最多的IP地址：") print(top_ips) # 分析状态码分布 status_codes = sh.awk('{print $9}', log_file) | sh.sort() | sh.uniq('-c') print("\n状态码分布：") print(status_codes) # 分析最耗时的请求 slow_requests = sh.awk('$NF > 1.0 {print $0}', log_file) | sh.sort('-k11', '-nr') | sh.head('-20') print("\n响应时间超过1秒的请求：") print(slow_requests) # 统计每小时的请求量 hourly_stats = sh.awk('{print $4}', log_file) | sh.cut('-c14-15') | sh.sort() | sh.uniq('-c') print("\n每小时请求量统计：") print(hourly_stats) # 使用示例 analyze_nginx_logs('/var/log/nginx/access.log')需要注意的坑参数传递的问题import sh # 错误的写法 # sh.find('/home -name "*.log"') # 这样会报错 # 正确的写法 sh.find('/home', '-name', '*.log') # 或者使用关键字参数 sh.find('/home', name='*.log')处理包含空格的路径import sh # 包含空格的路径需要特别处理 path_with_spaces = "/home/user/my documents" sh.ls(path_with_spaces) # sh库会自动处理空格长时间运行的命令import sh # 对于长时间运行的命令，建议设置超时 try: result = sh.rsync('-av', '/source/', '/destination/', _timeout=3600) except sh.TimeoutException: print("同步超时！")与其他库的对比说实话，除了sh库，我也用过其他一些执行系统命令的库：subprocess vs shsubprocess功能更全面，但代码冗长sh库更简洁，但在某些复杂场景下可能不够灵活os.system vs shos.system太原始，不推荐在生产环境使用sh库提供了更好的错误处理和输出控制pexpect vs shpexpect适合需要交互的场景sh库更适合执行简单的系统命令在容器化环境中的应用现在容器化部署很流行，sh库在这方面也很有用：import sh def manage_containers(): # 列出所有运行中的容器 running_containers = sh.docker('ps', '--format', 'table {{.Names}}\t{{.Status}}') print("运行中的容器：") print(running_containers) # 检查容器资源使用情况 container_stats = sh.docker('stats', '--no-stream', '--format', 'table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}') print("\n容器资源使用情况：") print(container_stats) # 清理无用的镜像 sh.docker('image', 'prune', '-f') print("\n已清理无用镜像") manage_containers()总结说了这么多，其实sh库最大的优势就是让Python代码更接近shell脚本的直觉性，同时又保持了Python的强大功能。在我的日常工作中，它已经成为了不可或缺的工具。当然，任何工具都有其适用场景。对于简单的系统命令执行，sh库绝对是首选；但如果需要复杂的进程控制或者跨平台兼容性，可能还是需要考虑其他方案。最重要的是，在使用任何执行系统命令的库时，都要时刻注意安全性。特别是在处理用户输入或者在生产环境中运行时，一定要做好输入验证和权限控制。希望这篇文章能帮到正在为subprocess复杂语法而头疼的朋友们。如果你有什么好的使用技巧或者踩过的坑，也欢迎在评论区分享交流。毕竟，工具是为了提高效率而存在的，选择最适合自己项目需求的工具才是最重要的。sh库只是众多选择中的一个，但对于大部分运维场景来说，它确实是个很不错的选择。如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
从JumpServer安装脚本看Shell编程：一个运维人员的实战解析
最近在部署JumpServer的时候，仔细研究了一下它的安装脚本，发现这个脚本写得还挺有意思的。作为一个天天和Shell打交道的运维，我觉得这个脚本很适合用来讲解Shell编程的一些实用技巧。说实话，很多人觉得Shell脚本就是简单的命令堆砌，但实际上一个好的Shell脚本包含了很多编程思想和最佳实践。今天就通过这个JumpServer的jmsctl.sh脚本，来聊聊Shell编程中那些值得学习的地方。脚本结构设计：模块化思维很重要看这个脚本的整体结构，我第一眼就被它的组织方式吸引了。作者把整个脚本分成了几个清晰的部分：#!/usr/bin/env bash export SHELLOPTS PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" cd "${PROJECT_DIR}" || exit 1 . "${PROJECT_DIR}/scripts/utils.sh"这几行代码看似简单，但包含了很多门道。#!/usr/bin/env bash这个shebang比直接写#!/bin/bash要好，因为它会在PATH中查找bash，兼容性更强。export SHELLOPTS这行我之前还真没怎么用过，查了一下发现它是用来导出Shell选项的，这样在调用子脚本时能保持一致的行为。获取脚本所在目录的那行代码写得很巧妙：PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"${BASH_SOURCE[0]}比$0更可靠，特别是在脚本被source的时候。&>/dev/null把错误输出也重定向了，避免在某些环境下出现奇怪的错误信息。参数处理：简单但实用action=${1-} target=${2-} args=("$@")这种参数处理方式很实用。${1-}的写法比直接用$1安全，当没有参数时不会报错。把所有参数存到数组里也是个好习惯，后面需要传递给其他命令时很方便。我以前写脚本经常直接用$1、$2，但在一些边界情况下容易出问题。现在我也开始用这种写法了。配置文件检查：防御性编程function check_config_file() { if [[ ! -f "${CONFIG_FILE}" ]]; then echo "$(gettext 'Configuration file not found'): ${CONFIG_FILE}" echo "$(gettext 'If you are upgrading from v1.5.x, please copy the config.txt To') ${CONFIG_FILE}" return 3 fi # ... }这个函数体现了防御性编程的思想。不仅检查配置文件是否存在，还给出了具体的解决建议。返回值用3而不是1，这样可以区分不同类型的错误。软链接的处理也很细致：if -f .env ; then if ! ls -l .env | grep "${CONFIG_FILE}" &>/dev/null; then echo ".env $(gettext 'There is a problem with the soft connection, Please update it again')" rm -f .env fi fi检查软链接是否正确指向目标文件，如果不对就重新创建。这种处理方式在实际运维中很有用，避免了因为软链接问题导致的各种奇怪错误。国际化支持：细节决定成败脚本中大量使用了$(gettext '...')来支持多语言，这个细节让我印象深刻。虽然很多人写脚本时不会考虑国际化，但对于要分发给全球用户的开源项目来说，这确实很重要。echo "$(gettext 'JumpServer Deployment Management Script')"gettext是GNU的国际化工具，通过它可以根据系统语言环境显示对应的文本。虽然在内部项目中可能用不到，但这种思维方式值得学习。帮助信息：用户体验很关键function usage() { echo "$(gettext 'JumpServer Deployment Management Script')" echo echo "Usage: " echo " ./jmsctl.sh [COMMAND] [ARGS...]" echo " ./jmsctl.sh --help" echo echo "Installation Commands: " echo " install $(gettext 'Install JumpServer')" echo " upgrade $(gettext 'Upgrade JumpServer')" # ... }这个帮助函数写得很规范，命令分类清晰，每个命令都有说明。我见过太多脚本要么没有帮助信息，要么帮助信息写得很随意。一个好的帮助信息能大大提升用户体验。服务管理：Docker Compose的封装function start() { ${EXE} up -d } function stop() { if [[ -n "${target}" ]]; then ${EXE} stop "${target}" && ${EXE} rm -f "${target}" return fi ${EXE} down -v }这里的设计很巧妙，${EXE}变量存储的是docker-compose的完整命令行，包括配置文件路径等参数。这样在各个函数中就可以直接使用，避免了重复代码。stop函数还考虑了单个服务和全部服务的不同处理方式，这种灵活性在实际使用中很有价值。错误处理：让脚本更健壮cd "${PROJECT_DIR}" || exit 1这种写法比单独的cd命令要安全得多。如果目录切换失败，脚本会立即退出，避免在错误的目录下执行后续命令。pre_check || return 3在主要操作前进行预检查，失败时返回特定的错误码。这样调用者可以根据返回值判断具体的错误类型。版本检查：自动化运维的体现function check_update() { current_version=$(get_current_version) latest_version=$(get_latest_version) if [[ "${current_version}" == "${latest_version}" ]]; then echo_green "$(gettext 'The current version is up to date'): ${latest_version}" echo return fi # ... }自动检查版本更新的功能很实用，特别是对于需要经常更新的系统。这种自动化的思维在运维工作中很重要。平台兼容性：考虑周全if [[ "${OS}" == 'Darwin' ]]; then echo echo "$(gettext 'Unsupported Operating System Error')" echo "$(gettext 'macOS installer please see'): https://github.com/jumpserver/Dockerfile" exit 0 fi检查操作系统类型并给出相应的提示，这种处理方式很专业。不是简单地报错退出，而是告诉用户应该怎么办。Case语句：清晰的命令分发case "${action}" in install) bash "${SCRIPT_DIR}/4_install_jumpserver.sh" ;; upgrade) bash "${SCRIPT_DIR}/7_upgrade.sh" "$target" ;; # ... esac用case语句来处理不同的命令，比一堆if-elif要清晰得多。每个命令对应一个具体的操作，有些调用其他脚本，有些执行内部函数，分工明确。日志查看：实用的调试功能tail) if [[ -z "${target}" ]]; then ${EXE} logs --tail 100 -f else docker_name=$(service_to_docker_name "${target}") docker logs -f "${docker_name}" --tail 100 fi ;;这个tail命令的实现很贴心，既可以查看所有服务的日志，也可以查看单个服务的日志。在调试问题时这个功能特别有用。一些值得学习的编程技巧看完这个脚本，我总结了几个值得学习的地方：变量命名要有意义。像PROJECT_DIR、CONFIG_FILE这样的变量名一看就知道是什么，比dir、file要好得多。善用数组。args=("$@")把所有参数存到数组里，后面使用时很方便。返回值要有意义。不同的错误情况返回不同的值，这样调用者可以做相应的处理。输出重定向要合理。&>/dev/null、2>/dev/null这些用法要根据实际需要选择。字符串比较用双括号。[[ ]]比[ ]功能更强，支持模式匹配等高级特性。说到这里，我想起之前写过一个类似的部署脚本，当时图省事很多地方都没考虑周全，后来在生产环境中遇到了不少问题。现在回头看，确实应该多花点时间在错误处理和边界情况上。实际应用中的思考这个脚本还有一个很好的地方就是它的扩展性。通过raw命令可以直接执行docker-compose的原生命令：raw) ${EXE} "${args[@]:1}" ;;这样既提供了便捷的封装命令，又保留了原生命令的灵活性。这种设计思路在很多场景下都适用。另外，脚本中的模块化设计也很值得借鉴。主脚本负责参数解析和命令分发，具体的功能实现放在单独的脚本文件中。这样既保持了主脚本的简洁，又便于维护和测试。不过这个脚本也不是完美的，比如错误信息的处理还可以更统一一些，某些函数的命名可能不够直观。但总体来说，这是一个很好的Shell脚本实践案例。源码#!/usr/bin/env bash # export SHELLOPTS PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" cd "${PROJECT_DIR}" || exit 1 . "${PROJECT_DIR}/scripts/utils.sh" action=${1-} target=${2-} args=("$@") function check_config_file() { if [[ ! -f "${CONFIG_FILE}" ]]; then echo "$(gettext 'Configuration file not found'): ${CONFIG_FILE}" echo "$(gettext 'If you are upgrading from v1.5.x, please copy the config.txt To') ${CONFIG_FILE}" return 3 fi if -f .env ; then if ! ls -l .env | grep "${CONFIG_FILE}" &>/dev/null; then echo ".env $(gettext 'There is a problem with the soft connection, Please update it again')" rm -f .env fi fi if ! -f ".env" ; then ln -s "${CONFIG_FILE}" .env fi if ! -f "./compose/.env" ; then ln -s "${CONFIG_FILE}" ./compose/.env fi } function pre_check() { check_config_file || return 3 } function usage() { echo "$(gettext 'JumpServer Deployment Management Script')" echo echo "Usage: " echo " ./jmsctl.sh [COMMAND] [ARGS...]" echo " ./jmsctl.sh --help" echo echo "Installation Commands: " echo " install $(gettext 'Install JumpServer')" echo " upgrade $(gettext 'Upgrade JumpServer')" echo echo "Management Commands: " echo " config $(gettext 'Configuration Tools')" echo " start $(gettext 'Start JumpServer')" echo " stop $(gettext 'Stop JumpServer')" echo " restart $(gettext 'Restart JumpServer')" echo " status $(gettext 'Check JumpServer')" echo " down $(gettext 'Offline JumpServer')" echo " uninstall $(gettext 'Uninstall JumpServer')" echo echo "More Commands: " echo " load_image $(gettext 'Loading docker image')" echo " backup_db $(gettext 'Backup database')" echo " restore_db [file] $(gettext 'Data recovery through database backup file')" echo " raw $(gettext 'Execute the original docker compose command')" echo " tail [service] $(gettext 'View log')" echo } function service_to_docker_name() { service=$1 if [[ "${service:0:3}" != "jms" ]]; then service=jms_${service} fi echo "${service}" } EXE="" function start() { ${EXE} up -d } function stop() { if [[ -n "${target}" ]]; then ${EXE} stop "${target}" && ${EXE} rm -f "${target}" return fi ${EXE} down -v } function close() { if [[ -n "${target}" ]]; then ${EXE} stop "${target}" return fi services=$(get_docker_compose_services ignore_db) for i in ${services}; do ${EXE} stop "${i}" done } function pull() { if [[ -n "${target}" ]]; then ${EXE} pull "${target}" return fi ${EXE} pull } function restart() { stop echo -e "\n" start } function check_update() { current_version=$(get_current_version) latest_version=$(get_latest_version) if [[ "${current_version}" == "${latest_version}" ]]; then echo_green "$(gettext 'The current version is up to date'): ${latest_version}" echo return fi if [[ -n "${latest_version}" ]] && [[ ${latest_version} =~ v.* ]]; then echo -e "\033[32m$(gettext 'The latest version is'): ${latest_version}\033[0m" else exit 1 fi } function video-worker() { EXE=$(get_video_worker_cmd_line) if [[ ! "${EXE}" ]]; then return fi if [[ "${target}" == "start" ]]; then ${EXE} up -d fi if [[ "${target}" == "stop" ]]; then ${EXE} down -v fi if [[ "${target}" == "restart" ]]; then ${EXE} down -v ${EXE} up -d fi if [[ "${target}" == "status" ]]; then ${EXE} ps fi } function main() { if [[ "${OS}" == 'Darwin' ]]; then echo echo "$(gettext 'Unsupported Operating System Error')" echo "$(gettext 'macOS installer please see'): https://github.com/jumpserver/Dockerfile" exit 0 fi if [[ "${OS}" =~ MINGW.* ]]; then echo echo "$(gettext 'Unsupported Operating System Error')" echo "$(gettext 'Windows installer please see'): https://github.com/jumpserver/Dockerfile" exit 0 fi if [[ "${action}" == "help" || "${action}" == "h" || "${action}" == "-h" || "${action}" == "--help" ]]; then echo "" elif [[ "${action}" == "install" || "${action}" == "config" || "${action}" == "reconfig" ]]; then echo "" else pre_check || return 3 EXE=$(get_docker_compose_cmd_line) fi case "${action}" in install) bash "${SCRIPT_DIR}/4_install_jumpserver.sh" ;; upgrade) bash "${SCRIPT_DIR}/7_upgrade.sh" "$target" ;; check_update) check_update ;; config) bash "${SCRIPT_DIR}/config.sh" "$target" ;; reconfig) ${EXE} down -v bash "${SCRIPT_DIR}/1_config_jumpserver.sh" ;; start) start ;; restart) restart ;; stop) stop ;; pull) pull ;; close) close ;; status) ${EXE} ps ;; down) if [[ -z "${target}" ]]; then ${EXE} down -v else ${EXE} stop "${target}" && ${EXE} rm -f "${target}" fi ;; uninstall) bash "${SCRIPT_DIR}/8_uninstall.sh" ;; backup_db) bash "${SCRIPT_DIR}/5_db_backup.sh" ;; restore_db) bash "${SCRIPT_DIR}/6_db_restore.sh" "$target" ;; load_image) bash "${SCRIPT_DIR}/3_load_images.sh" ;; pull_images) pull_images ;; cmd) echo "${EXE}" ;; tail) if [[ -z "${target}" ]]; then ${EXE} logs --tail 100 -f else docker_name=$(service_to_docker_name "${target}") docker logs -f "${docker_name}" --tail 100 fi ;; show_services) get_docker_compose_services ;; init_db) perform_db_migrations ;; video-worker) video-worker ;; raw) ${EXE} "${args[@]:1}" ;; version) get_current_version ;; help) usage ;; --help) usage ;; -h) usage ;; *) echo "No such command: ${action}" usage ;; esac } main "$@"写在最后通过分析这个JumpServer的安装脚本，我们可以看到一个好的Shell脚本应该具备的特质：结构清晰、错误处理完善、用户体验友好、扩展性良好。Shell编程虽然看起来简单，但要写好并不容易。需要考虑各种边界情况，处理不同的错误场景，还要保证代码的可读性和可维护性。我觉得每个运维人员都应该认真对待Shell脚本的编写，毕竟这是我们日常工作中最常用的工具之一。一个好的脚本不仅能提高工作效率，还能减少出错的可能性。希望这次的分析对大家有所帮助。如果你也有类似的脚本编写经验或者问题，欢迎在评论区交流讨论。如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
CDN优化实战：从命中率30%到95%的血泪史，AWS CloudFront踩坑全记录
最近客户cdn命中率异常，让我优化下缓存策略。经过一番折腾，总算把命中率提上来了！！！今天就把这些年踩过的坑和一些实用的经验分享给大家。其实CDN优化这事儿，说简单也简单，说复杂也复杂。很多人以为接入个CDN就万事大吉了，结果发现命中率低得可怜，钱花了不少效果却不明显。我之前就遇到过这种情况，CDN命中率只有30%多，简直是在烧钱。先说说CDN的那些关键指标做CDN优化，你得先知道怎么看数据。就像开车要看仪表盘一样，不然怎么知道车子跑得怎么样？命中率（Hit Ratio）这个是最重要的指标，没有之一。命中率就是CDN直接返回缓存内容的请求占总请求的比例。一般来说，静态资源的命中率应该在90%以上，如果你的命中率只有50-60%，那肯定有问题。在AWS CloudFront里查看命中率很简单，进入CloudFront控制台，选择你的分发，然后点击"Monitoring"标签页。这里会显示Cache hit rate的图表，可以看到不同时间段的命中率变化。回源率（Origin Request Rate）这个和命中率是相对的，回源率高说明CDN经常要去源站拿数据，这样就失去了CDN的意义。正常情况下回源率应该控制在10%以下。响应时间包括CDN响应时间和回源响应时间。CDN响应时间一般应该在100ms以内，如果超过这个数值就要检查是不是节点选择有问题。我记得有次优化一个电商网站，发现图片加载特别慢。一查监控发现CDN响应时间居然有2秒多，后来才发现是缓存策略配置错误，每次都要回源验证。带宽使用情况这个主要是看成本，CDN的费用很大一部分就是带宽费用。通过监控带宽使用情况，可以了解流量分布，优化成本。在AWS CloudFront的监控面板里，你还能看到：Origin latency（回源延迟）Error rate（错误率）Requests（请求数）Bytes downloaded（下载字节数）这些数据都很有用，特别是错误率，如果突然飙升可能是源站出问题了。命中率低的常见原因和解决方案说到命中率低，我遇到过各种奇葩的情况。缓存策略配置不当这是最常见的问题。很多人图省事，直接用默认配置，结果发现很多本该缓存的内容都没有缓存。比如说，有些动态参数会导致URL不同，CDN就认为是不同的资源。像这种情况：https://example.com/image.jpg?timestamp=1234567890 https://example.com/image.jpg?timestamp=1234567891CDN会认为这是两个不同的文件，实际上内容是一样的。解决办法是在CloudFront的Behavior设置里配置Query String参数处理，可以选择忽略某些参数或者只转发特定参数。TTL设置太短Time To Live设置太短会导致缓存频繁过期。我见过有人把图片的TTL设置成5分钟，这不是瞎搞吗？静态资源的TTL至少要设置几个小时，甚至几天。在CloudFront里可以设置三种TTL：Minimum TTL：最小缓存时间Maximum TTL：最大缓存时间Default TTL：默认缓存时间一般我会这样设置：图片、CSS、JS等静态资源：24小时到7天HTML页面：1-6小时API接口：根据业务需求，可能几分钟到几小时源站响应头配置问题有时候源站返回了不合适的Cache-Control头，比如no-cache或者max-age=0，这会导致CDN不缓存内容。我之前遇到过一个案例，开发在Nginx配置里给所有静态资源都加了no-cache，结果CDN命中率惨不忍睹。后来改成这样：location ~* \.(jpg|jpeg|png|gif|css|js)$ { expires 7d; add_header Cache-Control "public, immutable"; }预热不充分新上线的CDN或者清除缓存后，需要一段时间来"预热"。这期间命中率会比较低，这是正常现象。但如果长时间命中率都上不去，就要检查其他配置了。CloudFront支持手动预热，可以通过Invalidation功能清除缓存，然后用脚本批量访问重要资源来预热缓存。CDN优化策略详解现在说说aws托管的缓存策略AWS CDN托管的缓存策略详细说明策略名称缓存内容不缓存内容使用场景Amplify缓存大多数静态资源和部分动态内容不缓存包含特定身份验证头的请求Amplify应用程序的通用缓存策略Amplify-Default缓存静态资源和基于常用头部的内容，保留某些Cookie不缓存带有特定认证头的请求，某些动态API响应Amplify应用的标准配置，平衡缓存与个性化Amplify-Default-V2与Amplify-Default类似，但有优化的TTL设置与Amplify-Default类似需要改进的缓存性能的Amplify应用Amplify-DefaultNoCookies缓存静态资源和页面内容不缓存任何Cookie信息，不缓存认证请求不需要基于Cookie区分用户的Amplify应用Amplify-DefaultNoCookies-V2与DefaultNoCookies类似，但有更长的TTL与DefaultNoCookies类似追求更高缓存命中率的静态Amplify应用Amplify-ImageOptimization缓存图像文件(jpg, png, gif等)，支持不同的图像格式不缓存非图像内容，不保留大多数请求头图像密集型网站，图片库，电商产品展示Amplify-ImageOptimization-V2与ImageOptimization类似，增加了WebP等现代格式支持与ImageOptimization类似需要现代图像格式支持的应用Amplify-StaticContent缓存JS, CSS, 字体, 图像等静态资源，长TTL不缓存HTML或动态内容，不保留Cookie静态资源的长期缓存，提高重复访问性能Amplify-StaticContent-V2与StaticContent类似，但有更优化的压缩设置与StaticContent类似需要最佳静态资源性能的应用CachingDisabled不缓存任何内容所有内容都直接从源站获取实时数据，个性化内容，需要实时源站响应的场景CachingOptimized缓存大多数内容，启用压缩，优化TTL不缓存POST/PUT请求，特定动态API路径通用Web应用，需要平衡缓存与新鲜度的场景CachingOptimizedForUncompressedObjects缓存未压缩的对象，自动应用适当的TTL不缓存已经压缩的内容，不处理某些动态请求源站不提供压缩的情况，CDN负责压缩Elemental-MediaPackage缓存视频分段，流媒体清单文件不缓存用户特定的媒体请求，认证令牌视频流媒体，直播，点播视频服务UseOriginCacheControlHeaders根据源站的Cache-Control头决定缓存行为如果源站指示不缓存，则不缓存；忽略查询参数源站已有完善缓存策略的情况UseOriginCacheControlHeaders-QueryStrings根据源站Cache-Control头和URL查询参数缓存如源站指示不缓存则不缓存基于查询参数提供不同内容且源站控制缓存的场景AWS CloudFront 源请求策略详细说明策略名称转发内容不转发内容使用场景AllViewer转发所有查看者请求参数，包括查询字符串、标头和Cookie不限制任何参数的转发需要将所有客户端请求信息传递给源站的场景，适用于高度依赖客户端信息的动态内容AllViewerAndCloudFrontHeaders-2022-06转发所有查看者请求参数和截至2022年6月的所有CloudFront标头不限制任何参数或CloudFront标头的转发需要完整客户端信息以及CloudFront添加的信息(如设备类型、地理位置)的场景AllViewerExceptHostHeader转发除Host标头外的所有查看者请求参数不转发Host标头当源站需要使用不同的主机名处理请求，但仍需其他所有客户端信息的场景CORS-CustomOrigin转发与CORS(跨源资源共享)相关的标头到自定义源不转发与CORS无关的其他标头需要支持跨域请求的自定义源站(非S3)场景，如API或Web应用CORS-S3Origin转发与CORS相关的标头到S3源不转发与CORS无关的其他标头需要支持跨域请求的S3存储桶场景，如从浏览器直接访问S3资源Elemental-MediaTailor-PersonalizedManifests转发Elemental MediaTailor个性化清单所需的标头不转发与媒体个性化无关的标头视频流媒体场景，特别是需要个性化内容或广告插入的流媒体服务HostHeaderOnly仅转发Host标头到源站不转发任何其他查看者请求参数简单的静态内容分发场景，源站仅需要知道请求的主机名UserAgentReferrerHeaders转发User-Agent(用户代理)和Referer(引用页)标头到源站不转发其他查看者请求参数源站需要了解访问者浏览器类型和来源网站的场景，如内容适配或引用统计这些源请求策略控制CloudFront将哪些信息从查看者(客户端)请求中转发到源站。选择合适的策略可以优化源站请求处理，减少不必要的信息传递，同时确保源站获得所需的上下文信息来正确响应请求。AWS CloudFront 响应标头策略详细说明策略名称添加/修改内容不添加/不修改内容使用场景CORS-and-SecurityHeadersPolicy添加CORS相关标头和安全标头到响应中不修改源站提供的其他标头需要同时支持跨域请求和增强安全性的Web应用，如需要跨域访问的API且需要防XSS、点击劫持等安全保护CORS-With-Preflight添加CORS相关标头并支持预检(OPTIONS)请求不添加安全标头，不修改源站提供的其他标头需要支持复杂跨域请求的场景，特别是涉及非简单请求(如带自定义标头或使用PUT/DELETE方法)的APICORS-with-preflight-and-SecurityHeadersPolicy添加CORS相关标头(含预检支持)和安全标头不修改源站提供的其他标头需要全面CORS支持(包括复杂请求)和安全增强的Web应用，如SPA(单页应用)与后端API交互SecurityHeadersPolicy仅添加安全相关标头到每个响应不添加CORS标头，不修改源站提供的其他标头注重安全性但不需要跨域支持的网站，如企业内部应用或对安全有高要求的公共网站SimpleCORS仅添加基本CORS标头，支持简单跨域请求不添加安全标头，不支持预检请求，不修改源站提供的其他标头只需基本跨域支持的简单场景，如只允许GET请求的公共API或静态资源这些响应标头策略控制CloudFront如何修改从源站返回给客户端的HTTP响应标头。适当的策略可以增强Web应用的安全性，启用跨域资源共享功能，而无需修改源站配置。安全标头通常包括内容安全策略(CSP)、X-XSS-Protection、X-Frame-Options等，用于防止常见的Web安全威胁。当然要是这些不满足还可以自定义！！其他一些缓存策略分层缓存策略不同类型的资源用不同的缓存策略。我一般会创建多个Behavior，针对不同的路径模式设置不同的缓存规则：/api/* - TTL: 5分钟，转发所有参数 /images/* - TTL: 7天，忽略查询参数 /css/* - TTL: 1天，启用压缩 /js/* - TTL: 1天，启用压缩 /* - TTL: 1小时，默认行为启用压缩CloudFront支持自动压缩，可以显著减少传输数据量。在Behavior设置里开启"Compress Objects Automatically"就行了。不过要注意，压缩会消耗一些CPU资源，对于已经压缩过的文件（如图片）就没必要再压缩了。合理使用Origin Shield这是CloudFront的一个高级功能，相当于在CDN和源站之间加了一层缓存。对于回源比较频繁的场景很有用，可以减少源站压力。我在一个视频网站项目中用过Origin Shield，效果很明显。因为视频文件比较大，用户分布又比较集中，开启Origin Shield后回源请求减少了60%多。HTTP/2和HTTP/3支持现在的CDN基本都支持HTTP/2了，CloudFront也不例外。HTTP/2的多路复用特性可以显著提升页面加载速度，特别是对于有很多小文件的网站。智能路由CloudFront会自动选择最优的边缘节点，但有时候你可能需要手动干预。比如某些地区的节点质量不好，可以通过地理位置限制来避免使用这些节点。AWS CloudFront的一些实用技巧用了这么久CloudFront，发现了一些比较实用的技巧。Lambda@Edge这个功能很强大，可以在CDN边缘节点运行代码。我用过几个场景：动态修改响应头根据用户地理位置返回不同内容简单的A/B测试图片格式转换（WebP支持检测）比如这个检测WebP支持的例子：exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers; const accept = headers.accept && headers.accept[0] && headers.accept[0].value; if (accept && accept.includes('image/webp')) { request.uri = request.uri.replace(/\.(jpg|jpeg|png)$/i, '.webp'); } callback(null, request); };实时日志分析CloudFront可以把访问日志实时推送到Kinesis Data Streams，然后用Lambda处理。我用这个功能做过实时监控，当错误率超过阈值时自动发送告警。多源站配置CloudFront支持配置多个源站，可以实现负载均衡和故障转移。我一般会配置主源站和备用源站，当主源站出问题时自动切换到备用源站。自定义错误页面通过配置Custom Error Pages，可以为不同的HTTP错误码返回自定义页面。这样用户体验会好很多，而且可以减少无效的回源请求。监控和告警设置CDN优化不是一次性的工作，需要持续监控和调优。CloudWatch监控CloudFront的所有指标都会推送到CloudWatch，可以设置各种告警：命中率低于阈值告警错误率超过阈值告警回源延迟过高告警带宽使用异常告警我一般会设置这些告警规则：命中率 < 85% 持续10分钟 -> 发送告警 4xx错误率 > 5% 持续5分钟 -> 发送告警 5xx错误率 > 1% 持续5分钟 -> 发送告警 回源延迟 > 2秒 持续5分钟 -> 发送告警第三方监控工具除了CloudWatch，我还会用一些第三方工具来监控CDN性能，比如Pingdom、GTmetrix等。这些工具可以从用户角度测试网站性能，发现一些CloudWatch监控不到的问题。成本优化CDN的费用不便宜，特别是流量大的网站。Price Class选择CloudFront有三个价格等级，包含的边缘节点数量不同。如果你的用户主要在北美和欧洲，选择Price Class 100就够了，没必要选择包含全球所有节点的Price Class All。缓存策略优化提高命中率不仅能改善性能，还能降低成本。因为CDN的费用主要是按流量计算的，命中率高意味着回源流量少，总体费用就低。如果命中率低就会出现流量放大效应：• 正常命中率80%：1GB实际需求 = 1.25GB CDN流量• 当前命中率可能<20%：1GB实际需求 = 5GB+ CDN流量压缩和格式优化启用压缩可以减少传输数据量，WebP格式的图片比JPEG小30-50%。这些优化都能直接降低CDN费用。我之前优化过一个图片网站，通过启用压缩和WebP转换，CDN费用降低了40%多。常见问题排查做CDN优化经常会遇到各种问题，分享几个排查思路。缓存不生效首先检查响应头，看看有没有Cache-Control、Expires等缓存相关的头。然后检查CloudFront的Behavior配置，确认TTL设置正确。可以用curl命令测试：curl -I https://your-domain.com/test.jpg看响应头里的X-Cache字段，Hit表示命中缓存，Miss表示没有命中。某些地区访问慢可能是CDN节点选择有问题。用不同地区的VPS测试访问速度，确定是哪些地区有问题。然后检查是不是需要调整Price Class或者配置地理位置限制。缓存穿透如果某个资源一直无法缓存，可能是URL参数或者响应头有问题。检查是不是有随机参数，或者源站返回了no-cache头。我遇到过一个奇葩问题，开发在图片URL后面加了随机数防止缓存，结果CDN命中率为0。后来改成用版本号，问题就解决了。总结CDN优化是个系统工程，需要从多个维度来考虑。命中率是最重要的指标，但不是唯一的指标。要结合业务特点，制定合适的缓存策略。AWS CloudFront功能很强大，但配置也比较复杂。建议先从基础配置开始，逐步优化。不要一开始就搞得很复杂，容易出问题。最重要的是要持续监控和优化，CDN不是配置好就不管了。用户行为会变化，业务需求也会变化，CDN策略也要跟着调整。我现在维护的几个网站，CDN命中率都在95%以上，用户访问速度提升了3-5倍。虽然前期折腾了不少时间，但效果还是很明显的。如果你也在做CDN优化，遇到问题可以多交流。这块水还是挺深的，大家一起学习进步。如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
服务器500错误又来了？别慌！这份排查指南让你秒变故障终结者
前言说起500错误，我想每个做运维的朋友都有一肚子苦水要倒。昨天晚上11点多，我正准备洗洗睡了，手机突然疯狂震动——客户问题又来了。打开一看，果然是熟悉的HTTP 500错误，用户访问网站直接白屏。这种时候真的是又爱又恨，爱的是能锻炼自己的技术能力，恨的是为什么总是在最不合适的时候出现。不过经过这么多年的摸爬滚打，我总结出了一套比较实用的排查思路，今天就分享给大家。500错误其实就是服务器内部错误，说白了就是服务器遇到了意外情况，不知道怎么处理了。但是这个"意外情况"可能的原因太多了，从代码bug到服务器资源不足，从数据库连接问题到配置文件错误，每一个都可能是罪魁祸首。第一步：快速定位问题范围遇到500错误，我的第一反应不是立马去翻日志，而是先搞清楚影响范围。这个很重要，因为它决定了你处理问题的优先级和方式。我一般会快速检查几个点：是所有页面都500还是只有特定页面（可以通过浏览器f12查看报错接口）是所有用户都受影响还是部分用户错误是突然出现的还是逐渐增多的记得有一次，有个业务网站突然开始报500错误。我当时就先测试了几个不同的页面，发现只有特定页有问题，首页和其他功能页面都正常。这就大大缩小了排查范围，基本可以确定是相关的功能出了问题。如果你用的是负载均衡，还要检查一下是不是某台服务器的问题。我习惯直接访问每台服务器的IP，看看是不是所有服务器都有问题。有时候可能只是其中一台服务器出了状况。深入日志分析确定了影响范围之后，就该看日志了。日志是我们排查问题最重要的线索，但是看日志也有技巧。Web服务器日志先看Web服务器的错误日志，比如Nginx的error.log或者Apache的error_log。这里通常能看到最直接的错误信息。tail -f /var/log/nginx/error.log我经常看到的错误类型有：连接超时：upstream timed out连接被拒绝：connect() failed (111: Connection refused)文件权限问题：Permission denied配置语法错误：nginx configuration test failed有一次我遇到一个很奇怪的问题，Nginx日志显示"upstream timed out"，但是应用服务器看起来运行正常。后来发现是因为某个接口的处理时间突然变长了，超过了Nginx设置的超时时间。调整了一下proxy_read_timeout就解决了。系统资源检查有时候500错误不是代码问题，而是服务器资源不够用了。这种情况下，即使代码没问题，服务器也处理不了请求。内存使用情况free -h如果可用内存很少，或者swap使用率很高，那很可能就是内存不足导致的问题。我遇到过好几次因为内存不足导致的500错误，特别是在流量突然增大的时候。还可以用top或者htop看看哪个进程占用内存最多：top -o %MEMCPU使用率topCPU使用率持续100%也会导致服务器响应缓慢或者直接返回500错误。我见过有些服务器因为某个进程死循环，CPU占用率一直是100%，导致整个网站都访问不了。磁盘空间df -h磁盘空间不足也是一个常见原因，特别是日志文件增长太快的时候。我就遇到过因为某个日志文件疯狂增长，把磁盘空间占满了，导致应用无法写入临时文件而报500错误。不同应用类型的专项排查根据我这些年的经验，不同技术栈的应用出现500错误时，排查重点还是有些区别的。Java应用排查Java应用的500错误排查，我一般从这几个方面入手：JVM内存问题Java应用最容易出现的就是内存问题，特别是OutOfMemoryError。jstat -gc <pid> jmap -histo <pid>我记得有一次，一个Spring Boot应用突然开始频繁500，通过jstat发现老年代内存使用率一直在99%以上，明显是内存泄漏了。后来用MAT分析heap dump，发现是某个缓存没有设置过期时间，导致对象越积累越多。线程池状态jstack <pid>线程池满了也会导致请求无法处理。我见过有些应用因为某个接口响应特别慢，把线程池都占满了，新的请求进来就直接500了。GC问题jstat -gc <pid> 1s如果Full GC频繁或者GC时间过长，也会影响应用响应。我遇到过一次，应用每隔几分钟就会卡顿几秒钟，用户访问就会超时报500，原因就是Full GC时间太长了。应用日志Java应用的日志通常在logs目录下，或者通过logback、log4j配置的路径：tail -f /app/logs/application.log grep "ERROR" /app/logs/application.log | tail -20常见的错误类型：数据库连接池耗尽空指针异常类加载失败配置文件读取失败PHP应用排查PHP应用的排查相对简单一些，但也有自己的特点。PHP-FPM进程状态systemctl status php-fpm ps aux | grep php-fpmPHP-FPM进程数不够或者进程死掉了，都会导致500错误。我遇到过好几次因为php-fpm配置的max_children太小，高并发时进程不够用的情况。PHP错误日志tail -f /var/log/php/error.logPHP的错误日志通常能直接告诉你问题所在：Fatal error: 致命错误，比如内存不足、语法错误Parse error: 语法解析错误Warning: 警告，可能导致功能异常内存限制PHP有memory_limit限制，如果脚本占用内存超过这个值就会报Fatal error：php -i | grep memory_limit我见过有些数据导入脚本，处理大文件时内存不够用，直接就500了。文件权限PHP应用对文件权限比较敏感，特别是上传目录、缓存目录等：ls -la /var/www/html/Python应用排查Python应用的排查重点又不太一样。WSGI服务器状态如果用的是Gunicorn或者uWSGI：ps aux | grep gunicorn systemctl status gunicornPython进程内存Python应用也容易出现内存泄漏，特别是使用了某些C扩展的时候：ps -o pid,ppid,cmd,%mem,%cpu --sort=-%mem | headDjango/Flask应用日志tail -f /var/log/django/error.log tail -f /var/log/flask/app.logPython应用常见的500错误：模块导入失败数据库连接问题模板渲染错误第三方库版本冲突我遇到过一次很坑的情况，服务器上同时跑着Python 2和Python 3的应用，结果某次系统更新后，Python 2的一些依赖包出问题了，导致应用启动失败。Go应用排查Go应用相对来说比较稳定，但也有自己的问题。Goroutine泄漏curl http://localhost:6060/debug/pprof/goroutine?debug=1如果开启了pprof，可以通过这个接口查看goroutine数量。goroutine泄漏会导致内存占用越来越高。应用日志Go应用的日志格式比较自由，通常在应用目录或者系统日志里：journalctl -u your-go-app -f tail -f /var/log/your-app.logpanic恢复Go应用如果没有正确处理panic，就会导致整个程序崩溃：defer func() { if r := recover(); r != nil { log.Printf("Recovered from panic: %v", r) } }()Node.js应用排查Node.js应用的排查也有自己的特点。进程管理器状态如果用的是PM2：pm2 status pm2 logs内存泄漏检查Node.js应用容易出现内存泄漏，特别是事件监听器没有正确移除的时候：node --inspect your-app.js然后用Chrome DevTools连接进行内存分析。事件循环阻塞const blocked = require('blocked-at'); blocked((time, stack) => { console.log(`Blocked for ${time}ms, operation started here:`, stack); });数据库连接问题排查数据库问题是导致500错误的一个大头。我遇到的数据库相关的500错误主要有几种：连接数耗尽SHOW PROCESSLIST; SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';如果连接数接近max_connections的值，就说明数据库连接池满了。这时候新的请求就会因为无法获取数据库连接而报500错误。我记得有一次双11活动，流量突然暴增，数据库连接数瞬间就满了。当时紧急调整了max_connections的值，同时优化了应用的连接池配置，才解决了问题。慢查询SHOW PROCESSLIST;如果看到很多查询处于"Sending data"或者"Copying to tmp table"状态，说明有慢查询在拖累整个数据库性能。可以开启慢查询日志来定位具体是哪些SQL语句有问题：SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 2;锁等待SHOW ENGINE INNODB STATUS;这个命令可以看到InnoDB的详细状态，包括是否有死锁或者锁等待的情况。配置文件和环境问题配置文件的问题也经常导致500错误，而且这种问题通常比较隐蔽。Web服务器配置检查Nginx或Apache的配置文件语法：nginx -t apache2ctl configtest我见过有人修改配置文件后忘记检查语法，重启服务后直接就500了。还有一种情况是配置文件的路径写错了，或者权限设置不对。应用配置应用程序的配置文件也要检查，比如数据库连接配置、缓存配置等。有时候可能是配置文件被意外修改了，或者环境变量没有正确设置。我遇到过一次很坑的情况，开发同事在测试环境修改了数据库配置，结果不小心把生产环境的配置也改了，导致应用连不上数据库，全站500。环境变量很多现代应用都依赖环境变量来配置：env | grep APP_ printenv特别是容器化部署的应用，环境变量配置错误是常见的500错误原因。网络和依赖服务问题有时候500错误不是应用本身的问题，而是依赖的外部服务出了问题。第三方API现在的应用很少是完全独立的，通常都会调用各种第三方API。如果这些API出问题了，也可能导致应用报500错误。我建议在调用第三方API的时候一定要做好异常处理和超时设置：import requests from requests.exceptions import RequestException, Timeout try: response = requests.get(api_url, timeout=5) response.raise_for_status() except Timeout: # 处理超时，返回默认值或降级处理 return default_response except RequestException as e: # 处理其他请求异常 logger.error(f"API调用失败: {e}") return error_response内部服务依赖如果是微服务架构，要检查各个服务之间的调用是否正常：curl -I http://internal-service:8080/health telnet internal-service 8080我遇到过一次，用户服务突然开始报500，排查了半天发现是依赖的订单服务挂了。这种情况下，最好是有熔断机制，避免级联故障。DNS解析有时候DNS解析出问题也会导致服务调用失败：nslookup your-service-domain dig your-service-domain我见过有些公司内部DNS服务器不稳定，偶尔会解析失败，导致应用无法连接到数据库或者其他服务。实战案例分享说了这么多理论，我来分享几个实际遇到的案例，希望能给大家一些启发。案例一：Java应用内存泄漏有一次我们的一个Spring Boot应用开始间歇性500错误，刚开始以为是偶发问题，但是随着时间推移，错误频率越来越高。排查过程是这样的：检查了应用日志，发现有OutOfMemoryError用jstat查看GC情况，发现老年代内存使用率持续上升生成heap dump进行分析，发现某个Map对象占用了大量内存代码review发现是缓存没有设置过期策略最后给缓存添加了LRU策略和过期时间，问题就解决了。这个案例告诉我们，缓存虽然能提高性能，但是一定要合理设置过期策略。案例二：PHP-FPM进程不足这是一个WordPress网站，在某次营销活动后开始频繁500错误。排查步骤：检查Nginx日志，发现大量"upstream timed out"查看PHP-FPM状态，发现进程数已经达到上限检查PHP-FPM配置，max_children设置得太小了调整配置后重启服务，问题解决这个案例说明容量规划很重要，要根据实际业务量来调整配置。案例三：Python应用模块导入失败有一次客户的一个Django应用突然开始500，但是重启后又正常了，过一段时间又开始500。最后发现是某个Python包的版本有问题，在特定条件下会导入失败。这种间歇性的问题最难排查，需要仔细分析日志中的错误模式。案例四：Go应用goroutine泄漏这是一个Go写的API服务，运行一段时间后开始出现500错误。通过pprof发现goroutine数量异常增多，最后定位到某个HTTP客户端没有正确设置超时，导致goroutine一直阻塞。案例五：Node.js事件循环阻塞一个Node.js应用，用户反馈页面加载很慢，有时候会500。用blocked模块检测发现事件循环被阻塞，原因是某个同步文件操作阻塞了事件循环。改为异步操作后问题解决。工具推荐最后推荐一些我常用的排查工具：系统监控htop: 更好用的topiotop: 磁盘IO监控nethogs: 网络流量监控dstat: 综合系统监控日志分析ELK Stack: Elasticsearch + Logstash + KibanaFluentd: 日志收集Loki: 轻量级日志系统应用监控Prometheus: 指标收集Grafana: 可视化Jaeger: 分布式追踪APM工具: New Relic、DataDog、Skywalking数据库监控pt-query-digest: MySQL慢查询分析pgbadger: PostgreSQL日志分析Redis监控: redis-cli --latency这些工具都是我在实际工作中用过的，效果还不错。当然，工具只是辅助，关键还是要有正确的排查思路。写在最后500错误虽然让人头疼，但是只要有系统的排查思路，大部分问题都能快速定位和解决。我总结的排查步骤是：快速定位影响范围分析各种日志检查系统资源根据应用类型进行专项排查检查数据库连接排查配置和环境问题检查网络和依赖服务不同技术栈的应用有不同的排查重点，Java应用重点关注JVM内存和GC，PHP应用重点关注进程和权限，Python应用重点关注模块导入和WSGI服务器，Go应用重点关注goroutine泄漏，Node.js应用重点关注事件循环阻塞。记住，排查问题的时候要保持冷静，按照步骤一步步来。不要一上来就乱改配置，那样可能会让问题变得更复杂。最重要的是，要从每次故障中学习，不断完善监控和预防措施。如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记
网络慢如蜗牛？教你几招Linux测带宽的绝活，让网络问题无处遁形！
昨天又有客户问我，说他们的应用突然变慢了，怀疑是网络问题。我问他网络带宽多少，他说不知道...这种情况我见得太多了，很多人遇到网络问题就是一脸懵，完全不知道从哪里下手。其实测网络带宽这个事情，我刚开始做运维的时候也是摸着石头过河。记得有一次领导问我们机房的带宽利用率怎么样，我愣是说不出个所以然来，那个尴尬啊...从那以后我就下定决心要把这些基本功练扎实。今天就来分享一下我这些年积累的Linux网络带宽测试经验，保证让你以后遇到网络问题不再抓瞎。speedtest-cli - 最简单粗暴的方法说到测带宽，我第一个想到的就是speedtest-cli。这玩意儿就像网页版的speedtest.net，但是可以在命令行里跑，特别适合我们这些服务器党。安装很简单：# CentOS/RHEL系列 yum install epel-release yum install python-pip pip install speedtest-cli # Ubuntu/Debian系列 apt update # 安装 pipx sudo apt install pipx pipx ensurepath # 安装应用 pipx install speedtest-cli #添加环境变量 echo 'export PATH="$PATH:/root/.local/bin"' >> ~/.bashrc source ~/.bashrc装好之后直接运行：speedtest-cli它会自动帮你选择最近的测试服务器，然后开始测试。输出大概是这样的：不过有时候自动选择的服务器不太理想，你可以手动指定。先看看附近有哪些服务器：speedtest-cli --list | grep China然后选一个延迟比较低的：speedtest-cli --server 服务器ID我记得有一次我们的业务突然变慢，用户投诉不断。我用speedtest-cli一测，发现下载速度只有平时的一半，赶紧联系运营商，原来是他们在做线路维护忘记通知我们了。iperf3 - 专业级的带宽测试工具speedtest-cli虽然方便，但有个问题就是测试的是到公网的速度。如果你想测试内网带宽，或者两台服务器之间的带宽，那就得用iperf3了。安装iperf3：# CentOS/RHEL yum install iperf3 # Ubuntu/Debian apt install iperf3iperf3需要一台服务器做服务端，一台做客户端。在服务端运行：iperf3 -s在客户端运行：iperf3 -c 服务端IP默认测试10秒钟，你也可以指定时间：iperf3 -c 服务端IP -t 30测试UDP带宽：iperf3 -c 服务端IP -u客户端结果：服务端结果：我特别喜欢用iperf3测试新部署的服务器之间的网络性能。有一次我们要在两个机房之间做数据同步，用iperf3测了一下专线带宽，发现实际速度比运营商承诺的要低不少，最后让他们重新调试了线路。wget/curl - 简单实用的下载测试有时候你就想快速测一下下载速度，不想装额外的工具，那wget和curl就够用了。用wget测试：wget -O /dev/null http://speedtest.tele2.net/100MB.zip这个命令会下载一个100MB的文件到/dev/null（相当于丢弃），然后显示下载速度。用curl测试：curl -o /dev/null http://speedtest.tele2.net/100MB.zip如果你想看更详细的信息：curl -o /dev/null -w "下载速度: %{speed_download} bytes/sec\n总时间: %{time_total}s\n" http://speedtest.tele2.net/100MB.zip这种方法的好处是可以测试到特定服务器的速度，比如你的CDN节点或者合作伙伴的服务器。nload - 实时监控网络流量有时候你不是要测最大带宽，而是想看看当前的网络使用情况，nload就很合适。安装nload：# CentOS/RHEL yum install nload # Ubuntu/Debian apt install nload运行很简单：nload它会显示一个实时的图表，显示当前的上传和下载速度。如果你有多个网卡，可以用左右箭头键切换。我经常用nload来监控服务器的网络使用情况，特别是在做数据迁移或者大文件传输的时候，可以实时看到网络利用率。iftop - 看看谁在占用带宽iftop比nload更进一步，它不仅能显示总的流量，还能显示每个连接的流量情况。安装iftop：# CentOS/RHEL yum install iftop # Ubuntu/Debian apt install iftop运行需要root权限：sudo iftopiftop的界面有点像top命令，显示当前所有网络连接的流量情况。你可以看到每个IP地址的上传下载速度，这对于排查网络问题特别有用。有一次我们的服务器网络突然变慢，用iftop一看，发现有个IP在疯狂下载数据，原来是有人在爬我们的网站...nethogs - 进程级别的网络监控如果你想知道是哪个进程在占用网络带宽，nethogs就派上用场了。安装nethogs：# CentOS/RHEL yum install nethogs # Ubuntu/Debian apt install nethogs运行：sudo nethogsnethogs会显示每个进程的网络使用情况，包括进程名、PID、用户等信息。这对于排查哪个应用程序占用带宽特别有用。一些实用的测试技巧选择合适的测试时间网络带宽测试最好在不同时间段多测几次，因为网络状况会随时间变化。我一般会在早上、中午、晚上各测一次，这样能得到比较全面的数据。注意测试方向很多人只测下载速度，忽略了上传速度。但对于服务器来说，上传速度同样重要，特别是如果你的服务器需要向用户推送大量数据的话。考虑并发连接单线程测试得到的带宽可能不能反映真实情况。可以试试多线程测试：iperf3 -c 服务端IP -P 4这个命令会启动4个并行连接进行测试。记录测试结果我建议把测试结果记录下来，建立一个基线。这样以后出现问题的时候，你就能快速判断是不是网络带宽的问题了。遇到问题怎么办如果测试结果不理想，不要慌。我的经验是按这个顺序排查：先确认是不是测试方法的问题，换个工具再测一次检查服务器的网卡配置，看看是不是限速了联系网络管理员或者运营商，确认线路状况检查防火墙和安全组设置，看看是不是有限制有一次我们的测试结果特别差，折腾了半天才发现是网卡驱动的问题，更新驱动之后速度立马就上来了。写在最后网络带宽测试看起来简单，但要做好还是需要一些经验的。我建议大家平时多练习，熟悉这些工具的使用方法。更重要的是，要建立定期测试的习惯，这样才能及时发现问题。说实话，网络问题往往是最难排查的，因为涉及的环节太多了。但只要你掌握了这些基本的测试方法，至少能快速定位问题的大概范围，这就已经成功了一半。记住，做运维最重要的就是要有数据支撑，不能凭感觉。网络慢不慢，不是你说了算，是测试结果说了算！如果这篇文章对你有帮助，别忘了点赞转发支持一下！想了解更多运维实战经验和技术干货，记得关注微信公众号@运维躬行录，领取学习大礼包！！！我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习，共同进步！公众号：运维躬行录个人博客：躬行笔记