.png)
AIOps系列 | 基础设施即代码
✍ 道路千万条,安全第一条。操作不规范,运维两行泪。
最近在学习《AIOps》相关的知识课程,为了让学习有一定的收获,所以将其进行了总结分享,如果你恰好也需要,很荣幸能帮到你。
概述
讲 AIOps 的时候为什么要讲 基础设施即代码(Infrastructure as Code,简称IaC) 呢?
在企业中,不论是先进的技术也好,还是优秀的思想也罢,终究都是服务于业务,这些业务都是部署在各种基础设施之上,包括 AIOps 本身的应用,所以可以理解 IaC 是 AIOps 实现自动化的基础平台。
在 AIOps 的加持下,IaC 可以变的更聪明,比如:
- 自动识别资源的浪费或瓶颈
- 根据负载预测推荐最优的实例类型
- 在部署失败的时候提供根因分析和修复建议
那什么是 基础设施即代码(以下简称IaC) 呢?
顾名思义,基础设施即代码(Infrastructure as Code,简称 IaC) 就是一种通过 代码 来定义、管理和部署 IT 基础设施的技术方法。换句话说就是 把以前“手动点击配置服务器”的过程,变成像写程序一样用“代码”来描述和部署基础设施。
举个例子,过去你可能这样做:
- 登录云平台控制台
- 手动创建 VPC、子网、安全组
- 创建 EC2 实例并安装软件
- 配置负载均衡器和数据库
现在你可以这样写一段代码(比如用 Terraform):
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
然后运行:
terraform apply
系统就会自动帮你创建一个 AWS 实例。
通过 IaC 可以将重复简单的工作简单化,它主要有以下优势:
特性 | 描述 |
---|---|
🔁 可重复 | 每次部署都是一样的结果,避免“在我机器上能跑”的问题 |
📦 可版本化 | 使用 Git 管理基础设施的变更历史 |
🚀 可自动化 | 能与 CI/CD 流程集成,实现一键部署 |
🔄 易于扩展 | 快速复制环境(开发、测试、生产) |
🛠️ 可维护性强 | 修改配置只需改代码,无需手动操作 |
常见的一些 IaC 工具有:
类型 | 工具 | 说明 |
---|---|---|
声明式(Declarative) | Terraform、Kubernetes(YAML)、AWS CloudFormation | 描述目标状态,工具负责实现 |
命令式(Imperative) | Ansible、SaltStack、Chef、Puppet | 编写具体指令一步步执行 |
容器编排 | Docker Compose、Helm Charts | 定义容器化应用的部署结构 |
云厂商专用 | AWS CDK、Azure Bicep | 结合云平台特性的 IaC 工具 |
我们知道了 IaC 的优势以及常用的工具集,那我们具体能用它做什么呢?
我们可以:
- 通过声明式方式定义和创建基础设施,使基础设施的管理类似于编写代码,实现创建和更新的自动化。
- 统一管理所有资源,无论这些资源最初是否由IC工具创建或管理,都可以导入进行统一的管理,提升整体的资源管控效率。
- 提供更安全的基础设施更改流程,通过预先列出影响范围并经由工程师确认后再执行变更,确保更改的安全性和准确性。
- 与CICD工具整合,形成基础设施管理的自动化工作流,例如在工作流中自动开通开发环境所需的云基础设施,实现环境快速搭建和标准化。
- 提供可复用的模块,编写的IC代码可以作为模块供其他团队复用,促进团队间协作和代码的标准化使用,实现基础设施管理流程的标准化和高效性。
在众多 IaC 工具中, Terraform 一直是优选工具,所以下面会主要介绍 Terraform 。
什么是Terraform
Terraform 是 IaC 的一个开源工具,它由 HashiCorp 开发,用于安全高效地 预配、管理和销毁基础设施资源 。它就像是一把“万能钥匙”,你可以用它来创建服务器、数据库、网络、容器、负载均衡器等资源,而无需手动点击云平台界面或写脚本去一个个配置。
Terraform 采用 HCL(HashiCorp Configuration Language) 语言来定义这些文档,HCL 比较简单易学,它是一种DSL(即领域特定语言),用于简化基础设施参数配置的复杂性,它具有以下优势:
- 保留YAML和JSON的可读性优势,同时引入动态编程特性,使其在编写时类似编程语言,但专注于配置
- 使用HCL描述和编写JSON对象时,代码变得更清晰且简洁
比如要在AWS上创建一台EC2,只需要使用 HCL 编写以下文档:
# main.tf
# 指定 AWS 提供商
provider "aws" {
region = "us-west-2"
}
# 创建 EC2 实例
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 AMI ID
instance_type = "t2.micro"
}
运行 terraform init
和 terraform apply
后,你的 AWS 控制台中就会出现这台新创建的 EC2 实例。
Terraform架构
在 Terraform 的执行过程中,主要包含以下几个组件配置:
- Terraform Config:使用 HCL 编写的声明式配置,用于描述用户希望的状态
- Terraform Core:它是Terraform的核心编排引擎,负责处理基础设施的变更,它会通过用户编写的HCL代码转换成期望的基础设施最终状态。
- Terraform Provider:Provider是Terraform和云厂商通信的插件,负责将 HCL 配置映射为云平台的具体 API 请求。不同云厂商有不同的Provider,这些Provider一般由云厂商自己维护。
- Terraform State:State文件用于记录当前基础设施的真实状态,由Terraform进行管理,用于保持配置和实际状态是一致的,当有变更操作的时候,Terraform就会对比期望状态和实际状态,然后生成对应的变更计划。
特别说明:
- 注意 Provider 版本兼容性
- 引入不同 Provider 时,需关注其版本,避免因上游更新导致代码异常或参数废弃。
- 推荐固定版本以确保稳定性
- 建议使用固定版本号 ,防止因自动更新带来的不兼容问题。
- 使用“波浪号加大于号(~>)”进行版本约束
- 这是一种悲观约束符 ,用于限制 Provider 的版本范围。
- 它允许小版本更新(如 bug 修复、功能增强) ,但禁止大版本升级(可能带来破坏性变更) 。
比如,采用以下配置表示允许使用 5.0.0
到 5.999.999
之间的版本,但不会升级到 6.0
:
provider "aws" {
version = "~> 5.0"
}
Terraform的核心命令
Terraform 的命令可以使用 terraform -help 来查看,这里介绍一个常用的命令。
基础命令
常用的基础命令有:
- terraform init:初始化工作目录,下载provider插件等依赖
- terraform plan:查看将要执行的操作,不会实际执行
- terraform apply:执行配置,该命令执行后会实际执行
- terraform destroy:销毁所有由terraform创建的资源
状态管理命令
常用的状态管理命令有:
- terraform state list:列出当前状态中的所有资源
- terraform state show
<resource>
:查看某资源的具体信息 - terraform state rm
<resource>
:从状态中移除某个资源(不删除真实资源) - terraform state pull:从远端拉取状态到本地
- terraform state push:更新本地的状态到远端
- terraform refresh:从基础设施实际状态更新 state 状态
- terraform import
<resource> <id>
:将已有资源导入到terraform中
查询与调试命令
常用的查询与调试命令有:
- terraform show:显示当前状态文件的内容
- terraform output:显示outputs中定义的输出值
- terraform graph:生成资源配置的依赖图
- terraform validate:检查配置语法是否正确
其他命令
还有一些比较实用的命令:
- terraform fmt:格式化
.tf
文件,统一风格 - terraform taint
<resource>
:标记某个资源为“污染”,下次apply的时候重建 - terraform workspace:管理多个环境(dev / staging / prod)
Terraform State
这里着重把 Terraform State 拿出来讲,是因为它是 Terraform 生命周期中必不可少的元素,它保存了真实基础设施的所有元数据。默认情况下,这些信息保存在一个名为 "terraform.tfstate" 的文件中。
Terraform 使用状态来创建执行计划并更改您的基础设施。当 Terraform 通过配置文件创建或者更改了远端对象时,它会将该远端对象的标识记录在与之对应的资源实例中,并保存在状态文件中,之后,Terraform 可能会根据未来的配置更改更新或删除该对象。
每个在资源块中创建的基础设施资源都是通过其resource_name
在 Terraform 状态中进行标识的,其对资源的管理流程大致如下:
- 当第一次通过 terraform apply 应用 Terraform 配置时,会创建基础设施资源,同时自动生成一个状态文件,该文件引用资源块中声明的名称
- 如果一个资源已经在 Terraform 状态文件中有标识,那么 Terraform 会将配置文件与状态文件和当前的资源远端的实际状态进行比较,并根据比较结果,会生成一个执行计划
- 当执行该计划时,它会更新资源的状态以匹配配置文件中的定义,如果由于远端 API 限制无法实现就地更新参数,那么该执行计划将会先销毁资源,然后再重新创建新的资源;如果是一个资源销毁的计划,将发起资源的销毁操作
- 计划执行成功后,Terraform 状态文件会更新以反映当前的基础设施状态
- 如果某资源已从当前 Terraform 配置中移除但在状态文件中仍然存在,Terraform 则会比较配置文件并销毁不再存在的资源
Terraform 默认将本地状态文件保存在当前工作目录中,扩展名为 .tfstate,因此它们不需要额外的维护。本地状态文件适用于只有一个开发人员工作的项目,当多个开发人员同时运行 Terraform 并且每台机器都有对当前基础设施的理解和配置时,默认的本地状态文件的配置方式就会变得棘手。
在团队协作开发场景中使用本地状态时主要存在以下几个问题:
- 本地状态没有共享访问权限:当使用 Terraform 更新你的基础设施,团队中的每个成员都需要访问相同的状态文件,这意味着这些文件必须存储在一个共享的位置,比如 ECS 实例特定的位置,而这无形增加了管理成本。
- 不能锁定本地状态文件:如果两个团队成员同时运行 Terraform,他们可能会遇到竞争条件,因为多个 Terraform 进程可能同时在更新状态文件。在这种情况下,可能带来导致冲突、数据丢失和状态文件损坏等风险。
- 本地状态文件不保密:当信息以明文形式存储在状态文件中时,敏感数据将存在被暴露的风险,例如数据库凭证,SSH 登录密码等。
因此,在团队协作开发的场景中,推荐使用远程替代本地存储,这种存储模式下会:
- 远端状态文件会自动更新:每次使用 plan 或者 apply 命令的时候会自动从远端加载状态文件,每次执行apply之后也会把状态文件自动同步到远端存储中。
- 远端状态文件支持状态锁定:当执行 terraform 的时候,可以对远端状态文件进行加锁,这样在多个开发人员同时运行 terraform apply 的时候不会因同时更新而损坏。
- 远端状态文件存储比本地存储更安全:比如像 OSS 这类远端存储不仅支持精细化控制访问权限,还支持传输和远端加密功能。
比如采用 阿里云OSS 作为后端存储,其配置如下:
terraform {
backend "oss" {
bucket = "bucket-for-terraform-state"
prefix = "path/mystate"
key = "version-1.tfstate"
region = "cn-beijing"
tablestore_endpoint = "https://terraform-remote.cn-hangzhou.ots.aliyuncs.com"
tablestore_table = "statelock"
}
}
其中:
- bucket:为阿里云OSS Bucket的名称
- prefix:在Bucket中存放状态文件的路径前缀
- key:状态文件的名称
- region:Bucket所在区域
- tablestore_endpoint:tablestore的访问地址,用于状态锁
- tablestore_table:用于状态锁的tablestore表名
说明:当你运行
terraform apply
或destroy
时,Terraform 会尝试对状态文件加锁。如果已有其他人在操作,就会提示“lock failed”,防止并发修改导致错误。
Terraform项目布局
任何项目的开发都需要比较好的项目布局,Terraform 也不例外,在简单的项目中推荐使用 Terraform Layout 的项目布局方式,将文件拆分成四个文件:
- main.tf:表示主要的业务逻辑
- outputs.tf:定义输出的内容
- variables.tf:定义变量参数
- version.tf:定义依赖和版本
其中, main.tf 是业务的主逻辑,比如这里要创建一个EC2,定义如下:
resource "aws_instance" "my-example" {
ami = "ami-xxxxxxx"
instance_type = var.instance_type
}
这里的 var.instance_type
是定义的变量,所以我们还要创建一个 variables.tf
,内容如下:
variable "instance_type" {
description = "EC2 实例类型"
default = "t2.micro"
}
然后,我们喜欢得到创建后的EC2实例的IP,所以我们还要定义 outputs.tf
,如下:
output "public_ip" {
value = aws_instance.my-example.public_ip
}
另外,还有一个 version.tf
,这个文件主要是用来固定版本的,避免上游版本变化导致脚本出现不可预知的BUG。其定义大概如下:
# versions.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
# 示例:阿里云 Provider
alicloud = {
source = "aliyun/alicloud"
version = "~> 1.2.0"
}
}
}
如果项目比较复杂,可以将 main.tf 再进行拆分,比如我们要使用 terraform 部署 vpc、rds,则可以将其按模块进行拆分,如下:
main.tf
variables.tf
outputs.tf
modules/
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf
└── rds/
├── main.tf
├── variables.tf
└── outputs.tf
然后在 main.tf 中去引用即可,如下:
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
tags = { Name = "my-vpc" }
}
如果你的项目涉及多个环境(dev/staging/prod),且要提高代码的复用,我们可以将目录结构定义如下:
project-root/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── staging/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── modules/ # 可选:用于复用的模块
│ ├── vpc/
│ └── rds/
├── versions.tf # 固定版本号(推荐)
└── backend.tf # 远程后端配置(可选)
在具体使用的时候我们应该先创建 workspace ,然后再执行。比如要在 dev 环境执行命令,则用:
$ terraform workspace new dev # 创建 dev 的workspace
$ cd environments/dev # 切换到dev的主入口
$ terrafotm apply --auto-approve # 在dev workspace 中创建资源
# 如果不清楚目前有哪些 workspace,可以使用 terraform workspace list 查看
$ terraform workspace list
# 如果想切换到某个 workspace,可以使用 terraform workspace select <workspace> 进行切换
$ terraform workspace select dev
# 如果要删除某个 workspace 中的资源,可以使用 terraform destroy --auto-approve 命令
$ terraform destroy --auto-approve
Terraform实战
下面我们将以 模块化 + 多环境支持 的方式设计这个 Terraform 项目,并以 阿里云(Alibaba Cloud) 平台为例进行说明。
Tips:代码未经调试,原因是没钱
整体的项目需求是:
- 创建VPC
- 创建ECS
- 在ECS中部署K8S,版本是1.32.1
- 在K8S中部署Nginx,版本是latest
整体的项目目录结构规划如下:
terraform-k8s-on-ecs/
├── environments/
│ └── dev/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── ecs/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── k8s-install/
│ │ ├── main.tf
│ │ ├── install-k8s.sh
│ │ └── variables.tf
│ └── k8s-deploy-nginx/
│ ├── main.tf
│ └── nginx-deployment.yaml
├── versions.tf
└── providers.tf
1、固定版本号
# versions.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
alicloud = {
source = "aliyun/alicloud"
version = "~> 1.2.0"
}
null = {
source = "hashicorp/null"
version = "~> 3.0"
}
template = {
source = "hashicorp/template"
version = "~> 2.0"
}
}
}
2、创建VPC模块
(1)定义变量
# modules/vpc/variables.tf
variable "vpc_name" {
description = "VPC 名称"
type = string
}
variable "cidr_block" {
description = "VPC CIDR"
type = string
default = "10.0.0.0/16"
}
variable "subnet_cidr" {
description = "子网 CIDR"
type = string
default = "10.0.1.0/24"
}
variable "zone_id" {
description = "可用区 ID"
type = string
}
(2)定义主入口
# modules/vpc/main.tf
resource "alicloud_vpc" "main" {
vpc_name = var.vpc_name
cidr_block = var.cidr_block
}
resource "alicloud_vswitch" "main" {
vswitch_name = "${var.vpc_name}-vsw"
cidr_block = var.subnet_cidr
vpc_id = alicloud_vpc.main.id
zone_id = var.zone_id
}
3、创建ECS模块
(1)定义变量
# modules/ecs/variables.tf
variable "instance_name" {
description = "ECS 实例名称"
type = string
}
variable "image_id" {
description = "镜像 ID(如 centos_7_9_x64_20G_alibase_20220310.vhd)"
type = string
}
variable "instance_type" {
description = "实例类型(如 ecs.n4.small)"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "vswitch_id" {
description = "VSwitch ID"
type = string
}
variable "zone_id" {
description = "可用区 ID"
type = string
}
variable "root_password" {
description = "ECS root 密码"
type = string
}
(2)定义主入口
# modules/ecs/main.tf
resource "alicloud_security_group" "k8s-node" {
name = "k8s-node-sg"
vpc_id = var.vpc_id
}
resource "alicloud_instance" "k8s-master" {
instance_name = var.instance_name
image_id = var.image_id
instance_type = var.instance_type
availability_zone = var.zone_id
system_disk_category = "cloud_efficiency"
system_disk_size = 40
vswitch_id = var.vswitch_id
security_groups = [alicloud_security_group.k8s-node.id]
internet_max_bandwidth_out = 100
password = var.root_password
}
4、创建Kubernetes集群(采用kubeadm)
(1)创建安装K8S脚本
# modules/k8s-install/install-k8s.sh
#!/bin/bash
# 安装 Docker
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
systemctl start docker
systemctl enable docker
# 安装 kubelet kubeadm kubectl
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kube*
EOF
yum install -y kubelet-1.32.1 kubeadm-1.32.1 kubectl-1.32.1
systemctl enable kubelet
systemctl start kubelet
# 初始化集群
kubeadm init --pod-network-cidr=10.244.0.0/16
# 配置 kubectl
mkdir -p $HOME/.kube
cp /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# 安装 CNI(Flannel)
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
(1)定义变量
# modules/k8s-install/variables.tf
variable "ecs_public_ip" {
description = "ECS 公网 IP"
type = string
}
variable "root_password" {
description = "ECS root 密码"
type = string
}
(2)定义主入口
# modules/k8s-install/main.tf
resource "null_resource" "install_k8s" {
connection {
type = "ssh"
user = "root"
password = var.root_password
host = var.ecs_public_ip
}
provisioner "file" {
source = "${path.module}/install-k8s.sh"
destination = "/root/install-k8s.sh"
}
provisioner "remote-exec" {
inline = [
"chmod +x /root/install-k8s.sh",
"/root/install-k8s.sh"
]
}
}
5、在K8S中部署Nginx
(1)、创建NG的Deployment
# modules/k8s-deploy-nginx/nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort
(2)、定义变量
# modules/k8s-deploy-nginx/variables.tf
variable "ecs_public_ip" {
description = "ECS 公网 IP"
type = string
}
variable "root_password" {
description = "ECS root 密码"
type = string
}
(3)、定义主入口
# modules/k8s-deploy-nginx/main.tf
resource "null_resource" "deploy_nginx" {
depends_on = [module.k8s-install]
connection {
type = "ssh"
user = "root"
password = var.root_password
host = var.ecs_public_ip
}
provisioner "file" {
source = "${path.module}/nginx-deployment.yaml"
destination = "/root/nginx-deployment.yaml"
}
provisioner "remote-exec" {
inline = [
"kubectl apply -f /root/nginx-deployment.yaml"
]
}
}
6、配置开发环境的主业务逻辑
# environments/dev/main.tf
# 创建 VPC
module "vpc" {
source = "../../modules/vpc"
vpc_name = "dev-vpc"
cidr_block = "10.0.0.0/16"
subnet_cidr = "10.0.1.0/24"
zone_id = "cn-beijing-a"
}
# 创建 ECS,依赖 VPC
module "ecs" {
source = "../../modules/ecs"
instance_name = "k8s-master"
image_id = "centos_7_9_x64_20G_alibase_20220310.vhd"
instance_type = "ecs.n4.small"
vpc_id = module.vpc.vpc_id
vswitch_id = module.vpc.vswitch_id
zone_id = "cn-beijing-a"
root_password = "your-root-password-123"
}
# 安装 Kubernetes,依赖 ECS
module "k8s-install" {
source = "../../modules/k8s-install"
ecs_public_ip = module.ecs.ecs_public_ip
root_password = "your-root-password-123"
depends_on = [module.ecs] # 等 ECS 创建完成后再安装 K8s
}
# 部署 Nginx,依赖 Kubernetes 安装完成
module "deploy-nginx" {
source = "../../modules/k8s-deploy-nginx"
ecs_public_ip = module.ecs.ecs_public_ip
root_password = "your-root-password-123"
depends_on = [module.k8s-install] # 等 K8s 安装完成后再部署应用
}
配置输出:
# environments/dev/outputs.tf
output "vpc_id" {
value = module.vpc.vpc_id
}
output "ecs_public_ip" {
value = module.ecs.ecs_public_ip
}
output "k8s_config" {
value = "/root/.kube/config"
}
7、部署
当所有脚本开发完成后,为了保证后续的可扩展,整体的命令执行流程是:
# 切换到 dev 目录下
$ cd environments/dev
# 创建 dev workspace
$ terraform workspace new dev
# 初始化下载依赖
$ terraform init
# 预执行
$ terraform plan
# 执行部署
$ terraform apply
总结
综上所述,文章系统阐述了基础设施即代码(IaC)的核心概念、与AIOps的关联及显著优势,并聚焦IaC工具Terraform,深入解析其定义、架构、核心命令、状态管理、项目布局等关键内容,最后通过在阿里云平台部署VPC、ECS、K8S及Nginx的实战案例,完整呈现了Terraform在模块化和多环境支持下的应用流程。这不仅展现了IaC通过代码化管理基础设施的高效性与规范性,也为实际运维中利用Terraform实现自动化部署提供了清晰的思路与参考。
引用
[1] https://developer.hashicorp.com/terraform/docs
[2] https://help.aliyun.com/zh/terraform/what-is-terraform?spm=a2c4g.help-menu-95817.d_0_1.dee2c092b7ORVO