使用 Terraform 基于 Excel 表格数据创建资源的解决方案

一、背景

在基础设施即代码(IaC)实践中,经常需要基于表格数据批量创建云资源。本文介绍一种通过 Terraform 结合 Excel 表格数据批量创建资源的完整方案,适用于需要根据结构化表格数据快速生成标准化基础设施的场景。

二、核心思路

该方案通过 "数据转换 - 动态配置 - 模块化管理" 的流程实现实现批量资源创建,核心步骤包括:

  1. 数据提取与转换:将 Excel 表格数据转换为 Terraform 可直接解析的 JSON 格式
  1. 动态资源配置:利用 Terraform 的 for_each 循环,基于转换后的数据动态生成资源
  1. 模块化设计:将资源配置封装为 Terraform 模块,提升复用性和可维护性
  1. 校验与容错:在数据转换和资源创建过程中加入校验机制,确保配置合法性

三、实战步骤

3.1 准备 Excel 数据

以云服务器(CVM)配置为例,Excel 表格需包含资源创建的关键参数(可根据实际需求扩展):

Instance Name Instance Type Image ID Subnet ID Security Groups SystemDiskSize Availability Zone
xuel-web-server-01 SA5.MEDIUM2 img-l8og963d subnet-aosdz1ku sg-1olrbu1b 50 ap-guangzhou-6
xuel-app-server-01 SA5.MEDIUM2 img-l8og963d subnet-aosdz1ku sg-1olrbu1b 50 ap-guangzhou-6
xuel-db-server-01 SA5.MEDIUM2 img-l8og963d subnet-aosdz1ku sg-1olrbu1b 50 ap-guangzhou-6

3.2 数据转换:Python 脚本处理 Excel

使用 Python 脚本将 Excel 数据转换为 Terraform 可识别的 JSON 格式,实现自动化数据处理。

脚本实现

python 复制代码
import pandas as pd
import json
import os
import argparse
import traceback
def excel_to_terraform_vars(excel_file, output_file):
    # 处理文件路径为绝对路径
    excel_file = os.path.abspath(excel_file)
    output_file = os.path.abspath(output_file)
    
    # 校验输入文件是否存在
    if not os.path.exists(excel_file):
        raise FileNotFoundError(f"Excel文件不存在: {excel_file}")
    
    # 识别文件类型(CSV或Excel)
    file_extension = os.path.splitext(excel_file)[1].lower()
    
    try:
        if file_extension == '.csv':
            # 读取CSV文件
            df = pd.read_csv(
                excel_file, 
                header=0, 
                names=['Instance Name', 'Instance Type', 'Image ID', 'Subnet ID', 
                       'Security Groups', 'SystemDiskSize', 'Availability Zone']
            )
        else:
            # 尝试多种引擎读取Excel文件
            engines_to_try = ['openpyxl', 'xlrd', 'odf', 'pyxlsb']
            df = None
            for engine in engines_to_try:
                try:
                    df = pd.read_excel(excel_file, engine=engine)
                    break
                except Exception as e:
                    print(f"使用{engine}引擎读取失败: {e}")
            if df is None:
                raise ValueError(f"无法读取Excel文件 {excel_file},尝试了所有可用引擎")
        
        # 校验必填列
        required_columns = ['Instance Name', 'Instance Type', 'Image ID', 'Subnet ID', 
                           'Security Groups', 'SystemDiskSize', 'Availability Zone']
        missing_columns = [col for col in required_columns if col not in df.columns]
        if missing_columns:
            raise ValueError(f"缺少必填列: {missing_columns},可用列: {list(df.columns)}")
        
        # 移除全为空的行
        df = df.dropna(how='all')
        
        resources = {}
        for index, row in df.iterrows():
            # 跳过无效行(标题行、分隔行或空行)
            if (pd.isna(row['Instance Name']) or 
                row['Instance Name'] in ['---', ''] or 
                row['Instance Name'].startswith(('---', '|'))):
                continue
            
            # 校验系统盘大小(必须为数字)
            try:
                system_disk_size = int(row['SystemDiskSize'])
            except (ValueError, TypeError):
                print(f"跳过无效行(系统盘大小非数字): {row['SystemDiskSize']}")
                continue
            
            # 组装资源数据
            instance_name = row['Instance Name']
            resources[instance_name] = {
                'instance_type': str(row['Instance Type']).strip(),
                'image_id': str(row['Image ID']).strip(),
                'subnet_id': str(row['Subnet ID']).strip(),
                'security_groups': [sg.strip() for sg in str(row['Security Groups']).split(',') if sg.strip()],
                'system_disk_size': system_disk_size,
                'availability_zone': str(row['Availability Zone']).strip()
            }
        # 确保输出目录存在
        os.makedirs(os.path.dirname(output_file), exist_ok=True)
        
        # 写入JSON文件
        with open(output_file, 'w') as f:
            json.dump(resources, f, indent=2)
        
        print(f"数据转换完成: {excel_file} -> {output_file}")
    
    except Exception as e:
        print(f"处理失败: {e}")
        raise
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Excel转Terraform变量工具')
    parser.add_argument('excel_file', help='输入Excel/CSV文件路径')
    parser.add_argument('--output', default=None, help='输出tfvars.json文件路径')
    
    args = parser.parse_args()
    
    # 自动生成输出文件名(默认与输入文件同路径,后缀为.tfvars.json)
    output_file = args.output or f"{os.path.splitext(args.excel_file)[0]}.tfvars.json"
    
    excel_to_terraform_vars(args.excel_file, output_file)

