Skip to main content
Mandacode mandacode
搭建家庭集群 2: 使用 Terraform 自动化集群配置
·
Kubernetes Proxmox VE Talos Linux Terraform Terragrunt

搭建家庭集群 2: 使用 Terraform 自动化集群配置

在 Proxmox VE 上从用 Terraform 创建 VM 到建立集群

继上次关于家庭集群硬件选择和配置的文章,这次我们将讨论如何使用 Terraform 自动化配置 Proxmox VE 节点上的集群。

什么是 Terraform?

Terraform 是一种 IaC(基础设施即代码)工具,可让您通过代码管理基础设施。

我曾有过在 AWS 环境或家庭实验室中直接管理基础设施的经历。为了运行服务器,我需要手动访问 shell 来配置必要的依赖项和基础设施,对于 AWS,则需要在控制台中一一调整实例规格,这些手动操作耗费了大量时间。

之后,接触到 Terraform 后,我认为这是一种可以最大化自动化和便利的工具。尤其是在管理 Kubernetes 集群或像 Proxmox VE 这样的虚拟机管理程序,以及 AWS 等云基础设施时,与直接在仪表板上控制的方式相比,通过代码管理基础设施在扩展性方面更有优势,并且在将来配置规模大或复杂的基础设施时,能够跟踪变更记录或避免重复工作带来了很大的好处。

简介-Terraform 工作流程

(Terraform workflow)

Terraform 主要由三个阶段组成。

  1. **Write**: 定义基础设施配置要素的配置文件。

  2. **Plan**: 与当前基础设施配置比较,确认变更事项。

  3. **Apply**: 实际将配置应用于基础设施并更新 state 文件。

基础设施通过声明方式被定义和管理,允许一致和便捷的基础设施配置。此外,使用模块结构可以在不进行重复工作的情况下完成配置。

$ tree minimal-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf

$ tree complete-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│   ├── nestedA/
│   │   ├── README.md
│   │   ├── variables.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   ├── nestedB/
│   ├── .../
├── examples/
│   ├── exampleA/
│   │   ├── main.tf
│   ├── exampleB/
│   ├── .../

(Terraform 模块结构)

什么是 Terragrunt?

Terraform 本身是一个非常优秀的工具,可以方便地配置基础设施。然而,需要为多个部署环境重复编写代码,同时由于是声明式工具,组件间存在依赖关系时可能会使问题复杂化。Terragrunt 是 Terraform 的包装工具,解决了上述问题。

单元结构

单元是指`terragrunt.hcl`文件所在的目录,是 Terragrunt 中可部署的最小单位。

terraform {
  # 在 stage 部署版本 v0.0.3
  source = "git::git@github.com:foo/modules.git//app?ref=v0.0.3"
}

inputs = {
  instance_count = 3
  instance_type  = "t4g.micro"
}

如上声明后,配置为可以向预先编写的模块传入输入值。

Includes

而不是在多个环境中反复设置 Provider,而是可以根据环境或共同部分进行配置的方法。

通常在根目录下创建`root.hcl`以进行全局设置。

# root.hcl
remote_state {
  backend = "s3"
  config = {
    bucket         = "my-tofu-state"
    key            = "${path_relative_to_include()}/tofu.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "my-lock-table"
  }
}

generate "provider" {
  path = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents = <<EOF
provider "aws" {
  assume_role {
    role_arn = "arn:aws:iam::0123456789:role/terragrunt"
  }
}
EOF
}

# app/terragrunt.hcl
include "root" {
  path = find_in_parent_folders("root.hcl")
}

不仅可以使用上述结构,还可以像 root.hcl 这样的文件在不同环境中(prod/env)定义,以便根据环境进行设置。

include "root" {
  path = find_in_parent_folders("root.hcl")
}

include "k8s-providers" {
  path = find_in_parent_folders("k8s-providers.hcl")
}

# 存储需要 cert-manager 进行 CSI webhook 验证
dependency "security" {
  config_path  = "../05_security"
  skip_outputs = true
}

terraform {
  source = "${get_repo_root()}/modules/rook-ceph"
}

inputs = {
  kubeconfig_path = "${get_repo_root()}/.cache/production/kubeconfig"
}

状态后端

