news 2026/7/5 18:10:38

SaltStack Formula自动化构建AWS VPC基础设施

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SaltStack Formula自动化构建AWS VPC基础设施

1. 项目概述:用SaltStack自动化构建AWS VPC——不是写脚本,是建基础设施的“施工图纸”

你有没有在AWS控制台里点过上百次鼠标,只为配好一个VPC?子网、路由表、NAT网关、安全组、IGW、EIP……每新建一个环境,都要重复一遍这套“点点点”流程。更糟的是,开发、测试、预发、生产四套环境配置稍有差异,上线前一小时发现测试环境少开了一个端口,临时改配置,心跳加速手心冒汗——这种经历,我干了七年运维和云平台工程,至少踩过23次坑。SaltStack VPC公式(SaltStack Formulas)这个标题背后,根本不是教你怎么写几行YAML,而是一套把AWS网络基础设施当“建筑蓝图”来管理的方法论:它让VPC从“手动搭积木”变成“自动浇筑混凝土”,所有配置可版本化、可复现、可审计、可回滚。关键词里的AWS VPC是目标对象,SaltStack是执行引擎,而Formulas才是灵魂——它不是代码,是声明式基础设施的“配方说明书”。适合三类人:正在被多环境网络配置压得喘不过气的SRE;想把IaC落地但又不想被Terraform状态文件绑架的中小团队;以及刚学完AWS基础、正卡在“怎么让配置不随人走”的DevOps新人。它解决的不是“能不能做”,而是“能不能每次做得一模一样,且出了问题5分钟内拉出上一版重来”。我去年用这套方案重构了公司6个业务线的VPC体系,部署时间从平均47分钟压缩到92秒,配置漂移率归零。下面拆解的,全是我在真实产线反复打磨过的硬核细节。

2. 整体设计思路与方案选型逻辑:为什么是SaltStack Formula,而不是Terraform或CloudFormation?

2.1 核心矛盾:声明式 vs. 过程式,谁更适合企业级VPC治理?

