原创 吴就业 159 0 2023-10-13
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/69b80bc6a84c45aca93cc689048a2b95
作者:吴就业
链接:https://wujiuye.com/article/69b80bc6a84c45aca93cc689048a2b95
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
由于我们的使用场景是将基础设施资源定义成KubeVela的组件,一个terraform “module”对应的就是一个kubevela的组件,对应terraform-controller的一个Configuration资源。因此导入的最小粒度是组件,即一个terraform “module”。
terraform原生支持一种导入资源的方式: terraform import
, 用于将资源信息导入到state文件中。
命令:terraform import <path> <resource_id>
如果是使用kubevela+terraform-controller,那么实际上我们是没有用terraform的module的,不需要考虑module的导入问题。这也是前面为什么给module加双引号的原因。
这个命令的缺点是,如果一个tf文件中,有多个resource就没办法支持,或者有的resource还是for_each申请的多个资源。
我们想到的办法是,由于一个tf文件中,肯定是只有一个“主”资源,其它是“子”资源,那么是不是可以只import主资源,然后通过datasource将子资源查出来,这样生成的tfstate就包含了子资源的。
举例,一个mycloud-net组件对应的tf代码:
locals {
db_subnet_ip_cap = 16
as_subnet_ip_cap = 16
k8s_subnet_ip_cap = 64
// vpc的ip cap大于等于所有子网的ip cap之和
vpc_ip_cap = tomap({
"aws" : local.as_subnet_ip_cap*4+local.db_subnet_ip_cap*2+local.k8s_subnet_ip_cap*2,
"aliyun" : local.as_subnet_ip_cap*2+local.db_subnet_ip_cap*2+local.k8s_subnet_ip_cap*2
})
}
variable "provider_code" {
type = string
validation {
condition = var.provider_code == "aws" || var.provider_code == "aliyun"
error_message = "Provider required. aws or aliyun."
}
}
variable "vpc_name" {
type = string
validation {
condition = var.vpc_name != ""
error_message = "VPC name required."
}
}
resource "mycloud_vpc" "vpc" {
name = var.vpc_name
provider_code = var.provider_code
// 取值必须是2的n次方
ip_capacity = pow(2, ceil(log(local.vpc_ip_cap[var.provider_code], 2)))
}
output "id" {
value = mycloud_vpc.vpc.global_id
}
output "cidr" {
value = mycloud_vpc.vpc.cidr
}
locals {
aws_subnet_names = toset(["k8s1", "k8s2", "DB1", "DB2", "AS1", "AS2", "AS_OUT1", "AS_OUT2"])
alibaba_subnet_names = toset(["k8s1", "k8s2", "DB1", "DB2", "AS1", "AS2"])
subnet_name_map = tomap({
"aws" : local.aws_subnet_names,
"aliyun" : local.alibaba_subnet_names
})
}
locals {
// 子网名称 -> 区域索引
az_map = tomap({
"k8s1" = 0, "k8s2" = 1,
"DB1" = 0, "DB2" = 1,
"AS1" = 0, "AS2" = 1,
"AS_OUT1" = 0, "AS_OUT2" = 1
})
// 子网名称 -> ip cap
subnet_ip_cap_map = tomap({
"DB1" = local.db_subnet_ip_cap, "DB2" = local.db_subnet_ip_cap,
"AS1" = local.as_subnet_ip_cap, "AS2" = local.as_subnet_ip_cap,
"AS_OUT1" = local.as_subnet_ip_cap, "AS_OUT2" = local.as_subnet_ip_cap,
"k8s1" = local.k8s_subnet_ip_cap, "k8s2" = local.k8s_subnet_ip_cap
})
}
data "mycloud_available_zones" "azs" {
provider_code = var.provider_code
}
resource "mycloud_subnet" "subnet" {
for_each = local.subnet_name_map[var.provider_code]
name = each.key
ip_capacity = local.subnet_ip_cap_map[each.key]
vpc_id = mycloud_vpc.vpc.id
zone_id = data.mycloud_available_zones.azs.available_zones[local.az_map[each.key]].zone_id
}
resource "mycloud_vpc_access_request" "access" {
vpc_id = mycloud_vpc.vpc.id
access_requests = [
for subnetName in local.subnet_name_map[var.provider_code] : {
subnet_id = irock_subnet.subnet[subnetName].id
from_internet = true
object_storage = true
to_internet = true
}
]
}
用于导入tfstate的tf代码:
resource "mycloud_vpc" "vpc" {
}
data "mycloud_subnet" "subnet" {
vpc_id = mycloud_vpc.vpc.id
}
data "mycloud_vpc_access_request" "access" {
vpc_id = mycloud_vpc.vpc.id
}
使用导入tf,执行terraform import mycloud_vpc.vpc 这里填id
命令,就可以得到一个tfstate文件,但这个tfstate与预期的不符。因为mycloud-net组件的tf中,mycloud_vpc_access_request.access是一个resource,而在导入tf中,它是一个datasource,mycloud_subnet.subnet同理。因此我们还需要修改tfstate文件,将状态文件中记录的mycloud_vpc_access_request.access、mycloud_subnet.subnet由datasource转为resource。
其实只要对比一下用mycloud-net组件的tf执行terraform apply命令生成的tfstate文件,和用导入tf执行terraform import
命令生成的tfstate比较一下,就能看出差异,就能知道怎么改。
将状态文件中记录的mycloud_vpc_access_request.access、mycloud_subnet.subnet由datasource转为resource的转换代码如下:
func ConverterTFState(importState *tfstate.TFState) (outState *tfstate.TFState, err error) {
outState = &tfstate.TFState{}
outState.Version = importState.Version
outState.TerraformVersion = importState.TerraformVersion
outState.Serial = importState.Serial
outState.Lineage = importState.Lineage
outState.Outputs = map[string]interface{}{}
outState.CheckResults = nil outState.Resources = []tfstate.Resource{}
for _, r := range importState.Resources {
var nr tfstate.Resource
// mode是managed说明是resource,否则是datasource
if r.Mode == "managed" {
nr = r
} else {
// 转换mycloud_subnet
if r.Type == "mycloud_subnet" {
// 转换为managed
nr = tfstate.Resource{
Mode: "managed",
Type: r.Type,
Name: r.Name,
Provider: r.Provider,
Instances: tfstate.Instances{},
}
subnets := r.Instances[0].Attributes["subnets"].([]interface{})
for _, s := range subnets {
ss := s.(map[string]interface{})
idxKey := ss["name"]
ni := tfstate.Instance{
IndexKey: idxKey,
SchemaVersion: r.Instances[0].SchemaVersion,
Attributes: ss,
SensitiveAttributes: nil,
Dependencies: []string{"data.mycloud_available_zones.azs", "mycloud_vpc.vpc"},
}
nr.Instances = append(nr.Instances, ni)
}
} else if r.Type == "mycloud_vpc_access_request" { // 转换mycloud_vpc_access_request
// 转换为managed
nr = tfstate.Resource{
Mode: "managed",
Type: r.Type,
Name: r.Name,
Provider: r.Provider,
Instances: tfstate.Instances{},
}
requests := r.Instances[0].Attributes["vpc_access_requests"].([]interface{})
if len(requests) > 0 {
ni := tfstate.Instance{
SchemaVersion: r.Instances[0].SchemaVersion,
Attributes: requests[0].(map[string]interface{}),
SensitiveAttributes: nil,
Dependencies: []string{"mycloud_vpc.vpc"},
}
nr.Instances = append(nr.Instances, ni)
}
}
}
outState.Resources = append(outState.Resources, nr)
}
return outState, nil }
代码不理解没关系,重点是方法。我们可以自己找一个资源,通过apply和import生成两个tfstate(一个是正常走申请生成的tfstate,一个是通过import生成的tfstate),然后对比一下这两个tfstate的内容,就知道差异在哪,也就知道应该怎么改了。
实现terraform-import Operator,自定义TerraformResourceImport CRD,并实现TerraformResourceImport资源的控制器。
CRD使用模版(TerraformResourceImport):
apiVersion: mycloud.com/v1beta1
kind: TerraformResourceImport
metadata:
name: tri-$(组件名)
namespace: default # cr安装在哪个namespace,组件就安装在哪个namespace
spec:
componentName: $(组件名)
componentType: mycloud-net
mainResourceGlobalId: vpc-xxxx
providerRef:
namespace: default
name: xxx
writeConnectionSecretToRef:
namespace: default
name: yyy
status:
phase: Completed
snapshot: $MD5(spec)
conditions:
TerraformResourceImport的Status的phase:
添加一个controller监听CR的创建/更新,实现下面逻辑:
一、ImportTFStateFileProgress状态:
1.1 根据组件类型生成导入tf代码,如本文的mycloud-net组件的导入案例。
resource "mycloud_vpc" "vpc" {
}
data "mycloud_subnet" "subnet" {
vpc_id = mycloud_vpc.vpc.id
}
data "mycloud_vpc_access_request" "access" {
vpc_id = mycloud_vpc.vpc.id
}
1.2 创建Job执行terraform import命令,参考terraform controller执行terraform apply命令的实现。
1.3 转换导入生成的tfstate,将datasource转为resource。
二、GeneratorConfigurationProgress:根据组件类型,从tfstate扣取输入变量,创建Configuration资源。(需要代码里处理每种resource需要那些输入变量),等待terraform controller处理Configuration资源执行terraform apply成功。
三、Failed:md5(spec)对比snapshot,如果spec有更新,则资源状态重置为ImportTFStateFileProgress阶段,且更新snapshot。
由于terraform-controller使用的terraform docker镜像使用的terraform版本太低,import不支持把data也渲染出来,所以需要自己打一个terraform docker镜像。另外,由于高版本import出来的tf,用低版本apply也会有问题,同样需要重新安装terraform-controller addon插件,指定terraform-controller使用我们自己打的terraform docker镜像。
由于terraform-controller不支持我们配置用于拉取私有镜像的Secret,因此需要将自己打的terraform docker镜像推送到公开的镜像仓库。
自定义terraform docker镜像的Dockerfile如下:
FROM --platform=linux/amd64 hashicorp/terraform:1.4.5
RUN \
apk update && \
apk add bash
VOLUME ["/data"]
WORKDIR /data
由于一个tf文件中,肯定是只有一个“主”资源,其它是“子”资源,所以我们可以只import主资源,然后通过datasource将子资源查出来,这样生成的tfstate就包含了子资源的。
但这个tfstate是有问题的,我们还需要做一下转化。通过apply和import生成两个tfstate(一个是正常走申请生成的tfstate,一个是通过import生成的tfstate),对比这两个tfstate的内容,找出差异,这些差异是有规律的,然后写代码实现将import生成的tfstate转换为apply生成的tfstate。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
在降低增笑的大背景下,如何在保证稳定性的前提下,做到极致压缩k8s资源的使用,实现基础设施真正的按需付费,是我们云原生项目的目标之一。要实现如Railway这种产品的基础设施按实际使用付费,不能简单的使用云Serverless产品,因为这些产品都有最低限额的要求,例如阿里云最低限制要求Pod至少0.25cpu+0.5g内存,但其实大多数应用这个配额都用不到,大量的时间cpu负载几乎为0,内存消耗可能也就几十M(Java应用除外),大量的低使用率的Pod会造成资源的浪费。
我们在做云原生调度技术调研的时候,为了做实验获取一些数据,需要编写一个demo,支持动态模拟cup使用率和内存使用,所以用go开发了这么一个web程序。
KubeVela于2020年年底开源,距离现在还未满三年时间,是一个非常年轻的产物。KubeVela是非常创新的产物,如OAM模型的抽象设计。所以也并未成熟,除了官方文档,找不到更多资料,在使用过程中,我们也遇到各种大大小小的问题。
官方提供的工作流步骤有限,另外,对于自研的PaaS平台,我们需要借助工作流步骤实现一些例如存量项目基础设施导入、项目环境初始化、平台组件共享基础设施需要解决的差异对比审核、基础设施漂移等。
我们基于KubeVela开发的云原生应用交付平台,提供如初始化基础设施导入、中间件部署共用基础设施等相关能力的测试,需要依赖基础设施。虽然terraform是面向公司内部的混合云平台,但是测试都要跨部门配置效率太低了,而且这种模式无法支持持续测试。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。