环境准备

安装依赖包:

arduino 复制代码
pip install pandas openpyxl xlrd  # openpyxl支持xlsx,xlrd支持xls

执行转换

bash 复制代码
# 转换CSV文件(Excel文件同理)
python excel_to_tfvars.py resources.csv --output resources.tfvars.json

转换结果(resources.tfvars.json)

json 复制代码
{
  "xuel-web-server-01": {
    "instance_type": "SA5.MEDIUM2",
    "image_id": "img-l8og963d",
    "subnet_id": "subnet-aosdz1ku",
    "security_groups": ["sg-1olrbu1b"],
    "system_disk_size": 50,
    "availability_zone": "ap-guangzhou-6"
  },
  "xuel-app-server-01": {
    "instance_type": "SA5.MEDIUM2",
    "image_id": "img-l8og963d",
    "subnet_id": "subnet-aosdz1ku",
    "security_groups": ["sg-1olrbu1b"],
    "system_disk_size": 50,
    "availability_zone": "ap-guangzhou-6"
  },
  "xuel-db-server-01": {
    "instance_type": "SA5.MEDIUM2",
    "image_id": "img-l8og963d",
    "subnet_id": "subnet-aosdz1ku",
    "security_groups": ["sg-1olrbu1b"],
    "system_disk_size": 50,
    "availability_zone": "ap-guangzhou-6"
  }
}

3.3 Terraform 配置实现

目录结构

bash 复制代码
.
├── modules/               # 资源模块目录
│   └── instance/          # 云服务器实例模块
│       ├── main.tf        # 资源定义
│       ├── variables.tf   # 模块变量
│       └── outputs.tf     # 模块输出
├── main.tf                # 主配置(调用模块)
├── providers.tf           # 云厂商配置
└── resources.tfvars.json  # 转换后的资源数据

关键配置文件

  1. providers.tf(云厂商配置)
ini 复制代码
terraform {
  required_providers {
    tencentcloud = {
      source  = "tencentcloudstack/tencentcloud"
      version = "~> 1.81.0"
    }
  }
}
provider "tencentcloud" {
  # 可通过环境变量配置:TENCENTCLOUD_SECRET_ID、TENCENTCLOUD_SECRET_KEY
  # secret_id  = "your-secret-id"
  # secret_key = "your-secret-key"
  region = "ap-guangzhou"  # 地域
}
  1. modules/instance/ variables.tf(模块变量定义)
ini 复制代码
variable "instance_name" {
  description = "实例名称"
  type        = string
}
variable "instance_type" {
  description = "实例类型"
  type        = string
}
variable "image_id" {
  description = "镜像ID"
  type        = string
}
variable "subnet_id" {
  description = "子网ID"
  type        = string
}
variable "security_groups" {
  description = "安全组ID列表"
  type        = list(string)
}
variable "system_disk_size" {
  description = "系统盘大小(GB)"
  type        = number
  default     = 50
}
variable "availability_zone" {
  description = "可用区"
  type        = string
}
  1. modules/instance/ main.tf(资源定义)
ini 复制代码
resource "tencentcloud_instance" "this" {
  instance_name     = var.instance_name
  instance_type     = var.instance_type
  image_id          = var.image_id
  subnet_id         = var.subnet_id
  security_groups   = var.security_groups
  availability_zone = var.availability_zone
  
  # 系统盘配置
  system_disk_size = var.system_disk_size
  system_disk_type = "CLOUD_PREMIUM"  # 高性能云硬盘
  
  # 其他可选配置(根据需求添加)
  # internet_charge_type = "TRAFFIC_POSTPAID_BY_HOUR"
  # internet_max_bandwidth_out = 100
}
  1. modules/instance/ outputs.tf(模块输出)
