first commit

This commit is contained in:
darnodo
2025-06-09 11:58:11 +02:00
commit 6a00eb2708
20 changed files with 532 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
# Fichiers d'état et de configuration locaux de Terraform
.terraform/
*.tfstate
*.tfstate.*
*.tfvars
# Fichiers sensibles d'Ansible
*.vault
*vault.yml
# Static hosts
00_static_hosts.yml
dns.home.arnodo.fr.yml
static_records.yml
# Fichier environment python et Ansible
.ansible
.env
.venv

50
README.md Normal file
View File

@@ -0,0 +1,50 @@
# iac-homelab
## Description
This repository contains Infrastructure as Code (IaC) configurations for a home lab environment. It includes Terraform scripts for provisioning infrastructure resources and Ansible playbooks for configuring those resources.
## Usage
### Prerequisites
- Terraform installed on your machine
- Ansible installed on your machine
- Proxmox VE installed on homelab
#### Proxmox Role and User
The Proxmox role is responsible for configuring Proxmox VE on the homelab. It includes tasks for setting up the Proxmox VE cluster, creating virtual machines, and configuring storage.
Following the least privilege principle, the role creates a dedicated user account with limited permissions for managing the Proxmox VE cluster.
```bash
pveum role add TerraformProv -privs "Datastore.Allocate Datastore.AllocateSpace Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify VM.Allocate VM.Audit VM.Clone VM.Config.CDROM VM.Config.Cloudinit VM.Config.CPU VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Console VM.Migrate VM.Monitor VM.PowerMgmt SDN.Use"
```
Create a dedicated user account with limited permissions for managing the Proxmox VE cluster.
```bash
pveum user add terraform@pve --password <password>
pveum acl modify / -user terraform@pve -role TerraformProv
```
Generate API keys for the Proxmox VE cluster.
```bash
pveum user token add terraform@pve terraform -expire 0 -privsep 0 -comment "Terraform token"
```
### Provisioning Infrastructure
1. Clone this repository to your local machine.
2. Navigate to the `terraform/prod` directory.
3. Run `terraform init` to initialize the Terraform environment.
4. Run `terraform plan` to preview the infrastructure changes.
5. Run `terraform apply` to apply the infrastructure changes.
### Configuring Infrastructure
1. Navigate to the `ansible` directory.
2. Run `ansible-playbook playbooks/setup_dns_server.yml` to set up the DNS server.
(Or `ansible-playbook playbooks/update_dns_records.yml` to update DNS records).

38
ansible/ansible.cfg Normal file
View File

@@ -0,0 +1,38 @@
# ================================================================
# Fichier de configuration principal pour Ansible
#
# Ces paramètres s'appliqueront par défaut lorsque vous lancerez
# des commandes Ansible depuis le dossier 'ansible/' ou sa racine.
# ================================================================
[defaults]
# Chemin vers le dossier contenant vos fichiers d'inventaire.
# C'est la directive la plus importante ! Ansible lira automatiquement
# tous les fichiers valides dans ce dossier (statiques et dynamiques).
inventory = ./inventory/
# Chemin vers le dossier contenant vos rôles.
roles_path = ./roles/
# Désactive la vérification des clés d'hôte SSH.
# Très UTILE en lab où les VMs sont souvent recréées avec de nouvelles clés SSH.
# ATTENTION : C'est moins sécurisé. À ne pas utiliser dans un environnement
# de production non contrôlé. Pour notre lab, c'est acceptable.
host_key_checking = False
# N'affiche pas les tâches qui sont sautées (skipped) dans la console.
# Cela rend la sortie de la commande un peu plus propre et lisible.
display_skipped_hosts = False
# Force l'utilisation de couleurs dans la sortie de la console pour une meilleure lisibilité.
force_color = 1
[privilege_escalation]
# Ne jamais demander le mot de passe pour l'escalade de privilèges (sudo).
# Cela suppose que vous avez configuré un accès sudo sans mot de passe
# pour votre utilisateur Ansible (une pratique courante pour l'automatisation).
# Si ce n'est pas le cas, Ansible resterait bloqué à attendre un mot de passe.
become_ask_pass = False

