
从零搭建 AtomOps 全栈运维平台4 台华为云 ECS 实战部署与踩坑实录本文属于「码动四季·开源同行」秋季征稿 —— 技术经验体系化沉淀赛道。记录开源项目 AtomOps 从开发到 4 服务器生产部署的完整实战包含架构设计、执行引擎实现、安全策略和 6 个真实踩坑案例。项目背景AtomOps 是一个面向中小企业的全栈运维管理平台托管在 AtomGitGitCode上。它不是又一个Wraps SSH 的 Web UI——核心目标是用工程化方式解决三个痛点脚本批量执行在数十台主机上并行跑 Shell/Python 脚本实时收集结果作业编排多步骤作业按顺序执行每步可指定不同脚本和目标主机定时运维Cron 表达式定时触发支持脚本/作业/巡检三种类型技术选型不追求新潮追求可维护层级技术选型理由前端Vue 3 Element Plus Vite生态成熟组件丰富后端FastAPI SQLAlchemy异步性能好自动生成 API 文档数据库MySQL 8.0 Redis 7运维熟悉稳定可靠监控Prometheus Grafana开源标配社区活跃部署Nginx systemd裸机部署不引入 K8s 复杂度一、架构设计4 服务器拓扑4 台华为云 ECS8vCPU/16GB/Ubuntu 24.04同一 VPC 内网互通┌─────────────────────────────────────────┐ │ 华为云 VPC 内网 │ ┌──────────┐ │ ┌──────────┐ ┌──────────┐ │ │ 用户 │─────┼─▶│ ecs-0001 │─▶│ ecs-0002 │ │ │ 浏览器 │ HTTP│ │ Nginx │ │ MySQL │ │ └──────────┘ │ │ FastAPI │ │ Redis │ │ │ └────┬─────┘ └──────────┘ │ │ │ │ │ ┌────▼─────┐ ┌──────────┐ │ │ │ ecs-0003 │ │ ecs-0004 │ │ │ │ Prometheus│ │ Test │ │ │ │ Grafana │ │ Client │ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────────┘职责分离原则数据库不放应用服务器监控不放数据库服务器。这样任何一台挂了其他服务仍能部分运作。内网通信避免公网暴露 MySQL/Redis 端口。二、部署实战2.1 数据库服务器ecs-0002MySQL 8.0 Redis 7Ubuntu 24.04 apt 直装# MySQLapt-getinstall-ymysql-server mysql-uroot-eALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY MyPass!;# 关键监听 0.0.0.0 让内网其他机器能连cat/etc/mysql/mysql.conf.d/zz-atomops.cnfEOF [mysqld] bind-address 0.0.0.0 innodb_buffer_pool_size 2G EOF# Redis 同理cat/etc/redis/redis.confEOF bind 0.0.0.0 requirepass RedisPass! EOF2.2 应用服务器ecs-0001后端 FastAPI 用 systemd 管理Nginx 反代# /etc/systemd/system/atomops-backend.service [Service] ExecStart/opt/AtomOps/backend/.venv/bin/uvicorn app.main:app \ --host 0.0.0.0 --port 8000 --workers 1 Restartalways KillModecontrol-group踩坑 #1一开始用--workers 2systemd 主进程追踪混乱reload 时旧 worker 不退出。改为--workers 1后问题消失。单 worker 对中小规模足够并发靠异步 I/O。Nginx 配置三个 location静态文件、API 反代、WebSocketWebShelllocation /api/ { proxy_pass http://127.0.0.1:8000; proxy_read_timeout 300s; # 脚本执行可能较慢 } location /api/coc/webshell/ws { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; # WebShell 长连接 }2.3 监控服务器ecs-0003Prometheus Grafana Alertmanager node_exporter 全套 apt 安装。踩坑 #2Grafana 官方 apt 仓库 GPG key 在 Ubuntu 24.04 上验证失败。解决方案直接下载.deb包安装绕过 apt 仓库签名问题。2.4 前端构建策略不在服务器上npm run build——服务器 Node.js 版本可能不一致构建可能失败。改为本地构建 上传 distcdfrontendnpmrun buildtarczf dist.tar.gz-Cdist.scpdist.tar.gz rootecs-0001:/tmp/sshrootecs-0001tar xzf /tmp/dist.tar.gz -C /opt/atomops-dist/三、核心执行引擎实现这是项目最核心的部分也是最容易出 bug 的地方。3.1 脚本执行并行 SSH用ThreadPoolExecutor在多台主机上并行执行fromconcurrent.futuresimportThreadPoolExecutor,as_completed _exec_poolThreadPoolExecutor(max_workers10)router.post(/scripts/{script_id}/execute)asyncdefexecute_script(script_id:int,body:ExecuteRequest):scriptdb.get(CocScript,script_id)command_build_command(script)futures{_exec_pool.submit(_run_on_host,hd,command):hdforhdintarget_hosts}results[f.result()forfinas_completed(futures)]return{results:results}脚本类型处理——Python 脚本用 heredoc 传递多行内容def_build_command(script):ifscript.script_typepython:returnfpython3 - ATOMOPS_EOF\n{script.content}\nATOMOPS_EOFreturnscript.content# shell 直接执行3.2 作业执行顺序步骤作业按steps数组顺序执行每步在所有目标主机上并行all_successTrueforstepinjob.steps:scriptdb.get(CocScript,step[script_id])futures{_pool.submit(_exec,hd,command):hdforhdinhost_dicts}step_results[f.result()forfinas_completed(futures)]ifnotall(r.get(status)successforrinstep_results):all_successFalse踩坑 #3作业执行记录的status一直是failed但实际执行都成功了。排查发现r[status]用了硬编码 key 访问个别结果 dict 缺少statuskey 导致 KeyError。改为r.get(status)后修复。3.3 定时调度APSchedulerdefload_scheduled_tasks():tasksdb.query(CocScheduledTask).filter_by(enabledTrue).all()fortaskintasks:scheduler.add_job(_run_scheduled_task,CronTrigger.from_crontab(task.cron_expr),args[task.id])踩坑 #4定时任务触发报 “脚本不存在”。根因是target_id字段语义冲突——同时用于主机选择和脚本引用。解决方案新增target_hosts字段JSON 数组专管主机选择target_id专管脚本/作业引用。这个设计错误暴露了早期数据模型考虑不周。3.4 SSH 安全策略不使用简单的AutoAddPolicy不安全也不用RejectPolicy部署时 known_hosts 未加载会全部拒绝。采用白名单 后置指纹校验def_get_client(host):# 1. 白名单检查主机指纹必须预先注册known{h[ip]:h[fingerprint]forhinlist_known_hosts()}ifhost[ip]notinknown:raiseSSHException(f主机{host[ip]}未注册)# 2. AutoAddPolicy 建立连接client.connect(**kwargs)# 3. 连接后校验实际指纹 vs 记录指纹fp_get_fingerprint(client.get_transport())ifknown[host[ip]]!fp:raiseSSHException(指纹不匹配可能中间人攻击)踩坑 #5新部署的服务器 IP 未注册到 known_hosts所有脚本执行都报错。写了个自动注册脚本通过公网 IP 获取指纹用内网 IP 注册因为后端通过内网 SSH。四、前端交互设计4.1 统一列表页 执行对话框所有 CRUD 页面复用ListPage.vue通过菜单配置驱动// menu.js 配置驱动{path:/coc/scripts,page:list,endpoint:/api/coc/scripts,columns:[{prop:name,label:脚本名称,type:text},{prop:script_type,label:类型,type:select,options:[[Shell,shell],[Python,python]]},{prop:content,label:内容,type:textarea},],actions:[{label:执行,exec:script}]}执行对话框支持多选主机、实时结果展示el-select v-modelexecHosts multiple filterable el-option v-forh in allHosts :label${h.hostname} (${h.ip}) / /el-select4.2 作业步骤编辑器不用原始 JSON 编辑自定义type: steps编辑器div v-for(step, i) in jobSteps el-input v-modelstep.name placeholder步骤名称 / el-select v-modelstep.script_id focusloadScripts el-option v-fors in scriptOptions :labels.name :values.id / /el-select /div五、最棘手的问题登录后闪退这个问题花了最长时间排查。现象用户登录成功页面跳转到首页后立即闪退回登录页。排查过程✅ 登录 API 返回 200token 正常✅ 前端localStorage已设置登录标志❌ 跳转后 API 请求全部返回 401401 拦截器清除 localStorage → 跳回登录页 → 闪退根因后端设置 auth cookie 时response.set_cookie(keyaccess_token,valuetoken,httponlyTrue,securesettings.is_production,# 生产模式下 Truesamesitelax,)生产模式下secureTrue但前端通过HTTP非 HTTPS访问。浏览器规范拒绝存储 Secure cookie 在 HTTP 连接上。所以 cookie 根本没存进去后续请求自然没有认证。修复secureFalse,# HTTP 部署时必须 False教训Securecookie 标志只适用于 HTTPS 部署。HTTP 环境下设置SecureTrue会导致 cookie 被浏览器静默丢弃没有任何错误提示极难排查。六、部署验证结果端到端测试全部通过脚本执行系统健康检查 × 2 台主机 → ✅ 2/2 success 定时任务脚本每日健康检查 × 4 台主机 → ✅ 4/4 success 定时任务作业部署前检查2 步骤× 4 台主机 → ✅ 全部成功 作业历史记录#2 statussuccess result共 2 步, 全部成功外部访问验证服务地址状态前端http://1.92.103.196✅ 200Grafanahttp://1.94.202.193:3000✅ 302Prometheushttp://1.94.202.193:9090✅ 302七、总结与反思做对了什么职责分离4 台服务器各司其职不混部服务内网通信MySQL/Redis 不暴露公网安全组最小权限白名单 SSH指纹校验防中间人攻击比 AutoAddPolicy 安全配置驱动前端菜单配置生成 CRUD 页面新增模块只需加配置本地构建前端避免服务器环境不一致踩了哪些坑坑根因教训登录闪退Securecookie HTTPHTTP 部署必须secureFalse作业状态错误硬编码 key 访问用.get()防 KeyError定时任务找不到脚本target_id语义冲突字段不要复用SSH 拒绝连接known_hosts 未注册部署后自动注册指纹Grafana 安装失败GPG key 问题直接用 .deb 包Nginx 404f-string 花括号配置文件不走 f-string给开源新手的建议先跑通再优化第一版用最简单的方案跑通了再迭代部署文档要趁热写部署完立刻记录过两天就忘细节了踩坑是最好的老师每个坑都值得记录比成功经验更有价值安全要前置考虑cookie secure、SSH 指纹、CSRF 防护不要等上线再补项目仓库https://gitcode.com/cpyaxjq/AtomOps部署文档仓库docs/ECS_4SERVER_DEPLOYMENT.md包含完整部署步骤和运维手册本文为 AtomGit「码动四季·开源同行」秋季征稿投稿属于技术经验体系化沉淀赛道。如果觉得有帮助欢迎到仓库点个 Star ⭐