在 Terraform 中,state 是在本地管理的,这可能不适合操作环境或多个团队管理。使用 State Backend 可将 state 远程与 S3 等外部存储集成,并支持变量、函数、表达式,以实现稳定一致的 state 管理。

remote_state {
  backend = "s3"
  config = {
    bucket       = "my-tofu-state"
    key          = "${path_relative_to_include()}/tofu.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }
}

应用基础设施配置

当前正在配置的家庭集群是在 Proxmox 上创建多个虚拟机并利用 Talos Linux 构建的 Kubernetes 集群。

由于使用了多个虚拟机,因此需要自动化和一致性,而使用上述 IaC 工具能够解决这一问题。

当时,未来计划通过 ArgoCD 部署服务,由于想将其构建为单独的项目,我们将基础设施领域和服务领域确定为如下:

  • **基础设施领域** - 运行集群所需的配置

  • **服务领域** - 在配置的集群上运行的服务

模块结构

将虚拟机、CNI、存储(Ceph)、秘密管理器、ArgoCD 等在集群上运行服务所需的项视为基础设施领域,并构建了如下的 Terraform 模块。

> tree -L 1 modules/
modules/
├── bootstrap
├── cluster
├── gitops
├── network
├── secrets
└── storage

> cat modules/network/main.tf                                                                                                                                                                    
resource "helm_release" "cilium" {
  name       = "cilium"
  repository = "https://helm.cilium.io/"
  chart      = "cilium"
  version    = var.cilium_version
  namespace  = "kube-system"

  wait          = true
  wait_for_jobs = true
  timeout       = 600

  set = [
    {
      name  = "kubeProxyReplacement"
      value = "true"
    },
    {
      name  = "k8sServiceHost"
      value = "localhost"
    },
    {
      name  = "k8sServicePort"
      value = "7445"
    },
    {
      name  = "ipam.mode"
      value = "kubernetes"
    },
    {
      name  = "cgroup.autoMount.enabled"
      value = "false"
    },
    {
      name  = "cgroup.hostRoot"
      value = "/sys/fs/cgroup"
    },
...

> cat modules/network/outputs.tf                                                                                                                                                                 
output "cilium_status" {
  description = "Status metadata for the Cilium Helm release"
  value = {
    release   = helm_release.cilium.status
    version   = helm_release.cilium.version
    namespace = helm_release.cilium.namespace
  }
}

output "cilium_lb_pool_name" {
  description = "Name of the Cilium LoadBalancer IPPool"
  value       = kubectl_manifest.cilium_loadbalancer_ip_pool.name
}

output "cilium_l2_policy_name" {
  description = "Name of the Cilium L2 Announcement Policy"
  value       = kubectl_manifest.cilium_l2_announcement_policy.name
}

如上,将每个模块按责任进行构建,并编写`outputs.tf`以便在下一个模块中使用结果。

Terragrunt 层次结构

要将每个模块输出的结果传递给下一个模块,并为全局或分环境指定设置,需要使用 Terragrunt。

此外,在这样的基础设施项目中,每段代码应具有**直观性****可预测性**,因此配置了如下顺序应用的文件夹结构和依赖性。

> tree -L 2 environments/                                                                                                                                                                        
environments/
└── production
    ├── 01_bootstrap
    ├── 02_cluster
    ├── 03_network
    ├── 04_storage
    ├── 05_secrets
    ├── 06_gitops
    ├── env.hcl
    └── k8s-providers.hcl

> cat environments/production/03_network/terragrunt.hcl                                                                                                                                          
include "root" {
  path = find_in_parent_folders("root.hcl")
}

include "k8s-providers" {
  path = find_in_parent_folders("k8s-providers.hcl")
}

# CNI 必须在集群引导后部署
dependency "cluster" {
  config_path = "../02_cluster"
  skip_outputs = true
}

terraform {
  source = "${get_repo_root()}/modules/network"
}

inputs = {
  kubeconfig_path = "${get_repo_root()}/.cache/production/kubeconfig"

  cilium_version = "1.19.4"
  cilium_lb_pool = "192.168.0.80-192.168.0.99"
}

总结

在本文中,我们介绍了使用 Terraform 和 Terragrunt 自动化集群配置的方法。下一篇文章将讨论使用 ArgoCD 的部署策略。

此外,将进一步解释利用 Cilium CNI 进行网络配置和集成 AWS OIDC 的方法。

谢谢大家 :)