View File

@@ -0,0 +1,14 @@
---
# ================================================================
# Playbook d'Installation Initiale du Serveur DNS
#
# À lancer une seule fois pour provisionner un nouveau serveur.
# Il installe Dnsmasq et déploie la configuration complète.
# USAGE : ansible-playbook -i inventory/ playbooks/setup_dns_server.yml
# ================================================================
- name: "Provisionner et configurer le serveur DNS"
hosts: dns_servers
become: true
roles:
- role: dns_server

View File

@@ -0,0 +1,15 @@
---
# ================================================================
# Playbook de Mise à Jour des Enregistrements DNS
#
# C'est le playbook à lancer après un `terraform apply` ou
# une modification des variables DNS.
# Il garantit que la configuration est à jour.
# USAGE : ansible-playbook -i inventory/ playbooks/update_dns_records.yml
# ================================================================
- name: "Synchroniser les enregistrements DNS"
hosts: dns_servers
become: true
roles:
- role: dns_server

View File

@@ -0,0 +1,12 @@
---
# ================================================================
# Handlers pour le rôle dns_server
#
# Ces tâches ne sont exécutées que si elles sont "notifiées"
# par une autre tâche qui a effectué un changement.
# ================================================================
- name: Restart dnsmasq
ansible.builtin.service:
name: dnsmasq
state: restarted

View File

@@ -0,0 +1,55 @@
---
# Tâches principales pour le rôle dns_server
- name: "[DNS] Installation du paquet Dnsmasq"
tags: install
ansible.builtin.apt:
name: dnsmasq
state: present
update_cache: true
- name: "[DNS] Création du dossier pour les fichiers d'hôtes"
tags: configure
ansible.builtin.file:
path: /etc/dnsmasq.hosts.d
state: directory
owner: root
group: root
mode: '0755'
- name: "[DNS] Création du fichier de configuration principal (dnsmasq.conf)"
tags: configure
ansible.builtin.template:
src: dnsmasq.conf.j2
dest: /etc/dnsmasq.conf
owner: root
group: root
mode: '0644'
notify: Restart dnsmasq
- name: "[DNS] Déploiement des enregistrements DNS statiques (home)"
tags: configure
ansible.builtin.template:
src: records.home.j2
dest: /etc/dnsmasq.hosts.d/records.home.arnodo.fr
owner: root
group: root
mode: '0644'
notify: Restart dnsmasq
- name: "[DNS] Déploiement des enregistrements DNS dynamiques (lab)"
tags: configure
ansible.builtin.template:
src: records.lab.j2
dest: /etc/dnsmasq.hosts.d/records.lab.arnodo.fr
owner: root
group: root
mode: '0644'
notify: Restart dnsmasq
- name: "[DNS] Vérification que le service Dnsmasq est actif et activé au démarrage"
tags: service
ansible.builtin.service:
name: dnsmasq
state: started
enabled: true

View File

@@ -0,0 +1,33 @@
# ================================================================
# Fichier de configuration principal de Dnsmasq
# CE FICHIER EST GÉRÉ PAR ANSIBLE - NE PAS MODIFIER MANUELLEMENT
# ================================================================
# Ne jamais transmettre les requêtes pour le domaine local vers l'extérieur
local=/{{ dnsmasq_domain_name }}/
# Définit le domaine de base pour les noms courts
domain={{ dnsmasq_domain_name }}
# Active la fonctionnalité 'domain' ci-dessus pour les noms dans les fichiers d'hôtes
expand-hosts
# Ne pas lire le fichier /etc/resolv.conf du système
no-resolv
# Interfaces d'écoute
{% for addr in dnsmasq_listen_addresses %}
listen-address={{ addr }}
{% endfor %}
# Serveurs DNS en amont (upstream)
{% for server in dnsmasq_upstream_servers %}
server={{ server }}
{% endfor %}
# Taille du cache
cache-size={{ dnsmasq_cache_size }}
# On indique explicitement où trouver nos listes d'hôtes additionnelles
addn-hosts=/etc/dnsmasq.hosts.d/records.home.arnodo.fr
addn-hosts=/etc/dnsmasq.hosts.d/records.lab.arnodo.fr