ini 复制代码
output "instance_id" {
  description = "实例ID"
  value       = tencentcloud_instance.this.id
}
output "public_ip" {
  description = "公网IP"
  value       = tencentcloud_instance.this.public_ip
}
output "private_ip" {
  description = "内网IP"
  value       = tencentcloud_instance.this.private_ip
}
  1. main.tf(主配置,批量创建资源)
ini 复制代码
# 读取JSON数据
locals {
  instances = jsondecode(file("${path.module}/resources.tfvars.json"))
}
# 循环调用模块创建实例
module "instances" {
  source   = "./modules/instance"
  for_each = local.instances
  
  # 传递变量(与JSON字段对应)
  instance_name     = each.key
  instance_type     = each.value.instance_type
  image_id          = each.value.image_id
  subnet_id         = each.value.subnet_id
  security_groups   = each.value.security_groups
  system_disk_size  = each.value.system_disk_size
  availability_zone = each.value.availability_zone
}
# 全局输出所有实例信息
output "all_instances" {
  description = "所有实例信息"
  value = {
    for name, instance in module.instances :
    name => {
      id         = instance.instance_id
      public_ip  = instance.public_ip
      private_ip = instance.private_ip
    }
  }
}

3.4 执行 Terraform 命令

  1. 初始化工作目录
csharp 复制代码
terraform init  # 下载Provider和模块
  1. 预览资源创建计划
bash 复制代码
terraform plan  # 检查配置并预览将要创建的资源
  1. 执行资源创建
bash 复制代码
terraform apply  # 确认后输入yes执行创建
  1. 查看输出结果
bash 复制代码
terraform output  # 显示所有实例的ID和IP信息

四、方案总结

本方案通过 "Excel 表格→Python 转换→Terraform 批量创建" 的流程,实现了基于表格数据的标准化资源部署,核心优势包括:

  • 高效批量操作:避免手动编写重复的资源配置,通过表格批量管理资源参数
  • 可扩展性强:支持扩展 Excel 字段(如磁盘类型、网络带宽等),只需同步更新脚本和 Terraform 模块
  • 标准化配置:通过模块确保资源配置的一致性,降低人为配置错误风险

五、注意事项

  1. 权限配置:确保 Terraform 已配置云厂商 API 密钥(如腾讯云的TENCENTCLOUD_SECRET_ID和TENCENTCLOUD_SECRET_KEY)
  1. 参数校验:生产环境中建议增强 Python 脚本的校验逻辑(如实例类型合法性、可用区匹配性等)
  1. 版本兼容:注意 Terraform Provider 版本与云厂商 API 的兼容性
  1. 敏感信息:避免在 Excel 或 JSON 中存储敏感信息(如密钥),建议通过 Terraform 变量或环境变量注入
  1. 备份策略:重要表格数据建议版本化管理(如纳入 Git),便于追溯配置变更

六、扩展方案

除 Python 转换外,Terraform 官方提供csvdecode函数可直接解析 CSV 文件(无需 Python 脚本),示例:

ini 复制代码
locals {
  # 直接解析CSV文件
  instances = csvdecode(file("${path.module}/resources.csv"))
}
# 后续使用方式与JSON方案一致

选择哪种方式取决于需求:

  • 简单场景(纯 CSV、无复杂校验):优先使用csvdecode
  • 复杂场景(Excel 格式、需数据清洗 / 校验):使用 Python 脚本方案
相关推荐
观测云2 小时前
AWS VPC NAT 网关可观测最佳实践
云计算·aws
明月看潮生5 小时前
编程与数学 03-002 计算机网络 17_云计算与网络
计算机网络·青少年编程·云计算·编程与数学
云和数据.ChenGuang5 小时前
云计算k8s集群部署配置问题总结
云原生·容器·kubernetes·云计算
kaliarch7 小时前
迈向云基础设施自动化 - Terraformer 助力腾讯云资源管理转型
后端·云原生·自动化运维
kaliarch7 小时前
Terraform 存量资源手动导入IaC管控方案
后端·自动化运维
kaliarch7 小时前
Terraform 导入存量云资源方案
后端·自动化运维
kaliarch7 小时前
IaC 管控资源发生属性偏移修正方案
后端·架构·自动化运维
kaliarch7 小时前
Terraform 合并多个项目(独立目录)解决方案
后端·自动化运维
snpgroupcn8 小时前
如何管理数据足迹,实现SAP S/4HANA的无缝迁移
大数据·云计算