尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

自动化运维三件套:Ansible+Shell+CI/CD实战

自动化运维三件套:Ansible+Shell+CI/CD实战 网上到处是“一口气学完 Ansible Shell CI/CD”的标题但很多人跟着学完依然不知道这三样东西到底怎么配合。有人把 Ansible 当成 Shell 的替代品结果越学越迷糊有人天天背 Linux 常用命令面试时却被一个“讲讲你的部署流程”问住。先给一个明确判断在自动化运维的日常里Ansible 负责批量指挥Shell 负责单机细节CI/CD 负责自动触发。三者不是同一个层面的工具缺一个都会让自动化停在半路。这篇文章会按照“为什么要学 → 三件套分工 → 环境搭建 → 实例落地 → 排错建议”的顺序展开适合零基础转行运维、初级运维以及后端开发中需要自己管服务器的读者。目标很具体读完你能跑通一个最小的自动化部署流程而不是只记住几个命令。我会尽量少讲抽象概念多给能直接复制的命令和配置涉及版本的地方以当前主流写法为准不绑定某个具体旧版本。1. 这篇文章真正要解决的问题很多想入行运维的朋友学习路径非常相似先刷 Linux 命令再学 Shell然后发现招聘 JD 里全是 Ansible 和 CI/CD。等真正进了项目组又要面对几十台服务器的手工部署、环境不一致、漏配项、大版本升级回滚无门。问题往往不是命令不够熟而是缺少一套把重复操作变成一次性任务的体系。具体来说这三样东西分别解决不同的问题工具/概念解决的核心问题典型场景Shell单台机器上的具体操作备份日志、启动服务、清理临时文件Ansible多台机器批量执行同一套操作给 10 台服务器统一复制配置、调整权限CI/CD当代码变更时自动触发整套流程代码提交后自动构建、自动部署、自动通知很多人以为自动化运维就是“会写 Shell”于是花了大量时间背命令、刷脚本题。但真正进入生产环境后你会发现单机脚本写得再漂亮如果没法批量执行仍然要在一台台机器上手工跑批量执行做得再好如果每次都要人工触发也算不上真正的自动化。这篇文章要帮你做的事情有三件理清 Ansible、Shell、CI/CD 各自的位置不再混淆。从零搭建一个最小环境完成批量复制、批量授权、批量执行脚本。用一个综合示例把“提交代码 → 自动触发 → 批量部署”这条链路跑通。如果你还是零基础完全没关系。往下看的第一步不需要任何自动化经验只需要一台 Linux 机器或者一台虚拟机。2. 自动化运维三件套Ansible、Shell、CI/CD 的关系用一个现实场景来说清楚假设线上有 10 台服务器这次发版需要更新一个配置文件、重启 Web 服务、检查健康状态。如果只靠 Shell你会写一个脚本然后登录到每一台服务器上执行。10 台还可以接受100 台呢而且每台机器的目录结构、用户权限只要有一点不同脚本就可能出错。Ansible 改变的是这一层它有一台控制节点通过 SSH 连接所有受管节点把你要执行的“同一个任务”批量发过去。Ansible 本身不需要在受管节点安装任何客户端只要 SSH 能通就行。CI/CD 改变的是更上一层部署动作不再需要手动敲命令而是由代码仓库触发。比如你往 Git 主分支推了一次代码GitLab Runner 自动拉取代码、执行测试、调用 Ansible 完成部署。所以三者关系可以用一句话概括Shell 是你“怎么做”的细节。Ansible 是你“怎么批量做”的框架。CI/CD 是“什么时候自动做”的开关。常见的误区有两个。第一个误区以为会了 Ansible 就不需要学 Shell。实际上 Ansible 的很多模块底层调用的就是系统命令而且文件中包含复杂判断、循环、交互逻辑时写 Shell 脚本仍然是最直接的方式。第二个误区以为 CI/CD 只是开发的事。实际上发布流水线本身就是运维工作的一部分只是以前叫“上线流程”现在统一叫 CI/CD。小结论三件套组合起来才是一个完整的自动化运维最小闭环。单独学任何一样到了真实项目里都会觉得差点什么。3. 环境准备与前置条件进入实操之前先准备好最小环境。3.1 拓扑结构自动化运维的最少实验环境需要两台 Linux 机器控制节点用来安装 Ansible、存放 Inventory 和 Playbook。受管节点被 Ansible 管理的目标机器。如果只有一台机器可以用虚拟机VMware / VirtualBox或云服务器再开一台。也可以在一台 Linux 机器上通过容器模拟多个受管节点但初学阶段不建议先折腾容器避免把问题复杂化。操作系统建议控制节点使用 CentOS 7、Rocky Linux、Ubuntu 20.04 或国产 Linux 发行版均可。Ansible 基于 SSH 管理对受管节点的发行版没有强限制只要能通过 SSH 登录即可。3.2 安装 Ansible在控制节点上执行安装命令。CentOS / Rocky 系sudo yum install -y epel-release sudo yum install -y ansibleUbuntu / Debian 系sudo apt update sudo apt install -y ansible如果你的控制节点上已经配置好了 Python3 环境也可以使用 pip 方式安装并建议放入虚拟环境避免污染系统 Pythonpython3 -m venv ~/ansible-venv source ~/ansible-venv/bin/activate pip install ansible安装完成后验证版本ansible --version如果能看到 ansible core 版本号和 python 版本说明安装成功。3.3 配置 SSH 免密登录Ansible 通过 SSH 连接受管节点所以必须先保证控制节点能直接登录受管节点。在控制节点生成 SSH 密钥ssh-keygen -t ed25519 -C ansible-control一路回车就会生成默认密钥。然后把公钥复制到受管节点ssh-copy-id -i ~/.ssh/id_ed25519.pub root192.168.1.101这里的root192.168.1.101换成你自己的受管节点 IP 和登录用户。复制过程中需要输入一次密码之后就可以密钥登录了。手动测试一下ssh root192.168.1.101 uname -a如果不需要输入密码就能看到内核信息说明免密配置成功。3.4 配置 Inventory 主机清单Inventory 是 Ansible 管理主机的清单文件。默认路径是/etc/ansible/hosts但更推荐在项目目录里单独维护方便入库管理。创建一个项目目录和 inventory 文件mkdir -p ~/automation-demo cd ~/automation-demo vim inventory.ini内容如下[web] node1 ansible_host192.168.1.101 ansible_userroot node2 ansible_host192.168.1.102 ansible_userroot [db] db1 ansible_host192.168.1.201 ansible_userdeploy这里的[web]和[db]是分组名可以在 Playbook 或命令中直接指定组。node1、node2是主机别名ansible_host是真实 IPansible_user是登录用户。如果受管节点 SSH 端口不是默认的 22可以追加ansible_port22或直接改成对应端口。配置完成后测试控制节点能不能批量连通ansible all -i inventory.ini -m ping每个节点如果返回ping: pong说明环境已经就绪。小结论Ansible 不需要在受管节点安装 agent只要控制节点能 SSH 登录批量管理就能成立。这个特性让它特别适合混合环境包括不同发行版和国产化服务器。4. Ansible 基础操作从 Ad-Hoc 到 PlaybookAnsible 有两种常用使用方式Ad-Hoc 临时命令和 Playbook 剧本。4.1 Ad-Hoc 临时命令Ad-Hoc 适合临时快速执行一条命令。比如查看所有 web 节点的运行时间ansible web -i inventory.ini -m command -a uptime-m指定模块command是最基础的模块直接执行命令。-a是模块参数。如果命令中包含管道符、重定向等特殊符号command模块可能不支持需要用shell模块ansible web -i inventory.ini -m shell -a df -h | grep /data对于最常见的连通性测试使用ping模块ansible all -i inventory.ini -m ping4.2 批量复制文件并授权 777 权限“用 ansible 复制文件到所有节点并授权 777 权限”是运维群里出现频率很高的需求。这也是最容易踩坑的一个需求。先在控制节点上创建一个测试文件echo app_config_v1.0 /tmp/app.conf然后通过 Ansible 批量复制到所有节点ansible all -i inventory.ini -m copy -a src/tmp/app.conf dest/opt/app.conf mode0777 ownerroot grouproot命令执行后Ansible 会先把文件传输到每个受管节点再校验目标文件内容。如果文件已经是最新内容就不会重复复制这是 copy 模块自带的幂等行为。这里真正要提醒的是生产环境不要无脑用 0777。777 表示任何用户都可读、可写、可执行这在多租户或安全要求较高的环境中非常危险。更合理的做法是显式指定用户、用户组和权限比如普通配置文件用0644可执行脚本用0755ansible all -i inventory.ini -m copy -a src/tmp/app.conf dest/opt/app.conf mode0644 ownerroot grouproot学习阶段你可以用 0777 体验效果但要把“最小权限”这个意识刻进日常习惯里。4.3 使用 file 模块创建目录和调整权限复制文件之外还有一类高频操作是创建目录、调整文件属主和权限。Ansible 的file模块就是为此设计的ansible all -i inventory.ini -m file -a path/opt/myapp statedirectory mode0755 ownerroot grouprootstatedirectory表示最终要得到一个目录如果目录不存在就创建已存在就不动。这也是幂等思维不是“执行创建命令”而是“确保最终状态是指定状态”。4.4 编写第一个 PlaybookAd-Hoc 命令适合验证但正式流程要固化下来应该写成 Playbook。以“复制配置文件并重启 nginx”为例子创建deploy.yml--- - name: Deploy app config to web nodes hosts: web become: yes tasks: - name: Copy config file ansible.builtin.copy: src: /tmp/app.conf dest: /opt/app.conf mode: 0644 owner: root group: root - name: Ensure nginx is restarted ansible.builtin.systemd: name: nginx state: restarted运行 Playbookansible-playbook -i inventory.ini deploy.yml执行过程中Ansible 会对每个节点、每个 task 显示状态。第一次执行时通常会有changed第二次再执行只要文件内容和系统状态没有变化就会变成ok。这个“重复执行结果稳定”的能力是 Ansible 最核心的价值也是它比裸写 Shell 更适合作业管理的根本原因。小结论Ad-Hoc 命令适合“临时看一眼”Playbook 适合“固化流程”。真实项目中凡是需要重复执行的操作都建议直接写成 Playbook。5. Shell 脚本入门自动化运维的最后一公里Ansible 可以完成批量调度但具体到单机上的复杂逻辑比如备份、循环清理、健康检查还是需要 Shell 脚本。学习 Shell 不是为了替代 Ansible而是为了让 Ansible 执行的任务细节更可控。5.1 脚本基础结构创建一个最简单的备份脚本backup_logs.sh#!/bin/bash APP_DIR/opt/myapp TS$(date %Y%m%d%H%M%S) if [ ! -d $APP_DIR/backup ]; then mkdir -p $APP_DIR/backup fi for file in /data/logs/*.log; do if [ -f $file ]; then cp $file $APP_DIR/backup/$(basename $file).$TS fi done echo backup finished at $TS几个基础点必须掌握第一行#!/bin/bash是脚本解释器声明。变量赋值时等号两边不能有空格引用变量建议加双引号避免空格问题。date %Y%m%d%H%M%S或$(date %Y%m%d%H%M%S)都是取当前时间。for循环用来遍历文件列表if [ -f $file ]用来判断是不是常规文件。执行前先授权chmod x backup_logs.sh ./backup_logs.sh5.2 shift 参数位移在写运维脚本时经常需要逐个处理命令行参数。shift命令会左移位置参数常用于处理不定长参数#!/bin/bash while [ $# -gt 0 ]; do echo current arg: $1 shift done执行bash test_args.sh a b c会依次输出 a、b、c。这个技巧在处理参数较多的发布脚本中很实用。5.3 错误处理严格模式与忽略错误Shell 脚本默认情况下某条命令失败并不会中断脚本这可能让错误一路累积到脚本末尾。更稳妥的做法是在脚本开头启用严格模式#!/bin/bash set -euo pipefailset -e命令出错立即退出。set -u使用未定义变量时报错。pipefail管道命令中只要有一个命令失败整个管道就返回失败。但有些场景下你确实希望“忽略错误继续执行”。比如清理不存在的历史文件删除失败不应该中断脚本。这时有两种常见写法rm -f /tmp/nonexist || true或者临时关闭严格模式再恢复set e curl -s http://example.com/health /dev/null set -e“忽略错误继续执行”和“出错立即退出”并不矛盾关键是明确区分哪些命令失败是可接受的哪些是必须中断的。5.4 在 Ansible 中调用 Shell 脚本Shell 脚本写好后可以让 Ansible 批量执行。Ansible 官方更推荐script模块它把控制节点上的脚本传到受管节点并执行- name: Run backup script on all nodes ansible.builtin.script: backup_logs.sh如果要执行一个已经存在于受管节点上的脚本或者执行一段复杂命令可以使用shell模块- name: Execute deploy script ansible.builtin.shell: | bash /opt/myapp/deploy.sh args: executable: /bin/bash小结论Shell 是 Ansible 的“细节实现层”。把重复性操作封装成函数和脚本后Ansible 只需要负责“把脚本送到哪里、在什么时候执行”。6. CI/CD 与自动化运维从手动执行到自动触发现在到了最容易被误解的部分CI/CD 到底和运维有什么关系以前上线一套系统流程通常是开发提交代码 → 运维手动拉取包 → 传给服务器 → 停止服务 → 替换文件 → 启动服务。整个流程依赖人工操作最容易出错的阶段就是“手动传包”和“手动执行”。CI/CD 把这一串动作变成了自动化流水线CIContinuous Integration代码提交后自动完成编译、构建、单元测试。CDContinuous Delivery / Continuous Deployment构建产物自动进入测试环境通过验证后自动部署到生产。对运维来说发布流水线就是 CI/CD 的一种落地形式。常见工具包括 GitLab CI/CD、Jenkins、GitHub Actions。本文以 GitLab CI/CD 为例因为它在代码仓库里直接写流水线配置对入门者比较直观。6.1 一个最小 GitLab CI/CD 配置在项目根目录创建.gitlab-ci.ymlstages: - build - deploy build-job: stage: build script: - echo Building project... - tar -czf app.tar.gz app.conf deploy.yml scripts/ deploy-job: stage: deploy script: - ansible-playbook -i inventory.ini deploy.yml only: - main几个核心概念stages定义流水线的阶段。build-job、deploy-job两个 job 分别属于不同阶段。scriptjob 要执行的命令。only: - main只在 main 分支变更时执行部署阶段。当代码提交到 GitLab 后Runner 会检测到.gitlab-ci.yml自动创建一条 pipeline。build 阶段完成打包deploy 阶段调用之前写好的 Ansible Playbook。CI/CD 不是银弹。它要求底层的部署过程本身可重复、可回滚否则流水线只会把错误执行得更快。这也是为什么这篇文章强调先学 Ansible 和 Shell再接入 CI/CD。7. 综合示例从 Git 提交到多节点自动部署前面的内容都是单项拆解这一节把它们串起来。我会用一个最小项目来演示代码提交后自动触发 Ansible 将部署脚本分发到两台 Web 服务器并执行。7.1 项目结构automation-demo/ ├── inventory.ini ├── deploy.yml ├── scripts/ │ └── deploy_app.sh └── .gitlab-ci.yml7.2 inventory.ini[web] node1 ansible_host192.168.1.101 ansible_userroot node2 ansible_host192.168.1.102 ansible_userroot7.3 deploy.yml--- - name: Deploy application to web nodes hosts: web become: yes vars: app_dir: /opt/myapp tasks: - name: Ensure app directory exists ansible.builtin.file: path: {{ app_dir }} state: directory mode: 0755 - name: Copy application script ansible.builtin.copy: src: scripts/deploy_app.sh dest: {{ app_dir }}/deploy_app.sh mode: 0755 - name: Execute application deployment script ansible.builtin.shell: cmd: bash {{ app_dir }}/deploy_app.sh register: deploy_result - name: Show deployment result ansible.builtin.debug: msg: {{ deploy_result.stdout }}这个 Playbook 做了什么先保证/opt/myapp目录存在。把scripts/deploy_app.sh复制到所有 web 节点。在每台节点上执行该脚本。最后打印脚本输出。7.4 scripts/deploy_app.sh#!/bin/bash set -e APP_DIR/opt/myapp TS$(date %Y%m%d%H%M%S) echo deploy start at ${TS} if [ ! -d ${APP_DIR}/backup ]; then mkdir -p ${APP_DIR}/backup fi if [ -f ${APP_DIR}/app.conf ]; then cp ${APP_DIR}/app.conf ${APP_DIR}/backup/app.conf.${TS} fi cp /tmp/app.conf ${APP_DIR}/app.conf if [ -f /usr/bin/systemctl ]; then systemctl reload nginx || true fi echo config updated to version: ${TS}这个脚本演示了部署中的两个常见动作先备份旧配置再更新新配置同时尝试重载服务。set -e用来保证关键步骤失败时立刻退出。7.5 .gitlab-ci.ymlstages: - deploy deploy-to-web: stage: deploy script: - ansible-playbook -i inventory.ini deploy.yml only: - main tags: - shell整个流程是这样的开发人员将修改后的文件提交并 push 到 GitLab。GitLab Runner 检测到 main 分支更新启动 pipeline。deploy job 在 Runner 上执行ansible-playbook。Ansible 连接两台 web 节点按 Playbook 完成目录创建、脚本分发、脚本执行。受管节点上的 Shell 脚本完成备份、更新配置、重载服务。这个例子虽然简单但已经把 Ansible、Shell、CI/CD 三者完整串联。8. 运行结果与效果验证在本地手动模拟整个流程可以先不接入 GitLab直接运行cd ~/automation-demo ansible-playbook -i inventory.ini deploy.yml -v预期输出大致如下TASK [Ensure app directory exists] ******************************** ok: [node1] ok: [node2] TASK [Copy application script] ************************************ changed: [node1] changed: [node2] TASK [Execute application deployment script] ********************** changed: [node1] changed: [node2] TASK [Show deployment result] ************************************* ok: [node1] { msg: deploy start at 20250801120000\nconfig updated to version: 20250801120000 } ok: [node2] { msg: deploy start at 20250801120000\nconfig updated to version: 20250801120000 } PLAY RECAP ******************************************************** node1 : ok4 changed3 unreachable0 failed0 node2 : ok4 changed3 unreachable0 failed0看到unreachable0和failed0说明两台节点都成功执行。进一步验证受管节点上的结果ssh root192.168.1.101 ls -l /opt/myapp cat /opt/myapp/app.conf应该能看到部署脚本和最新的配置文件。接入 CI/CD 后验证方式变成在 GitLab 项目页面查看 Pipeline 状态。点击 deploy job 查看日志。日志中出现PLAY RECAP ... failed0即为成功。如果失败排查顺序建议是先看 Ansible 报错在哪个 task再检查受管节点权限和目录最后看 Runner 环境是否安装了 ansible。小结论验证要分成“机器状态验证”和“流水线状态验证”两个层面。机器上文件正确流水线日志干净才算真正完成。9. 常见问题与排查思路实操过程中最容易遇到的问题整理如下问题现象可能原因排查方式解决方案ansible all -m ping显示 unreachableSSH 端口、用户、密钥不正确控制节点手动执行ssh userhost检查 inventory 配置重新配置免密登录出现Host key verification failed控制节点没有受管节点的主机指纹查看~/.ssh/known_hosts首次连接手动确认或使用ssh-keyscan导入copy 模块执行后权限不是期望值mode 写法错误或受到 umask 影响在受管节点执行ls -l查看实际权限在 copy 参数中显式写mode: 0644Playbook 第二次执行还是 changed任务写得不幂等例如每次都执行 shell 命令对 Playbook 使用--check观察改用 copy、file、lineinfile 等幂等模块脚本报bad interpreter脚本文件是 CRLF 换行符执行file scripts/deploy_app.sh查看格式用sed -i s/\r$// file或者 dos2unix 转换CI/CD 中 ansible 命令找不到Runner 环境没有安装 ansible或 PATH 不对在 job 日志中执行which ansible在 Runner 上安装 ansible或在流水线中使用专用镜像Shell 脚本因某条命令失败而中断启用了set -e但该命令失败是可接受的查看脚本退出码和日志对可容忍失败的命令使用command || true或临时set eAnsible 执行结果全是 ok但环境没有变化目标状态已经在预期状态幂等生效手动检查目标文件修改源文件内容后重新执行10. 最佳实践与工程建议自动化运维项目有一个特点前期搭环境很快后期维护成本主要来自“不可控的约定”。下面这些建议是踩过不少坑后的经验整理。10.1 使用变量和 Inventory 分离避免写死 IPPlaybook 中尽量不要写死 IP、路径、端口。把环境信息放进 Inventory 和变量文件把逻辑写在 Playbook 中。这样测试环境、生产环境只需要切换不同 Inventory 文件而不是改 Playbook。10.2 敏感信息加密SSH 密码、应用密钥、数据库账号不应当明文写在 Git 里。Ansible 自带ansible-vault可以对变量文件加密ansible-vault encrypt secrets.yml ansible-playbook deploy.yml --ask-vault-passwordCI/CD 中可以将 vault 密码配置为 GitLab CI/CD 的变量避免把密码写进代码仓库。10.3 最小权限原则复制文件、创建目录时先想清楚“运行进程的用户是谁”再设置权限。生产环境把配置文件设为 777等同于给攻击者留下后门。建议配置文件统一0644可执行脚本统一0755目录根据实际需要设置。10.4 能用模块就不要用裸命令Ansible 的好处在于模块化、幂等化。比如复制文件应该用copy模块而不是在shell模块里写cp。模块会帮你处理文件变化检测、权限设置、失败重试裸命令则没有这些保证。10.5 脚本也要记录日志Shell 脚本里执行的关键步骤建议把时间戳和操作内容写到日志文件。否则一台机器出问题时你只能靠猜。最小日志写法LOG_FILE/var/log/deploy.log echo $(date %Y-%m-%d %H:%M:%S) deploying app... $LOG_FILE10.6 所有配置和脚本都入库Playbook、Shell 脚本、Invenotory 结构都放进 Git 仓库。任何变更走 Git才可能有审计、回滚和团队协作。第一次用 Git 管理运维脚本时可能会觉得麻烦但出过一次“服务器上脚本被别人修改过”的问题你就会理解版本控制的价值。10.7 先小批量验证再全量执行即使已经写好 Playbook也不要第一次就在 50 台机器上执行。可以先限制到一台测试机ansible-playbook -i inventory.ini deploy.yml --limit node1确认无误后再去掉限制。10.8 自动化运维的未来边界Kubernetes 普及之后容器内的发布已经由 Deployment、Helm 等工具接管。但云主机、裸机、边缘节点、数据库服务器、网络设备的初始化配置仍然需要 Ansible 和 Shell。自动化运维的方向不是被容器取代而是越来越走向“平台化 可观测”即把批量配置、自动发布、监控告警、日志采集统一起来。11. 总结与后续学习方向这里把文章核心串一遍Shell 解决单机细节Ansible 解决批量调度CI/CD 解决自动触发。三者层层递进共同构成自动化运维的最小链路。建议你接下来做这样一组实验准备控制节点 一台受管节点。用 Ansible 批量复制文件并调整权限。写一个带参数、有错误处理的 Shell 脚本交给 Ansible 批量执行。把整个部署流程整理成 Playbook。接入 GitLab CI/CD 或 GitHub Actions体验代码提交后自动部署。后续可以继续深入的方向包括Ansible Roles 角色化、Jinja2 模板渲染、ansible-vault 加密实践、Jenkins 流水线、Prometheus 监控、日志收集系统等。面试时最常被追问的“Playbook 幂等性怎么保证”“一条发布流程包含哪些环节”在这篇文章里都已经埋下答案。如果只能记住一句话那就是**先写出一个能随时重跑的部署脚本再谈自动化。**运维自动化从来不是某个工具的神话而是一套你自己能解释清楚、能重复执行的流程。
返回列表