View File

@@ -0,0 +1,8 @@
# ===================================================
# Fichier des enregistrements DNS statiques (home)
# GÉRÉ PAR ANSIBLE
# Source: group_vars/all/static_records.yml
# ===================================================
{% for record in dns_records_home %}
{{ record.ip }} {{ record.names | join(' ') }}
{% endfor %}

View File

@@ -0,0 +1,11 @@
# ===================================================
# Fichier des enregistrements DNS dynamiques (lab)
# GÉRÉ PAR ANSIBLE
# Source: Inventaire généré par Terraform
# ===================================================
#
# IP Address FQDN Short Name
#
{% for host in groups['lab'] | default([]) %}
{{ hostvars[host].ansible_host }} {{ hostvars[host].inventory_hostname }} {{ hostvars[host].inventory_hostname_short }}
{% endfor %}

43
terraform/prod/.terraform.lock.hcl generated Normal file
View File

@@ -0,0 +1,43 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/bpg/proxmox" {
version = "0.43.0"
constraints = "0.43.0"
hashes = [
"h1:T1KwNv9zR6q+UTTa7cXa6qAsV1IR4YEkrCpZkM5xAdo=",
"zh:15be827515be0e5fee5128a82f294a9eba9b1dde22a203dc40f6d604832cf5e6",
"zh:1afbe8d25ad1acff6e01f0746df204e43d9841603bc68c47efbd78acb94d09e6",
"zh:1c3873fb2eee657c6acc035b52fd1fea1b8d8173651ae59d9dbd809d9586d5c7",
"zh:2a6f20b2e12b583b9cc53e3e9304a542da8ddb2ac1ba0c4eaf7963702e6d127c",
"zh:3c5fda4aac500ca49c9c33f5de08c9f5abe89106eb7e1546543678d83c189a8b",
"zh:7fb4de0b5fb2cfd57071675c0e118268eff04b2fda73e7f48251b93e1810b83a",
"zh:8fdec222b07106e011722163e1bd6b69bce866a63a1be55b724da8c271f62405",
"zh:900b297a21bdb2bf55e247e2a69e55563cf31f8ff189c2134612c7a5aedea970",
"zh:a24713eace6ba6ae0d19429c083b6bb2d88421d476819df62ccd9e0bb646410f",
"zh:b3267ca029946881b367baa579d49751e0f04f42f5cca1bbab34d02dad844bcd",
"zh:c5bf667aa91cd3fa17b3ef3f3020bd5984996eccc554c28990b5aba70bce4717",
"zh:db3cfe28bd077012804c3cb255771b8c13ffe37e6ffe9c7730ee3192ad974c93",
"zh:f11feed213e61cac4e00f568a6fdcee7eef9a4e3a28b605e7e0419ab36ccee49",
"zh:ff79ccc3535cce0aa00e77f2c3979ef492b9aa611aee643aadb104cffd591c46",
]
}
provider "registry.terraform.io/hashicorp/local" {
version = "2.5.3"
hashes = [
"h1:MCzg+hs1/ZQ32u56VzJMWP9ONRQPAAqAjuHuzbyshvI=",
"zh:284d4b5b572eacd456e605e94372f740f6de27b71b4e1fd49b63745d8ecd4927",
"zh:40d9dfc9c549e406b5aab73c023aa485633c1b6b730c933d7bcc2fa67fd1ae6e",
"zh:6243509bb208656eb9dc17d3c525c89acdd27f08def427a0dce22d5db90a4c8b",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:885d85869f927853b6fe330e235cd03c337ac3b933b0d9ae827ec32fa1fdcdbf",
"zh:bab66af51039bdfcccf85b25fe562cbba2f54f6b3812202f4873ade834ec201d",
"zh:c505ff1bf9442a889ac7dca3ac05a8ee6f852e0118dd9a61796a2f6ff4837f09",
"zh:d36c0b5770841ddb6eaf0499ba3de48e5d4fc99f4829b6ab66b0fab59b1aaf4f",
"zh:ddb6a407c7f3ec63efb4dad5f948b54f7f4434ee1a2607a49680d494b1776fe1",
"zh:e0dafdd4500bec23d3ff221e3a9b60621c5273e5df867bc59ef6b7e41f5c91f6",
"zh:ece8742fd2882a8fc9d6efd20e2590010d43db386b920b2a9c220cfecc18de47",
"zh:f4c6b3eb8f39105004cf720e202f04f57e3578441cfb76ca27611139bc116a82",
]
}