很多人第一反应是:“VPC不就该用Terraform吗?”——这恰恰是最大误区。Terraform强在资源编排,弱在状态治理。举个真实案例:某次紧急修复,运维直接在控制台删掉了一个被Terraform管理的NAT网关,Terraform下次apply时不会报错,而是默默重建一个新NAT并更新路由表——结果旧NAT的流量还在跑,新NAT空转,监控告警全失效。我们花了38分钟定位,根源就是Terraform的状态文件和真实云环境脱节。而SaltStack Formula的设计哲学完全不同:它不维护“状态快照”,而是持续校验+强制收敛。Salt的state.apply命令每次执行,都会调用AWS API实时查询当前VPC结构(比如aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-xxxx"),再比对Formula中定义的期望状态(如cidr_block: 10.10.20.0/24),只对差异项执行操作。这意味着:哪怕有人偷偷在控制台改了安全组规则,下一次state.apply就会自动把它打回原形。这不是“防君子不防小人”,而是把人为误操作纳入系统防御闭环。

2.2 SaltStack Formula的不可替代性:YAML即文档,Git即审计日志

SaltStack Formula本质是标准化的YAML模板集合,目录结构严格遵循约定:

saltstack-formula-aws-vpc/ ├── vpc/ # 主模块 │ ├── init.sls # 入口文件,定义VPC核心参数 │ ├── subnets.sls # 子网定义(公有/私有/数据库专用) │ ├── routing.sls # 路由表与关联 │ └── security.sls # 安全组规则 ├── pillar/ # 敏感数据隔离区(VPC ID、密钥等) │ └── example.sls # 环境变量示例 └── README.md # 配方使用说明(含参数表)

这个结构的价值在于:YAML文件本身既是代码,也是架构文档。开发看subnets.sls就能知道生产环境有3个私有子网,每个子网的AZ分布和CIDR;安全团队直接审计security.sls里的ingress_rules列表,确认是否禁用了SSH公网访问。更重要的是,所有变更必须走Git PR流程——谁在什么时候修改了哪个子网的ACL规则,Git历史里清清楚楚。我们曾用这个特性快速定位一次合规审计问题:安全团队发现某VPC的数据库子网意外开放了3306端口,通过git blame vpc/subnets.sls直接查到是DBA在凌晨2点合并的PR,5分钟内回滚。这种“代码即审计日志”的能力,是Terraform的tfstate文件永远做不到的。

2.3 为什么不用CloudFormation?——模板膨胀与调试地狱

CloudFormation模板动辄上千行JSON/YAML,一个VPC模板常包含200+行参数定义。更致命的是调试体验:CFN堆栈创建失败,错误信息只显示CREATE_FAILED,你需要翻10层嵌套日志才能找到是哪个安全组规则语法错了。而SaltStack Formula的调试是线性的:salt-call state.apply vpc.subnets --log-level=debug,输出会清晰告诉你“第47行:map.jinja未找到vpc_cidr变量”,甚至标出具体文件路径。我们做过对比测试:同样配置一个含6个子网、3个路由表、2个NAT网关的VPC,CFN模板维护成本是SaltStack Formula的3.2倍(基于Jira工时统计)。尤其当需要动态生成子网CIDR(如根据VPC主CIDR自动计算10.10.0.0/16下的10.10.10.0/2410.10.20.0/24),SaltStack的Jinja2模板引擎天然支持数学运算,CFN却要写复杂的Fn::SelectFn::Split组合,可读性归零。

2.4 方案边界:SaltStack Formula管什么,不管什么?

必须划清红线:SaltStack Formula只负责基础设施的静态结构定义,绝不碰应用层逻辑。它会确保:

  • VPC存在且CIDR为10.10.0.0/16
  • us-east-1a有公有子网10.10.10.0/24,关联IGW
  • us-east-1b有私有子网10.10.20.0/24,路由指向NAT网关
  • 默认安全组禁止所有入站流量

但它绝不会

  • 部署EC2实例(那是ec2-formula的事)
  • 配置RDS参数组(那是mysql-formula的职责)
  • 管理IAM策略(需单独iam-formula

这种“单一职责”设计,让每个Formula像乐高积木一样可插拔。我们生产环境同时运行着aws-vpc-formulaaws-eks-formulaaws-rds-formula,它们通过Pillar数据共享VPC ID和子网ID,但彼此零耦合。上周升级EKS集群时,我只改了aws-eks-formula的Kubernetes版本参数,VPC结构纹丝不动——这才是企业级IaC该有的稳定性。

3. 核心细节解析与实操要点:从YAML到真实VPC的12个关键决策点

3.1 VPC主CIDR规划:别迷信10.0.0.0/8,用/16才是生产级起点

新手常犯的致命错误:为图省事用10.0.0.0/8作为VPC CIDR。看似空间大,实则埋雷。AWS要求VPC CIDR必须是连续地址块,而10.0.0.0/8包含1677万个IP,其中10.0.0.0/16(65536个IP)常被本地IDC占用。一旦未来要建VPN连接,两个10.0.0.0/16网段必然冲突。我们的生产规范是:所有VPC强制使用/16掩码,且起始地址避开常见网段。例如:

# pillar/vpc.sls vpc: cidr: 10.10.0.0/16 # ✅ 避开10.0.0.0/16(IDC常用)、172.16.0.0/12(Docker默认) name: prod-vpc

计算依据:10.10.0.0/16提供65534个可用IP,足够支撑500+台EC2。若真需要更大规模,应采用VPC对等连接(VPC Peering)而非扩大单个VPC——这是AWS官方推荐的扩展模式。实测中,我们曾因CIDR规划不当导致跨区域灾备失败,重做VPC耗时17小时。现在所有新VPC都走自动化检查:SaltStack在apply前会调用aws ec2 describe-vpcs验证CIDR是否与其他已存在VPC重叠,冲突则立即中止。

3.2 子网划分策略:AZ感知+业务分层,拒绝“一刀切”

子网不是简单按AZ平分CIDR。我们采用三级分层法:

  1. AZ感知层:每个AZ至少部署1个公有子网+1个私有子网,避免单点故障
  2. 业务分层层:公有子网只放ALB/NAT,私有子网按业务域切分(web、app、db)
  3. 安全隔离层:数据库子网启用enable_dns_hostnames: false,彻底阻断DNS解析

对应YAML实现:

# vpc/subnets.sls {% set azs = ['us-east-1a', 'us-east-1b', 'us-east-1c'] %} {% set vpc_cidr = salt['pillar.get']('vpc:cidr', '10.10.0.0/16') %} # 公有子网:每个AZ一个,用于ALB和NAT {% for az in azs %} public-subnet-{{ az }}: aws_subnet.present: - name: {{ az }}-public - vpc_id: {{ salt['pillar.get']('vpc:id') }} - cidr_block: {{ network.calc_subnet(vpc_cidr, loop.index0*2, 24) }} # 自动计算10.10.10.0/24, 10.10.20.0/24... - availability_zone: {{ az }} - map_public_ip_on_launch: true - tags: Name: {{ az }}-public Tier: public {% endfor %} # 数据库私有子网:仅在2个AZ部署,启用加密 {% for az in azs[0:2] %} db-subnet-{{ az }}: aws_subnet.present: - name: {{ az }}-db - vpc_id: {{ salt['pillar.get']('vpc:id') }} - cidr_block: {{ network.calc_subnet(vpc_cidr, loop.index0*2+10, 24) }} # 10.10.110.0/24, 10.10.120.0/24 - availability_zone: {{ az }} - map_public_ip_on_launch: false - enable_dns_hostnames: false # 🔑 关键安全开关 - tags: Name: {{ az }}-db Tier: db Encryption: enabled {% endfor %}

提示:network.calc_subnet是自定义的Jinja2过滤器,输入10.10.0.0/16、偏移量10、掩码24,输出10.10.10.0/24。这比硬编码CIDR强100倍——当VPC主CIDR从10.10.0.0/16升级到10.20.0.0/16,所有子网自动重新计算,无需人工改20个文件。

3.3 NAT网关部署:为什么必须用弹性IP(EIP),且每个AZ独立部署?

很多教程教你在单个AZ部署NAT网关供全VPC使用,这是严重反模式。NAT网关是AZ绑定资源,若us-east-1a的NAT宕机,us-east-1b的私有子网将完全失联。我们的方案是:每个AZ部署独立NAT网关+独立EIP。YAML关键片段:

# vpc/nat.sls {% for az in azs %} nat-gateway-{{ az }}: aws_nat_gateway.present: - name: nat-{{ az }} - subnet_id: {{ salt['pillar.get']('vpc:subnets:public', {})[az] }} # 关联本AZ公有子网 - allocation_id: {{ salt['pillar.get']('vpc:eips:nat', {})[az] }} # 绑定本AZ EIP - tags: Name: nat-{{ az }} AZ: {{ az }} # 路由表关联:私有子网只走本AZ NAT private-route-table-{{ az }}: aws_route_table.present: - name: rtb-private-{{ az }} - vpc_id: {{ salt['pillar.get']('vpc:id') }} - routes: - destination_cidr_block: 0.0.0.0/0 nat_gateway_id: {{ salt['pillar.get']('vpc:nat_gateways', {})[az] }} - subnet_ids: - {{ salt['pillar.get']('vpc:subnets:private', {})[az] }} # 仅关联本AZ私有子网 {% endfor %}

EIP的必要性在于:NAT网关重启后IP会变,而EIP是静态的。我们通过Pillar预分配EIP:

# pillar/eip.sls vpc: eips: nat: us-east-1a: eipalloc-0a1b2c3d4e5f67890 us-east-1b: eipalloc-0b2c3d4e5f67890a1 us-east-1c: eipalloc-0c3d4e5f67890a1b2

注意:EIP必须在NAT网关创建前分配,且需在相同AZ。我们用SaltStack的require_in确保执行顺序:aws_eip.presentaws_nat_gateway.present

3.4 安全组精细化控制:用“最小权限矩阵”替代“全通规则”

安全组不是防火墙,它是实例级别的状态化包过滤器。新手常写0.0.0.0/0放行所有端口,这是重大风险。我们的做法是:为每个业务层定义专属安全组,并用矩阵式规则控制流量。例如Web层安全组:

# vpc/security.sls web-sg: aws_security_group.present: - name: web-sg - description: Web servers security group - vpc_id: {{ salt['pillar.get']('vpc:id') }} - rules: # 入站:只允许ALB健康检查和HTTPS - ip_permissions: - ip_protocol: tcp from_port: 443 to_port: 443 sources: - {{ salt['pillar.get']('alb:security_group_id') }} # ALB安全组ID - ip_protocol: tcp from_port: 80 to_port: 80 sources: - {{ salt['pillar.get']('alb:security_group_id') }} # 出站:只允许访问App层安全组 - ip_permissions_egress: - ip_protocol: tcp from_port: 8080 to_port: 8080 sources: - {{ salt['pillar.get']('app:security_group_id') }}

关键技巧:用安全组ID而非IP段做源/目标。这样当App层实例扩缩容时,安全组ID不变,规则自动生效。我们曾因此避免一次DDoS攻击扩散:攻击者攻破Web层后,因出站规则限制,无法扫描App层内网端口。

3.5 路由表设计:为什么默认路由表必须锁定,自定义路由表才是王道?

AWS为每个VPC创建默认路由表,默认允许所有子网互通。这在生产环境是灾难——数据库子网本该只响应App层请求,却可能被Web层意外访问。我们的铁律:禁用默认路由表,所有子网强制关联自定义路由表。实现方式:

# vpc/routing.sls # 删除默认路由表的所有关联(保留其存在,但清空子网) default-route-table: aws_route_table.absent: - name: default - vpc_id: {{ salt['pillar.get']('vpc:id') }} - purge_subnets: true # 🔑 关键参数:解除所有子网关联 # 创建专用路由表 web-route-table: aws_route_table.present: - name: rtb-web - vpc_id: {{ salt['pillar.get']('vpc:id') }} - routes: - destination_cidr_block: 0.0.0.0/0 gateway_id: {{ salt['pillar.get']('vpc:igw_id') }} # 指向IGW - subnet_ids: - {{ salt['pillar.get']('vpc:subnets:public', {})|first }} # 仅关联公有子网

注意:aws_route_table.absentpurge_subnets: true会主动解除子网关联,而非等待AWS后台清理。这是防止路由混乱的关键一步。

3.6 标签(Tags)体系:用标签驱动自动化,而非人工记忆

标签不是装饰品,是自动化系统的“神经末梢”。我们在所有资源打上4层标签:

标签键示例值用途
EnvironmentprodSaltStack Pillar选择依据
Teampayment成本分摊到具体团队
ManagedBysaltstack区分手工创建与自动化创建资源
TTL30d自动清理过期测试环境

YAML中统一注入:

# vpc/init.sls {% set common_tags = { 'Environment': salt['pillar.get']('env'), 'Team': salt['pillar.get']('team'), 'ManagedBy': 'saltstack', 'TTL': salt['pillar.get']('ttl', 'never') } %} # 所有资源均引用common_tags vpc-resource: aws_vpc.present: - name: {{ salt['pillar.get']('vpc:name') }} - cidr_block: {{ salt['pillar.get']('vpc:cidr') }} - tags: {{ common_tags }}

这套标签体系让后续动作水到渠成:aws ec2 describe-instances --filters "Name=tag:Environment,Values=staging"一键查所有测试实例;aws rds describe-db-instances --filters "Name=tag:TTL,Values=7d"自动清理7天前的测试RDS。

3.7 Pillar数据隔离:敏感信息不进Git,用GPG加密+环境变量注入

Pillar是SaltStack的“秘密保险箱”,但很多人直接把AWS密钥写进pillar/aws.sls,这是高危操作。我们的生产方案是:Pillar文件只存占位符,真实密钥通过环境变量注入。目录结构:

pillar/ ├── top.sls # 环境映射 ├── base/ │ └── aws.sls # 定义占位符:access_key: '{{ salt['environ.get']('AWS_ACCESS_KEY_ID') }}' └── prod/ └── vpc.sls # 生产环境具体值

执行时:

# 在CI/CD中设置环境变量 export AWS_ACCESS_KEY_ID=$(vault read -field=value secret/aws/prod/key) export AWS_SECRET_ACCESS_KEY=$(vault read -field=value secret/aws/prod/secret) salt-call state.apply vpc --pillar-root=pillar

实操心得:我们曾因Pillar文件误提交密钥导致GitHub泄露,紧急启用了Vault + GPG双加密。现在所有Pillar中的敏感字段都用{{ salt['gpg.decrypt']('encrypted_string') }}包裹,解密密钥由CI/CD系统动态注入。

3.8 错误处理机制:SaltStack的“原子性”如何保障VPC创建不半途而废?

SaltStack的State系统天然具备原子性:单个State(如aws_vpc.present)要么全成功,要么全失败。但跨State依赖(如先建VPC再建子网)需要显式声明。我们用requireonfail构建韧性链路:

# vpc/init.sls vpc-create: aws_vpc.present: - name: {{ salt['pillar.get']('vpc:name') }} - cidr_block: {{ salt['pillar.get']('vpc:cidr') }} - tags: {{ common_tags }} # 子网创建强依赖VPC存在 subnet-create: aws_subnet.present: - name: public-subnet-1a - vpc_id: {{ salt['pillar.get']('vpc:id') }} - require: - aws_vpc: vpc-create # 🔑 必须vpc-create成功才执行 # 若VPC创建失败,自动触发清理 vpc-cleanup: cmd.run: - name: echo "VPC creation failed, cleaning up..." - onfail: - aws_vpc: vpc-create

实测中,当VPC CIDR与现有VPC冲突时,vpc-create失败,subnet-create被跳过,vpc-cleanup也不会执行(因onfail只针对本State),但整个流程在3秒内终止,不会留下残缺资源。

3.9 性能优化:批量API调用与并发控制

SaltStack默认串行执行State,创建6个子网要调6次AWS API,耗时翻倍。我们启用并发:

# /etc/salt/master state_top_saltenv: base top_file_merging_strategy: same # 关键配置 state_aggregate: True # 合并同类State state_verbose: False # 关闭冗余日志

并在State中启用批量:

# vpc/subnets.sls # 使用salt['aws.query']批量创建,而非aws_subnet.present单个调用 batch-subnets: module.run: - name: aws.query - func: create_subnets - kwargs: VpcId: {{ salt['pillar.get']('vpc:id') }} SubnetCidrBlocks: - 10.10.10.0/24 - 10.10.20.0/24 - 10.10.30.0/24 AvailabilityZone: us-east-1a

实测效果:子网创建从12.3秒降至2.1秒。

3.10 权限最小化:IAM角色策略精确到API级别

SaltStack执行需要AWS权限,但绝不能给AdministratorAccess。我们的生产策略精确到API:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:CreateVpc", "ec2:DescribeVpcs", "ec2:ModifyVpcAttribute", "ec2:CreateSubnet", "ec2:DescribeSubnets" ], "Resource": "*" }, { "Effect": "Allow", "Action": "ec2:CreateTags", "Resource": "arn:aws:ec2:*:*:vpc/*" } ] }

提示:Describe*权限必不可少——SaltStack每次执行都要先查现状。我们曾因漏配DescribeVpcs导致State无限重试,日志刷屏。

3.11 版本兼容性:SaltStack 3000+与AWS API的适配陷阱

SaltStack 3000版本废弃了aws_ec2模块,全面转向aws模块。但AWS API也在演进,例如DescribeSubnets在2023年新增Filters参数。我们的应对策略:

  • 所有Formula锁定SaltStack 3006+版本(LTS)
  • 在State中添加API版本检测:
# vpc/init.sls check-aws-api-version: module.run: - name: aws.query - func: describe_regions - kwargs: Filters: # 如果此参数被忽略,说明API太旧 - Name: endpoint, Values: [ec2.us-east-1.amazonaws.com] - onfail: - cmd.run: api-version-warning
  • 建立内部API兼容矩阵表,每月同步AWS更新日志。

3.12 测试验证:用InSpec编写基础设施合规性检查

Formula部署完不等于万事大吉,必须验证。我们用InSpec编写VPC合规检查:

# test/vpc_spec.rb describe aws_vpc('prod-vpc') do it { should exist } its('cidr_block') { should eq '10.10.0.0/16' } end describe aws_subnets.where{ vpc_id == 'vpc-xxxx' } do its('count') { should be >= 6 } # 至少6个子网 its('map_public_ip_on_launch.count') { should eq 3 } # 公有子网数 end describe aws_security_group('web-sg') do it { should exist } its('ip_permissions.count') { should eq 2 } # 仅2条入站规则 end

CI/CD中集成:inspec exec test/ --target aws:// --controls vpc_spec.rb,失败则阻断发布。

4. 实操过程与核心环节实现:从零开始部署一个生产级VPC的完整流水线

4.1 环境准备:SaltStack Master与AWS凭证的最小化配置

第一步不是写YAML,而是搭建SaltStack执行环境。我们放弃传统Master-Minion架构,采用SaltStack SSH模式——无Agent、免维护、权限可控。安装步骤:

# 在跳板机(Jump Host)执行 curl -fsSL https://bootstrap.saltproject.io | sudo sh -s -- -P -x python3 sudo systemctl enable salt-master sudo systemctl start salt-master # 配置SSH密钥(非密码登录) ssh-keygen -t rsa -b 4096 -f /etc/salt/pki/master/ssh/salt-ssh-key -N "" ssh-copy-id -i /etc/salt/pki/master/ssh/salt-ssh-key.pub ec2-user@<jump-host-ip>

AWS凭证不存本地,而是通过IAM角色授予跳板机:

# 跳板机上执行(自动获取临时凭证) aws sts get-caller-identity # 验证角色生效

实操心得:我们曾用密码登录导致SaltStack日志泄露凭证,现在所有跳板机强制使用IAM角色+SSM Session Manager,彻底杜绝密钥硬编码。

4.2 目录初始化:创建符合SaltStack Formula规范的项目结构

在Git仓库中初始化标准结构:

mkdir -p aws-vpc-formula/{vpc,pillar,tests} touch aws-vpc-formula/{vpc/init.sls,vpc/subnets.sls,pillar/top.sls} echo "base:" > aws-vpc-formula/pillar/top.sls echo " '*':" >> aws-vpc-formula/pillar/top.sls echo " - vpc" >> aws-vpc-formula/pillar/top.sls

关键点:pillar/top.sls必须存在,否则SaltStack找不到Pillar数据。我们用脚本自动校验:

#!/bin/bash # validate-structure.sh if ! grep -q "vpc" pillar/top.sls; then echo "ERROR: pillar/top.sls missing vpc reference" exit 1 fi if [ ! -f vpc/init.sls ]; then echo "ERROR: vpc/init.sls not found" exit 1 fi

4.3 编写核心VPC State:从init.sls到完整的网络骨架

vpc/init.sls是入口文件,定义VPC主体:

# vpc/init.sls include: - vpc.subnets - vpc.routing - vpc.security # VPC资源定义 vpc-main: aws_vpc.present: - name: {{ salt['pillar.get']('vpc:name', 'default-vpc') }} - cidr_block: {{ salt['pillar.get']('vpc:cidr', '10.10.0.0/16') }} - enable_dns_support: true - enable_dns_hostnames: true - tags: Name: {{ salt['pillar.get']('vpc:name', 'default-vpc') }} Environment: {{ salt['pillar.get']('env', 'dev') }} ManagedBy: saltstack # 记录VPC ID到Pillar,供后续State使用 vpc-id-output: module.run: - name: pillar.set_val - m_name: vpc:id - v: {{ salt['aws.query']('describe_vpcs', Filters=[{'Name':'tag:Name','Values':[salt['pillar.get']('vpc:name')]}])['Vpcs'][0]['VpcId'] }} - require: - aws_vpc: vpc-main

注意module.run调用pillar.set_val动态写入VPC ID,这是跨State传递数据的关键技巧。

4.4 Pillar数据注入:用环境变量驱动多环境部署

pillar/vpc.sls不写死值,而是用环境变量:

# pillar/vpc.sls vpc: name: {{ salt['environ.get']('VPC_NAME', 'dev-vpc') }} cidr: {{ salt['environ.get']('VPC_CIDR', '10.10.0.0/16') }} igw_id: {{ salt['environ.get']('IGW_ID', '') }} subnets: public: us-east-1a: {{ salt['environ.get']('PUBLIC_SUBNET_A', '') }} us-east-1b: {{ salt['environ.get']('PUBLIC_SUBNET_B', '') }} private: us-east-1a: {{ salt['environ.get']('PRIVATE_SUBNET_A', '') }}

CI/CD中注入:

# .gitlab-ci.yml stages: - deploy deploy-prod: stage: deploy script: - export VPC_NAME="prod-vpc" - export VPC_CIDR="10.20.0.0/16" - export AWS_PROFILE=prod - salt-call state.apply vpc --pillar-root=pillar environment: production

4.5 执行部署:从本地测试到生产发布的全流程

本地验证(Dry Run):

# 模拟执行,不真实调用AWS salt-call state.apply vpc test=True --log-level=warning # 查看将要创建的资源 salt-call state.show_sls vpc --out=json | jq '.[] | select(.result==null)'

生产执行:

# 设置生产环境变量 export VPC_NAME="payment-prod-vpc" export VPC_CIDR="10.30.0.0/16" export AWS_PROFILE=payment-prod # 执行(带详细日志) salt-call state.apply vpc --log-level=info --state-verbose=False # 验证结果 salt-call state.show_low_sls vpc | grep -E "(name|result)"

实操心得:我们规定所有生产部署必须加--log-level=info,日志存档30天。曾靠日志快速定位一次NAT网关创建失败:日志显示AllocationId not found,立刻查Pillar中EIP ID拼写错误。

4.6 自动化测试:用InSpec验证VPC合规性

编写测试用例后,在CI中执行:

# 安装InSpec curl -L https://omnitruck.chef.io/install.sh | sudo bash -s -- -P inspec # 执行测试 inspec exec tests/vpc_spec.rb \ --target aws:// \ --input-file pillar/vpc.sls \ --reporter json:reports/vpc-report.json # 解析报告 cat reports/vpc-report.json | jq '.profiles[].controls[].results[] | select(.status=="failed")'

失败示例:

{ "status": "failed", "code_desc": "aws_vpc 'payment-prod-vpc' should exist", "message": "expected AwsVpc 'payment-prod-vpc' to exist" }

此时立即触发告警,通知SRE介入。

4.7 变更管理:Git PR驱动的VPC演进流程

所有VPC变更必须走Git Flow:

  1. 开发者fork仓库,创建feature/vpc-add-db-subnet分支
  2. 修改vpc/subnets.sls,添加数据库子网定义
  3. 提交PR,CI自动运行:
    • salt-call state.apply vpc test=True(Dry Run)
    • inspec exec tests/vpc_spec.rb(合规检查)
    • yamllint vpc/*.sls(YAML语法检查)
  4. 通过后,SRE审核PR,重点关注:
    • CIDR是否与现有子网冲突(用ipcalc工具验证)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/5 18:09:59

sprocketnes高级技巧:提升帧率、优化音频和自定义控制映射

sprocketnes高级技巧&#xff1a;提升帧率、优化音频和自定义控制映射 【免费下载链接】sprocketnes NES emulator written in Rust 项目地址: https://gitcode.com/gh_mirrors/sp/sprocketnes sprocketnes是一款用Rust编写的NES模拟器&#xff0c;通过掌握一些高级技巧…

作者头像 李华
网站建设 2026/7/5 18:09:52

锂离子电池过压保护与BQ29200方案设计

1. 锂离子电池过压保护的必要性与BQ29200方案选型锂离子电池因其高能量密度和长循环寿命&#xff0c;已成为便携式电子设备、电动工具乃至电动汽车的主流储能方案。但过压充电是导致锂电热失控的三大主因之一&#xff08;另两者为过放和短路&#xff09;。当单节锂电电压超过4.…

作者头像 李华
网站建设 2026/7/5 18:08:48

vCheck-vSphere与PowerCLI集成:7个高级自动化技巧和实用脚本示例

vCheck-vSphere与PowerCLI集成&#xff1a;7个高级自动化技巧和实用脚本示例 【免费下载链接】vCheck-vSphere vCheck Daily Report for vSphere 项目地址: https://gitcode.com/gh_mirrors/vc/vCheck-vSphere vCheck-vSphere是一款专为vSphere环境设计的日常报告工具&a…

作者头像 李华
网站建设 2026/7/5 18:08:11

终极Kindle漫画转换指南:如何让电子墨水屏完美显示漫画

终极Kindle漫画转换指南&#xff1a;如何让电子墨水屏完美显示漫画 【免费下载链接】kcc KCC (a.k.a. Kindle Comic Converter) is a comic and manga converter for ebook readers. 项目地址: https://gitcode.com/gh_mirrors/kc/kcc 还在为Kindle、Kobo等电子阅读器上…

作者头像 李华
网站建设 2026/7/5 18:06:57

CANN/docs DVPP算子

媒体数据处理算子 【免费下载链接】docs 该仓库用于维护cann公共文档 项目地址: https://gitcode.com/cann/docs 基本概念 调用媒体数据处理算子通常采用“两段式接口”形式&#xff0c;具体如下&#xff0c;其中_“acldvpp”表示算子接口前缀&#xff1b;而“Xxx”_表…

作者头像 李华
网站建设 2026/7/5 18:05:43

手机HTTPS抓包实战:BurpSuite配置、证书安装与疑难排查全解析

1. 项目概述&#xff1a;为什么手机HTTPS抓包是安全测试的“必修课”在移动互联网时代&#xff0c;超过90%的应用交互都通过HTTPS加密通道进行。作为一名长期从事应用安全测试的从业者&#xff0c;我深刻体会到&#xff0c;不会对手机App进行HTTPS抓包分析&#xff0c;就如同医…

作者头像 李华