使用Terraform和Ansible创建K8s集群
目录
本文介绍如何使用Terraform和Ansible在Proxmox的虚拟机上创建Kubernetes集群。通过Terraform自动化创建虚拟机,并使用Ansible配置Kubernetes环境,实现快速部署和管理集群。
- Ansible主要用于配置和管理
- Terraform主要用于创建虚拟机资源
Step 1: 使用Ansible获取image并生成virtual machine模板#
vm cluster可以重复销毁和创建,但是vm template只需要创建一次,后续可以重复使用。
下载Ubuntu 24.04 cloud image,给Proxmox使用,并生成虚拟机模板。
- name: Download Ubuntu Cloud Image for Proxmox
hosts: proxmox # Target Proxmox hosts
become: true
vars:
image_url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
image_name: "ubuntu-24.04-noble-cloudimg.img"
# Proxmox 存储路径,根据实际情况修改
image_dest_path: "/mnt/pve/iso-templates/template/iso/"
tasks:
- name: Ensure destination directory exists
ansible.builtin.file:
path: "{{ image_dest_path }}"
state: directory
mode: '0755'
- name: Download Ubuntu 24.04 cloud image
ansible.builtin.get_url:
url: "{{ image_url }}"
dest: "{{ image_dest_path }}{{ image_name }}"
mode: '0644'
生成Proxmox虚拟机模板:
- name: Create Ubuntu 24.04 Cloud-Init Template from existing image
hosts: proxmox
become: true
vars:
# --- Template Configuration ---
template_vmid: 9000 # 使用一个高的、固定的ID作为模板ID
template_name: "ubuntu-2404-cloud-template"
template_memory: 2048
template_cores: 2
template_storage: "local-lvm" # 模板的虚拟磁盘将存放在此
# --- Image Source ---
image_name: "ubuntu-24.04-noble-cloudimg.img"
# !! 重要: 这个路径必须与 'download-cloud-image.yaml' 中的 'image_dest_path' 完全匹配
image_source_path: "/mnt/pve/iso-templates/template/iso/{{ image_name }}"
tasks:
- name: Check if template already exists
ansible.builtin.command: "qm status {{ template_vmid }}"
register: template_check
failed_when: false # 即使命令失败(模板不存在)也不要报错
changed_when: false
- name: Stop if template already exists
ansible.builtin.meta: end_play
when: template_check.rc == 0
- name: Check if the source image exists
ansible.builtin.stat:
path: "{{ image_source_path }}"
register: image_file
- name: Fail if source image is not found
ansible.builtin.fail:
msg: >
Source image not found at {{ image_source_path }}.
Please run the 'download-cloud-image' task first.
when: not image_file.stat.exists
# --- Block for creating the template ---
- name: Create a new template from the existing image
block:
- name: Create a new temporary VM
ansible.builtin.command: >
qm create {{ template_vmid }} --name {{ template_name }}-builder --memory {{ template_memory }}
--cores {{ template_cores }} --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci
changed_when: true
- name: Import the disk from the existing image
ansible.builtin.command: >
qm importdisk {{ template_vmid }} {{ image_source_path }} {{ template_storage }}
changed_when: true
- name: Attach the new disk to the VM as scsi0
ansible.builtin.command: >
qm set {{ template_vmid }} --scsi0 {{ template_storage }}:vm-{{ template_vmid }}-disk-0,discard=on
changed_when: true
- name: Add Cloud-Init CD-ROM drive
ansible.builtin.command: >
qm set {{ template_vmid }} --ide2 {{ template_storage }}:cloudinit
changed_when: true
- name: Set boot order to boot from the new disk
ansible.builtin.command: >
qm set {{ template_vmid }} --boot c --bootdisk scsi0
changed_when: true
- name: Add a serial console for debugging
ansible.builtin.command: >
qm set {{ template_vmid }} --serial0 socket --vga serial0
changed_when: true
- name: Convert the VM to a template
ansible.builtin.command: "qm template {{ template_vmid }}"
changed_when: true
when: template_check.rc != 0
Step 2: 使用Terraform创建K8s集群虚拟机#
Terraform根据ansible生成的虚拟机模板,创建K8s集群所需的虚拟机。
# --- Proxmox Provider Configuration ---
variable "proxmox_endpoint" {
description = "The endpoint URL of the Proxmox API (e.g., https://pve.example.com:8006)."
type = string
}
variable "proxmox_username" {
description = "The username for the Proxmox API (e.g., root@pam)."
type = string
}
variable "proxmox_password" {
description = "The password for the Proxmox API."
type = string
sensitive = true
}
# --- VM Configuration ---
variable "vms" {
description = "A map of virtual machines to create."
type = map(object({
cores = number
memory = number
disk_size = number
user = string
ip = string
gateway = string
}))
default = {}
}
variable "proxmox_node" {
description = "The Proxmox node where the VMs will be created."
type = string
}
variable "template_vmid" {
description = "The VM ID of the Proxmox template to clone from."
type = number
default = 9000 # 匹配 Ansible playbook 中创建的模板 ID
}
variable "ssh_public_key" {
description = "The SSH public key to add to the 'ubuntu' user."
type = string
sensitive = true
}
通过Terraform创建虚拟机:
# Create Ubuntu VM
resource "proxmox_virtual_environment_vm" "ubuntu_vm" {
for_each = var.vms
name = each.key
node_name = var.proxmox_node
# Clone from the specified template
clone {
vm_id = var.template_vmid
full = true
}
# should be true if qemu agent is not installed / enabled on the VM
stop_on_destroy = true
initialization {
ip_config {
ipv4 {
address = each.value.ip
gateway = each.value.gateway
}
}
user_account {
username = each.value.user
keys = [var.ssh_public_key]
}
}
network_device {
bridge = "vmbr0"
}
cpu {
cores = each.value.cores
}
memory {
dedicated = each.value.memory
}
serial_device {}
disk {
# The disk interface must match the template's disk (scsi0 in this case)
interface = "scsi0"
datastore_id = "local-lvm"
size = each.value.disk_size
discard = "on"
}
}
Step 3: 使用Ansible配置K8s集群#
- name: Install MicroK8s on all nodes
hosts: k8s_cluster
become: true
pre_tasks:
- name: Wait for snapd to be fully ready by polling 'snap list'
ansible.builtin.command: snap list
register: snap_list_result
until: snap_list_result.rc == 0
retries: 15
delay: 10
changed_when: false
tasks:
- name: Check if MicroK8s is already installed
ansible.builtin.command: snap list microk8s
register: microk8s_check
failed_when: false
changed_when: false
- name: Install MicroK8s using snap command
ansible.builtin.command: snap install microk8s --classic
when: microk8s_check.rc != 0
# Only install if the previous check failed (meaning microk8s is not installed)
- name: Add ubuntu user to microk8s group
ansible.builtin.user:
name: ubuntu
groups: microk8s
append: true
- name: Configure Firewall on all nodes
hosts: k8s_cluster
become: true
tasks:
- name: Allow SSH connections
community.general.ufw:
rule: allow
name: OpenSSH
- name: Allow all traffic from other k8s nodes
community.general.ufw:
rule: allow
src: "{{ hostvars[item]['ansible_host'] }}"
loop: "{{ groups['k8s_cluster'] }}"
- name: Allow traffic on CNI pods' CIDR (Calico)
community.general.ufw:
rule: allow
src: 10.1.0.0/16
# --- NEW TASK: Allow Kubernetes API Server access ---
- name: Allow Kubernetes API Server access (port 16443)
community.general.ufw:
rule: allow
port: '16443'
proto: tcp
when: inventory_hostname in groups['k8s_masters']
# Only apply this rule to master nodes
- name: Enable ufw
community.general.ufw:
state: enabled
- name: Initialize Master Node and Generate Join Tokens
hosts: k8s_masters
become: true
tasks:
- name: Wait for MicroK8s to be ready
ansible.builtin.command: microk8s status --wait-ready
changed_when: false
- name: Generate a unique join token for each worker node
ansible.builtin.command: microk8s add-node
register: join_command_result
loop: "{{ groups['k8s_workers'] }}"
loop_control:
loop_var: worker_host
changed_when: false
- name: Set the unique join command as a fact for each worker
ansible.builtin.set_fact:
join_command: "{{ item.stdout_lines | select('search', 'microk8s join') | first }}"
loop: "{{ join_command_result.results }}"
loop_control:
index_var: idx
delegate_to: "{{ groups['k8s_workers'][idx] }}"
delegate_facts: true
- name: Join Worker Nodes to the Cluster
hosts: k8s_workers
become: true
tasks:
- name: Join the cluster using the unique token
ansible.builtin.command: "{{ join_command }}"
register: join_result
changed_when: "'Contacting cluster' in join_result.stdout"
- name: Enable Addons on Master
hosts: k8s_masters
become: true
tasks:
- name: Enable core addons (DNS, default storage, dashboard)
ansible.builtin.command: "microk8s enable {{ item }}"
loop:
- dns
- hostpath-storage
- dashboard
changed_when: true
源码#
Read other posts