View File

View File

@@ -0,0 +1,75 @@
# Fichier: terraform/environments/prod/02-monitoring-services.tf
# 1. On cherche TOUS les templates disponibles sur le noeud Proxmox
data "proxmox_virtual_environment_vms" "all_templates" {
node_name = var.target_node
tags = ["template"] # On suppose que tous tes templates ont le tag "template"
}
# 2. On transforme la liste de templates en une map pratique pour les retrouver par OS
locals {
# Liste des tags d'OS que nous utilisons
known_os_tags = toset(["ubuntu", "alpine", "rocky", "debian"])
# Crée une map du type : { "ubuntu" = { vm_id = 101, name = "ubuntu-..." }, "alpine" = { ... } }
template_map = {
for t in data.proxmox_virtual_environment_vms.all_templates.vms :
one(setintersection(local.known_os_tags, t.tags)) => t
}
}
# 3. On crée nos VMs en utilisant la syntaxe correcte
resource "proxmox_virtual_environment_vm" "monitoring_vms" {
for_each = var.monitoring_vms
name = "${each.key}.lab.home.arnodo.fr"
node_name = var.target_node
tags = ["terraform-managed", "monitoring", each.value.os]
on_boot = true
agent {
enabled = true
}
cpu {
cores = each.value.cores
type = lookup(each.value, "cpu_type", "qemu64")
}
memory {
dedicated = each.value.memory
}
disk {
interface = "scsi0"
datastore_id = "local-lvm"
size = each.value.disk_gb
}
network_device {
bridge = "vmbr0"
model = "virtio"
}
# Bloc de clonage utilisant l'ID du template trouvé dynamiquement
clone {
vm_id = local.template_map[each.value.os].vm_id
}
# Bloc d'initialisation (Cloud-Init) simplifié
initialization {
# Configuration réseau
ip_config {
ipv4 {
address = "${each.value.ip}/24"
gateway = each.value.gateway
}
}
# Configuration de l'utilisateur
user_account {
username = "damien" # Ou un autre utilisateur par défaut
keys = [file(var.admin_ssh_public_key_path)]
}
}
}

View File

View File

@@ -0,0 +1,36 @@
# inventory.tf
# On fusionne toutes nos ressources VM en une seule map
locals {
all_lab_vms = merge(
proxmox_virtual_environment_vm.monitoring_vms,
)
}
locals {
# On boucle sur chaque VM créée par Terraform pour enrichir les données.
vms_with_metadata = {
for key, vm in local.all_lab_vms : key => {
# On garde les informations de base
name : vm.name
ip_address : var.monitoring_vms[key].ip
proxmox_node : vm.node_name
# On trouve le tag de l'OS de manière robuste
# - setintersection compare les tags de la VM avec notre liste d'OS connus.
# - Le résultat est une liste avec l'élément commun (ex: ["ubuntu"]).
# - one() prend cet unique élément et échoue si on trouve 0 ou plus d'un OS, ce qui est une sécurité.
os_distro : one(setintersection(local.known_os_tags, toset(vm.tags)))
}
}
}
# Création du fichier d'inventaire pour Ansible
resource "local_file" "ansible_inventory" {
filename = "${path.module}/../../ansible/inventory/01_lab_hosts.yml"
# On envoie notre nouvelle map enrichie au template !
content = templatefile("${path.module}/templates/inventory.yml.tftpl", {
vms = local.vms_with_metadata # On utilise la nouvelle variable locale
})
}

