让开发环境和生产环境跑同一套代码不难,难的是让它们跑同一套环境。特别是上了云原生之后,环境里的组件多到让人头大,镜像版本、网络策略、存储配置,稍有差池,昨晚还好好的应用今天就理直气壮地挂给你看。所以,我们需要一种办法,把环境本身也当成代码来管理,并且保证在开发、测试、生产这些地方,跑出来的环境是一模一样的。
一、为什么云原生环境总是“水土不服”?
你想想看,以前一台服务器上放一个Tomcat,配置也就那么几个文件,手改还能凑合。现在呢?一个应用拆成几十个微服务,每个服务又依赖各种配置项,什么环境变量、ConfigMap、Secret、Ingress规则。如果靠人肉去维护这些,那就等于每次部署都在碰运气。更别说云原生的底座还可能是Kubernetes集群,不同集群的版本、网络插件、存储类都可能不一样。你在开发环境起个Pod,轻轻松松;到了生产环境,镜像拉不下来、权限不足、节点污点没处理,各种千奇百怪的问题就都冒出来了。
说白了,环境不一致的根源,不是环境本身有多复杂,而是我们缺少一个“标准的描述方式”。每个人按照自己的理解去搭环境,结果自然是五花八门。要让环境一致,第一步就是把这个“描述方式”定下来。
二、Ansible凭啥能收拾这个烂摊子?
Ansible是一个自动化工具,它最讨人喜欢的一点,就是“不用装客户端”。你只需要一台控制机,通过SSH连到目标机器上,就能执行各种任务。对于云原生环境,Ansible还可以直接调用Kubernetes的API,把Pod、Service、Deployment这些资源都“安排”得明明白白。
它用的是YAML写剧本(Playbook),写出来的东西就像一份“环境说明书”。这份说明书既认识字的人能看懂,机器也能看懂。更关键的是,Ansible讲究“幂等性”,也就是说,你把这套Playbook跑一遍和跑十遍,最终环境的状态都是一样的。比如你明明已经安装了nginx,再跑一次它不会因为“已存在”就报错,而是会告诉你“一切正常”。这一点对于我们保证跨环境一致来说,简直是雪中送炭。
三、开工前,先把环境差异装进变量里
要跨环境,就得把那些会变的东西抽出来。比如开发环境和生产环境的命名空间、镜像版本、副本数,这些都不一样。我们不要在每个Playbook里硬编码,而是定义成变量,然后让Ansible根据当前在哪个环境,自动去加载对应的值。
3.1 定义不同环境的库存
我们先建一个Hosts文件,里面写上开发环境和生产环境的节点信息。这里注意,虽然云原生环境我们一般操作的是Kubernetes集群,但Ansible还是需要连到一台能访问集群的机器上(比如跳板机)。
技术栈:Ansible(INI配置)
# hosts.ini
# 这是开发环境的“入口”机器
[dev]
dev-control ansible_host=192.168.1.10
# 这是生产环境的“入口”机器
[prod]
prod-control ansible_host=192.168.2.10
3.2 用变量文件描述“环境长什么样”
然后我们创建两个变量文件,分别放在group_vars/dev.yml和group_vars/prod.yml里。Ansible会自动根据当前主机所在的组加载对应的文件。
技术栈:Ansible(YAML变量文件)
# group_vars/dev.yml
# 开发环境配置
namespace: myapp-dev
image: nginx:1.21
replicas: 1
技术栈:Ansible(YAML变量文件)
# group_vars/prod.yml
# 生产环境配置
namespace: myapp-prod
image: nginx:1.25
replicas: 3
这样,同一个Playbook,在不同的环境里就会自动使用不同的值。你不需要写两套部署脚本,只要切换一下目标主机,环境差异就全部由变量接管了。
四、写一个能“一鱼两吃”的playbook
现在我们来写核心的Playbook。这个Playbook的任务是:在Kubernetes集群里创建一个命名空间,然后部署一个Nginx应用,最后给它暴露一个Service。我们用的Ansible模块是kubernetes.core.k8s。为了让代码更清晰,我们把Deployment和Service的定义做成模板文件,然后在Playbook中通过template参数直接引用。
4.1 准备部署用的K8s配置模板
技术栈:Ansible(Jinja2模板,渲染后为YAML)
# templates/deployment.yaml.j2
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
namespace: {{ namespace }}
spec:
replicas: {{ replicas }}
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: {{ image }}
ports:
- containerPort: 80
技术栈:Ansible(Jinja2模板,渲染后为YAML)
# templates/service.yaml.j2
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: {{ namespace }}
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
4.2 主部署playbook示例
技术栈:Ansible(YAML Playbook)
# deploy.yml
- name: 部署云原生应用到K8s
hosts: "{{ target }}" # 通过命令行传入 target 来选择环境
gather_facts: no # 这里连的是跳板机,不需要收集系统信息
tasks:
- name: 确保命名空间存在
kubernetes.core.k8s:
name: "{{ namespace }}"
api_version: v1
kind: Namespace
state: present
- name: 渲染Deployment模板并创建资源
kubernetes.core.k8s:
state: present
template: deployment.yaml.j2 # 使用模板文件,自动替换变量
- name: 渲染Service模板并创建资源
kubernetes.core.k8s:
state: present
template: service.yaml.j2
你发现没有?这份Playbook里没有出现具体的环境名称,也没有出现具体的镜像版本。所有可变的值都通过namespace、image、replicas这几个变量引用。而这几个变量的值,会根据你选择的环境自动从group_vars里读取。这,就是“一套代码,到处跑”的底气。
五、跑起来,看看是不是真的“一套配置走天下”
在命令行里,执行下面这条命令,就能把应用部署到开发环境:
技术栈:Ansible(命令行)
ansible-playbook -i hosts.ini deploy.yml -e "target=dev"
这里-i指定库存文件,-e传入变量覆盖默认值。由于dev主机在hosts.ini的[dev]组,Ansible会自动加载group_vars/dev.yml,于是namespace、image、replicas统统变成了开发环境的设定。
接着部署生产环境,只需要把target换成prod:
技术栈:Ansible(命令行)
ansible-playbook -i hosts.ini deploy.yml -e "target=prod"
你发现没有?Playbook本身一个字都没改,改的只是目标环境。这正是基础设施即代码的威力:把环境差异隔离在变量里,把公共逻辑固化成代码。执行完,你可以到对应集群里检查Pod是不是按预期数量跑起来了,不过那就是另一个话题了。
六、这套方案还能用在哪些地方?
这套玩法不止能装Nginx,稍微扩展一下,几乎云原生环境的日常运维都能包进去。
第一,环境初始化。新招来的同事没有开发环境?不用再发给他一份手写文档了。把Playbook给他,让他自己跑一遍,十分钟后环境就和各地区的老员工一模一样。第二,CI/CD持续交付。代码合并后,流水线自动触发Ansible,先部署到测试环境,跑完自动化测试,再手动点一下部署到生产环境。整个过程环境配置完全一致,再也不用担心“在我电脑上明明是好的”。第三,多云管理。你的业务可能同时跑在阿里云、AWS和自建机房上,底层的Kubernetes版本不一样,网络插件也不一样。但只要抽象成变量,那么Playbook的核心逻辑就能统一管理,差异被关在变量里,不会到处乱窜。第四,灾难恢复。假如生产环境被不小心搞挂了,没关系,只要代码仓库还在,几分钟就能在另一个集群上把环境重新拉起来。这份Playbook就是你的“后悔药”。
七、优点和缺点都要摆到台面上
7.1 优点
Ansible最让人喜欢的地方就是“省事”。不用在目标机器上装Agent,控制机只需要能SSH就能干活。学习成本也不高,YAML语法简单直白,运维和开发都容易上手。而且它的幂等性特别适合做环境一致性:不管跑多少次,终态都一样,这就避免了重复执行带来的脏数据。变量机制更是天生为多环境准备的,配合group_vars,写一套代码,管理多个环境的复杂度瞬间降了下来。
7.2 缺点
但Ansible也不是银弹。它的执行方式一直被人吐槽“龟速”,因为基于SSH的串行执行,节点一多,跑一次Playbook就要等半天。另外,它不像Terraform那样有完整的“状态管理”文件,很多时候它只负责“把资源变成目标状态”,但不维护资源之间的状态关系。也就是说,你删掉了Playbook里的某个资源定义,Ansible并不会自动帮你把对应的资源从K8s里删掉,这可能让别人产生困惑。还有,它依赖SSH和Python环境,如果跳板机网络不稳定,或者目标机器Python版本太乱,你的自动化流程就容易被卡住。
八、搞生产环境前必须注意的坎儿
第一,敏感信息千万别明文存在Playbook或变量文件里。生产环境的凭证、密钥一定要用Ansible Vault加密,并且把密码放到专门的密钥管理系统里。第二,需要保持Playbook的幂等性。编写时多想想“这个Task跑一遍和跑十遍,结果是否一样”,写完后最好反复执行几次测试。第三,环境变量的校验要做足。比如开发环境用的镜像版本和生产环境不同,如果不小心把target=prod写成了target=dev,可能就把测试版本推到生产了。建议在Playbook里加一个只服务于生产环境的判断或审批门槛。第四,确保网络安全。Ansible控制机需要能连通跳板机的SSH端口,而跳板机需要能访问Kubernetes的API服务。任何一个端口不通,自动化流程就会断在黑暗中。第五,所有配置文件必须纳入Git管理。没有版本控制,就没有真正的“一致”,一旦有人手动改了某个环境,Git能帮你重新拉回正道。第六,定期演练从零开始重建环境。别等到生产故障时才想起自己从没跑过完整流程。
九、总结
说到底,Ansible能不能保障云原生环境的跨环境一致性,不在于它有多厉害,而在于你有多愿意把环境当作代码来认真对待。变量管理让你把环境差异关进笼子里,Playbook让你把部署过程固化成文字和逻辑,幂等性让你不会因为重复操作而提心吊胆。虽然它有自己的短板,但只要你合理设计目录结构、妥善管理变量和密钥、保持对生产环境的敬畏,这套方案完全能成为你日常运维的一把“万能钥匙”。环境不再是一门“玄学”,而是一份干净、可审查、可复现的工程资产。让你在开发环境和生产环境之间来回切换时,心里踏实得很。
评论
围绕“利用Ansible实现云原生环境基础设施即代码的跨环境一致性保障方案”参与讨论