From 054835e2a75b4ac6463eef8368d5716e86092746 Mon Sep 17 00:00:00 2001 From: duynguyen Date: Sun, 14 Jun 2026 14:21:09 +0700 Subject: [PATCH] initial commit --- RUNBOOK.md | 339 +++++++++++++++++++++++++++ ansible/ansible.cfg | 13 + ansible/inventory.ini | 11 + ansible/playbooks/01-common.yml | 40 ++++ ansible/playbooks/02-hosts.yml | 15 ++ ansible/playbooks/03-containerd.yml | 35 +++ ansible/playbooks/04-kube-tools.yml | 35 +++ ansible/playbooks/05-master-init.yml | 67 ++++++ ansible/playbooks/06-worker-join.yml | 21 ++ terraform/.env.example | 2 + terraform/.gitignore | 10 + terraform/cloud_image.tf | 8 + terraform/main.tf | 14 ++ terraform/outputs.tf | 11 + terraform/terraform.tfvars | 5 + terraform/variables.tf | 24 ++ terraform/vms.tf | 121 ++++++++++ 17 files changed, 771 insertions(+) create mode 100644 RUNBOOK.md create mode 100644 ansible/ansible.cfg create mode 100644 ansible/inventory.ini create mode 100644 ansible/playbooks/01-common.yml create mode 100644 ansible/playbooks/02-hosts.yml create mode 100644 ansible/playbooks/03-containerd.yml create mode 100644 ansible/playbooks/04-kube-tools.yml create mode 100644 ansible/playbooks/05-master-init.yml create mode 100644 ansible/playbooks/06-worker-join.yml create mode 100644 terraform/.env.example create mode 100644 terraform/.gitignore create mode 100644 terraform/cloud_image.tf create mode 100644 terraform/main.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/terraform.tfvars create mode 100644 terraform/variables.tf create mode 100644 terraform/vms.tf diff --git a/RUNBOOK.md b/RUNBOOK.md new file mode 100644 index 0000000..1d44ca7 --- /dev/null +++ b/RUNBOOK.md @@ -0,0 +1,339 @@ +# K8s Cluster Runbook — Build From Scratch + +## Architecture + +``` +Client machine (192.168.1.39) + ├── Terraform → Proxmox API → provision VMs + └── Ansible → SSH → configure k8s + +Proxmox cluster + ├── proxmox (node 1) + ├── proxmox02 (node 2) + └── proxmox03 (node 3, 192.168.1.18) ← all VMs here + +VMs (CentOS Stream 10, cloud-init) + ├── master01 192.168.1.31 2 vCPU 8GB RAM 100GB disk + ├── worker01 192.168.1.35 2 vCPU 12GB RAM 100GB disk + ├── worker02 192.168.1.36 2 vCPU 12GB RAM 100GB disk + └── worker03 192.168.1.37 2 vCPU 12GB RAM 100GB disk + +Storage: xpen (NFS NAS) — all VM disks + local — cloud image download + cloud-init drive +Pool: IaC +``` + +--- + +## Phase 1 — Client machine setup + +### 1.1 Install tools + +```bash +# Terraform +yum install -y yum-utils +yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo +yum install -y terraform +terraform version + +# Ansible +pip3 install ansible +# or: dnf install ansible + +# Git +yum install -y git +``` + +### 1.2 Clone repo + +```bash +git clone ~/k8s +cd ~/k8s +``` + +### 1.3 Set up Terraform credentials + +```bash +cp cluster-init/terraform/.env.example cluster-init/terraform/.env +# edit cluster-init/terraform/.env — fill in endpoint + token +``` + +`cluster-init/terraform/.env` is gitignored — never commit it. + +### 1.4 Generate SSH keypair for VM access + +> Skip if `cluster-init/terraform/k8s_terraform` already exists. + +```bash +ssh-keygen -t ed25519 -f ~/k8s/cluster-init/terraform/k8s_terraform -N "" -C "terraform-k8s" +``` + +Private key: `cluster-init/terraform/k8s_terraform` (gitignored — do not commit) +Public key: `cluster-init/terraform/k8s_terraform.pub` (gitignored — injected into VMs via cloud-init) + +--- + +## Phase 2 — Proxmox preparation + +### 2.1 Enable local storage content types + +**Proxmox UI → Datacenter → Storage → local → Edit → Content** + +Enable: +- `Disk image (import)` — for cloud image download via Terraform +- `Snippets` — for cloud-init drive + +### 2.2 Create Terraform user, role, token + +SSH into any Proxmox node as root: + +```bash +# Create role +pveum role add TerraformRole --privs \ + "VM.Allocate,VM.Clone,VM.Config.Disk,VM.Config.CPU,\ +VM.Config.Memory,VM.Config.Network,VM.Config.Options,\ +VM.Config.CloudInit,VM.Config.CDROM,VM.PowerMgmt,VM.Audit,\ +Datastore.AllocateSpace,Datastore.AllocateTemplate,\ +Datastore.Allocate,Datastore.Audit,Sys.Audit,Sys.Modify,\ +Pool.Allocate,SDN.Use" + +# Create user + token — SAVE the secret, shown only once +pveum user add terraform@pve --comment "Terraform IaC" +pveum user token add terraform@pve terraform --privsep 0 + +# ACLs +pveum acl modify /nodes --user terraform@pve --role TerraformRole +pveum acl modify /nodes/proxmox03 --user terraform@pve --role TerraformRole +pveum acl modify /storage/local --user terraform@pve --role TerraformRole +pveum acl modify /storage/xpen --user terraform@pve --role TerraformRole +pveum acl modify /pool/IaC --user terraform@pve --role TerraformRole +pveum acl modify /sdn/zones/localnetwork/vmbr0 --user terraform@pve --role TerraformRole + +# Required for proxmox_download_file — Sys.Audit + Sys.Modify must be at root +pveum role add SysDownloadRole --privs "Sys.Audit,Sys.Modify" +pveum acl modify / --user terraform@pve --role SysDownloadRole + +# SDN — token needs explicit ACL (separate from user ACL) +pveum acl modify /sdn/zones/localnetwork/vmbr0 \ + --token terraform@pve!terraform --role TerraformRole +``` + +Update `cluster-init/terraform/.env` with the token secret. + +--- + +## Phase 3 — Provision VMs with Terraform + +### 3.1 Init + +```bash +cd ~/k8s/cluster-init/terraform +source .env +terraform init +``` + +### 3.2 Plan + +```bash +source .env +terraform plan +``` + +Expected: 5 resources to create +- `proxmox_download_file.centos10` +- `proxmox_virtual_environment_vm.master` +- `proxmox_virtual_environment_vm.workers["worker01"]` +- `proxmox_virtual_environment_vm.workers["worker02"]` +- `proxmox_virtual_environment_vm.workers["worker03"]` + +### 3.3 Apply + +```bash +terraform apply -parallelism=1 +``` + +> Must use `-parallelism=1` — parallel disk clones to xpen NFS cause storage lock timeout. + +Takes ~10-15 min (cloud image download ~1 min + 4 VMs × ~3 min each). + +### 3.4 Post-apply state sync + +After first apply, run once to sync pool membership state: +```bash +terraform apply -refresh-only +``` + +Type `yes`. Then verify no further changes: +```bash +terraform plan +``` + +### 3.5 Verify VMs + +```bash +ssh -i cluster-init/terraform/k8s_terraform cloud-user@192.168.1.31 +hostname # master01 +free -h # Swap: 0 (disabled by cloud-init) +``` + +--- + +## Phase 4 — Configure k8s with Ansible + +> Playbooks in `cluster-init/ansible/` + +### 4.1 Run all playbooks + +```bash +cd ~/k8s/cluster-init/ansible + +# All nodes — OS prerequisites (swap, kernel modules, sysctl) +ansible-playbook -i inventory.ini playbooks/01-common.yml + +# All nodes — /etc/hosts (nodes know each other by hostname) +ansible-playbook -i inventory.ini playbooks/02-hosts.yml + +# All nodes — container runtime (containerd) +ansible-playbook -i inventory.ini playbooks/03-containerd.yml + +# All nodes — kubeadm/kubelet/kubectl +ansible-playbook -i inventory.ini playbooks/04-kube-tools.yml + +# master01 only — init cluster + CNI +ansible-playbook -i inventory.ini playbooks/05-master-init.yml + +# worker01/02/03 — join cluster +ansible-playbook -i inventory.ini playbooks/06-worker-join.yml +``` + +### 4.2 Verify cluster (from master01) + +```bash +ssh -i ../terraform/k8s_terraform cloud-user@192.168.1.31 +kubectl get nodes +``` + +All 4 nodes should be `Ready`. + +--- + +## Phase 5 — kubectl from client machine (192.168.1.39) + +### 5.1 Install kubectl + +```bash +# Match the cluster version (1.32) +curl -LO "https://dl.k8s.io/release/v1.32.0/bin/linux/amd64/kubectl" +chmod +x kubectl +sudo mv kubectl /usr/local/bin/kubectl +kubectl version --client +``` + +### 5.2 Copy kubeconfig from master01 + +```bash +mkdir -p ~/.kube +scp -i ~/k8s/cluster-init/terraform/k8s_terraform \ + cloud-user@192.168.1.31:/home/cloud-user/.kube/config \ + ~/.kube/config +``` + +### 5.3 Add cluster nodes to client /etc/hosts + +```bash +sudo tee -a /etc/hosts <<'EOF' + +# k8s cluster nodes +192.168.1.31 master01 +192.168.1.35 worker01 +192.168.1.36 worker02 +192.168.1.37 worker03 +EOF +``` + +### 5.4 Verify + +```bash +kubectl get nodes +kubectl get pods -A +``` + +--- + +## Teardown + +```bash +cd ~/k8s/cluster-init/terraform +source .env +terraform destroy -parallelism=1 +``` + +Destroys all 4 VMs + cloud image file. Re-run Phase 3 to rebuild from scratch. + +--- + +## SSH access + +```bash +ssh -i ~/k8s/cluster-init/terraform/k8s_terraform cloud-user@192.168.1.31 # master01 +ssh -i ~/k8s/cluster-init/terraform/k8s_terraform cloud-user@192.168.1.35 # worker01 +ssh -i ~/k8s/cluster-init/terraform/k8s_terraform cloud-user@192.168.1.36 # worker02 +ssh -i ~/k8s/cluster-init/terraform/k8s_terraform cloud-user@192.168.1.37 # worker03 +``` + +Or add to `~/.ssh/config`: + +``` +Host master01 + HostName 192.168.1.31 + User cloud-user + IdentityFile ~/k8s/cluster-init/terraform/k8s_terraform + +Host worker01 + HostName 192.168.1.35 + User cloud-user + IdentityFile ~/k8s/cluster-init/terraform/k8s_terraform + +Host worker02 + HostName 192.168.1.36 + User cloud-user + IdentityFile ~/k8s/cluster-init/terraform/k8s_terraform + +Host worker03 + HostName 192.168.1.37 + User cloud-user + IdentityFile ~/k8s/cluster-init/terraform/k8s_terraform +``` + +Then: `ssh master01`, `ssh worker01`, etc. + +--- + +## File reference + +``` +k8s/cluster-init/ +├── RUNBOOK.md +├── terraform/ +│ ├── .env # Terraform credentials (gitignored) +│ ├── .env.example # template — safe to commit +│ ├── main.tf # provider config +│ ├── variables.tf # input variables +│ ├── terraform.tfvars # non-secret defaults +│ ├── cloud_image.tf # CentOS Stream 10 cloud image download +│ ├── vms.tf # master01 + worker01/02/03 +│ ├── outputs.tf # IPs + SSH command +│ ├── k8s_terraform # SSH private key (gitignored) +│ ├── k8s_terraform.pub # SSH public key (gitignored) +│ └── .gitignore +└── ansible/ + ├── inventory.ini + ├── ansible.cfg + └── playbooks/ + ├── 01-common.yml + ├── 02-hosts.yml + ├── 03-containerd.yml + ├── 04-kube-tools.yml + ├── 05-master-init.yml + └── 06-worker-join.yml +``` diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..7c66068 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,13 @@ +[defaults] +inventory = inventory.ini +remote_user = cloud-user +private_key_file = ../terraform/k8s_terraform +host_key_checking = False +stdout_callback = default +result_format = yaml +interpreter_python = /usr/bin/python3.12 +deprecation_warnings = False + +[privilege_escalation] +become = True +become_method = sudo diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..8e699ce --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,11 @@ +[masters] +master01 ansible_host=192.168.1.31 + +[workers] +worker01 ansible_host=192.168.1.35 +worker02 ansible_host=192.168.1.36 +worker03 ansible_host=192.168.1.37 + +[k8s:children] +masters +workers diff --git a/ansible/playbooks/01-common.yml b/ansible/playbooks/01-common.yml new file mode 100644 index 0000000..86bb7ee --- /dev/null +++ b/ansible/playbooks/01-common.yml @@ -0,0 +1,40 @@ +--- +- name: Common OS prerequisites for all k8s nodes + hosts: k8s + become: true + + tasks: + - name: Disable swap now + command: swapoff -a + when: ansible_facts['swaptotal_mb'] > 0 + + - name: Remove swap from /etc/fstab + replace: + path: /etc/fstab + regexp: '^([^#].*\s+swap\s+.*)$' + replace: '# \1' + + - name: Load kernel modules at boot + copy: + dest: /etc/modules-load.d/k8s.conf + content: | + overlay + br_netfilter + + - name: Load kernel modules now + modprobe: + name: "{{ item }}" + loop: + - overlay + - br_netfilter + + - name: Set sysctl for k8s networking + copy: + dest: /etc/sysctl.d/k8s.conf + content: | + net.bridge.bridge-nf-call-iptables = 1 + net.bridge.bridge-nf-call-ip6tables = 1 + net.ipv4.ip_forward = 1 + + - name: Apply sysctl + command: sysctl --system diff --git a/ansible/playbooks/02-hosts.yml b/ansible/playbooks/02-hosts.yml new file mode 100644 index 0000000..e6f1bc9 --- /dev/null +++ b/ansible/playbooks/02-hosts.yml @@ -0,0 +1,15 @@ +--- +- name: Configure /etc/hosts for all nodes + hosts: k8s + become: true + + tasks: + - name: Add cluster nodes to /etc/hosts + blockinfile: + path: /etc/hosts + marker: "# {mark} k8s cluster nodes" + block: | + 192.168.1.31 master01 + 192.168.1.35 worker01 + 192.168.1.36 worker02 + 192.168.1.37 worker03 diff --git a/ansible/playbooks/03-containerd.yml b/ansible/playbooks/03-containerd.yml new file mode 100644 index 0000000..34b22a5 --- /dev/null +++ b/ansible/playbooks/03-containerd.yml @@ -0,0 +1,35 @@ +--- +- name: Install and configure containerd + hosts: k8s + become: true + + tasks: + - name: Add Docker CE repo (provides containerd.io) + get_url: + url: https://download.docker.com/linux/centos/docker-ce.repo + dest: /etc/yum.repos.d/docker-ce.repo + + - name: Install containerd + dnf: + name: containerd.io + state: present + + - name: Create containerd config directory + file: + path: /etc/containerd + state: directory + + - name: Generate default containerd config (overwrites Docker CE default which disables CRI) + shell: containerd config default > /etc/containerd/config.toml + + - name: Enable SystemdCgroup in containerd config + replace: + path: /etc/containerd/config.toml + regexp: 'SystemdCgroup = false' + replace: 'SystemdCgroup = true' + + - name: Enable and start containerd + systemd: + name: containerd + enabled: true + state: restarted diff --git a/ansible/playbooks/04-kube-tools.yml b/ansible/playbooks/04-kube-tools.yml new file mode 100644 index 0000000..e04cb73 --- /dev/null +++ b/ansible/playbooks/04-kube-tools.yml @@ -0,0 +1,35 @@ +--- +- name: Install kubeadm, kubelet, kubectl + hosts: k8s + become: true + + vars: + kube_version: "1.32" + + tasks: + - name: Add Kubernetes repo + copy: + dest: /etc/yum.repos.d/kubernetes.repo + content: | + [kubernetes] + name=Kubernetes + baseurl=https://pkgs.k8s.io/core:/stable:/v{{ kube_version }}/rpm/ + enabled=1 + gpgcheck=1 + gpgkey=https://pkgs.k8s.io/core:/stable:/v{{ kube_version }}/rpm/repodata/repomd.xml.key + exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni + + - name: Install kubeadm, kubelet, kubectl + dnf: + name: + - kubelet + - kubeadm + - kubectl + state: present + disable_excludes: kubernetes + + - name: Enable kubelet + systemd: + name: kubelet + enabled: true + state: started diff --git a/ansible/playbooks/05-master-init.yml b/ansible/playbooks/05-master-init.yml new file mode 100644 index 0000000..1e8e964 --- /dev/null +++ b/ansible/playbooks/05-master-init.yml @@ -0,0 +1,67 @@ +--- +- name: Initialize Kubernetes master + hosts: masters + become: true + + vars: + pod_network_cidr: "10.244.0.0/16" # Flannel default + + tasks: + - name: Check if cluster already initialized + stat: + path: /etc/kubernetes/admin.conf + register: kubeadm_init_done + + - name: Run kubeadm init + command: > + kubeadm init + --apiserver-advertise-address={{ ansible_host }} + --pod-network-cidr={{ pod_network_cidr }} + when: not kubeadm_init_done.stat.exists + register: kubeadm_output + + - name: Create .kube dir for cloud-user + file: + path: /home/cloud-user/.kube + state: directory + mode: "0700" + owner: cloud-user + group: cloud-user + + - name: Copy admin.conf to cloud-user kubeconfig + copy: + src: /etc/kubernetes/admin.conf + dest: /home/cloud-user/.kube/config + remote_src: true + owner: cloud-user + group: cloud-user + mode: "0600" + + - name: Create .kube dir for root (enables sudo kubectl) + file: + path: /root/.kube + state: directory + mode: "0700" + + - name: Copy admin.conf to root kubeconfig + copy: + src: /etc/kubernetes/admin.conf + dest: /root/.kube/config + remote_src: true + mode: "0600" + + - name: Install Flannel CNI + command: kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml + environment: + KUBECONFIG: /etc/kubernetes/admin.conf + + - name: Get join command + command: kubeadm token create --print-join-command + register: join_command + + - name: Save join command to local file + local_action: + module: copy + content: "{{ join_command.stdout }}" + dest: /tmp/k8s_join_command.sh + mode: "0600" diff --git a/ansible/playbooks/06-worker-join.yml b/ansible/playbooks/06-worker-join.yml new file mode 100644 index 0000000..c812f97 --- /dev/null +++ b/ansible/playbooks/06-worker-join.yml @@ -0,0 +1,21 @@ +--- +- name: Join workers to Kubernetes cluster + hosts: workers + become: true + + tasks: + - name: Check if already joined + stat: + path: /etc/kubernetes/kubelet.conf + register: kubelet_conf + + - name: Read join command from local file + local_action: + module: slurp + src: /tmp/k8s_join_command.sh + register: join_command_b64 + when: not kubelet_conf.stat.exists + + - name: Join cluster + command: "{{ join_command_b64.content | b64decode | trim }}" + when: not kubelet_conf.stat.exists diff --git a/terraform/.env.example b/terraform/.env.example new file mode 100644 index 0000000..41b1e36 --- /dev/null +++ b/terraform/.env.example @@ -0,0 +1,2 @@ +export TF_VAR_proxmox_endpoint=https://:8006 +export TF_VAR_proxmox_api_token=terraform@pve!terraform= diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 0000000..76bbf57 --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,10 @@ +.env +.terraform/ +.terraform.lock.hcl +terraform.tfstate +terraform.tfstate.backup +*.tfstate +*.tfstate.* +k8s_terraform +k8s_terraform.pub +*.tfvars.local diff --git a/terraform/cloud_image.tf b/terraform/cloud_image.tf new file mode 100644 index 0000000..583f1fd --- /dev/null +++ b/terraform/cloud_image.tf @@ -0,0 +1,8 @@ +resource "proxmox_download_file" "centos10" { + content_type = "import" + datastore_id = "local" + node_name = var.proxmox_node + url = "https://cloud.centos.org/centos/10-stream/x86_64/images/CentOS-Stream-GenericCloud-10-latest.x86_64.qcow2" + file_name = "centos-stream-10-cloud.qcow2" + overwrite = false +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..2832b03 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "~> 0.107" + } + } +} + +provider "proxmox" { + endpoint = var.proxmox_endpoint + api_token = var.proxmox_api_token + insecure = true +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..51ac12c --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,11 @@ +output "master_ip" { + value = "192.168.1.31" +} + +output "worker_ips" { + value = { for k, v in proxmox_virtual_environment_vm.workers : k => v.initialization[0].ip_config[0].ipv4[0].address } +} + +output "ssh_command" { + value = "ssh -i ${path.module}/k8s_terraform cloud-user@192.168.1.31" +} diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 0000000..bdabc92 --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,5 @@ +proxmox_endpoint = "https://192.168.1.18:8006" +proxmox_node = "proxmox03" +proxmox_pool = "IaC" +gateway = "192.168.1.1" +dns_server = "192.168.1.1" diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..044c60d --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,24 @@ +variable "proxmox_endpoint" { + description = "Proxmox API endpoint URL" +} + +variable "proxmox_api_token" { + description = "Proxmox API token (user@realm!tokenid=secret)" + sensitive = true +} + +variable "proxmox_node" { + default = "proxmox03" +} + +variable "proxmox_pool" { + default = "IaC" +} + +variable "gateway" { + default = "192.168.1.1" +} + +variable "dns_server" { + default = "192.168.1.1" +} diff --git a/terraform/vms.tf b/terraform/vms.tf new file mode 100644 index 0000000..c5338a5 --- /dev/null +++ b/terraform/vms.tf @@ -0,0 +1,121 @@ +resource "proxmox_virtual_environment_vm" "master" { + name = "master01" + vm_id = 131 + node_name = var.proxmox_node + pool_id = var.proxmox_pool + stop_on_destroy = true + + cpu { + cores = 2 + type = "host" + } + + memory { + dedicated = 8192 + } + + scsi_hardware = "virtio-scsi-single" + + disk { + datastore_id = "xpen" + import_from = proxmox_download_file.centos10.id + interface = "scsi0" + iothread = true + size = 100 + } + + network_device { + bridge = "vmbr0" + model = "virtio" + } + + boot_order = ["scsi0"] + + agent { + enabled = true + } + + initialization { + datastore_id = "local" + + ip_config { + ipv4 { + address = "192.168.1.31/24" + gateway = var.gateway + } + } + + dns { + servers = [var.dns_server] + } + + user_account { + username = "cloud-user" + keys = [trimspace(file("${path.module}/k8s_terraform.pub"))] + } + } +} + +resource "proxmox_virtual_environment_vm" "workers" { + for_each = { + worker01 = { vm_id = 135, ip = "192.168.1.35" } + worker02 = { vm_id = 136, ip = "192.168.1.36" } + worker03 = { vm_id = 137, ip = "192.168.1.37" } + } + + name = each.key + vm_id = each.value.vm_id + node_name = var.proxmox_node + pool_id = var.proxmox_pool + stop_on_destroy = true + + cpu { + cores = 2 + type = "host" + } + + memory { + dedicated = 12288 + } + + scsi_hardware = "virtio-scsi-single" + + disk { + datastore_id = "xpen" + import_from = proxmox_download_file.centos10.id + interface = "scsi0" + iothread = true + size = 100 + } + + network_device { + bridge = "vmbr0" + model = "virtio" + } + + boot_order = ["scsi0"] + + agent { + enabled = true + } + + initialization { + datastore_id = "local" + + ip_config { + ipv4 { + address = "${each.value.ip}/24" + gateway = var.gateway + } + } + + dns { + servers = [var.dns_server] + } + + user_account { + username = "cloud-user" + keys = [trimspace(file("${path.module}/k8s_terraform.pub"))] + } + } +}