View File

View File

@@ -0,0 +1,26 @@
# ================================================================
# Déclaration des Providers Terraform
#
# Ce fichier indique à Terraform quels "plugins" il doit
# télécharger pour interagir avec les API externes (Proxmox, etc.)
# et comment les configurer.
# ================================================================
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.43.0"
}
}
}
provider "proxmox" {
endpoint = var.proxmox_url
api_token = var.proxmox_api_token
insecure = true
ssh {
agent = true
username = "root"
}
}

View File

@@ -0,0 +1,13 @@
# Ansible Inventory - Généré par Terraform le ${timestamp()}
# Ne pas modifier ce fichier manuellement, il sera écrasé.
all:
children:
lab:
hosts:
%{ for key, vm_data in vms }
${vm_data.name}:
ansible_host: ${vm_data.ip_address}
os_distro: "${vm_data.os_distro}"
proxmox_node: "${vm_data.proxmox_node}"
%{ endfor }

View File

@@ -0,0 +1,47 @@
# Fichier: terraform/prod/terraform.tfvars.example
#
# Copiez ce fichier vers terraform.tfvars et remplissez les valeurs appropriées.
# Le fichier terraform.tfvars est ignoré par Git (voir .gitignore) pour protéger les informations sensibles.
# Le nom du noeud Proxmox sur lequel déployer les VMs.
# Par défaut : "pve01"
# target_node = "pve01"
# URL du noeud Proxmox sur lequel déployer les VMs. (Requis)
# Exemple : "https://votre-proxmox-ip-ou-hostname:8006"
proxmox_url = "https://proxmox.example.com:8006"
# Token pour accéder à l'API Proxmox. (Requis et sensible)
# Créez un token dans Proxmox (Datacenter -> Permissions -> API Tokens)
# Exemple : "utilisateur@pve!nom_token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
proxmox_api_token = "terraform@pve!terraform_token=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
# Chemin vers le fichier de la clé SSH publique à autoriser pour l'utilisateur admin sur les VMs.
# Par défaut : "~/.ssh/keys/id_rsa.pub"
# admin_ssh_public_key_path = "~/.ssh/id_rsa.pub"
# Une map décrivant les VMs pour la supervision.
# Vous pouvez laisser cette map vide si vous ne souhaitez pas déployer de VMs de supervision
# ou la remplir comme dans l'exemple ci-dessous.
#
# monitoring_vms = {
# "prometheus" = {
# os = "ubuntu" # Nom du template ou de l'image ISO
# cores = 2
# cpu_type = "host" # Type de CPU (kvm64, host, etc.)
# memory = 2048 # En Mo
# disk_gb = 20 # En Go
# ip = "192.168.1.50/24" # IP statique avec le masque CIDR
# gateway = "192.168.1.1" # Passerelle
# },
# "grafana" = {
# os = "debian"
# cores = 1
# cpu_type = "kvm64"
# memory = 1024
# disk_gb = 10
# ip = "192.168.1.51/24"
# gateway = "192.168.1.1"
# }
# }
monitoring_vms = {}

View File

@@ -0,0 +1,37 @@
# Fichier: terraform/environments/prod/variables.tf
variable "target_node" {
description = "Le nom du noeud Proxmox sur lequel déployer les VMs."
type = string
default = "pve01"
}
variable "proxmox_url" {
description = "URL du noeud Proxmox sur lequel déployer les VMs."
type = string
}
variable "proxmox_api_token" {
description = "Token pour accéder à l'API Proxmox"
type = string
}
variable "admin_ssh_public_key_path" {
description = "Chemin vers le fichier de la clé SSH publique à autoriser."
type = string
default = "~/.ssh/keys/id_rsa.pub"
}
variable "monitoring_vms" {
description = "Une map décrivant les VMs pour la supervision."
type = map(object({
os = string
cores = number
cpu_type = string
memory = number
disk_gb = number
ip = string
gateway = string
}))
default = {}
}